Coverage Report

Created: 2025-12-08 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/irssi/src/irc/core/irc.c
Line
Count
Source
1
/*
2
 irc.c : irssi
3
4
    Copyright (C) 1999 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 along
17
    with this program; if not, write to the Free Software Foundation, Inc.,
18
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
*/
20
21
#include "module.h"
22
#include <irssi/src/core/misc.h>
23
#include <irssi/src/core/modules.h>
24
#include <irssi/src/core/net-sendbuffer.h>
25
#include <irssi/src/core/network.h>
26
#include <irssi/src/core/rawlog.h>
27
#include <irssi/src/core/refstrings.h>
28
29
#include <irssi/src/irc/core/irc-channels.h>
30
#include <irssi/src/irc/core/irc-servers.h>
31
#include <irssi/src/irc/core/servers-redirect.h>
32
33
char *current_server_event;
34
static int signal_default_event;
35
static int signal_server_event;
36
static int signal_server_event_tags;
37
static int signal_server_incoming;
38
39
#ifdef BLOCKING_SOCKETS
40
#  define MAX_SOCKET_READS 1
41
#else
42
0
#  define MAX_SOCKET_READS 5
43
#endif
44
45
static void strip_params_colon(char *const);
46
47
/* The core of the irc_send_cmd* functions. If `raw' is TRUE, the `cmd'
48
   won't be checked at all if it's 512 bytes or not, or if it contains
49
   line feeds or not. Use with extreme caution! */
50
void irc_send_cmd_full(IRC_SERVER_REC *server, const char *cmd, int irc_send_when, int raw)
51
173k
{
52
173k
  GString *str;
53
173k
  int len;
54
173k
  guint pos;
55
173k
  gboolean server_supports_tag;
56
57
173k
  g_return_if_fail(server != NULL);
58
173k
  g_return_if_fail(cmd != NULL);
59
60
173k
  if (server->connection_lost)
61
3.08k
    return;
62
63
169k
  str = g_string_sized_new(MAX_IRC_USER_TAGS_LEN + 2 /* `@'+SPACE */ +
64
169k
         server->max_message_len + 2 /* CR+LF */ + 1 /* `\0' */);
65
66
169k
  if (server->cmdcount == 0)
67
9.58k
    irc_servers_start_cmd_timeout();
68
169k
  server->cmdcount++;
69
70
169k
  pos = g_slist_length(server->cmdqueue);
71
169k
  if (server->cmdlater > pos / 2) {
72
0
    server->cmdlater = pos / 2;
73
0
    pos = 0;
74
169k
  } else {
75
169k
    pos -= 2 * server->cmdlater;
76
169k
  }
77
78
169k
  if (!raw) {
79
169k
    const char *tmp = cmd;
80
81
169k
    server_supports_tag = server->cap_supported != NULL &&
82
29.9k
      g_hash_table_lookup_extended(server->cap_supported, CAP_MESSAGE_TAGS, NULL, NULL);
83
84
169k
    if (*cmd == '@' && server_supports_tag) {
85
0
      const char *end;
86
87
0
      while (*tmp != ' ' && *tmp != '\0')
88
0
        tmp++;
89
90
0
      end = tmp;
91
92
0
      if (tmp - cmd > MAX_IRC_USER_TAGS_LEN) {
93
0
        g_warning("irc_send_cmd_full(); tags too long(%ld)", tmp - cmd);
94
0
        while (tmp - cmd > MAX_IRC_USER_TAGS_LEN && cmd != tmp - 1) tmp--;
95
0
        while (*tmp != ',' && cmd != tmp - 1) tmp--;
96
0
      }
97
0
      if (cmd != tmp)
98
0
        g_string_append_len(str, cmd, tmp - cmd);
99
100
0
      tmp = end;
101
0
      while (*tmp == ' ') tmp++;
102
103
0
      if (*tmp != '\0' && str->len > 0)
104
0
        g_string_append_c(str, ' ');
105
0
    }
106
169k
    len = strlen(tmp);
107
108
    /* check that we don't send any longer commands
109
       than 510 bytes (2 bytes for CR+LF) */
110
169k
    g_string_append_len(str, tmp, len > server->max_message_len ?
111
169k
            server->max_message_len : len);
112
169k
  } else {
113
0
    g_string_append(str, cmd);
114
0
  }
115
116
169k
  if (!raw) {
117
    /* Add CR+LF to command */
118
169k
    g_string_append(str, "\r\n");
119
169k
  }
120
121
169k
  if (irc_send_when == IRC_SEND_NOW) {
122
108k
    irc_server_send_and_redirect(server, str, server->redirect_next);
123
108k
    g_string_free(str, TRUE);
124
108k
  } else if (irc_send_when == IRC_SEND_NEXT) {
125
    /* add to queue */
126
0
    server->cmdqueue = g_slist_prepend(server->cmdqueue, server->redirect_next);
127
0
    server->cmdqueue = g_slist_prepend(server->cmdqueue, g_string_free(str, FALSE));
128
61.2k
  } else if (irc_send_when == IRC_SEND_NORMAL) {
129
54.4k
    server->cmdqueue = g_slist_insert(server->cmdqueue, server->redirect_next, pos);
130
54.4k
    server->cmdqueue = g_slist_insert(server->cmdqueue, g_string_free(str, FALSE), pos);
131
54.4k
  } else if (irc_send_when == IRC_SEND_LATER) {
132
6.79k
    server->cmdqueue = g_slist_append(server->cmdqueue, g_string_free(str, FALSE));
133
6.79k
    server->cmdqueue = g_slist_append(server->cmdqueue, server->redirect_next);
134
6.79k
    server->cmdlater++;
135
6.79k
  } else {
136
0
    g_warn_if_reached();
137
0
  }
138
139
169k
  server->redirect_next = NULL;
140
169k
}
141
142
/* Send command to IRC server */
143
void irc_send_cmd(IRC_SERVER_REC *server, const char *cmd)
144
73.1k
{
145
73.1k
  gint64 now;
146
73.1k
  int send_now;
147
148
73.1k
  now = g_get_real_time();
149
73.1k
  send_now = now >= server->wait_cmd &&
150
72.7k
             (server->cmdcount < server->max_cmds_at_once ||
151
54.0k
        server->cmd_queue_speed <= 0);
152
153
73.1k
  irc_send_cmd_full(server, cmd, send_now ? IRC_SEND_NOW : IRC_SEND_NORMAL, FALSE);
154
73.1k
}
155
156
/* Send command to IRC server */
157
void irc_send_cmdv(IRC_SERVER_REC *server, const char *cmd, ...)
158
73.1k
{
159
73.1k
  va_list args;
160
73.1k
  char *str;
161
162
73.1k
  va_start(args, cmd);
163
164
73.1k
  str = g_strdup_vprintf(cmd, args);
165
73.1k
  irc_send_cmd(server, str);
166
73.1k
  g_free(str);
167
168
73.1k
  va_end(args);
169
73.1k
}
170
171
/* Send command to server immediately bypassing all flood protections
172
   and queues. */
173
void irc_send_cmd_now(IRC_SERVER_REC *server, const char *cmd)
174
93.0k
{
175
93.0k
  g_return_if_fail(cmd != NULL);
176
177
93.0k
  irc_send_cmd_full(server, cmd, IRC_SEND_NOW, FALSE);
178
93.0k
}
179
180
/* Send command to server putting it at the beginning of the queue of
181
    commands to send -- it will go out as soon as possible in accordance
182
    to the flood protection settings. */
183
void irc_send_cmd_first(IRC_SERVER_REC *server, const char *cmd)
184
0
{
185
0
  g_return_if_fail(cmd != NULL);
186
187
0
  irc_send_cmd_full(server, cmd, IRC_SEND_NEXT, FALSE);
188
0
}
189
190
/* Send command to server putting it at the end of the queue. */
191
void irc_send_cmd_later(IRC_SERVER_REC *server, const char *cmd)
192
6.86k
{
193
6.86k
  g_return_if_fail(cmd != NULL);
194
195
6.86k
  irc_send_cmd_full(server, cmd, IRC_SEND_LATER, FALSE);
196
6.86k
}
197
198
static char *split_nicks(const char *cmd, char **pre, char **nicks, char **post, int arg)
199
0
{
200
0
  char *p;
201
202
0
  *pre = g_strdup(cmd);
203
0
  *post = *nicks = NULL;
204
205
0
  if (**pre == '@') {
206
    /* the message-tags "add" one space separated argument
207
       in front of the non message-tagged IRC commands. So
208
       the nicks are now off-set by one to the right. */
209
0
    arg++;
210
0
  }
211
212
0
  for (p = *pre; *p != '\0'; p++) {
213
0
    if (*p != ' ')
214
0
      continue;
215
216
0
    if (arg == 1) {
217
      /* text after nicks */
218
0
      *p++ = '\0';
219
0
      while (*p == ' ') p++;
220
0
      *post = p;
221
0
      break;
222
0
    }
223
224
    /* find nicks */
225
0
    while (p[1] == ' ') p++;
226
0
    if (--arg == 1) {
227
0
      *p = '\0';
228
0
      *nicks = p+1;
229
0
    }
230
0
  }
231
232
0
  return *pre;
233
0
}
234
235
void irc_send_cmd_split(IRC_SERVER_REC *server, const char *cmd,
236
      int nickarg, int max_nicks)
237
0
{
238
0
  char *str, *pre, *post, *nicks;
239
0
  char **nicklist, **tmp;
240
0
  GString *nickstr;
241
0
  int count;
242
243
0
  g_return_if_fail(server != NULL);
244
0
  g_return_if_fail(cmd != NULL);
245
246
0
  str = split_nicks(cmd, &pre, &nicks, &post, nickarg);
247
0
  if (nicks == NULL) {
248
                /* no nicks given? */
249
0
    g_free(str);
250
0
    return;
251
0
  }
252
253
  /* split the nicks */
254
0
  nickstr = g_string_new(NULL);
255
0
  nicklist = g_strsplit(nicks, ",", -1); count = 0;
256
257
0
  tmp = nicklist;
258
0
  for (;; tmp++) {
259
0
    if (*tmp != NULL) {
260
0
      g_string_append_printf(nickstr, "%s,", *tmp);
261
0
      if (++count < max_nicks)
262
0
        continue;
263
0
    }
264
265
0
    count = 0;
266
0
    if (nickstr->len > 0)
267
0
      g_string_truncate(nickstr, nickstr->len-1);
268
269
0
    if (post == NULL)
270
0
      irc_send_cmdv(server, "%s %s", pre, nickstr->str);
271
0
    else
272
0
      irc_send_cmdv(server, "%s %s %s", pre, nickstr->str, post);
273
274
0
    g_string_truncate(nickstr, 0);
275
276
0
    if (*tmp == NULL || tmp[1] == NULL)
277
0
      break;
278
0
  }
279
0
  g_strfreev(nicklist);
280
0
  g_string_free(nickstr, TRUE);
281
282
0
  g_free(str);
283
0
}
284
285
/* Get next parameter */
286
char *event_get_param(char **data)
287
6.22M
{
288
6.22M
  char *pos;
289
290
6.22M
  g_return_val_if_fail(data != NULL, NULL);
291
6.22M
  g_return_val_if_fail(*data != NULL, NULL);
292
293
6.22M
  if (**data == ':') {
294
    /* last parameter */
295
81.8k
    pos = *data;
296
81.8k
    *data += strlen(*data);
297
81.8k
    return pos+1;
298
81.8k
  }
299
300
6.14M
  pos = *data;
301
99.9M
  while (**data != '\0' && **data != ' ') (*data)++;
302
6.14M
  if (**data == ' ') *(*data)++ = '\0';
303
304
6.14M
  return pos;
305
6.22M
}
306
307
/* Get count parameters from data */
308
char *event_get_params(const char *data, int count, ...)
309
2.29M
{
310
2.29M
  char **str, *tmp, *duprec, *datad;
311
2.29M
  gboolean rest;
312
2.29M
  va_list args;
313
314
2.29M
  g_return_val_if_fail(data != NULL, NULL);
315
316
2.29M
  va_start(args, count);
317
2.29M
  duprec = datad = g_strdup(data);
318
319
2.29M
  rest = count & PARAM_FLAG_GETREST;
320
2.29M
  count = PARAM_WITHOUT_FLAGS(count);
321
322
8.68M
  while (count-- > 0) {
323
6.39M
    str = (char **) va_arg(args, char **);
324
6.39M
    if (count == 0 && rest) {
325
      /* Put the rest into the last parameter. */
326
164k
      strip_params_colon(datad);
327
164k
      tmp = datad;
328
6.22M
    } else {
329
6.22M
      tmp = event_get_param(&datad);
330
6.22M
    }
331
6.39M
    if (str != NULL) *str = tmp;
332
6.39M
  }
333
2.29M
  va_end(args);
334
335
2.29M
  return duprec;
336
2.29M
}
337
338
/* Given a string containing <params>, strip any colon prefixing <trailing>. */
339
static void strip_params_colon(char *const params)
340
164k
{
341
164k
  char *s;
342
343
164k
  if (params == NULL) {
344
0
    return;
345
0
  }
346
347
164k
  s = params;
348
825k
  while (*s != '\0') {
349
780k
    if (*s == ':') {
350
5.78k
      memmove(s, s+1, strlen(s+1)+1);
351
5.78k
      return;
352
5.78k
    }
353
354
775k
    s = strchr(s, ' ');
355
775k
    if (s == NULL) {
356
113k
      return;
357
113k
    }
358
359
1.59M
    while (*s == ' ') {
360
934k
      s++;
361
934k
    }
362
661k
  }
363
164k
}
364
365
static void irc_server_event(IRC_SERVER_REC *server, const char *line,
366
           const char *nick, const char *address)
367
1.03M
{
368
1.03M
        const char *signal;
369
1.03M
  char *event, *args;
370
371
1.03M
  g_return_if_fail(line != NULL);
372
373
  /* split event / args */
374
1.03M
  event = g_strconcat("event ", line, NULL);
375
1.03M
  args = strchr(event+6, ' ');
376
1.03M
  if (args != NULL) *args++ = '\0'; else args = "";
377
1.13M
  while (*args == ' ') args++;
378
1.03M
  ascii_strdown(event);
379
380
        /* check if event needs to be redirected */
381
1.03M
  signal = server_redirect_get_signal(server, nick, event, args);
382
1.03M
  if (signal == NULL)
383
1.03M
    signal = event;
384
3.10k
        else
385
3.10k
    rawlog_redirect(server->rawlog, signal);
386
387
        /* emit it */
388
1.03M
  current_server_event = event+6;
389
1.03M
  if (!signal_emit(signal, 4, server, args, nick, address))
390
346k
    signal_emit_id(signal_default_event, 4, server, line, nick, address);
391
1.03M
  current_server_event = NULL;
392
393
1.03M
  g_free(event);
394
1.03M
}
395
396
static void unescape_tag(char *tag)
397
8.68k
{
398
8.68k
  char *tmp;
399
400
8.68k
  if (tag == NULL)
401
7.75k
    return;
402
403
929
  tmp = tag;
404
4.51M
  for (; *tmp != '\0'; tmp++, tag++) {
405
4.51M
    if (*tmp == '\\') {
406
425
      tmp++;
407
425
      if (*tmp == '\0')
408
207
        break;
409
218
      switch (*tmp) {
410
0
      case ':':
411
0
        *tag = ';';
412
0
        break;
413
0
      case 'n':
414
0
        *tag = '\n';
415
0
        break;
416
0
      case 'r':
417
0
        *tag = '\r';
418
0
        break;
419
0
      case 's':
420
0
        *tag = ' ';
421
0
        break;
422
218
      default:
423
218
        *tag = *tmp;
424
218
        break;
425
218
      }
426
4.51M
    } else {
427
4.51M
      *tag = *tmp;
428
4.51M
    }
429
4.51M
  }
430
929
  *tag = '\0';
431
929
}
432
433
static gboolean i_str0_equal(const char *s1, const char *s2)
434
1.37k
{
435
1.37k
  return g_strcmp0(s1, s2) == 0;
436
1.37k
}
437
438
GHashTable *irc_parse_message_tags(const char *tags)
439
3.55k
{
440
3.55k
  char **split, **tmp, **kv;
441
3.55k
  GHashTable *hash;
442
443
3.55k
  hash = g_hash_table_new_full(g_str_hash, (GEqualFunc) i_str0_equal,
444
3.55k
                               (GDestroyNotify) i_refstr_release, (GDestroyNotify) g_free);
445
3.55k
  split = g_strsplit(tags, ";", -1);
446
12.4k
  for (tmp = split; *tmp != NULL; tmp++) {
447
8.89k
    if (*tmp[0] == '\0')
448
214
      continue;
449
8.68k
    kv = g_strsplit(*tmp, "=", 2);
450
8.68k
    unescape_tag(kv[1]);
451
8.68k
    g_hash_table_replace(hash, i_refstr_intern(kv[0]),
452
8.68k
                         g_strdup(kv[1] == NULL ? "" : kv[1]));
453
8.68k
    g_strfreev(kv);
454
8.68k
  }
455
3.55k
  g_strfreev(split);
456
3.55k
  return hash;
457
3.55k
}
458
459
static void irc_server_event_tags(IRC_SERVER_REC *server, const char *line, const char *nick,
460
                                  const char *address, const char *tags)
461
1.03M
{
462
1.03M
  char *timestr;
463
1.03M
  GHashTable *tags_hash = NULL;
464
465
1.03M
  if (tags != NULL && *tags != '\0') {
466
3.55k
    tags_hash = irc_parse_message_tags(tags);
467
3.55k
    if ((timestr = g_hash_table_lookup(tags_hash, "time")) != NULL) {
468
783
      server_meta_stash(SERVER(server), "time", timestr);
469
783
    }
470
3.55k
  }
471
472
1.03M
  if (*line != '\0')
473
1.03M
    signal_emit_id(signal_server_event, 4, server, line, nick, address);
474
475
1.03M
  if (tags_hash != NULL)
476
3.55k
    g_hash_table_destroy(tags_hash);
477
1.03M
}
478
479
static char *irc_parse_prefix(char *line, char **nick, char **address, char **tags)
480
1.04M
{
481
1.04M
  char *p;
482
483
1.04M
  *nick = *address = *tags = NULL;
484
485
  /* ["@" <tags> SPACE] :<nick> [["!" <user>] "@" <host>] SPACE */
486
487
1.04M
  if (*line == '@') {
488
3.55k
    *tags = ++line;
489
6.75M
    while (*line != '\0' && *line != ' ') {
490
6.75M
      line++;
491
6.75M
    }
492
3.55k
    if (*line == ' ') {
493
2.40k
      *line++ = '\0';
494
3.27k
      while (*line == ' ') line++;
495
2.40k
    }
496
3.55k
  }
497
498
1.04M
  if (*line != ':')
499
631k
    return line;
500
501
410k
  *nick = ++line; p = NULL;
502
4.98M
  while (*line != '\0' && *line != ' ') {
503
4.57M
    if (*line == '!' || *line == '@') {
504
35.6k
      p = line;
505
35.6k
      if (*line == '!')
506
743
        break;
507
35.6k
    }
508
4.57M
    line++;
509
4.57M
  }
510
511
410k
  if (p != NULL) {
512
31.7k
    line = p;
513
31.7k
    *line++ = '\0';
514
31.7k
    *address = line;
515
708k
    while (*line != '\0' && *line != ' ')
516
677k
      line++;
517
31.7k
  }
518
519
410k
  if (*line == ' ') {
520
403k
    *line++ = '\0';
521
427k
    while (*line == ' ') line++;
522
403k
  }
523
524
410k
  return line;
525
1.04M
}
526
527
/* Parse command line sent by server */
528
static void irc_parse_incoming_line(IRC_SERVER_REC *server, char *line)
529
1.04M
{
530
1.04M
  char *nick, *address, *tags;
531
532
1.04M
  g_return_if_fail(server != NULL);
533
1.04M
  g_return_if_fail(line != NULL);
534
535
1.04M
  line = irc_parse_prefix(line, &nick, &address, &tags);
536
1.04M
  if (*line != '\0' || tags != NULL)
537
1.03M
    signal_emit_id(signal_server_event_tags, 5, server, line, nick, address, tags);
538
539
1.04M
  server_meta_clear_all(SERVER(server));
540
1.04M
}
541
542
/* input function: handle incoming server messages */
543
static void irc_parse_incoming(SERVER_REC *server)
544
0
{
545
0
  char *str;
546
0
  int count;
547
0
  int ret;
548
549
0
  g_return_if_fail(server != NULL);
550
551
  /* Some commands can send huge replies and irssi might handle them
552
     too slowly, so read only a few times from the socket before
553
     letting other tasks to run. */
554
0
  count = 0;
555
0
  ret = 0;
556
0
  server_ref(server);
557
0
  while (!server->disconnected &&
558
0
         (ret = net_sendbuffer_receive_line(server->handle, &str, count < MAX_SOCKET_READS)) > 0) {
559
0
    rawlog_input(server->rawlog, str);
560
0
    signal_emit_id(signal_server_incoming, 2, server, str);
561
562
0
    if (server->connection_lost)
563
0
      server_disconnect(server);
564
565
0
    count++;
566
0
  }
567
0
  if (ret == -1) {
568
    /* connection lost */
569
0
    server->connection_lost = TRUE;
570
0
    server_disconnect(server);
571
0
  }
572
0
  server_unref(server);
573
0
}
574
575
static void irc_init_server(IRC_SERVER_REC *server)
576
0
{
577
0
  g_return_if_fail(server != NULL);
578
579
0
  if (!IS_IRC_SERVER(server))
580
0
    return;
581
582
0
  server->readtag = i_input_add(net_sendbuffer_handle(server->handle), I_INPUT_READ,
583
0
                                (GInputFunction) irc_parse_incoming, server);
584
0
}
585
586
void irc_irc_init(void)
587
41.5k
{
588
41.5k
  signal_add("server event", (SIGNAL_FUNC) irc_server_event);
589
41.5k
  signal_add("server event tags", (SIGNAL_FUNC) irc_server_event_tags);
590
41.5k
  signal_add("server connected", (SIGNAL_FUNC) irc_init_server);
591
41.5k
  signal_add("server connection switched", (SIGNAL_FUNC) irc_init_server);
592
41.5k
  signal_add("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line);
593
594
41.5k
  current_server_event = NULL;
595
41.5k
  signal_default_event = signal_get_uniq_id("default event");
596
41.5k
  signal_server_event = signal_get_uniq_id("server event");
597
41.5k
  signal_server_event_tags = signal_get_uniq_id("server event tags");
598
41.5k
  signal_server_incoming = signal_get_uniq_id("server incoming");
599
41.5k
}
600
601
void irc_irc_deinit(void)
602
41.5k
{
603
41.5k
  signal_remove("server event", (SIGNAL_FUNC) irc_server_event);
604
41.5k
  signal_remove("server event tags", (SIGNAL_FUNC) irc_server_event_tags);
605
41.5k
  signal_remove("server connected", (SIGNAL_FUNC) irc_init_server);
606
41.5k
  signal_remove("server connection switched", (SIGNAL_FUNC) irc_init_server);
607
  signal_remove("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line);
608
41.5k
}