source: nick.c @ c608891

Last change on this file since c608891 was c608891, checked in by Wilmer van der Gaast <wilmer@…>, at 2013-04-23T16:20:06Z

Simple (and possibly still fragile) support for UTF-8 nicknames.

  • Property mode set to 100644
File size: 9.9 KB
RevLine 
[b7d3cc34]1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
[0e788f5]4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
[b7d3cc34]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
[2e0eaac]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
[5b52a48]35/* Store handles in lower case and strip spaces, because AIM is braindead. */
36static char *clean_handle( const char *orig )
[b7d3cc34]37{
[5b52a48]38        char *new = g_malloc( strlen( orig ) + 1 );
39        int i = 0;
[b7d3cc34]40       
[5b52a48]41        do {
42                if (*orig != ' ')
43                        new[i++] = tolower( *orig );
[b7d3cc34]44        }
[5b52a48]45        while (*(orig++));
[b7d3cc34]46       
[5b52a48]47        return new;
48}
49
[b1f818b]50void nick_set_raw( account_t *acc, const char *handle, const char *nick )
[5b52a48]51{
52        char *store_handle, *store_nick = g_malloc( MAX_NICK_LENGTH + 1 );
[e277e80]53        irc_t *irc = (irc_t *) acc->bee->ui_data;
[b7d3cc34]54       
[5b52a48]55        store_handle = clean_handle( handle );
[b1f818b]56        store_nick[MAX_NICK_LENGTH] = '\0';
[5b52a48]57        strncpy( store_nick, nick, MAX_NICK_LENGTH );
[e277e80]58        nick_strip( irc, store_nick );
[b7d3cc34]59       
[5b52a48]60        g_hash_table_replace( acc->nicks, store_handle, store_nick );
[b7d3cc34]61}
62
[b1f818b]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 )
[b7d3cc34]69{
70        static char nick[MAX_NICK_LENGTH+1];
[5b52a48]71        char *store_handle, *found_nick;
[e277e80]72        irc_t *irc = (irc_t *) bu->bee->ui_data;
[b7d3cc34]73       
74        memset( nick, 0, MAX_NICK_LENGTH + 1 );
75       
[b1f818b]76        store_handle = clean_handle( bu->handle );
[5b52a48]77        /* Find out if we stored a nick for this person already. If not, try
78           to generate a sane nick automatically. */
[b1f818b]79        if( ( found_nick = g_hash_table_lookup( bu->ic->acc->nicks, store_handle ) ) )
[5b52a48]80        {
81                strncpy( nick, found_nick, MAX_NICK_LENGTH );
82        }
[2e0eaac]83        else if( ( found_nick = nick_gen( bu ) ) )
84        {
85                strncpy( nick, found_nick, MAX_NICK_LENGTH );
86                g_free( found_nick );
87        }
[5b52a48]88        else
[b7d3cc34]89        {
[2e0eaac]90                /* Keep this fallback since nick_gen() can return NULL in some cases. */
[b7d3cc34]91                char *s;
92               
[b1f818b]93                g_snprintf( nick, MAX_NICK_LENGTH, "%s", bu->handle );
[b7d3cc34]94                if( ( s = strchr( nick, '@' ) ) )
95                        while( *s )
96                                *(s++) = 0;
97               
[e277e80]98                nick_strip( irc, nick );
[b1f818b]99                if( set_getbool( &bu->bee->set, "lcnicks" ) )
[e277e80]100                        nick_lc( irc, nick );
[b7d3cc34]101        }
[5b52a48]102        g_free( store_handle );
[b7d3cc34]103       
[d06eabf]104        /* Make sure the nick doesn't collide with an existing one by adding
105           underscores and that kind of stuff, if necessary. */
[b1f818b]106        nick_dedupe( bu, nick );
[d06eabf]107       
108        return nick;
109}
110
[b1f818b]111char *nick_gen( bee_user_t *bu )
[d06eabf]112{
[2e0eaac]113        gboolean ok = FALSE; /* Set to true once the nick contains something unique. */
[c608891]114        GString *ret = g_string_sized_new( MAX_NICK_LENGTH + 1 );
115        char *rets;
116        irc_t *irc = (irc_t *) bu->bee->ui_data;
[2e0eaac]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        {
[f7d12f7]122                char *part = NULL, chop = '\0', *asc = NULL;
[2e0eaac]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' )
[9ed0081]139                                {
140                                        g_string_free( ret, TRUE );
[2e0eaac]141                                        return NULL;
[9ed0081]142                                }
[2e0eaac]143                                fmt += 2;
144                        }
[09dfb68]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                        }
[2e0eaac]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                        }
[09dfb68]174                        else if( g_strncasecmp( fmt, "group", 5 ) == 0 )
175                        {
176                                part = bu->group ? bu->group->name : NULL;
177                                fmt += 5;
178                                break;
179                        }
[3b3c50d9]180                        else if( g_strncasecmp( fmt, "account", 7 ) == 0 )
181                        {
182                                part = bu->ic->acc->tag;
183                                fmt += 7;
184                                break;
185                        }
[2e0eaac]186                        else
187                        {
[9ed0081]188                                g_string_free( ret, TRUE );
[2e0eaac]189                                return NULL;
190                        }
191                }
192               
[c608891]193                if( !part )
194                        continue;
195               
[badd148]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. */
[c608891]200                if( !( irc && irc->status & IRC_UTF8_NICKS ) )
[560129a]201                        part = asc = g_convert_with_fallback( part, -1, "ASCII//TRANSLIT",
202                                                              "UTF-8", "", NULL, NULL, NULL );
[badd148]203               
[c608891]204                if( part )
205                        g_string_append( ret, part );
[badd148]206                g_free( asc );
[2e0eaac]207        }
208       
[c608891]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;
[b1f818b]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;
[d06eabf]223        int inf_protection = 256;
[badd148]224        irc_user_t *iu;
[d06eabf]225       
[5b52a48]226        /* Now, find out if the nick is already in use at the moment, and make
227           subtle changes to make it unique. */
[e277e80]228        while( !nick_ok( irc, nick ) ||
[badd148]229               ( ( iu = irc_user_by_name( irc, nick ) ) && iu->bu != bu ) )
[b7d3cc34]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                       
[c608891]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                       
[b7d3cc34]252                        break;
253                }
254        }
255}
256
[d323394c]257/* Just check if there is a nickname set for this buddy or if we'd have to
258   generate one. */
[b1f818b]259int nick_saved( bee_user_t *bu )
[d323394c]260{
261        char *store_handle, *found;
262       
[b1f818b]263        store_handle = clean_handle( bu->handle );
264        found = g_hash_table_lookup( bu->ic->acc->nicks, store_handle );
[d323394c]265        g_free( store_handle );
266       
267        return found != NULL;
268}
269
[b1f818b]270void nick_del( bee_user_t *bu )
[b7d3cc34]271{
[b1f818b]272        g_hash_table_remove( bu->ic->acc->nicks, bu->handle );
[b7d3cc34]273}
274
275
[e277e80]276void nick_strip( irc_t *irc, char *nick )
[b7d3cc34]277{
[c608891]278        int len = 0;
[b7d3cc34]279       
[c608891]280        if( irc && ( irc->status & IRC_UTF8_NICKS ) )
[b7d3cc34]281        {
[c608891]282                gunichar c;
283                char *p = nick, *n, tmp[strlen(nick)+1];
284               
285                while( p && *p )
[b7d3cc34]286                {
[c608891]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                        }
[b7d3cc34]315                }
316        }
[0f47613]317        if( isdigit( nick[0] ) )
318        {
319                char *orig;
320               
[c608891]321                /* First character of a nick can't be a digit, so insert an
322                   underscore if necessary. */
[0f47613]323                orig = g_strdup( nick );
324                g_snprintf( nick, MAX_NICK_LENGTH, "_%s", orig );
325                g_free( orig );
[c608891]326                len ++;
[0f47613]327        }
[c608891]328        while( len <= MAX_NICK_LENGTH )
329                nick[len++] = '\0';
[b7d3cc34]330}
331
[c608891]332gboolean nick_ok( irc_t *irc, const char *nick )
[b7d3cc34]333{
[1eddf6b]334        const char *s;
[b7d3cc34]335       
[0f47613]336        /* Empty/long nicks are not allowed, nor numbers at [0] */
337        if( !*nick || isdigit( nick[0] ) || strlen( nick ) > MAX_NICK_LENGTH )
[c608891]338                return 0;
[b7d3cc34]339       
[c608891]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        }
[b7d3cc34]365       
[c608891]366        return TRUE;
[b7d3cc34]367}
368
[e277e80]369int nick_lc( irc_t *irc, char *nick )
[b7d3cc34]370{
[d323394c]371        static char tab[128] = { 0 };
[b7d3cc34]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       
[c608891]381        if( irc && ( irc->status & IRC_UTF8_NICKS ) )
[b7d3cc34]382        {
[c608891]383                gchar *down = g_utf8_strdown( nick, -1 );
384                if( strlen( down ) > strlen( nick ) )
[b7d3cc34]385                {
[c608891]386                        /* Well crap. Corrupt it if we have to. */
387                        down[strlen(nick)] = '\0';
[b7d3cc34]388                }
[c608891]389                strcpy( nick, down );
390                g_free( down );
391        }
[b7d3cc34]392       
393        for( i = 0; nick[i]; i ++ )
394                nick[i] = tab[(int)nick[i]];
395       
[c608891]396        return nick_ok( irc, nick );
[b7d3cc34]397}
398
[e277e80]399int nick_cmp( irc_t *irc, const char *a, const char *b )
[b7d3cc34]400{
401        char aa[1024] = "", bb[1024] = "";
402       
403        strncpy( aa, a, sizeof( aa ) - 1 );
404        strncpy( bb, b, sizeof( bb ) - 1 );
[e277e80]405        if( nick_lc( irc, aa ) && nick_lc( irc, bb ) )
[b7d3cc34]406        {
407                return( strcmp( aa, bb ) );
408        }
409        else
410        {
411                return( -1 );   /* Hmm... Not a clear answer.. :-/ */
412        }
413}
Note: See TracBrowser for help on using the repository browser.