/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* Some stuff to fetch, save and handle nicknames for your buddies */ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License with the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define BITLBEE_CORE #include "bitlbee.h" /* Character maps, _lc_[x] == _uc_[x] (but uppercase), according to the RFC's. With one difference, we allow dashes. These are used to do uc/lc conversions and strip invalid chars. */ static char *nick_lc_chars = "0123456789abcdefghijklmnopqrstuvwxyz{}^`-_|"; static char *nick_uc_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]~`-_\\"; /* Store handles in lower case and strip spaces, because AIM is braindead. */ static char *clean_handle( const char *orig ) { char *new = g_malloc( strlen( orig ) + 1 ); int i = 0; do { if (*orig != ' ') new[i++] = tolower( *orig ); } while (*(orig++)); return new; } 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( irc, store_nick ); g_hash_table_replace( acc->nicks, store_handle, store_nick ); } void nick_set( bee_user_t *bu, const char *nick ) { nick_set_raw( bu->ic->acc, bu->handle, nick ); } 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 ); store_handle = clean_handle( bu->handle ); /* Find out if we stored a nick for this person already. If not, try to generate a sane nick automatically. */ if( ( found_nick = g_hash_table_lookup( bu->ic->acc->nicks, store_handle ) ) ) { strncpy( nick, found_nick, MAX_NICK_LENGTH ); } else if( ( found_nick = nick_gen( bu ) ) ) { strncpy( nick, found_nick, MAX_NICK_LENGTH ); g_free( found_nick ); } else { /* Keep this fallback since nick_gen() can return NULL in some cases. */ char *s; g_snprintf( nick, MAX_NICK_LENGTH, "%s", bu->handle ); if( ( s = strchr( nick, '@' ) ) ) while( *s ) *(s++) = 0; nick_strip( irc, nick ); if( set_getbool( &bu->bee->set, "lcnicks" ) ) nick_lc( irc, nick ); } g_free( store_handle ); /* Make sure the nick doesn't collide with an existing one by adding underscores and that kind of stuff, if necessary. */ nick_dedupe( bu, nick ); return nick; } char *nick_gen( bee_user_t *bu ) { gboolean ok = FALSE; /* Set to true once the nick contains something unique. */ 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; if( *fmt != '%' ) { g_string_append_c( ret, *fmt ); fmt ++; continue; } fmt ++; while( *fmt ) { /* -char means chop off everything from char */ if( *fmt == '-' ) { chop = fmt[1]; if( chop == '\0' ) { g_string_free( ret, TRUE ); return NULL; } fmt += 2; } else if( g_strncasecmp( fmt, "nick", 4 ) == 0 ) { part = bu->nick ? : bu->handle; fmt += 4; ok |= TRUE; break; } else if( g_strncasecmp( fmt, "handle", 6 ) == 0 ) { part = bu->handle; fmt += 6; ok |= TRUE; break; } else if( g_strncasecmp( fmt, "full_name", 9 ) == 0 ) { part = bu->fullname; fmt += 9; ok |= part && *part; break; } else if( g_strncasecmp( fmt, "first_name", 10 ) == 0 ) { part = bu->fullname; fmt += 10; ok |= part && *part; chop = ' '; break; } else if( g_strncasecmp( fmt, "group", 5 ) == 0 ) { part = bu->group ? bu->group->name : NULL; fmt += 5; break; } else if( g_strncasecmp( fmt, "account", 7 ) == 0 ) { part = bu->ic->acc->tag; fmt += 7; break; } else { g_string_free( ret, TRUE ); return NULL; } } 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( !( irc && irc->status & IRC_UTF8_NICKS ) ) part = asc = g_convert_with_fallback( part, -1, "ASCII//TRANSLIT", "UTF-8", "", NULL, NULL, NULL ); if( part ) g_string_append( ret, part ); g_free( asc ); } 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] ) { irc_t *irc = (irc_t*) bu->bee->ui_data; int inf_protection = 256; irc_user_t *iu; /* Now, find out if the nick is already in use at the moment, and make subtle changes to make it unique. */ while( !nick_ok( irc, nick ) || ( ( iu = irc_user_by_name( irc, nick ) ) && iu->bu != bu ) ) { if( strlen( nick ) < ( MAX_NICK_LENGTH - 1 ) ) { nick[strlen(nick)+1] = 0; nick[strlen(nick)] = '_'; } else { nick[0] ++; } if( inf_protection-- == 0 ) { 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; } } } /* Just check if there is a nickname set for this buddy or if we'd have to generate one. */ int nick_saved( bee_user_t *bu ) { char *store_handle, *found; store_handle = clean_handle( bu->handle ); found = g_hash_table_lookup( bu->ic->acc->nicks, store_handle ); g_free( store_handle ); return found != NULL; } void nick_del( bee_user_t *bu ) { g_hash_table_remove( bu->ic->acc->nicks, bu->handle ); } void nick_strip( irc_t *irc, char *nick ) { int len = 0; if( irc && ( irc->status & IRC_UTF8_NICKS ) ) { gunichar c; char *p = nick, *n, tmp[strlen(nick)+1]; 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 ) ) { 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 ); len ++; } while( len <= MAX_NICK_LENGTH ) nick[len++] = '\0'; } 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; 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 TRUE; } int nick_lc( irc_t *irc, char *nick ) { static char tab[128] = { 0 }; int i; if( tab['A'] == 0 ) for( i = 0; nick_lc_chars[i]; i ++ ) { tab[(int)nick_uc_chars[i]] = nick_lc_chars[i]; tab[(int)nick_lc_chars[i]] = nick_lc_chars[i]; } if( irc && ( irc->status & IRC_UTF8_NICKS ) ) { gchar *down = g_utf8_strdown( nick, -1 ); if( strlen( down ) > strlen( nick ) ) { /* 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( nick[i] < 0x7f ) nick[i] = tab[(int)nick[i]]; return nick_ok( irc, nick ); } 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( irc, aa ) && nick_lc( irc, bb ) ) { return( strcmp( aa, bb ) ); } else { return( -1 ); /* Hmm... Not a clear answer.. :-/ */ } }