AmendHub

Download:

jcs

/

wallops

/

amendments

/

8

irc: Simplify protocol processing, implement more action responses


jcs made amendment 8 over 2 years ago
--- irc.c Fri Feb 4 12:58:13 2022 +++ irc.c Sun Feb 6 19:30:53 2022 @@ -33,6 +33,11 @@ void irc_login(struct chatter *chatter); void irc_process_server(struct chatter *chatter); void irc_set_active_channel(struct chatter *chatter, char *channame); void irc_parse_names(struct chatter *chatter, char *line); +void irc_add_user_to_channel(struct chatter *chatter, + struct irc_user *user); +void irc_remove_nick_from_channel(struct chatter *chatter, char *nick); +void irc_parse_channel_mode_change(struct chatter *chatter, char *mode, + char *args); void irc_process(struct chatter *chatter) @@ -126,6 +131,9 @@ irc_init(struct chatter *chatter) void irc_abort(struct chatter *chatter) { + chatter->irc_state = IRC_STATE_DISCONNECTED; + irc_set_active_channel(chatter, NULL); + _TCPClose(&chatter->irc_close_pb, chatter->irc_stream, nil, nil, false); _TCPAbort(&chatter->irc_close_pb, chatter->irc_stream, nil, nil, @@ -142,7 +150,6 @@ irc_close(struct chatter *chatter) chatter_printf(chatter, "$B*!*$0 Disconnecting"); irc_abort(chatter); - chatter->irc_state = IRC_STATE_DISCONNECTED; } short @@ -251,9 +258,6 @@ irc_printf(struct chatter *chatter, const char *format buf[len - 1] = '\0'; } -#if 0 - chatter_printf(chatter, ">>>[%ld] %s", len, buf); -#endif irc_send(chatter, buf, len); return len; @@ -347,7 +351,7 @@ irc_process_server(struct chatter *chatter) { struct irc_msg msg; struct irc_user *user; - char *line; + char *line, *word; size_t size, n, lastbreak; short state; @@ -356,124 +360,168 @@ irc_process_server(struct chatter *chatter) return; memset(&msg, 0, sizeof(msg)); + + word = strsep(&line, " "); - for (n = 0, lastbreak = 0, state = 0; n < size; n++) { - if (!(line[n] == ' ' || n == size - 1)) - continue; - - line[n] = '\0'; - - switch (state) { - case 0: - if (line[0] == ':') - /* :server.name 001 jcs :Hi -> msg.source=server.name */ - strlcpy(msg.source, line + 1, sizeof(msg.source)); - else - /* PING :server.name -> msg.cmd=PING */ - strlcpy(msg.cmd, line, sizeof(msg.cmd)); - state++; - break; - case 1: - if (isdigit(line[lastbreak]) && isdigit(line[lastbreak + 1]) && - isdigit(line[lastbreak + 2])) - /* :server.name 001 jcs :Hi -> msg.code=1 */ - msg.code = atoi(line + lastbreak); - else if (msg.cmd[0] != '\0') { - /* PING :server.name -> msg.msg=server.name */ - if (line[lastbreak + 1] == ':') - lastbreak++; - strlcpy(msg.msg, line + lastbreak + 1, sizeof(msg.msg)); - goto done_parsing; - } else - /* :jcs!~jcs@jcs JOIN #wallops -> msg.cmd=JOIN */ - strlcpy(msg.cmd, line + lastbreak, sizeof(msg.cmd)); - state++; - break; - case 2: - if (line[lastbreak] == ':') { - /* :jcs!... QUIT :Ping timeout -> msg.msg=Ping timeout */ - strlcpy(msg.msg, line + lastbreak + 1, - sizeof(msg.msg)); - goto done_parsing; - } - - /* :server.name 251 jcs :Blah -> msg.dest=jcs */ - strlcpy(msg.dest, line + lastbreak, sizeof(msg.dest)); - - if (line[n + 1] == ':') { - strlcpy(msg.msg, line + n + 2, sizeof(msg.msg)); - goto done_parsing; - } - state++; - break; - case 3: - if (line[lastbreak] != ':') { - /* :server 480 jcs #wallops :Cannot... -> msg.channel=#wallops */ - strlcpy(msg.channel, line + lastbreak, sizeof(msg.channel)); - lastbreak = n + 1; - } - - strlcpy(msg.msg, line + lastbreak + - (line[lastbreak] == ':' ? 1 : 0), sizeof(msg.msg)); - goto done_parsing; - } - - lastbreak = n + 1; + /* extract source before command */ + if (word[0] == ':') { + /* ":server.name 001 jcs :Hi" -> msg.source=server.name */ + strlcpy(msg.source, word + 1, sizeof(msg.source)); + word = strsep(&line, " "); } -done_parsing: - if (strcmp(msg.cmd, "PING") == 0) { - irc_printf(chatter, "PONG :%s\r\n", msg.source); - return; + /* code or a command name */ + if (isdigit(word[0]) && isdigit(word[1]) && isdigit(word[2])) + /* ":server.name 001 jcs :Hi" -> msg.code=1 */ + msg.code = atoi(word); + else + /* "PING :server.name" -> msg.cmd=PING */ + /* ":jcs!~jcs@jcs JOIN #wallops" -> msg.cmd=JOIN */ + strlcpy(msg.cmd, word, sizeof(msg.cmd)); + + /* parameters */ + + if (line[0] == ':') { + strlcpy(msg.msg, line + 1, sizeof(msg.msg)); + goto done_parsing; } - if (strcmp(msg.cmd, "NOTICE") == 0) { - chatter_printf(chatter, "%s", msg.msg); - return; + word = strsep(&line, " "); + if (word == NULL) { + strlcpy(msg.msg, line + 1, sizeof(msg.msg)); + goto done_parsing; } - if (strcmp(msg.cmd, "MODE") == 0) { - chatter_printf(chatter, "$B***$0 Mode change ($B%s$0) for $B%s$0", - msg.msg, msg.dest); - return; + strlcpy(msg.dest, word, sizeof(msg.dest)); + + if (line[0] == ':') { + strlcpy(msg.msg, line + 1, sizeof(msg.msg)); + goto done_parsing; } - if (strcmp(msg.cmd, "PRIVMSG") == 0) { - user = irc_parse_user(msg.source); - if (strcmp(msg.dest, chatter->irc_nick) == 0) - chatter_printf(chatter, "$B[$0%s$B($0%s@%s$B)]$0$/ %s", - user->nick, user->username, user->hostname, msg.msg); - else if (chatter->irc_channel && - strcmp(msg.dest, chatter->irc_channel->name) == 0) { - size = strlen(chatter->irc_nick); - if (strncmp(msg.msg, chatter->irc_nick, size) == 0 && - (msg.msg[size] == ' ' || msg.msg[size] == ':' || - msg.msg[size] == ',' || msg.msg[size] == '\0')) - chatter_printf(chatter, "<$U%s$0>$/ %s", user->nick, + word = strsep(&line, " "); + if (word == NULL) { + strlcpy(msg.msg, line + 1, sizeof(msg.msg)); + goto done_parsing; + } + strlcpy(msg.channel, word, sizeof(msg.channel)); + + if (line[0] == ':') { + strlcpy(msg.msg, line + 1, sizeof(msg.msg)); + goto done_parsing; + } + strlcpy(msg.msg, line, sizeof(msg.msg)); + +done_parsing: + if (msg.cmd[0]) { + if (strcmp(msg.cmd, "PING") == 0) { + irc_printf(chatter, "PONG :%s\r\n", msg.source); + return; + } + if (strcmp(msg.cmd, "PRIVMSG") == 0) { + user = irc_parse_user(msg.source); + if (strcmp(msg.dest, chatter->irc_nick) == 0) + chatter_printf(chatter, "$B[$0%s$B($0%s@%s$B)]$0$/ %s", + user->nick, user->username, user->hostname, msg.msg); + else if (chatter->irc_channel && + strcmp(msg.dest, chatter->irc_channel->name) == 0) { + size = strlen(chatter->irc_nick); + if (strncmp(msg.msg, chatter->irc_nick, size) == 0 && + (msg.msg[size] == ' ' || msg.msg[size] == ':' || + msg.msg[size] == ',' || msg.msg[size] == '\0')) { + /* highlight message */ + chatter_printf(chatter, "<$U%s$0>$/ %s", user->nick, + msg.msg); + if (!chatter->focusable->visible) + notify(); + } else + chatter_printf(chatter, "$/<%s> %s", user->nick, + msg.msg); + } else + chatter_printf(chatter, "$B[%s($0%s@%s$B):%s]$0$/ %s", + user->nick, user->username, user->hostname, msg.dest, msg.msg); + return; + } + if (strcmp(msg.cmd, "JOIN") == 0) { + user = irc_parse_user(msg.source); + chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has joined " + "$B%s$0", user->nick, user->username, user->hostname, + msg.dest); + if (strcmp(user->nick, chatter->irc_nick) == 0) + irc_set_active_channel(chatter, msg.dest); else - chatter_printf(chatter, "$/<%s> %s", user->nick, + irc_add_user_to_channel(chatter, user); + return; + } + if (strcmp(msg.cmd, "PART") == 0) { + user = irc_parse_user(msg.source); + chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has left " + "$B%s$0", user->nick, user->username, user->hostname, + msg.dest); + if (strcmp(user->nick, chatter->irc_nick) == 0) + irc_set_active_channel(chatter, NULL); + else + irc_remove_nick_from_channel(chatter, user->nick); + return; + } + if (strcmp(msg.cmd, "QUIT") == 0) { + user = irc_parse_user(msg.source); + chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has quit:$/ %s", + user->nick, user->username, user->hostname, msg.msg); + if (strcmp(user->nick, chatter->irc_nick) == 0) + irc_set_active_channel(chatter, NULL); + else + irc_remove_nick_from_channel(chatter, user->nick); + return; + } + if (strcmp(msg.cmd, "NICK") == 0) { + user = irc_parse_user(msg.source); + chatter_printf(chatter, "$B*** %s$0 is now known as $B%s$0", + user->nick, msg.msg); + irc_remove_nick_from_channel(chatter, user->nick); + strlcpy(user->nick, msg.msg, sizeof(user->nick)); + irc_add_user_to_channel(chatter, user); + return; + } + if (strcmp(msg.cmd, "MODE") == 0) { + if (strcmp(msg.dest, chatter->irc_nick) == 0) + chatter_printf(chatter, "$B***$0 Mode change ($B%s$0) " + "for user $B%s$0", msg.msg, msg.dest); + else { + /* msg.channel is mode */ + user = irc_parse_user(msg.source); + chatter_printf(chatter, "$B***$0 Mode change ($B%s %s$0) " + "on $B%s$0 by $B%s%0", msg.channel, msg.msg, msg.dest, + user->nick); + irc_parse_channel_mode_change(chatter, msg.channel, msg.msg); - } else - chatter_printf(chatter, "$B[%s($0%s@%s$B):%s]$0$/ %s", - user->nick, user->username, user->hostname, msg.dest, - msg.msg); - - return; + } + return; + } + if (strcmp(msg.cmd, "KICK") == 0) { + /* dest and channel are swapped */ + user = irc_parse_user(msg.source); + chatter_printf(chatter, "$B*** %s ($0%s$B)$0 was kicked " + "from $B%s$0 by $B%s%0:$/ %s", msg.channel, msg.dest, + user->nick, msg.msg); + if (strcmp(msg.channel, chatter->irc_nick) == 0) + irc_set_active_channel(chatter, NULL); + else + irc_remove_nick_from_channel(chatter, msg.channel); + return; + } + if (strcmp(msg.cmd, "NOTICE") == 0) { + chatter_printf(chatter, "%s", msg.msg); + return; + } + if (strcmp(msg.cmd, "ERROR") == 0) { + chatter_printf(chatter, "$B*!* Disconnected from %s:$0$/ %s", + chatter->irc_hostname, msg.msg); + irc_abort(chatter); + return; + } + + goto unknown; } - if (strcmp(msg.cmd, "JOIN") == 0) { - user = irc_parse_user(msg.source); - chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has joined $B%s$0", - user->nick, user->username, user->hostname, msg.dest); - if (strcmp(user->nick, chatter->irc_nick) == 0) - irc_set_active_channel(chatter, msg.dest); - return; - } - if (strcmp(msg.cmd, "PART") == 0) { - user = irc_parse_user(msg.source); - chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has left $B%s$0", - user->nick, user->username, user->hostname, msg.dest); - if (strcmp(user->nick, chatter->irc_nick) == 0) - irc_set_active_channel(chatter, NULL); - return; - } + switch (msg.code) { case 0: goto unknown; @@ -524,14 +572,17 @@ done_parsing: /* end of WHO */ goto unknown; case 353: - /* NAMES output */ - if (chatter->irc_channel) /* TODO: match arg to channel name */ + /* NAMES output, channel is first part of msg.msg, not channel */ + if (chatter->irc_channel && + strncmp(msg.msg, chatter->irc_channel->name, + strlen(chatter->irc_channel->name)) == 0) irc_parse_names(chatter, msg.msg); return; case 366: /* end of NAMES output */ - if (chatter->irc_channel) - chatter_sync_nick_list(chatter); + if (chatter->irc_channel && + strcmp(msg.channel, chatter->irc_channel->name) == 0) + chatter_sync_nick_list(chatter, true); return; case 372: case 375: @@ -616,7 +667,7 @@ irc_set_active_channel(struct chatter *chatter, char * } if (channame == NULL) - chatter_sync_nick_list(chatter); + chatter_sync_nick_list(chatter, false); else { channel = xmalloczero(sizeof(struct irc_channel)); strlcpy(channel->name, channame, sizeof(channel->name)); @@ -672,4 +723,98 @@ irc_parse_names(struct chatter *chatter, char *line) if (line == NULL) break; } +} + +void +irc_add_user_to_channel(struct chatter *chatter, struct irc_user *user) +{ + chatter->irc_channel->nusers++; + chatter->irc_channel->users = + xreallocarray(chatter->irc_channel->users, + sizeof(struct irc_channel_nick), chatter->irc_channel->nusers); + strlcpy(chatter->irc_channel->users[ + chatter->irc_channel->nusers - 1].nick, user->nick, + sizeof(chatter->irc_channel->users[0].nick)); + chatter_sync_nick_list(chatter, false); +} + +void +irc_remove_nick_from_channel(struct chatter *chatter, char *nick) +{ + size_t n; + + for (n = 0; n < chatter->irc_channel->nusers; n++) { + if (strcmp(chatter->irc_channel->users[n].nick, nick) == 0) { + LDelRow(1, n, chatter->nick_list); + + /* move everyone else up */ + for (; n < chatter->irc_channel->nusers - 1; n++) + chatter->irc_channel->users[n] = + chatter->irc_channel->users[n + 1]; + break; + } + } + + chatter->irc_channel->nusers--; + chatter->irc_channel->users = + xreallocarray(chatter->irc_channel->users, + sizeof(struct irc_channel_nick), chatter->irc_channel->nusers); +} + +void +irc_parse_channel_mode_change(struct chatter *chatter, char *mode, + char *args) +{ + size_t len; + struct irc_channel_nick *cnick; + char *user; + short n, j; + bool add = false, need_resync = false; + + len = strlen(mode); + + /* mode:"+mvo-vo" args:"nick1 nick2 nick3 nick4" */ + for (n = 0; n < len; n++) { + switch (mode[n]) { + case '+': + add = true; + break; + case '-': + add = false; + break; + case 'v': + case 'o': + user = strsep(&args, " "); + if (user == NULL) + user = args; + for (j = 0; j < chatter->irc_channel->nusers; j++) { + cnick = &chatter->irc_channel->users[j]; + if (strcmp(cnick->nick, user) != 0) + continue; + + if (mode[n] == 'o') { + if (add) + cnick->flags |= IRC_NICK_FLAG_OP; + else + cnick->flags &= ~IRC_NICK_FLAG_OP; + } else if (mode[n] == 'v') { + if (add) + cnick->flags |= IRC_NICK_FLAG_VOICE; + else + cnick->flags &= ~IRC_NICK_FLAG_VOICE; + } + + need_resync = true; + break; + } + + break; + default: + /* some other channel mode */ + break; + } + } + + if (need_resync) + chatter_sync_nick_list(chatter, false); }