This repository has been archived on 2021-04-17. You can view files and clone it, but cannot push or open issues or pull requests.
uIRC/src/uirc.c

317 lines
8.7 KiB
C

/*
* This file is part of uIRC. (https://git.redxen.eu/caskd/uIRC)
* Copyright (c) 2019, 2020 Alex-David Denes
*
* uIRC is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* uIRC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with uIRC. If not, see <https://www.gnu.org/licenses/>.
*/
#include "uirc.h"
const char* const uirc_ircmd[] = {
[ADMIN] = "ADMIN",
[AWAY] = "AWAY",
[CONNECT] = "CONNECT",
[DIE] = "DIE",
[ERROR] = "ERROR",
[INFO] = "INFO",
[INVITE] = "INVITE",
[ISON] = "ISON",
[JOIN] = "JOIN",
[KICK] = "KICK",
[KILL] = "KILL",
[LINKS] = "LINKS",
[LIST] = "LIST",
[LUSERS] = "LUSERS",
[MODE] = "MODE",
[MOTD] = "MOTD",
[NAMES] = "NAMES",
[NICK] = "NICK",
[NOTICE] = "NOTICE",
[OPER] = "OPER",
[PART] = "PART",
[PASS] = "PASS",
[PING] = "PING",
[PONG] = "PONG",
[PRIVMSG] = "PRIVMSG",
[QUIT] = "QUIT",
[REHASH] = "REHASH",
[RESTART] = "RESTART",
[SERVER] = "SERVER",
[SERVICE] = "SERVICE",
[SERVLIST] = "SERVLIST",
[SQUERY] = "SQUERY",
[SQUIT] = "SQUIT",
[STATS] = "STATS",
[SUMMON] = "SUMMON",
[TIME] = "TIME",
[TOPIC] = "TOPIC",
[TRACE] = "TRACE",
[USERHOST] = "USERHOST",
[USERS] = "USERS",
[USER] = "USER",
[VERSION] = "VERSION",
[WALLOPS] = "WALLOPS",
[WHOIS] = "WHOIS",
[WHOWAS] = "WHOWAS",
[WHO] = "WHO",
#ifdef UIRC_IRCV3
[ACCOUNT] = "ACCOUNT",
[ACC] = "ACC",
[ACK] = "ACK",
[AUTHENTICATE] = "AUTHENTICATE",
[BATCH] = "BATCH",
[CAP] = "CAP",
[CHGHOST] = "CHGHOST",
[FAIL] = "FAIL",
[MONITOR] = "MONITOR",
[NOTE] = "NOTE",
[RENAME] = "RENAME",
[RESUME] = "RESUME",
[SETNAME] = "SETNAME",
[WARN] = "WARN",
[WEBIRC] = "WEBIRC"
#endif
};
#ifdef UIRC_IRCV3
struct tagmapping {
const char* const name;
IRC_Tag* assg;
};
#endif
signed int Ircmd_stoi(char* str)
{
if (str == NULL)
return ERR_UIRC_NULL_ARGS;
for (unsigned short i = UIRC_FCMD; i <= UIRC_LCMD; i++) {
if (uirc_ircmd[i] != NULL && strcmp(uirc_ircmd[i], str) == 0)
return i;
}
return ERR_UIRC_UNKNOWN_TOKEN;
}
signed int Tok_mesg(char* str, IRC_Message* out)
{
if (str == NULL || out == NULL)
return ERR_UIRC_NULL_ARGS;
int ret;
char *progr = str, *command;
#ifdef UIRC_IRCV3
if (*progr == '@') {
char* tags;
if ((tags = strtok_r(progr, " ", &progr)) != NULL) {
if ((ret = Tok_tags(tags, &out->tags)) < 0)
return ret;
} else
return ERR_UIRC_INVALID_FORMAT;
}
#endif
if (*progr == ':') {
char* prefix;
if ((prefix = strtok_r(progr, " ", &progr)) != NULL) {
if ((ret = Tok_user(prefix, &out->name, false)) < 0)
return ret;
} else
return ERR_UIRC_INVALID_FORMAT;
}
if ((command = strtok_r(NULL, " ", &progr)) != NULL) {
if (!(out->cmd = (isalpha(*command)) ? Ircmd_stoi(command) : atoi(command)))
return ERR_UIRC_UNKNOWN_TOKEN;
} else {
return ERR_UIRC_INVALID_FORMAT;
}
int i;
for (i = 0; *progr != ':' && i < 14 && *progr; i++) {
if ((out->args[i] = strtok_r(NULL, " ", &progr)) == NULL)
return ERR_UIRC_INVALID_FORMAT;
}
out->args[i] = NULL;
if (*progr)
out->trailing = (*progr == ':') ? progr + 1 : progr;
return 1;
}
signed int Assm_mesg(char* buf, IRC_Message* in)
{
if (buf == NULL || in == NULL)
return ERR_UIRC_BUFFER_ERR;
char* pos = buf;
int cnt, ret;
#ifdef UIRC_IRCV3
if ((ret = Assm_tags(pos, &in->tags)) < 0)
return ret;
else
pos += ret;
#endif
if (in->name.nick != NULL || in->name.host != NULL) {
*(pos++) = ':';
if ((ret = Assm_user(pos, &in->name, false)) <= 0)
return ret;
else
pos += ret;
*(pos++) = ' ';
}
if ((uirc_ircmd[in->cmd] != NULL) && ((cnt = sprintf(pos, "%s", uirc_ircmd[in->cmd])) > 0))
pos += cnt;
else
return ERR_UIRC_GENERIC;
for (unsigned int i = 0; i < 14 && in->args[i] != NULL; i++) {
if ((cnt = sprintf(pos, " %s", in->args[i])) > 0)
pos += cnt;
else
return ERR_UIRC_BUFFER_ERR;
}
if (in->trailing != NULL) {
if ((cnt = sprintf(pos, " :%s", in->trailing)) > 0)
pos += cnt;
else
return ERR_UIRC_GENERIC;
}
if ((cnt = sprintf(pos, "\r\n")) == 2)
pos += cnt;
else
return ERR_UIRC_BUFFER_ERR;
return pos - buf;
}
#ifdef UIRC_IRCV3
signed int Assm_tags(char* buf, IRC_Tags* in)
{
if (buf == NULL || in == NULL)
return ERR_UIRC_NULL_ARGS;
char* pos = buf;
int cnt;
struct tagmapping tagmps[] = {
{.name = "time", .assg = &in->time},
{.name = "account", .assg = &in->account},
{.name = "batch", .assg = &in->batch},
{.name = "label", .assg = &in->label},
{.name = "msgid", .assg = &in->msgid},
{.name = "multiline-concat", .assg = &in->multiline_concat},
{.name = "typing", .assg = &in->typing},
{.name = "react", .assg = &in->react},
{.name = "reply", .assg = &in->reply}};
for (unsigned int i = 0; i < sizeof(tagmps) / sizeof(struct tagmapping); i++) {
if ((*tagmps[i].assg).value != NULL) {
if (pos == buf) {
if (sprintf(pos++, "@") != 1)
return ERR_UIRC_BUFFER_ERR;
} else {
if (sprintf(pos++, ";") != 1)
return ERR_UIRC_BUFFER_ERR;
}
if ((*tagmps[i].assg).clientbound) {
if (sprintf(pos++, "+") != 1)
return ERR_UIRC_BUFFER_ERR;
}
cnt = (*(*tagmps[i].assg).value) ? sprintf(pos, "%s=%s", tagmps[i].name, (*tagmps[i].assg).value) : sprintf(pos, "%s", tagmps[i].name);
if (cnt > 0)
pos += cnt;
else
return ERR_UIRC_BUFFER_ERR;
}
}
return pos - buf;
}
signed int Tok_tags(char* str, IRC_Tags* out)
{
if (str == NULL || out == NULL)
return ERR_UIRC_NULL_ARGS;
char *cval, *cpos = str, *ctag = NULL;
bool clientbound;
const struct tagmapping tagmps[] = {
{.name = "time", .assg = &out->time},
{.name = "account", .assg = &out->account},
{.name = "batch", .assg = &out->batch},
{.name = "label", .assg = &out->label},
{.name = "msgid", .assg = &out->msgid},
{.name = "multiline-concat", .assg = &out->multiline_concat},
{.name = "typing", .assg = &out->typing},
{.name = "react", .assg = &out->react},
{.name = "reply", .assg = &out->reply}};
if (*cpos == '@')
cpos++;
while ((ctag = strtok_r(NULL, "; ", &cpos)) != NULL) {
clientbound = false;
if (*ctag == '+') {
ctag++;
clientbound = true;
}
if ((cval = strchr(ctag, '=')) != NULL)
*(cval++) = '\0';
for (unsigned int i = 0; i < sizeof(tagmps) / sizeof(struct tagmapping); i++) {
if (!strcmp(ctag, tagmps[i].name)) {
/* If the tag is present we point it to the value if given, or to the delimiter ('\0')
* This is done for a few reasons. First, we have a non-null address so we show that
* the tag is present and second, we have no value that way
*/
(*tagmps[i].assg).value = (cval != NULL) ? cval : ctag + strlen(ctag);
(*tagmps[i].assg).clientbound = clientbound;
}
}
}
return 1;
}
#endif
signed int Assm_user(char* buf, IRC_User* in, bool useorig)
{
if (buf == NULL || in == NULL)
return ERR_UIRC_NULL_ARGS;
int cnt;
char* pos = buf;
if (in->nick == NULL && in->host != NULL) {
if ((cnt = sprintf(pos, "%s", in->host)) > 0)
pos += cnt;
else
return ERR_UIRC_BUFFER_ERR;
} else if (in->nick != NULL) {
if ((cnt = sprintf(pos, "%s", in->nick)) > 0)
pos += cnt;
else
return ERR_UIRC_BUFFER_ERR;
if (in->user != NULL) {
if ((cnt = sprintf(pos, "!%s", in->user)) > 0)
pos += cnt;
else
return ERR_UIRC_BUFFER_ERR;
}
if (useorig && in->orig != NULL) {
if ((cnt = sprintf(pos, "%%%s", in->orig)) > 0)
pos += cnt;
else
return ERR_UIRC_BUFFER_ERR;
}
if (in->host != NULL) {
if ((cnt = sprintf(pos, "@%s", in->host)) > 0)
pos += cnt;
else
return ERR_UIRC_BUFFER_ERR;
}
} else
return ERR_UIRC_NULL_ARGS;
return pos - buf;
}
signed int Tok_user(char* str, IRC_User* out, bool useorig)
{
char* pos = (*str == ':') ? str + 1 : str;
if ((out->host = strchr(pos, '@')) != NULL)
*(out->host++) = '\0';
if (useorig && (out->orig = strchr(pos, '%')) != NULL)
*(out->orig++) = '\0';
if ((out->user = strchr(pos, '!')) != NULL) /* RFC2812 says this cannot be here without the host but RFC1459 says it can, we accept both options */
*((char*)out->user++) = '\0';
if (!*(out->nick = pos)) /* NOTE: This may be the server instead of a nick according to RFC2812, it is left to the library out to decide how to interpret this based on the context. */
return ERR_UIRC_INVALID_FORMAT;
return 1;
}