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