ircd/irc_cmd.cpp
#include "irc_cmd.hpp"
#include "common.hpp"
#include <assert.h>
#define to_upper(c) (((c)>='a' && (c)<='z')? (c)-32: (c))
#define command_type_a_init(c, min, max, msg) {CMD_##c, STRINGIFY(c), min, (unsigned)max, msg}
static const Command::command_info_t command_type_a[] = {
command_type_a_init(PASS, 1, 1, false),
command_type_a_init(CAP, 0, -1, false),
command_type_a_init(NICK, 1, 1, false),
command_type_a_init(USER, 4, 4, false),
command_type_a_init(OPER, 2, 2, false),
command_type_a_init(MOTD, 0, 1, false),
command_type_a_init(JOIN, 1, 2, false),
command_type_a_init(WHO, 1, 1, false),
command_type_a_init(LIST, 0, 0, false),
command_type_a_init(AWAY, 0, 1, false),
command_type_a_init(MODE, 1, 2, false),
command_type_a_init(TOPIC, 1, 2, false),
command_type_a_init(PING, 0, 1, false),
command_type_a_init(PONG, 0, -1, false),
command_type_a_init(PRIVMSG, 2, 2, false),
command_type_a_init(PART, 1, 2, false),
command_type_a_init(QUIT, 0, 1, false),
command_type_a_init(NICKSERV, 1, 3, true),
{CMD_UNKNOWN, NULL, 1, 1, false}
};
static const Command::command_info_t* cmd_find(const char* s) {
const Command::command_info_t* cmds = command_type_a;
do {
if (strcasecmp(s, cmds->s) == 0) {
return cmds;
}
cmds++;
} while (cmds->t != CMD_UNKNOWN);
return cmds;
}
bool Command::is_command(const char* nick) {
return cmd_find(nick)->t != CMD_UNKNOWN; // even if not from_privmsg
}
Command::Command(const command_info_t* t): cleanup(NULL), type(t), params(), nparams(0) {
}
Command::~Command() {
if (cleanup) {
messages.push(cleanup);
}
}
Command* Command::parse(msg_t* mbuf) {
char* buf = (char*)mbuf;
const command_info_t* type = parse_cmd(buf);
if (!type) { // parsing err
LOG("invalid command string '%s'", (const char*)mbuf); // XXX: assumes buf gets tokenized and only includes valid chars
messages.push(mbuf);
return NULL;
}
Command* cmd = new Command(type);
if (type->t == CMD_UNKNOWN) {
cmd->nparams = 1;
cmd->params[0] = (char*)mbuf;
LOG("unknown command '%s'", cmd->params[0]);
} else {
if (!cmd->parse_params(buf)) {
LOG("cannot parse params for %s", type->s);
delete cmd;
messages.push(mbuf);
return NULL;
}
}
if (type->t == CMD_PRIVMSG && cmd->nparams == 2) {
const command_info_t* ntype = cmd_find(cmd->params[0]);
if (ntype->from_privmsg) {
buf = cmd->params[1];
delete cmd;
cmd = new Command(ntype);
if (!cmd->parse_params(buf)) {
LOG("cannot parse params for %s -> %s", type->s, cmd->type->s);
delete cmd;
messages.push(mbuf);
return NULL;
}
}
}
cmd->cleanup = mbuf;
return cmd;
}
const Command::command_info_t* Command::parse_cmd(char*& buf) {
char* s = buf;
while (is_letter(*buf)) {
*buf = to_upper(*buf); // still needed?
++buf;
}
if (*buf == '\0') {
// without args
} else if (*buf == ' ') {
*buf = '\0';
do { ++buf; } while (*buf == ' ');
} else {
*buf = '\0'; // terminate after valid chars in any case
return NULL;
}
return cmd_find(s);
}
bool Command::parse_params(char* buf) {
while (*buf) {
if (nparams == IRC_MAX_CMD_PARAMS) {
return false;
}
if (*buf == ':') { // special trailing arg until end supporting spaces
if (buf[1]) { // not if empty
params[nparams++] = buf+1;
}
break;
}
params[nparams++] = buf;
do {
if (*buf == ' ') {
*buf = '\0';
do {
++buf;
} while (*buf == ' ');
break;
}
++buf;
} while (*buf);
}
return true;
}