diff options
author | Wilmer van der Gaast <wilmer@gaast.net> | 2013-06-16 13:42:39 +0100 |
---|---|---|
committer | Wilmer van der Gaast <wilmer@gaast.net> | 2013-06-16 13:42:39 +0100 |
commit | 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f (patch) | |
tree | d3ae6d42d29b7b226abb017ab5dfb07536d2493b | |
parent | ab19567e25a35beb23f922303d1f60ed13228356 (diff) | |
parent | 5cb946132871ef97fe9eabacafa62f1064d80423 (diff) |
Merging utf8-nicks branch. This adds a utf8_nicks setting which removes the
ASCII restriction on contact nicknames. Use at your own risk!
-rw-r--r-- | doc/user-guide/commands.xml | 14 | ||||
-rw-r--r-- | ipc.c | 4 | ||||
-rw-r--r-- | irc.c | 19 | ||||
-rw-r--r-- | irc.h | 2 | ||||
-rw-r--r-- | irc_commands.c | 8 | ||||
-rw-r--r-- | irc_im.c | 2 | ||||
-rw-r--r-- | irc_user.c | 6 | ||||
-rw-r--r-- | nick.c | 207 | ||||
-rw-r--r-- | nick.h | 10 | ||||
-rw-r--r-- | root_commands.c | 4 | ||||
-rw-r--r-- | storage_xml.c | 6 |
11 files changed, 168 insertions, 114 deletions
diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml index 3af469c6..62e1e855 100644 --- a/doc/user-guide/commands.xml +++ b/doc/user-guide/commands.xml @@ -1619,6 +1619,20 @@ </description> </bitlbee-setting> + <bitlbee-setting name="utf8_nicks" type="boolean" scope="global"> + <default>false</default> + + <description> + <para> + Officially, IRC nicknames are restricted to ASCII. Recently some clients and servers started supporting Unicode nicknames though. To enable UTF-8 nickname support (contacts only) in BitlBee, enable this setting. + </para> + + <para> + To avoid confusing old clients, this setting is disabled by default. Be careful when you try it, and be prepared to be locked out of your BitlBee in case your client interacts poorly with UTF-8 nicknames. + </para> + </description> + </bitlbee-setting> + <bitlbee-setting name="web_aware" type="string" scope="account"> <default>false</default> @@ -151,7 +151,7 @@ void ipc_master_cmd_identify( irc_t *data, char **cmd ) { old = l->data; if( child != old && - old->nick && nick_cmp( old->nick, child->nick ) == 0 && + old->nick && nick_cmp( NULL, old->nick, child->nick ) == 0 && old->password && strcmp( old->password, child->password ) == 0 ) break; } @@ -297,7 +297,7 @@ static void ipc_child_cmd_kill( irc_t *irc, char **cmd ) if( !( irc->status & USTATUS_LOGGED_IN ) ) return; - if( nick_cmp( cmd[1], irc->user->nick ) != 0 ) + if( nick_cmp( NULL, cmd[1], irc->user->nick ) != 0 ) return; /* It's not for us. */ irc_write( irc, ":%s!%s@%s KILL %s :%s", irc->root->nick, irc->root->nick, irc->root->host, irc->user->nick, cmd[2] ); @@ -34,6 +34,7 @@ static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond ); static char *set_eval_charset( set_t *set, char *value ); static char *set_eval_password( set_t *set, char *value ); static char *set_eval_bw_compat( set_t *set, char *value ); +static char *set_eval_utf8_nicks( set_t *set, char *value ); irc_t *irc_new( int fd ) { @@ -133,6 +134,7 @@ irc_t *irc_new( int fd ) s = set_add( &b->set, "timezone", "local", set_eval_timezone, irc ); s = set_add( &b->set, "to_char", ": ", set_eval_to_char, irc ); s = set_add( &b->set, "typing_notice", "false", set_eval_bool, irc ); + s = set_add( &b->set, "utf8_nicks", "false", set_eval_utf8_nicks, irc ); irc->root = iu = irc_user_new( irc, ROOT_NICK ); iu->host = g_strdup( myhost ); @@ -961,6 +963,23 @@ static char *set_eval_bw_compat( set_t *set, char *value ) return SET_INVALID; } +static char *set_eval_utf8_nicks( set_t *set, char *value ) +{ + irc_t *irc = set->data; + gboolean val = bool2int( value ); + + /* Do *NOT* unset this flag in the middle of a session. There will + be UTF-8 nicks around already so if we suddenly disable support + for them, various functions might behave strangely. */ + if( val ) + irc->status |= IRC_UTF8_NICKS; + else if( irc->status & IRC_UTF8_NICKS ) + irc_rootmsg( irc, "You need to reconnect to BitlBee for this " + "change to take effect." ); + + return set_eval_bool( set, value ); +} + void register_irc_plugin( const struct irc_plugin *p ) { irc_plugins = g_slist_prepend( irc_plugins, (gpointer) p ); @@ -61,6 +61,8 @@ typedef enum OPER_HACK_REGISTER = 0x200, OPER_HACK_ACCOUNT_ADD = 0x400, OPER_HACK_ANY = 0x3700, /* To check for them all at once. */ + + IRC_UTF8_NICKS = 0x10000, /* Disable ASCII restrictions on buddy nicks. */ } irc_status_t; struct irc_user; diff --git a/irc_commands.c b/irc_commands.c index 740d3eb2..3db243bf 100644 --- a/irc_commands.c +++ b/irc_commands.c @@ -79,7 +79,7 @@ static void irc_cmd_nick( irc_t *irc, char **cmd ) { irc_send_num( irc, 433, "%s :This nick is already in use", cmd[1] ); } - else if( !nick_ok( cmd[1] ) ) + else if( !nick_ok( NULL, cmd[1] ) ) { /* [SH] Invalid characters. */ irc_send_num( irc, 432, "%s :This nick contains invalid characters", cmd[1] ); @@ -290,7 +290,7 @@ static void irc_cmd_mode( irc_t *irc, char **cmd ) } else { - if( nick_cmp( cmd[1], irc->user->nick ) == 0 ) + if( nick_cmp( NULL, cmd[1], irc->user->nick ) == 0 ) { if( cmd[2] ) irc_umode_set( irc, cmd[2], 0 ); @@ -391,7 +391,7 @@ static void irc_cmd_notice( irc_t *irc, char **cmd ) /* At least for now just echo. IIRC some IRC clients use self-notices for lag checks, so try to support that. */ - if( nick_cmp( cmd[1], irc->user->nick ) == 0 ) + if( nick_cmp( NULL, cmd[1], irc->user->nick ) == 0 ) irc_send_msg( irc->user, "NOTICE", irc->user->nick, cmd[2], NULL ); else if( ( iu = irc_user_by_name( irc, cmd[1] ) ) ) iu->f->privmsg( iu, cmd[2] ); @@ -592,7 +592,7 @@ static void irc_cmd_watch( irc_t *irc, char **cmd ) break; nick = g_strdup( cmd[i] + 1 ); - nick_lc( nick ); + nick_lc( irc, nick ); iu = irc_user_by_name( irc, nick ); @@ -696,7 +696,7 @@ static gboolean bee_irc_chat_name_hint( bee_t *bee, struct groupchat *c, const c stripped[MAX_NICK_LENGTH] = '\0'; irc_channel_name_strip( stripped ); if( set_getbool( &bee->set, "lcnicks" ) ) - nick_lc( stripped ); + nick_lc( irc, stripped ); if( stripped[0] == '\0' ) return FALSE; @@ -35,7 +35,7 @@ irc_user_t *irc_user_new( irc_t *irc, const char *nick ) iu->user = iu->host = iu->fullname = iu->nick; iu->key = g_strdup( nick ); - nick_lc( iu->key ); + nick_lc( irc, iu->key ); /* Using the hash table for speed and irc->users for easy iteration through the list (since the GLib API doesn't have anything sane for that.) */ @@ -106,7 +106,7 @@ irc_user_t *irc_user_by_name( irc_t *irc, const char *nick ) char key[strlen(nick)+1]; strcpy( key, nick ); - if( nick_lc( key ) ) + if( nick_lc( irc, key ) ) return g_hash_table_lookup( irc->nick_user_hash, key ); else return NULL; @@ -120,7 +120,7 @@ int irc_user_set_nick( irc_user_t *iu, const char *new ) GSList *cl; strcpy( key, new ); - if( iu == NULL || !nick_lc( key ) || + if( iu == NULL || !nick_lc( irc, key ) || ( ( new_iu = irc_user_by_name( irc, new ) ) && new_iu != iu ) ) return 0; @@ -50,11 +50,12 @@ static char *clean_handle( const char *orig ) void nick_set_raw( account_t *acc, const char *handle, const char *nick ) { char *store_handle, *store_nick = g_malloc( MAX_NICK_LENGTH + 1 ); + irc_t *irc = (irc_t *) acc->bee->ui_data; store_handle = clean_handle( handle ); store_nick[MAX_NICK_LENGTH] = '\0'; strncpy( store_nick, nick, MAX_NICK_LENGTH ); - nick_strip( store_nick ); + nick_strip( irc, store_nick ); g_hash_table_replace( acc->nicks, store_handle, store_nick ); } @@ -68,6 +69,7 @@ char *nick_get( bee_user_t *bu ) { static char nick[MAX_NICK_LENGTH+1]; char *store_handle, *found_nick; + irc_t *irc = (irc_t *) bu->bee->ui_data; memset( nick, 0, MAX_NICK_LENGTH + 1 ); @@ -93,9 +95,9 @@ char *nick_get( bee_user_t *bu ) while( *s ) *(s++) = 0; - nick_strip( nick ); + nick_strip( irc, nick ); if( set_getbool( &bu->bee->set, "lcnicks" ) ) - nick_lc( nick ); + nick_lc( irc, nick ); } g_free( store_handle ); @@ -109,14 +111,15 @@ char *nick_get( bee_user_t *bu ) char *nick_gen( bee_user_t *bu ) { gboolean ok = FALSE; /* Set to true once the nick contains something unique. */ - GString *ret = g_string_new( "" ); + GString *ret = g_string_sized_new( MAX_NICK_LENGTH + 1 ); + char *rets; + irc_t *irc = (irc_t *) bu->bee->ui_data; char *fmt = set_getstr( &bu->ic->acc->set, "nick_format" ) ? : set_getstr( &bu->bee->set, "nick_format" ); while( fmt && *fmt && ret->len < MAX_NICK_LENGTH ) { char *part = NULL, chop = '\0', *asc = NULL; - int len = MAX_NICK_LENGTH; if( *fmt != '%' ) { @@ -139,13 +142,6 @@ char *nick_gen( bee_user_t *bu ) } fmt += 2; } - else if( isdigit( *fmt ) ) - { - len = 0; - /* Grab a number. */ - while( isdigit( *fmt ) ) - len = len * 10 + ( *(fmt++) - '0' ); - } else if( g_strncasecmp( fmt, "nick", 4 ) == 0 ) { part = bu->nick ? : bu->handle; @@ -194,31 +190,31 @@ char *nick_gen( bee_user_t *bu ) } } + if( !part ) + continue; + /* Credits to Josay_ in #bitlbee for this idea. //TRANSLIT should do lossy/approximate conversions, so letters with accents don't just get stripped. Note that it depends on LC_CTYPE being set to something other than C/POSIX. */ - if( part ) + if( !( irc && irc->status & IRC_UTF8_NICKS ) ) part = asc = g_convert_with_fallback( part, -1, "ASCII//TRANSLIT", "UTF-8", "", NULL, NULL, NULL ); - if( ret->len == 0 && part && isdigit( *part ) ) - g_string_append_c( ret, '_' ); - - while( part && *part && *part != chop && len > 0 ) - { - if( strchr( nick_lc_chars, *part ) || - strchr( nick_uc_chars, *part ) ) - g_string_append_c( ret, *part ); - - part ++; - len --; - } + if( part ) + g_string_append( ret, part ); g_free( asc ); } - /* This returns NULL if the nick is empty or otherwise not ok. */ - return g_string_free( ret, ret->len == 0 || !ok ); + rets = g_string_free( ret, FALSE ); + if( ok && rets && *rets ) + { + nick_strip( irc, rets ); + rets[MAX_NICK_LENGTH] = '\0'; + return rets; + } + g_free( rets ); + return NULL; } void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] ) @@ -229,7 +225,7 @@ void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] ) /* Now, find out if the nick is already in use at the moment, and make subtle changes to make it unique. */ - while( !nick_ok( nick ) || + while( !nick_ok( irc, nick ) || ( ( iu = irc_user_by_name( irc, nick ) ) && iu->bu != bu ) ) { if( strlen( nick ) < ( MAX_NICK_LENGTH - 1 ) ) @@ -244,24 +240,15 @@ void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] ) if( inf_protection-- == 0 ) { - int i; - - irc_rootmsg( irc, "Warning: Almost had an infinite loop in nick_get()! " - "This used to be a fatal BitlBee bug, but we tried to fix it. " - "This message should *never* appear anymore. " - "If it does, please *do* send us a bug report! " - "Please send all the following lines in your report:" ); - - irc_rootmsg( irc, "Trying to get a sane nick for handle %s", bu->handle ); - for( i = 0; i < MAX_NICK_LENGTH; i ++ ) - irc_rootmsg( irc, "Char %d: %c/%d", i, nick[i], nick[i] ); - - irc_rootmsg( irc, "FAILED. Returning an insane nick now. Things might break. " - "Good luck, and please don't forget to paste the lines up here " - "in #bitlbee on OFTC or in a mail to wilmer@gaast.net" ); - g_snprintf( nick, MAX_NICK_LENGTH + 1, "xx%x", rand() ); + irc_rootmsg( irc, "Warning: Something went wrong while trying " + "to generate a nickname for contact %s on %s.", + bu->handle, bu->ic->acc->tag ); + irc_rootmsg( irc, "This might be a bug in BitlBee, or the result " + "of a faulty nick_format setting. Will use %s " + "instead.", nick ); + break; } } @@ -286,48 +273,100 @@ void nick_del( bee_user_t *bu ) } -void nick_strip( char *nick ) +void nick_strip( irc_t *irc, char *nick ) { - int i, j; + int len = 0; - for( i = j = 0; nick[i] && j < MAX_NICK_LENGTH; i++ ) + if( irc && ( irc->status & IRC_UTF8_NICKS ) ) { - if( strchr( nick_lc_chars, nick[i] ) || - strchr( nick_uc_chars, nick[i] ) ) + gunichar c; + char *p = nick, *n, tmp[strlen(nick)+1]; + + while( p && *p ) { - nick[j] = nick[i]; - j++; + c = g_utf8_get_char_validated( p, -1 ); + n = g_utf8_find_next_char( p, NULL ); + + if( ( c < 0x7f && !( strchr( nick_lc_chars, c ) || + strchr( nick_uc_chars, c ) ) ) || + !g_unichar_isgraph( c ) ) + { + strcpy( tmp, n ); + strcpy( p, tmp ); + } + else + p = n; + } + if( p ) + len = p - nick; + } + else + { + int i; + + for( i = len = 0; nick[i] && len < MAX_NICK_LENGTH; i++ ) + { + if( strchr( nick_lc_chars, nick[i] ) || + strchr( nick_uc_chars, nick[i] ) ) + { + nick[len] = nick[i]; + len++; + } } } if( isdigit( nick[0] ) ) { char *orig; + /* First character of a nick can't be a digit, so insert an + underscore if necessary. */ orig = g_strdup( nick ); g_snprintf( nick, MAX_NICK_LENGTH, "_%s", orig ); g_free( orig ); - j ++; + len ++; } - while( j <= MAX_NICK_LENGTH ) - nick[j++] = '\0'; + while( len <= MAX_NICK_LENGTH ) + nick[len++] = '\0'; } -int nick_ok( const char *nick ) +gboolean nick_ok( irc_t *irc, const char *nick ) { const char *s; /* Empty/long nicks are not allowed, nor numbers at [0] */ if( !*nick || isdigit( nick[0] ) || strlen( nick ) > MAX_NICK_LENGTH ) - return( 0 ); + return 0; - for( s = nick; *s; s ++ ) - if( !strchr( nick_lc_chars, *s ) && !strchr( nick_uc_chars, *s ) ) - return( 0 ); + if( irc && ( irc->status & IRC_UTF8_NICKS ) ) + { + gunichar c; + const char *p = nick, *n; + + while( p && *p ) + { + c = g_utf8_get_char_validated( p, -1 ); + n = g_utf8_find_next_char( p, NULL ); + + if( ( c < 0x7f && !( strchr( nick_lc_chars, c ) || + strchr( nick_uc_chars, c ) ) ) || + !g_unichar_isgraph( c ) ) + { + return FALSE; + } + p = n; + } + } + else + { + for( s = nick; *s; s ++ ) + if( !strchr( nick_lc_chars, *s ) && !strchr( nick_uc_chars, *s ) ) + return FALSE; + } - return( 1 ); + return TRUE; } -int nick_lc( char *nick ) +int nick_lc( irc_t *irc, char *nick ) { static char tab[128] = { 0 }; int i; @@ -339,47 +378,32 @@ int nick_lc( char *nick ) tab[(int)nick_lc_chars[i]] = nick_lc_chars[i]; } - for( i = 0; nick[i]; i ++ ) + if( irc && ( irc->status & IRC_UTF8_NICKS ) ) { - if( !tab[(int)nick[i]] ) - return( 0 ); - - nick[i] = tab[(int)nick[i]]; - } - - return( 1 ); -} - -int nick_uc( char *nick ) -{ - static char tab[128] = { 0 }; - int i; - - if( tab['A'] == 0 ) - for( i = 0; nick_lc_chars[i]; i ++ ) + gchar *down = g_utf8_strdown( nick, -1 ); + if( strlen( down ) > strlen( nick ) ) { - tab[(int)nick_uc_chars[i]] = nick_uc_chars[i]; - tab[(int)nick_lc_chars[i]] = nick_uc_chars[i]; + /* Well crap. Corrupt it if we have to. */ + down[strlen(nick)] = '\0'; } + strcpy( nick, down ); + g_free( down ); + } for( i = 0; nick[i]; i ++ ) - { - if( !tab[(int)nick[i]] ) - return( 0 ); - - nick[i] = tab[(int)nick[i]]; - } + if( nick[i] < 0x7f ) + nick[i] = tab[(int)nick[i]]; - return( 1 ); + return nick_ok( irc, nick ); } -int nick_cmp( const char *a, const char *b ) +int nick_cmp( irc_t *irc, const char *a, const char *b ) { char aa[1024] = "", bb[1024] = ""; strncpy( aa, a, sizeof( aa ) - 1 ); strncpy( bb, b, sizeof( bb ) - 1 ); - if( nick_lc( aa ) && nick_lc( bb ) ) + if( nick_lc( irc, aa ) && nick_lc( irc, bb ) ) { return( strcmp( aa, bb ) ); } @@ -388,8 +412,3 @@ int nick_cmp( const char *a, const char *b ) return( -1 ); /* Hmm... Not a clear answer.. :-/ */ } } - -char *nick_dup( const char *nick ) -{ - return g_strndup( nick, MAX_NICK_LENGTH ); -} @@ -30,10 +30,10 @@ char *nick_gen( bee_user_t *bu ); void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] ); int nick_saved( bee_user_t *bu ); void nick_del( bee_user_t *bu ); -void nick_strip( char *nick ); -int nick_ok( const char *nick ); -int nick_lc( char *nick ); -int nick_uc( char *nick ); -int nick_cmp( const char *a, const char *b ); +void nick_strip( irc_t *irc, char *nick ); +gboolean nick_ok( irc_t *irc, const char *nick ); +int nick_lc( irc_t *irc, char *nick ); +int nick_uc( irc_t *irc, char *nick ); +int nick_cmp( irc_t *irc, const char *a, const char *b ); char *nick_dup( const char *nick ); diff --git a/root_commands.c b/root_commands.c index 77f40060..0bd16163 100644 --- a/root_commands.c +++ b/root_commands.c @@ -701,7 +701,7 @@ static void cmd_add( irc_t *irc, char **cmd ) if( cmd[3] ) { - if( !nick_ok( cmd[3] ) ) + if( !nick_ok( irc, cmd[3] ) ) { irc_rootmsg( irc, "The requested nick `%s' is invalid", cmd[3] ); return; @@ -843,7 +843,7 @@ static void cmd_rename( irc_t *irc, char **cmd ) { irc_rootmsg( irc, "Use /nick to change your own nickname" ); } - else if( !nick_ok( cmd[2] ) ) + else if( !nick_ok( irc, cmd[2] ) ) { irc_rootmsg( irc, "Nick `%s' is invalid", cmd[2] ); } diff --git a/storage_xml.c b/storage_xml.c index b6f4a037..d32ed25f 100644 --- a/storage_xml.c +++ b/storage_xml.c @@ -180,7 +180,7 @@ static storage_status_t xml_load_real( irc_t *irc, const char *my_nick, const ch xd->irc = irc; strncpy( xd->given_nick, my_nick, MAX_NICK_LENGTH ); xd->given_nick[MAX_NICK_LENGTH] = '\0'; - nick_lc( xd->given_nick ); + nick_lc( NULL, xd->given_nick ); xd->given_pass = (char*) password; fn = g_strconcat( global.conf->configdir, xd->given_nick, ".xml", NULL ); @@ -367,7 +367,7 @@ static storage_status_t xml_save( irc_t *irc, int overwrite ) int fd; path2 = g_strdup( irc->user->nick ); - nick_lc( path2 ); + nick_lc( NULL, path2 ); g_snprintf( path, sizeof( path ) - 20, "%s%s%s", global.conf->configdir, path2, ".xml" ); g_free( path2 ); @@ -423,7 +423,7 @@ static storage_status_t xml_remove( const char *nick, const char *password ) return status; lc = g_strdup( nick ); - nick_lc( lc ); + nick_lc( NULL, lc ); g_snprintf( s, 511, "%s%s%s", global.conf->configdir, lc, ".xml" ); g_free( lc ); |