source: nick.c @ 664bac3

Last change on this file since 664bac3 was fed4f76, checked in by dequis <dx@…>, at 2015-01-16T19:50:25Z

Fix UTF8 nick truncation issues

When nicks exceeded the length limit, they were cut at 24 bytes and that
sometimes left invalid utf8 at the end, which made the nick_ok()
validation fail and often broke those nicks completely.

This adds a truncate_utf8 function to cut the string at a safe place

Also, the method to deduplicate nicks when there's no more place to add
underscores was changed to add "_XX" at the end, where XX are two random
hex chars. The previous method in those cases was increasing the value
of the first character of the nick... which leads to silly and confusing
results (i.e. FacebookUser -> GacebookUser)

  • Property mode set to 100644
File size: 10.5 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* Some stuff to fetch, save and handle nicknames for your buddies      */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#define BITLBEE_CORE
27#include "bitlbee.h"
28
29/* Character maps, _lc_[x] == _uc_[x] (but uppercase), according to the RFC's.
30   With one difference, we allow dashes. These are used to do uc/lc conversions
31   and strip invalid chars. */
32static char *nick_lc_chars = "0123456789abcdefghijklmnopqrstuvwxyz{}^`-_|";
33static char *nick_uc_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]~`-_\\";
34
35/* Store handles in lower case and strip spaces, because AIM is braindead. */
36static char *clean_handle( const char *orig )
37{
38        char *new = g_malloc( strlen( orig ) + 1 );
39        int i = 0;
40       
41        do {
42                if (*orig != ' ')
43                        new[i++] = g_ascii_tolower( *orig );
44        }
45        while (*(orig++));
46       
47        return new;
48}
49
50void nick_set_raw( account_t *acc, const char *handle, const char *nick )
51{
52        char *store_handle, *store_nick = g_malloc( MAX_NICK_LENGTH + 1 );
53        irc_t *irc = (irc_t *) acc->bee->ui_data;
54       
55        store_handle = clean_handle( handle );
56        store_nick[MAX_NICK_LENGTH] = '\0';
57        strncpy( store_nick, nick, MAX_NICK_LENGTH );
58        nick_strip( irc, store_nick );
59       
60        g_hash_table_replace( acc->nicks, store_handle, store_nick );
61}
62
63void nick_set( bee_user_t *bu, const char *nick )
64{
65        nick_set_raw( bu->ic->acc, bu->handle, nick );
66}
67
68char *nick_get( bee_user_t *bu )
69{
70        static char nick[MAX_NICK_LENGTH+1];
71        char *store_handle, *found_nick;
72        irc_t *irc = (irc_t *) bu->bee->ui_data;
73       
74        memset( nick, 0, MAX_NICK_LENGTH + 1 );
75       
76        store_handle = clean_handle( bu->handle );
77        /* Find out if we stored a nick for this person already. If not, try
78           to generate a sane nick automatically. */
79        if( ( found_nick = g_hash_table_lookup( bu->ic->acc->nicks, store_handle ) ) )
80        {
81                strncpy( nick, found_nick, MAX_NICK_LENGTH );
82        }
83        else if( ( found_nick = nick_gen( bu ) ) )
84        {
85                strncpy( nick, found_nick, MAX_NICK_LENGTH );
86                g_free( found_nick );
87        }
88        else
89        {
90                /* Keep this fallback since nick_gen() can return NULL in some cases. */
91                char *s;
92               
93                g_snprintf( nick, MAX_NICK_LENGTH, "%s", bu->handle );
94                if( ( s = strchr( nick, '@' ) ) )
95                        while( *s )
96                                *(s++) = 0;
97               
98                nick_strip( irc, nick );
99                if( set_getbool( &bu->bee->set, "lcnicks" ) )
100                        nick_lc( irc, nick );
101        }
102        g_free( store_handle );
103       
104        /* Make sure the nick doesn't collide with an existing one by adding
105           underscores and that kind of stuff, if necessary. */
106        nick_dedupe( bu, nick );
107       
108        return nick;
109}
110
111char *nick_gen( bee_user_t *bu )
112{
113        gboolean ok = FALSE; /* Set to true once the nick contains something unique. */
114        GString *ret = g_string_sized_new( MAX_NICK_LENGTH + 1 );
115        char *rets;
116        irc_t *irc = (irc_t *) bu->bee->ui_data;
117        char *fmt = set_getstr( &bu->ic->acc->set, "nick_format" ) ? :
118                    set_getstr( &bu->bee->set, "nick_format" );
119       
120        while( fmt && *fmt && ret->len < MAX_NICK_LENGTH )
121        {
122                char *part = NULL, chop = '\0', *asc = NULL, *s;
123                int len = INT_MAX;
124               
125                if( *fmt != '%' )
126                {
127                        g_string_append_c( ret, *fmt );
128                        fmt ++;
129                        continue;
130                }
131               
132                fmt ++;
133                while( *fmt )
134                {
135                        /* -char means chop off everything from char */
136                        if( *fmt == '-' )
137                        {
138                                chop = fmt[1];
139                                if( chop == '\0' )
140                                {
141                                        g_string_free( ret, TRUE );
142                                        return NULL;
143                                }
144                                fmt += 2;
145                        }
146                        else if( g_ascii_isdigit( *fmt ) )
147                        {
148                                len = 0;
149                                /* Grab a number. */
150                                while( g_ascii_isdigit( *fmt ) )
151                                        len = len * 10 + ( *(fmt++) - '0' );
152                        }
153                        else if( g_strncasecmp( fmt, "nick", 4 ) == 0 )
154                        {
155                                part = bu->nick ? : bu->handle;
156                                fmt += 4;
157                                ok |= TRUE;
158                                break;
159                        }
160                        else if( g_strncasecmp( fmt, "handle", 6 ) == 0 )
161                        {
162                                part = bu->handle;
163                                fmt += 6;
164                                ok |= TRUE;
165                                break;
166                        }
167                        else if( g_strncasecmp( fmt, "full_name", 9 ) == 0 )
168                        {
169                                part = bu->fullname;
170                                fmt += 9;
171                                ok |= part && *part;
172                                break;
173                        }
174                        else if( g_strncasecmp( fmt, "first_name", 10 ) == 0 )
175                        {
176                                part = bu->fullname;
177                                fmt += 10;
178                                ok |= part && *part;
179                                chop = ' ';
180                                break;
181                        }
182                        else if( g_strncasecmp( fmt, "group", 5 ) == 0 )
183                        {
184                                part = bu->group ? bu->group->name : NULL;
185                                fmt += 5;
186                                break;
187                        }
188                        else if( g_strncasecmp( fmt, "account", 7 ) == 0 )
189                        {
190                                part = bu->ic->acc->tag;
191                                fmt += 7;
192                                break;
193                        }
194                        else
195                        {
196                                g_string_free( ret, TRUE );
197                                return NULL;
198                        }
199                }
200               
201                if( !part )
202                        continue;
203               
204                /* Credits to Josay_ in #bitlbee for this idea. //TRANSLIT
205                   should do lossy/approximate conversions, so letters with
206                   accents don't just get stripped. Note that it depends on
207                   LC_CTYPE being set to something other than C/POSIX. */
208                if( !( irc && irc->status & IRC_UTF8_NICKS ) )
209                        part = asc = g_convert_with_fallback( part, -1, "ASCII//TRANSLIT",
210                                                              "UTF-8", "", NULL, NULL, NULL );
211               
212                if( part && chop && ( s = strchr( part, chop ) ) )
213                        len = MIN( len, s - part );
214               
215                if( part )
216                {
217                        if( len < INT_MAX )
218                                g_string_append_len( ret, part, len );
219                        else
220                                g_string_append( ret, part );
221                }
222                g_free( asc );
223        }
224       
225        rets = g_string_free( ret, FALSE );
226        if( ok && rets && *rets )
227        {
228                nick_strip( irc, rets );
229                truncate_utf8( rets, MAX_NICK_LENGTH );
230                return rets;
231        }
232        g_free( rets );
233        return NULL;
234}
235
236void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] )
237{
238        irc_t *irc = (irc_t*) bu->bee->ui_data;
239        int inf_protection = 256;
240        irc_user_t *iu;
241       
242        /* Now, find out if the nick is already in use at the moment, and make
243           subtle changes to make it unique. */
244        while( !nick_ok( irc, nick ) ||
245               ( ( iu = irc_user_by_name( irc, nick ) ) && iu->bu != bu ) )
246        {
247                if( strlen( nick ) < ( MAX_NICK_LENGTH - 1 ) )
248                {
249                        nick[strlen(nick)+1] = 0;
250                        nick[strlen(nick)] = '_';
251                }
252                else
253                {
254                        /* We've got no more space for underscores,
255                           so truncate it and replace the last three
256                           chars with a random "_XX" suffix */
257                        int len = truncate_utf8( nick, MAX_NICK_LENGTH - 3 );
258                        nick[len] = '_';
259                        g_snprintf(nick + len + 1, 3, "%2x", rand() );
260                }
261               
262                if( inf_protection-- == 0 )
263                {
264                        g_snprintf( nick, MAX_NICK_LENGTH + 1, "xx%x", rand() );
265                       
266                        irc_rootmsg( irc, "Warning: Something went wrong while trying "
267                                          "to generate a nickname for contact %s on %s.",
268                                          bu->handle, bu->ic->acc->tag );
269                        irc_rootmsg( irc, "This might be a bug in BitlBee, or the result "
270                                          "of a faulty nick_format setting. Will use %s "
271                                          "instead.", nick );
272                       
273                        break;
274                }
275        }
276}
277
278/* Just check if there is a nickname set for this buddy or if we'd have to
279   generate one. */
280int nick_saved( bee_user_t *bu )
281{
282        char *store_handle, *found;
283       
284        store_handle = clean_handle( bu->handle );
285        found = g_hash_table_lookup( bu->ic->acc->nicks, store_handle );
286        g_free( store_handle );
287       
288        return found != NULL;
289}
290
291void nick_del( bee_user_t *bu )
292{
293        g_hash_table_remove( bu->ic->acc->nicks, bu->handle );
294}
295
296
297void nick_strip( irc_t *irc, char *nick )
298{
299        int len = 0;
300       
301        if( irc && ( irc->status & IRC_UTF8_NICKS ) )
302        {
303                gunichar c;
304                char *p = nick, *n, tmp[strlen(nick)+1];
305               
306                while( p && *p )
307                {
308                        c = g_utf8_get_char_validated( p, -1 );
309                        n = g_utf8_find_next_char( p, NULL );
310                       
311                        if( ( c < 0x7f && !( strchr( nick_lc_chars, c ) ||
312                                             strchr( nick_uc_chars, c ) ) ) ||
313                            !g_unichar_isgraph( c ) )
314                        {
315                                strcpy( tmp, n );
316                                strcpy( p, tmp );
317                        }
318                        else
319                                p = n;
320                }
321                if( p )
322                        len = p - nick;
323        }
324        else
325        {
326                int i;
327               
328                for( i = len = 0; nick[i] && len < MAX_NICK_LENGTH; i++ )
329                {
330                        if( strchr( nick_lc_chars, nick[i] ) || 
331                            strchr( nick_uc_chars, nick[i] ) )
332                        {
333                                nick[len] = nick[i];
334                                len++;
335                        }
336                }
337        }
338        if( g_ascii_isdigit( nick[0] ) )
339        {
340                char *orig;
341               
342                /* First character of a nick can't be a digit, so insert an
343                   underscore if necessary. */
344                orig = g_strdup( nick );
345                g_snprintf( nick, MAX_NICK_LENGTH, "_%s", orig );
346                g_free( orig );
347                len ++;
348        }
349        while( len <= MAX_NICK_LENGTH )
350                nick[len++] = '\0';
351}
352
353gboolean nick_ok( irc_t *irc, const char *nick )
354{
355        const char *s;
356       
357        /* Empty/long nicks are not allowed, nor numbers at [0] */
358        if( !*nick || g_ascii_isdigit( nick[0] ) || strlen( nick ) > MAX_NICK_LENGTH )
359                return 0;
360       
361        if( irc && ( irc->status & IRC_UTF8_NICKS ) )
362        {
363                gunichar c;
364                const char *p = nick, *n;
365               
366                while( p && *p )
367                {
368                        c = g_utf8_get_char_validated( p, -1 );
369                        n = g_utf8_find_next_char( p, NULL );
370                       
371                        if( ( c < 0x7f && !( strchr( nick_lc_chars, c ) ||
372                                             strchr( nick_uc_chars, c ) ) ) ||
373                            !g_unichar_isgraph( c ) )
374                        {
375                                return FALSE;
376                        }
377                        p = n;
378                }
379        }
380        else
381        {
382                for( s = nick; *s; s ++ )
383                        if( !strchr( nick_lc_chars, *s ) && !strchr( nick_uc_chars, *s ) )
384                                return FALSE;
385        }
386       
387        return TRUE;
388}
389
390int nick_lc( irc_t *irc, char *nick )
391{
392        static char tab[128] = { 0 };
393        int i;
394       
395        if( tab['A'] == 0 )
396                for( i = 0; nick_lc_chars[i]; i ++ )
397                {
398                        tab[(int)nick_uc_chars[i]] = nick_lc_chars[i];
399                        tab[(int)nick_lc_chars[i]] = nick_lc_chars[i];
400                }
401       
402        if( irc && ( irc->status & IRC_UTF8_NICKS ) )
403        {
404                gchar *down = g_utf8_strdown( nick, -1 );
405                if( strlen( down ) > strlen( nick ) )
406                {
407                        truncate_utf8( down, strlen(nick) );
408                }
409                strcpy( nick, down );
410                g_free( down );
411        }
412       
413        for( i = 0; nick[i]; i ++ )
414                if( ((guchar)nick[i]) < 0x7f )
415                        nick[i] = tab[(guchar)nick[i]];
416       
417        return nick_ok( irc, nick );
418}
419
420int nick_cmp( irc_t *irc, const char *a, const char *b )
421{
422        char aa[1024] = "", bb[1024] = "";
423       
424        strncpy( aa, a, sizeof( aa ) - 1 );
425        strncpy( bb, b, sizeof( bb ) - 1 );
426        if( nick_lc( irc, aa ) && nick_lc( irc, bb ) )
427        {
428                return( strcmp( aa, bb ) );
429        }
430        else
431        {
432                return( -1 );   /* Hmm... Not a clear answer.. :-/ */
433        }
434}
Note: See TracBrowser for help on using the repository browser.