f9539ff3f246fe1eaeeab43b1b9b90a0c9f84bf3
[silc.git] / apps / irssi / src / core / nicklist.c
1 /*
2  nicklist.c : irssi
3
4     Copyright (C) 1999-2000 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "signals.h"
23 #include "misc.h"
24
25 #include "servers.h"
26 #include "channels.h"
27 #include "nicklist.h"
28 #include "masks.h"
29
30 #define isalnumhigh(a) \
31         (isalnum(a) || (unsigned char) (a) >= 128)
32
33 static void nick_hash_add(CHANNEL_REC *channel, NICK_REC *nick)
34 {
35         NICK_REC *list;
36
37         nick->next = NULL;
38
39         list = g_hash_table_lookup(channel->nicks, nick->nick);
40         if (list == NULL)
41                 g_hash_table_insert(channel->nicks, nick->nick, nick);
42         else {
43                 /* multiple nicks with same name */
44                 while (list->next != NULL)
45                         list = list->next;
46                 list->next = nick;
47         }
48
49         if (nick == channel->ownnick) {
50                 /* move our own nick to beginning of the nick list.. */
51                 nicklist_set_own(channel, nick);
52         }
53 }
54
55 static void nick_hash_remove(CHANNEL_REC *channel, NICK_REC *nick)
56 {
57         NICK_REC *list;
58
59         list = g_hash_table_lookup(channel->nicks, nick->nick);
60         if (list == NULL)
61                 return;
62
63         if (list == nick || list->next == NULL) {
64                 g_hash_table_remove(channel->nicks, nick->nick);
65                 if (list->next != NULL) {
66                         g_hash_table_insert(channel->nicks, nick->next->nick,
67                                             nick->next);
68                 }
69         } else {
70                 while (list->next != nick)
71                         list = list->next;
72                 list->next = nick->next;
73         }
74 }
75
76 /* Add new nick to list */
77 void nicklist_insert(CHANNEL_REC *channel, NICK_REC *nick)
78 {
79         MODULE_DATA_INIT(nick);
80
81         nick->type = module_get_uniq_id("NICK", 0);
82         nick->chat_type = channel->chat_type;
83
84         nick_hash_add(channel, nick);
85         signal_emit("nicklist new", 2, channel, nick);
86 }
87
88 /* Set host address for nick */
89 void nicklist_set_host(CHANNEL_REC *channel, NICK_REC *nick, const char *host)
90 {
91         g_return_if_fail(channel != NULL);
92         g_return_if_fail(nick != NULL);
93         g_return_if_fail(host != NULL);
94
95         g_free_not_null(nick->host);
96         nick->host = g_strdup(host);
97
98         signal_emit("nicklist host changed", 2, channel, nick);
99 }
100
101 static void nicklist_destroy(CHANNEL_REC *channel, NICK_REC *nick)
102 {
103         signal_emit("nicklist remove", 2, channel, nick);
104
105         g_free(nick->nick);
106         g_free_not_null(nick->realname);
107         g_free_not_null(nick->host);
108         g_free(nick);
109 }
110
111 /* Remove nick from list */
112 void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick)
113 {
114         g_return_if_fail(IS_CHANNEL(channel));
115         g_return_if_fail(nick != NULL);
116
117         nick_hash_remove(channel, nick);
118         nicklist_destroy(channel, nick);
119 }
120
121 static void nicklist_rename_list(SERVER_REC *server, void *new_nick_id,
122                                  const char *old_nick, const char *new_nick,
123                                  GSList *nicks)
124 {
125         CHANNEL_REC *channel;
126         NICK_REC *nickrec;
127         GSList *tmp;
128
129         for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
130                 channel = tmp->data;
131                 nickrec = tmp->next->data;
132
133                 /* remove old nick from hash table */
134                 nick_hash_remove(channel, nickrec);
135
136                 if (new_nick_id != NULL)
137                         nickrec->unique_id = new_nick_id;
138
139                 g_free(nickrec->nick);
140                 nickrec->nick = g_strdup(new_nick);
141
142                 /* add new nick to hash table */
143                 nick_hash_add(channel, nickrec);
144
145                 signal_emit("nicklist changed", 3, channel, nickrec, old_nick);
146         }
147         g_slist_free(nicks);
148 }
149
150 void nicklist_rename(SERVER_REC *server, const char *old_nick,
151                      const char *new_nick)
152 {
153         nicklist_rename_list(server, NULL, old_nick, new_nick,
154                              nicklist_get_same(server, old_nick));
155 }
156
157 void nicklist_rename_unique(SERVER_REC *server,
158                             void *old_nick_id, const char *old_nick,
159                             void *new_nick_id, const char *new_nick)
160 {
161         nicklist_rename_list(server, new_nick_id, old_nick, new_nick,
162                              nicklist_get_same_unique(server, old_nick_id));
163 }
164
165 static NICK_REC *nicklist_find_wildcards(CHANNEL_REC *channel,
166                                          const char *mask)
167 {
168         GSList *nicks, *tmp;
169         NICK_REC *nick;
170
171         nicks = nicklist_getnicks(channel);
172         nick = NULL;
173         for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
174                 nick = tmp->data;
175
176                 if (mask_match_address(channel->server, mask,
177                                        nick->nick, nick->host))
178                         break;
179         }
180         g_slist_free(nicks);
181         return tmp == NULL ? NULL : nick;
182 }
183
184 GSList *nicklist_find_multiple(CHANNEL_REC *channel, const char *mask)
185 {
186         GSList *nicks, *tmp, *next;
187
188         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
189         g_return_val_if_fail(mask != NULL, NULL);
190
191         nicks = nicklist_getnicks(channel);
192         for (tmp = nicks; tmp != NULL; tmp = next) {
193                 NICK_REC *nick = tmp->data;
194
195                 next = tmp->next;
196                 if (!mask_match_address(channel->server, mask,
197                                         nick->nick, nick->host))
198                         nicks = g_slist_remove(nicks, tmp->data);
199         }
200
201         return nicks;
202 }
203
204 /* Find nick */
205 NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *nick)
206 {
207         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
208         g_return_val_if_fail(nick != NULL, NULL);
209
210         return g_hash_table_lookup(channel->nicks, nick);
211 }
212
213 NICK_REC *nicklist_find_unique(CHANNEL_REC *channel, const char *nick,
214                                void *id)
215 {
216         NICK_REC *rec;
217
218         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
219         g_return_val_if_fail(nick != NULL, NULL);
220
221         rec = g_hash_table_lookup(channel->nicks, nick);
222         while (rec != NULL && rec->unique_id != id)
223                 rec = rec->next;
224
225         return rec;
226 }
227
228 /* Find nick mask, wildcards allowed */
229 NICK_REC *nicklist_find_mask(CHANNEL_REC *channel, const char *mask)
230 {
231         NICK_REC *nickrec;
232         char *nick, *host;
233
234         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
235         g_return_val_if_fail(mask != NULL, NULL);
236
237         nick = g_strdup(mask);
238         host = strchr(nick, '!');
239         if (host != NULL) *host++ = '\0';
240
241         if (strchr(nick, '*') || strchr(nick, '?')) {
242                 g_free(nick);
243                 return nicklist_find_wildcards(channel, mask);
244         }
245
246         nickrec = g_hash_table_lookup(channel->nicks, nick);
247
248         if (host != NULL) {
249                 while (nickrec != NULL) {
250                         if (nickrec->host != NULL &&
251                             match_wildcards(host, nickrec->host))
252                                 break; /* match */
253                         nickrec = nickrec->next;
254                 }
255         }
256         g_free(nick);
257         return nickrec;
258 }
259
260 static void get_nicks_hash(gpointer key, NICK_REC *rec, GSList **list)
261 {
262         while (rec != NULL) {
263                 *list = g_slist_append(*list, rec);
264                 rec = rec->next;
265         }
266 }
267
268 /* Get list of nicks */
269 GSList *nicklist_getnicks(CHANNEL_REC *channel)
270 {
271         GSList *list;
272
273         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
274
275         list = NULL;
276         g_hash_table_foreach(channel->nicks, (GHFunc) get_nicks_hash, &list);
277         return list;
278 }
279
280 typedef struct {
281         CHANNEL_REC *channel;
282         const char *nick;
283         GSList *list;
284 } NICKLIST_GET_SAME_REC;
285
286 static void get_nicks_same_hash(gpointer key, NICK_REC *nick,
287                                 NICKLIST_GET_SAME_REC *rec)
288 {
289         while (nick != NULL) {
290                 if (g_strcasecmp(nick->nick, rec->nick) == 0) {
291                         rec->list = g_slist_append(rec->list, rec->channel);
292                         rec->list = g_slist_append(rec->list, nick);
293                 }
294
295                 nick = nick->next;
296         }
297 }
298
299 GSList *nicklist_get_same(SERVER_REC *server, const char *nick)
300 {
301         NICKLIST_GET_SAME_REC rec;
302         GSList *tmp;
303
304         g_return_val_if_fail(IS_SERVER(server), NULL);
305
306         rec.nick = nick;
307         rec.list = NULL;
308         for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
309                 rec.channel = tmp->data;
310                 g_hash_table_foreach(rec.channel->nicks,
311                                      (GHFunc) get_nicks_same_hash, &rec);
312         }
313         return rec.list;
314 }
315
316 typedef struct {
317         CHANNEL_REC *channel;
318         void *id;
319         GSList *list;
320 } NICKLIST_GET_SAME_UNIQUE_REC;
321
322 static void get_nicks_same_hash_unique(gpointer key, NICK_REC *nick,
323                                        NICKLIST_GET_SAME_UNIQUE_REC *rec)
324 {
325         while (nick != NULL) {
326                 if (nick->unique_id == rec->id) {
327                         rec->list = g_slist_append(rec->list, rec->channel);
328                         rec->list = g_slist_append(rec->list, nick);
329                         break;
330                 }
331
332                 nick = nick->next;
333         }
334 }
335
336 GSList *nicklist_get_same_unique(SERVER_REC *server, void *id)
337 {
338         NICKLIST_GET_SAME_UNIQUE_REC rec;
339         GSList *tmp;
340
341         g_return_val_if_fail(IS_SERVER(server), NULL);
342         g_return_val_if_fail(id != NULL, NULL);
343
344         rec.id = id;
345         rec.list = NULL;
346         for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
347                 rec.channel = tmp->data;
348                 g_hash_table_foreach(rec.channel->nicks,
349                                      (GHFunc) get_nicks_same_hash_unique,
350                                      &rec);
351         }
352         return rec.list;
353 }
354
355 /* nick record comparision for sort functions */
356 int nicklist_compare(NICK_REC *p1, NICK_REC *p2)
357 {
358         if (p1 == NULL) return -1;
359         if (p2 == NULL) return 1;
360
361         if (p1->op && !p2->op) return -1;
362         if (!p1->op && p2->op) return 1;
363
364         if (!p1->op) {
365                 if (p1->voice && !p2->voice) return -1;
366                 if (!p1->voice && p2->voice) return 1;
367         }
368
369         return g_strcasecmp(p1->nick, p2->nick);
370 }
371
372 static void nicklist_update_flags_list(SERVER_REC *server, int gone,
373                                        int serverop, GSList *nicks)
374 {
375         GSList *tmp;
376         CHANNEL_REC *channel;
377         NICK_REC *rec;
378
379         g_return_if_fail(IS_SERVER(server));
380
381         for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
382                 channel = tmp->data;
383                 rec = tmp->next->data;
384
385                 rec->last_check = time(NULL);
386
387                 if (gone != -1 && (int)rec->gone != gone) {
388                         rec->gone = gone;
389                         signal_emit("nicklist gone changed", 2, channel, rec);
390                 }
391
392                 if (serverop != -1 && (int)rec->serverop != serverop) {
393                         rec->serverop = serverop;
394                         signal_emit("nicklist serverop changed", 2, channel, rec);
395                 }
396         }
397         g_slist_free(nicks);
398 }
399
400 void nicklist_update_flags(SERVER_REC *server, const char *nick,
401                            int gone, int serverop)
402 {
403         nicklist_update_flags_list(server, gone, serverop,
404                                    nicklist_get_same(server, nick));
405 }
406
407 void nicklist_update_flags_unique(SERVER_REC *server, void *id,
408                                   int gone, int serverop)
409 {
410         nicklist_update_flags_list(server, gone, serverop,
411                                    nicklist_get_same_unique(server, id));
412 }
413
414 /* Specify which nick in channel is ours */
415 void nicklist_set_own(CHANNEL_REC *channel, NICK_REC *nick)
416 {
417         NICK_REC *first, *next;
418
419         channel->ownnick = nick;
420
421         /* move our nick in the list to first, makes some things easier
422            (like handling multiple identical nicks in fe-messages.c) */
423         first = g_hash_table_lookup(channel->nicks, nick->nick);
424         if (first->next == NULL)
425                 return;
426
427         next = nick->next;
428         nick->next = first;
429
430         while (first->next != nick)
431                 first = first->next;
432         first->next = next;
433
434         g_hash_table_insert(channel->nicks, nick->nick, nick);
435 }
436
437 static void sig_channel_created(CHANNEL_REC *channel)
438 {
439         g_return_if_fail(IS_CHANNEL(channel));
440
441         channel->nicks = g_hash_table_new((GHashFunc) g_istr_hash,
442                                           (GCompareFunc) g_istr_equal);
443 }
444
445 static void nicklist_remove_hash(gpointer key, NICK_REC *nick,
446                                  CHANNEL_REC *channel)
447 {
448         NICK_REC *next;
449
450         while (nick != NULL) {
451                 next = nick->next;
452                 nicklist_destroy(channel, nick);
453                 nick = next;
454         }
455 }
456
457 static void sig_channel_destroyed(CHANNEL_REC *channel)
458 {
459         g_return_if_fail(IS_CHANNEL(channel));
460
461         g_hash_table_foreach(channel->nicks,
462                              (GHFunc) nicklist_remove_hash, channel);
463         g_hash_table_destroy(channel->nicks);
464 }
465
466 static NICK_REC *nick_nfind(CHANNEL_REC *channel, const char *nick, int len)
467 {
468         NICK_REC *rec;
469         char *tmpnick;
470
471         tmpnick = g_strndup(nick, len);
472         rec = g_hash_table_lookup(channel->nicks, tmpnick);
473
474         if (rec != NULL) {
475                 /* if there's multiple, get the one with identical case */
476                 while (rec->next != NULL) {
477                         if (strcmp(rec->nick, tmpnick) == 0)
478                                 break;
479                         rec = rec->next;
480                 }
481         }
482
483         g_free(tmpnick);
484         return rec;
485 }
486
487 /* Check is `msg' is meant for `nick'. */
488 int nick_match_msg(CHANNEL_REC *channel, const char *msg, const char *nick)
489 {
490         const char *msgstart, *orignick;
491         int len, fullmatch;
492
493         g_return_val_if_fail(nick != NULL, FALSE);
494         g_return_val_if_fail(msg != NULL, FALSE);
495
496         if (channel != NULL && channel->server->nick_match_msg != NULL)
497                 return channel->server->nick_match_msg(msg, nick);
498
499         /* first check for identical match */
500         len = strlen(nick);
501         if (g_strncasecmp(msg, nick, len) == 0 && !isalnumhigh((int) msg[len]))
502                 return TRUE;
503
504         orignick = nick;
505         for (;;) {
506                 nick = orignick;
507                 msgstart = msg;
508                 fullmatch = TRUE;
509
510                 /* check if it matches for alphanumeric parts of nick */
511                 while (*nick != '\0' && *msg != '\0') {
512                         if (toupper(*nick) == toupper(*msg)) {
513                                 /* total match */
514                                 msg++;
515                         } else if (isalnum(*msg) && !isalnum(*nick)) {
516                                 /* some strange char in your nick, pass it */
517                                 fullmatch = FALSE;
518                         } else
519                                 break;
520
521                         nick++;
522                 }
523
524                 if (msg != msgstart && !isalnumhigh(*msg)) {
525                         /* at least some of the chars in line matched the
526                            nick, and msg continue with non-alphanum character,
527                            this might be for us.. */
528                         if (*nick != '\0') {
529                                 /* remove the rest of the non-alphanum chars
530                                    from nick and check if it then matches. */
531                                 fullmatch = FALSE;
532                                 while (*nick != '\0' && !isalnum(*nick))
533                                         nick++;
534                         }
535
536                         if (*nick == '\0') {
537                                 /* yes, match! */
538                                 break;
539                         }
540                 }
541
542                 /* no match. check if this is a message to multiple people
543                    (like nick1,nick2: text) */
544                 while (*msg != '\0' && *msg != ' ' && *msg != ',') msg++;
545
546                 if (*msg != ',') {
547                         nick = orignick;
548                         break;
549                 }
550
551                 msg++;
552         }
553
554         if (*nick != '\0')
555                 return FALSE; /* didn't match */
556
557         if (fullmatch)
558                 return TRUE; /* matched without fuzzyness */
559
560         /* matched with some fuzzyness .. check if there's an exact match
561            for some other nick in the same channel. */
562         return nick_nfind(channel, msgstart, (int) (msg-msgstart)) == NULL;
563 }
564
565 void nicklist_init(void)
566 {
567         signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created);
568         signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
569 }
570
571 void nicklist_deinit(void)
572 {
573         signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created);
574         signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
575
576         module_uniq_destroy("NICK");
577 }