source: nick.c @ 2f9027c

Last change on this file since 2f9027c was 1a2c1c0, checked in by Wilmer van der Gaast <wilmer@…>, at 2013-06-16T12:42:39Z

Merging utf8-nicks branch. This adds a utf8_nicks setting which removes the
ASCII restriction on contact nicknames. Use at your own risk!

  • Property mode set to 100644
File size: 9.9 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., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  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++] = 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;
123               
124                if( *fmt != '%' )
125                {
126                        g_string_append_c( ret, *fmt );
127                        fmt ++;
128                        continue;
129                }
130               
131                fmt ++;
132                while( *fmt )
133                {
134                        /* -char means chop off everything from char */
135                        if( *fmt == '-' )
136                        {
137                                chop = fmt[1];
138                                if( chop == '\0' )
139                                {
140                                        g_string_free( ret, TRUE );
141                                        return NULL;
142                                }
143                                fmt += 2;
144                        }
145                        else if( g_strncasecmp( fmt, "nick", 4 ) == 0 )
146                        {
147                                part = bu->nick ? : bu->handle;
148                                fmt += 4;
149                                ok |= TRUE;
150                                break;
151                        }
152                        else if( g_strncasecmp( fmt, "handle", 6 ) == 0 )
153                        {
154                                part = bu->handle;
155                                fmt += 6;
156                                ok |= TRUE;
157                                break;
158                        }
159                        else if( g_strncasecmp( fmt, "full_name", 9 ) == 0 )
160                        {
161                                part = bu->fullname;
162                                fmt += 9;
163                                ok |= part && *part;
164                                break;
165                        }
166                        else if( g_strncasecmp( fmt, "first_name", 10 ) == 0 )
167                        {
168                                part = bu->fullname;
169                                fmt += 10;
170                                ok |= part && *part;
171                                chop = ' ';
172                                break;
173                        }
174                        else if( g_strncasecmp( fmt, "group", 5 ) == 0 )
175                        {
176                                part = bu->group ? bu->group->name : NULL;
177                                fmt += 5;
178                                break;
179                        }
180                        else if( g_strncasecmp( fmt, "account", 7 ) == 0 )
181                        {
182                                part = bu->ic->acc->tag;
183                                fmt += 7;
184                                break;
185                        }
186                        else
187                        {
188                                g_string_free( ret, TRUE );
189                                return NULL;
190                        }
191                }
192               
193                if( !part )
194                        continue;
195               
196                /* Credits to Josay_ in #bitlbee for this idea. //TRANSLIT
197                   should do lossy/approximate conversions, so letters with
198                   accents don't just get stripped. Note that it depends on
199                   LC_CTYPE being set to something other than C/POSIX. */
200                if( !( irc && irc->status & IRC_UTF8_NICKS ) )
201                        part = asc = g_convert_with_fallback( part, -1, "ASCII//TRANSLIT",
202                                                              "UTF-8", "", NULL, NULL, NULL );
203               
204                if( part )
205                        g_string_append( ret, part );
206                g_free( asc );
207        }
208       
209        rets = g_string_free( ret, FALSE );
210        if( ok && rets && *rets )
211        {
212                nick_strip( irc, rets );
213                rets[MAX_NICK_LENGTH] = '\0';
214                return rets;
215        }
216        g_free( rets );
217        return NULL;
218}
219
220void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] )
221{
222        irc_t *irc = (irc_t*) bu->bee->ui_data;
223        int inf_protection = 256;
224        irc_user_t *iu;
225       
226        /* Now, find out if the nick is already in use at the moment, and make
227           subtle changes to make it unique. */
228        while( !nick_ok( irc, nick ) ||
229               ( ( iu = irc_user_by_name( irc, nick ) ) && iu->bu != bu ) )
230        {
231                if( strlen( nick ) < ( MAX_NICK_LENGTH - 1 ) )
232                {
233                        nick[strlen(nick)+1] = 0;
234                        nick[strlen(nick)] = '_';
235                }
236                else
237                {
238                        nick[0] ++;
239                }
240               
241                if( inf_protection-- == 0 )
242                {
243                        g_snprintf( nick, MAX_NICK_LENGTH + 1, "xx%x", rand() );
244                       
245                        irc_rootmsg( irc, "Warning: Something went wrong while trying "
246                                          "to generate a nickname for contact %s on %s.",
247                                          bu->handle, bu->ic->acc->tag );
248                        irc_rootmsg( irc, "This might be a bug in BitlBee, or the result "
249                                          "of a faulty nick_format setting. Will use %s "
250                                          "instead.", nick );
251                       
252                        break;
253                }
254        }
255}
256
257/* Just check if there is a nickname set for this buddy or if we'd have to
258   generate one. */
259int nick_saved( bee_user_t *bu )
260{
261        char *store_handle, *found;
262       
263        store_handle = clean_handle( bu->handle );
264        found = g_hash_table_lookup( bu->ic->acc->nicks, store_handle );
265        g_free( store_handle );
266       
267        return found != NULL;
268}
269
270void nick_del( bee_user_t *bu )
271{
272        g_hash_table_remove( bu->ic->acc->nicks, bu->handle );
273}
274
275
276void nick_strip( irc_t *irc, char *nick )
277{
278        int len = 0;
279       
280        if( irc && ( irc->status & IRC_UTF8_NICKS ) )
281        {
282                gunichar c;
283                char *p = nick, *n, tmp[strlen(nick)+1];
284               
285                while( p && *p )
286                {
287                        c = g_utf8_get_char_validated( p, -1 );
288                        n = g_utf8_find_next_char( p, NULL );
289                       
290                        if( ( c < 0x7f && !( strchr( nick_lc_chars, c ) ||
291                                             strchr( nick_uc_chars, c ) ) ) ||
292                            !g_unichar_isgraph( c ) )
293                        {
294                                strcpy( tmp, n );
295                                strcpy( p, tmp );
296                        }
297                        else
298                                p = n;
299                }
300                if( p )
301                        len = p - nick;
302        }
303        else
304        {
305                int i;
306               
307                for( i = len = 0; nick[i] && len < MAX_NICK_LENGTH; i++ )
308                {
309                        if( strchr( nick_lc_chars, nick[i] ) || 
310                            strchr( nick_uc_chars, nick[i] ) )
311                        {
312                                nick[len] = nick[i];
313                                len++;
314                        }
315                }
316        }
317        if( isdigit( nick[0] ) )
318        {
319                char *orig;
320               
321                /* First character of a nick can't be a digit, so insert an
322                   underscore if necessary. */
323                orig = g_strdup( nick );
324                g_snprintf( nick, MAX_NICK_LENGTH, "_%s", orig );
325                g_free( orig );
326                len ++;
327        }
328        while( len <= MAX_NICK_LENGTH )
329                nick[len++] = '\0';
330}
331
332gboolean nick_ok( irc_t *irc, const char *nick )
333{
334        const char *s;
335       
336        /* Empty/long nicks are not allowed, nor numbers at [0] */
337        if( !*nick || isdigit( nick[0] ) || strlen( nick ) > MAX_NICK_LENGTH )
338                return 0;
339       
340        if( irc && ( irc->status & IRC_UTF8_NICKS ) )
341        {
342                gunichar c;
343                const char *p = nick, *n;
344               
345                while( p && *p )
346                {
347                        c = g_utf8_get_char_validated( p, -1 );
348                        n = g_utf8_find_next_char( p, NULL );
349                       
350                        if( ( c < 0x7f && !( strchr( nick_lc_chars, c ) ||
351                                             strchr( nick_uc_chars, c ) ) ) ||
352                            !g_unichar_isgraph( c ) )
353                        {
354                                return FALSE;
355                        }
356                        p = n;
357                }
358        }
359        else
360        {
361                for( s = nick; *s; s ++ )
362                        if( !strchr( nick_lc_chars, *s ) && !strchr( nick_uc_chars, *s ) )
363                                return FALSE;
364        }
365       
366        return TRUE;
367}
368
369int nick_lc( irc_t *irc, char *nick )
370{
371        static char tab[128] = { 0 };
372        int i;
373       
374        if( tab['A'] == 0 )
375                for( i = 0; nick_lc_chars[i]; i ++ )
376                {
377                        tab[(int)nick_uc_chars[i]] = nick_lc_chars[i];
378                        tab[(int)nick_lc_chars[i]] = nick_lc_chars[i];
379                }
380       
381        if( irc && ( irc->status & IRC_UTF8_NICKS ) )
382        {
383                gchar *down = g_utf8_strdown( nick, -1 );
384                if( strlen( down ) > strlen( nick ) )
385                {
386                        /* Well crap. Corrupt it if we have to. */
387                        down[strlen(nick)] = '\0';
388                }
389                strcpy( nick, down );
390                g_free( down );
391        }
392       
393        for( i = 0; nick[i]; i ++ )
394                if( nick[i] < 0x7f )
395                        nick[i] = tab[(int)nick[i]];
396       
397        return nick_ok( irc, nick );
398}
399
400int nick_cmp( irc_t *irc, const char *a, const char *b )
401{
402        char aa[1024] = "", bb[1024] = "";
403       
404        strncpy( aa, a, sizeof( aa ) - 1 );
405        strncpy( bb, b, sizeof( bb ) - 1 );
406        if( nick_lc( irc, aa ) && nick_lc( irc, bb ) )
407        {
408                return( strcmp( aa, bb ) );
409        }
410        else
411        {
412                return( -1 );   /* Hmm... Not a clear answer.. :-/ */
413        }
414}
Note: See TracBrowser for help on using the repository browser.