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/tokenizers.c

180 lines
5.5 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 "public/tokenizers.h"
#include "private/mappings.h"
#include "private/string.h"
#include "public/commands.h"
#include "public/converters.h"
#include "public/errors.h"
#include "public/tags.h"
#include "public/types.h"
#include <assert.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef UIRC_IRCV3
signed int
Tok_tags(char* str, IRC_Tags* out)
{
assert(str != NULL);
assert(out != NULL);
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_mr(&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 /* UIRC_IRCV3 */
signed int
Tok_user(char* str, IRC_User* out, bool useorig)
{
assert(str != NULL);
assert(out != NULL);
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 */
*(out->user++) = '\0';
if (!*(out->nick = pos)) return ERR_UIRC_INVALID_FORMAT;
/* NOTE: De-facto standard below
* This assumes that every prefix without a '@host' and '!user' is itself a host prefix only
* It might be a actual nickname, but this is the most common around every network
* It is left to the user to decide and handle the result accordingly
*/
if (out->host == NULL && out->user == NULL) {
out->host = out->nick;
out->nick = NULL;
}
return 1;
}
signed int
Tok_mesg(char* str, IRC_Message* out)
{
assert(str != NULL);
assert(out != NULL);
int ret;
char *progr = str, *command;
#ifdef UIRC_IRCV3
if (*progr == '@') {
char* tags;
if ((tags = strtok_mr(&progr, " ")) != NULL) {
if ((ret = Tok_tags(tags, &out->tags)) < 0) return ret;
} else
return ERR_UIRC_INVALID_FORMAT;
}
skip_spaces(&progr);
#endif /* UIRC_IRCV3 */
if (*progr == ':') {
char* prefix;
if ((prefix = strtok_mr(&progr, " ")) != NULL) {
if ((ret = Tok_user(prefix, &out->name, false)) < 0) return ret;
} else
return ERR_UIRC_INVALID_FORMAT;
skip_spaces(&progr);
}
if (isalnum(*progr) && (command = strtok_mr(&progr, " ")) != NULL) {
signed short temp;
if (isalpha(*command)) {
if ((temp = Ircmd_stoi(command)) < UIRC_FCMD || temp > UIRC_LCMD) return ERR_UIRC_UNKNOWN_TOKEN;
out->cmd = (IRC_Command) temp;
} else {
for (size_t l = 0; command[l]; l++) {
if (!isdigit(command[l])) return ERR_UIRC_INVALID_FORMAT;
}
out->cmd = (IRC_Command) atoi(command);
}
} else
return ERR_UIRC_INVALID_FORMAT;
skip_spaces(&progr);
short i;
for (i = 0; i < 15 && *progr;) {
if (i == 14 || *progr == ':') {
out->args[i++] = (*progr == ':') ? progr + 1 : progr;
out->trailing = true;
break;
} else {
if ((out->args[i++] = strtok_mr(&progr, " ")) == NULL) return ERR_UIRC_INVALID_FORMAT;
skip_spaces(&progr);
}
}
out->args[i] = NULL;
return 1;
}
#ifdef UIRC_HELPERS
void
Tok_cmd_PING(const IRC_Message* mesg, char** source, char** target)
{
assert(mesg != NULL);
assert(source != NULL);
assert(target != NULL);
*source = (mesg->args[0] == NULL && mesg->trailing) ? mesg->args[2] : (mesg->args[1] != NULL) ? mesg->args[0] : NULL;
*target = (mesg->args[1] == NULL) ? mesg->args[0] : mesg->args[1];
}
/* Use with WHOIS/LINKS
* (stands for first argument optional)
* [ <optarg> ] <reqarg>
*/
void
Tok_FArgOpt(const IRC_Message* mesg, char** optarg, char** reqarg)
{
assert(mesg != NULL);
assert(optarg != NULL);
assert(reqarg != NULL);
*optarg = (mesg->args[1] != NULL) ? mesg->args[0] : NULL;
*reqarg = (mesg->args[1] != NULL) ? mesg->args[1] : mesg->args[0];
}
#endif /* UIRC_HELPERS */