source: nick.c @ 19ba56e

Last change on this file since 19ba56e was 9c7ef22, checked in by dequis <dx@…>, at 2016-12-26T00:31:18Z

nick_strip: accept null irc parameter

Sometimes I'm randomly reminded that we have a test suite.

  • Property mode set to 100644
File size: 10.9 KB
RevLine 
[5ebff60]1/********************************************************************\
[b7d3cc34]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;
[6f10697]22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
[b7d3cc34]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. */
[5ebff60]36static char *clean_handle(const char *orig)
[b7d3cc34]37{
[5ebff60]38        char *new = g_malloc(strlen(orig) + 1);
[5b52a48]39        int i = 0;
[5ebff60]40
[5b52a48]41        do {
[5ebff60]42                if (*orig != ' ') {
43                        new[i++] = g_ascii_tolower(*orig);
44                }
45        } while (*(orig++));
46
[5b52a48]47        return new;
48}
49
[5ebff60]50void nick_set_raw(account_t *acc, const char *handle, const char *nick)
[5b52a48]51{
[5ebff60]52        char *store_handle, *store_nick = g_malloc(MAX_NICK_LENGTH + 1);
[e277e80]53        irc_t *irc = (irc_t *) acc->bee->ui_data;
[5ebff60]54
55        store_handle = clean_handle(handle);
[b1f818b]56        store_nick[MAX_NICK_LENGTH] = '\0';
[5ebff60]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);
[b7d3cc34]61}
62
[5ebff60]63void nick_set(bee_user_t *bu, const char *nick)
[b1f818b]64{
[5ebff60]65        nick_set_raw(bu->ic->acc, bu->handle, nick);
[b1f818b]66}
67
[5ebff60]68char *nick_get(bee_user_t *bu)
[b7d3cc34]69{
[5ebff60]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;
[5ebff60]73
74        memset(nick, 0, MAX_NICK_LENGTH + 1);
75
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. */
[5ebff60]79        if ((found_nick = g_hash_table_lookup(bu->ic->acc->nicks, store_handle))) {
80                strncpy(nick, found_nick, MAX_NICK_LENGTH);
81        } else if ((found_nick = nick_gen(bu))) {
82                strncpy(nick, found_nick, MAX_NICK_LENGTH);
83                g_free(found_nick);
84        } else {
[2e0eaac]85                /* Keep this fallback since nick_gen() can return NULL in some cases. */
[b7d3cc34]86                char *s;
[5ebff60]87
88                g_snprintf(nick, MAX_NICK_LENGTH, "%s", bu->handle);
89                if ((s = strchr(nick, '@'))) {
90                        while (*s) {
[b7d3cc34]91                                *(s++) = 0;
[5ebff60]92                        }
93                }
94
95                nick_strip(irc, nick);
[90254d0]96                if (set_getbool(&bu->bee->set, "nick_lowercase")) {
[5ebff60]97                        nick_lc(irc, nick);
98                }
[b7d3cc34]99        }
[5ebff60]100        g_free(store_handle);
101
[d06eabf]102        /* Make sure the nick doesn't collide with an existing one by adding
103           underscores and that kind of stuff, if necessary. */
[5ebff60]104        nick_dedupe(bu, nick);
105
[d06eabf]106        return nick;
107}
108
[5ebff60]109char *nick_gen(bee_user_t *bu)
[d06eabf]110{
[2e0eaac]111        gboolean ok = FALSE; /* Set to true once the nick contains something unique. */
[5ebff60]112        GString *ret = g_string_sized_new(MAX_NICK_LENGTH + 1);
[c608891]113        char *rets;
114        irc_t *irc = (irc_t *) bu->bee->ui_data;
[5ebff60]115        char *fmt = set_getstr(&bu->ic->acc->set, "nick_format") ? :
116                    set_getstr(&bu->bee->set, "nick_format");
117
118        while (fmt && *fmt && ret->len < MAX_NICK_LENGTH) {
[7e84168]119                char *part = NULL, chop = '\0', *asc = NULL, *s;
120                int len = INT_MAX;
[5ebff60]121
122                if (*fmt != '%') {
123                        g_string_append_c(ret, *fmt);
124                        fmt++;
[2e0eaac]125                        continue;
126                }
[5ebff60]127
128                fmt++;
129                while (*fmt) {
[2e0eaac]130                        /* -char means chop off everything from char */
[5ebff60]131                        if (*fmt == '-') {
[2e0eaac]132                                chop = fmt[1];
[5ebff60]133                                if (chop == '\0') {
134                                        g_string_free(ret, TRUE);
[2e0eaac]135                                        return NULL;
[9ed0081]136                                }
[2e0eaac]137                                fmt += 2;
[5ebff60]138                        } else if (g_ascii_isdigit(*fmt)) {
[7e84168]139                                len = 0;
140                                /* Grab a number. */
[5ebff60]141                                while (g_ascii_isdigit(*fmt)) {
142                                        len = len * 10 + (*(fmt++) - '0');
143                                }
144                        } else if (g_strncasecmp(fmt, "nick", 4) == 0) {
[09dfb68]145                                part = bu->nick ? : bu->handle;
146                                fmt += 4;
147                                ok |= TRUE;
148                                break;
[5ebff60]149                        } else if (g_strncasecmp(fmt, "handle", 6) == 0) {
[2e0eaac]150                                part = bu->handle;
151                                fmt += 6;
152                                ok |= TRUE;
153                                break;
[5ebff60]154                        } else if (g_strncasecmp(fmt, "full_name", 9) == 0) {
[2e0eaac]155                                part = bu->fullname;
156                                fmt += 9;
157                                ok |= part && *part;
158                                break;
[5ebff60]159                        } else if (g_strncasecmp(fmt, "first_name", 10) == 0) {
[2e0eaac]160                                part = bu->fullname;
161                                fmt += 10;
162                                ok |= part && *part;
163                                chop = ' ';
164                                break;
[5ebff60]165                        } else if (g_strncasecmp(fmt, "group", 5) == 0) {
[09dfb68]166                                part = bu->group ? bu->group->name : NULL;
167                                fmt += 5;
168                                break;
[5ebff60]169                        } else if (g_strncasecmp(fmt, "account", 7) == 0) {
[3b3c50d9]170                                part = bu->ic->acc->tag;
171                                fmt += 7;
172                                break;
[5ebff60]173                        } else {
174                                g_string_free(ret, TRUE);
[2e0eaac]175                                return NULL;
176                        }
177                }
[5ebff60]178
179                if (!part) {
[c608891]180                        continue;
[5ebff60]181                }
182
[badd148]183                /* Credits to Josay_ in #bitlbee for this idea. //TRANSLIT
184                   should do lossy/approximate conversions, so letters with
185                   accents don't just get stripped. Note that it depends on
186                   LC_CTYPE being set to something other than C/POSIX. */
[5ebff60]187                if (!(irc && irc->status & IRC_UTF8_NICKS)) {
[3a27896]188                        asc = g_convert_with_fallback(part, -1, "ASCII//TRANSLIT", "UTF-8", "", NULL, NULL, NULL);
189
190                        if (!asc) {
191                                /* If above failed, try again without //TRANSLIT.
192                                   //TRANSLIT is a GNU iconv special and is not POSIX.
193                                   Other platforms may not support it. */
194                                asc = g_convert_with_fallback(part, -1, "ASCII", "UTF-8", "", NULL, NULL, NULL);
195                        }
196
197                        part = asc;
[5ebff60]198                }
199
200                if (part && chop && (s = strchr(part, chop))) {
201                        len = MIN(len, s - part);
202                }
203
204                if (part) {
205                        if (len < INT_MAX) {
206                                g_string_append_len(ret, part, len);
207                        } else {
208                                g_string_append(ret, part);
209                        }
[7e84168]210                }
[5ebff60]211                g_free(asc);
[2e0eaac]212        }
[5ebff60]213
214        rets = g_string_free(ret, FALSE);
215        if (ok && rets && *rets) {
216                nick_strip(irc, rets);
[90254d0]217
218                if (set_getbool(&bu->bee->set, "nick_lowercase")) {
219                        nick_lc(irc, rets);
220                }
221
[5ebff60]222                truncate_utf8(rets, MAX_NICK_LENGTH);
[c608891]223                return rets;
224        }
[5ebff60]225        g_free(rets);
[c608891]226        return NULL;
[b1f818b]227}
228
[e3e2059]229/* Used for nicks and channel names too! */
230void underscore_dedupe(char nick[MAX_NICK_LENGTH + 1])
231{
232        if (strlen(nick) < (MAX_NICK_LENGTH - 1)) {
233                nick[strlen(nick) + 1] = 0;
234                nick[strlen(nick)] = '_';
235        } else {
236                /* We've got no more space for underscores,
237                   so truncate it and replace the last three
238                   chars with a random "_XX" suffix */
239                int len = truncate_utf8(nick, MAX_NICK_LENGTH - 3);
240                nick[len] = '_';
241                g_snprintf(nick + len + 1, 3, "%2x", rand());
242        }
243}
244
[5ebff60]245void nick_dedupe(bee_user_t *bu, char nick[MAX_NICK_LENGTH + 1])
[b1f818b]246{
[5ebff60]247        irc_t *irc = (irc_t *) bu->bee->ui_data;
[d06eabf]248        int inf_protection = 256;
[badd148]249        irc_user_t *iu;
[5ebff60]250
[5b52a48]251        /* Now, find out if the nick is already in use at the moment, and make
252           subtle changes to make it unique. */
[5ebff60]253        while (!nick_ok(irc, nick) ||
254               ((iu = irc_user_by_name(irc, nick)) && iu->bu != bu)) {
[e3e2059]255
256                underscore_dedupe(nick);
[5ebff60]257
258                if (inf_protection-- == 0) {
259                        g_snprintf(nick, MAX_NICK_LENGTH + 1, "xx%x", rand());
260
261                        irc_rootmsg(irc, "Warning: Something went wrong while trying "
262                                    "to generate a nickname for contact %s on %s.",
263                                    bu->handle, bu->ic->acc->tag);
264                        irc_rootmsg(irc, "This might be a bug in BitlBee, or the result "
265                                    "of a faulty nick_format setting. Will use %s "
266                                    "instead.", nick);
267
[b7d3cc34]268                        break;
269                }
270        }
271}
272
[d323394c]273/* Just check if there is a nickname set for this buddy or if we'd have to
274   generate one. */
[5ebff60]275int nick_saved(bee_user_t *bu)
[d323394c]276{
277        char *store_handle, *found;
[5ebff60]278
279        store_handle = clean_handle(bu->handle);
280        found = g_hash_table_lookup(bu->ic->acc->nicks, store_handle);
281        g_free(store_handle);
282
[d323394c]283        return found != NULL;
284}
285
[5ebff60]286void nick_del(bee_user_t *bu)
[b7d3cc34]287{
[5ebff60]288        g_hash_table_remove(bu->ic->acc->nicks, bu->handle);
[b7d3cc34]289}
290
291
[5ebff60]292void nick_strip(irc_t *irc, char *nick)
[b7d3cc34]293{
[c608891]294        int len = 0;
[9c7ef22]295        gboolean nick_underscores = irc ? set_getbool(&irc->b->set, "nick_underscores") : FALSE;
[5ebff60]296
297        if (irc && (irc->status & IRC_UTF8_NICKS)) {
[c608891]298                gunichar c;
[5ebff60]299                char *p = nick, *n, tmp[strlen(nick) + 1];
300
301                while (p && *p) {
302                        c = g_utf8_get_char_validated(p, -1);
303                        n = g_utf8_find_next_char(p, NULL);
304
[90254d0]305                        if (nick_underscores && c == ' ') {
306                                *p = '_';
307                                p = n;
308                        } else if ((c < 0x7f && !(strchr(nick_lc_chars, c) ||
309                                                  strchr(nick_uc_chars, c))) ||
[5ebff60]310                            !g_unichar_isgraph(c)) {
311                                strcpy(tmp, n);
312                                strcpy(p, tmp);
313                        } else {
[c608891]314                                p = n;
[5ebff60]315                        }
[c608891]316                }
[5ebff60]317                if (p) {
[c608891]318                        len = p - nick;
[5ebff60]319                }
320        } else {
[c608891]321                int i;
[5ebff60]322
323                for (i = len = 0; nick[i] && len < MAX_NICK_LENGTH; i++) {
[90254d0]324                        if (nick_underscores && nick[i] == ' ') {
325                                nick[len] = '_';
326                                len++;
327                        } else if (strchr(nick_lc_chars, nick[i]) ||
[5ebff60]328                            strchr(nick_uc_chars, nick[i])) {
[c608891]329                                nick[len] = nick[i];
330                                len++;
331                        }
[b7d3cc34]332                }
333        }
[5ebff60]334        if (g_ascii_isdigit(nick[0])) {
[0f47613]335                char *orig;
[5ebff60]336
[c608891]337                /* First character of a nick can't be a digit, so insert an
338                   underscore if necessary. */
[5ebff60]339                orig = g_strdup(nick);
340                g_snprintf(nick, MAX_NICK_LENGTH, "_%s", orig);
341                g_free(orig);
342                len++;
[0f47613]343        }
[5ebff60]344        while (len <= MAX_NICK_LENGTH) {
[c608891]345                nick[len++] = '\0';
[5ebff60]346        }
[b7d3cc34]347}
348
[5ebff60]349gboolean nick_ok(irc_t *irc, const char *nick)
[b7d3cc34]350{
[1eddf6b]351        const char *s;
[5ebff60]352
[0f47613]353        /* Empty/long nicks are not allowed, nor numbers at [0] */
[5ebff60]354        if (!*nick || g_ascii_isdigit(nick[0]) || strlen(nick) > MAX_NICK_LENGTH) {
[c608891]355                return 0;
[5ebff60]356        }
357
358        if (irc && (irc->status & IRC_UTF8_NICKS)) {
[c608891]359                gunichar c;
360                const char *p = nick, *n;
[5ebff60]361
362                while (p && *p) {
363                        c = g_utf8_get_char_validated(p, -1);
364                        n = g_utf8_find_next_char(p, NULL);
365
366                        if ((c < 0x7f && !(strchr(nick_lc_chars, c) ||
367                                           strchr(nick_uc_chars, c))) ||
368                            !g_unichar_isgraph(c)) {
[c608891]369                                return FALSE;
370                        }
371                        p = n;
372                }
[5ebff60]373        } else {
374                for (s = nick; *s; s++) {
375                        if (!strchr(nick_lc_chars, *s) && !strchr(nick_uc_chars, *s)) {
[c608891]376                                return FALSE;
[5ebff60]377                        }
378                }
[c608891]379        }
[5ebff60]380
[c608891]381        return TRUE;
[b7d3cc34]382}
383
[5ebff60]384int nick_lc(irc_t *irc, char *nick)
[b7d3cc34]385{
[d323394c]386        static char tab[128] = { 0 };
[b7d3cc34]387        int i;
[5ebff60]388
389        if (tab['A'] == 0) {
[fe63ed3]390                /* initialize table so nonchars are mapped to themselves */
391                for (i = 0; i < sizeof(tab); i++) {
392                        tab[i] = i;
393                }
394                /* replace uppercase chars with lowercase chars */
[5ebff60]395                for (i = 0; nick_lc_chars[i]; i++) {
396                        tab[(int) nick_uc_chars[i]] = nick_lc_chars[i];
[b7d3cc34]397                }
[5ebff60]398        }
399
400        if (irc && (irc->status & IRC_UTF8_NICKS)) {
401                gchar *down = g_utf8_strdown(nick, -1);
402                if (strlen(down) > strlen(nick)) {
403                        truncate_utf8(down, strlen(nick));
[b7d3cc34]404                }
[5ebff60]405                strcpy(nick, down);
406                g_free(down);
[c608891]407        }
[5ebff60]408
409        for (i = 0; nick[i]; i++) {
410                if (((guchar) nick[i]) < 0x7f) {
411                        nick[i] = tab[(guchar) nick[i]];
412                }
413        }
414
415        return nick_ok(irc, nick);
[b7d3cc34]416}
417
[5ebff60]418int nick_cmp(irc_t *irc, const char *a, const char *b)
[b7d3cc34]419{
420        char aa[1024] = "", bb[1024] = "";
[5ebff60]421
422        strncpy(aa, a, sizeof(aa) - 1);
423        strncpy(bb, b, sizeof(bb) - 1);
424        if (nick_lc(irc, aa) && nick_lc(irc, bb)) {
425                return(strcmp(aa, bb));
426        } else {
427                return(-1);     /* Hmm... Not a clear answer.. :-/ */
[b7d3cc34]428        }
429}
Note: See TracBrowser for help on using the repository browser.