ircd/irc_std.cpp
#include "irc_std.hpp"
#include "irc_msg.hpp"
#include "client_state.hpp"
#include "config.hpp"
#include <assert.h>
#include <stdarg.h>
static bool setstr(char*& b, size_t& l, char c) {
if (!l) return false;
*(b++) = c;
--l;
return true;
}
static bool setstr(char*& b, size_t& l, const char* c, size_t cl) {
if (cl > l) return false;
memcpy(b, c, cl);
b += cl;
l -= cl;
return true;
}
static bool setstr(char*& b, size_t& l, const char* c) {
return setstr(b, l, c, strlen(c));
}
static bool setstr(char*& b, size_t& l, const char* fmt, va_list args) {
int rv = vsnprintf(b, l, fmt, args);
if (rv < 0 || (size_t)rv >= l) {
return false;
}
b += rv;
l -= rv;
return true;
}
bool raw_reply_msg(Client* dst, const char* str, size_t len) {
if (len > messages.len()) {
return dst->send(NULL, 0);
}
msg_t* msg = messages.pop();
memcpy(msg, str, len);
return dst->send(msg, len);
}
bool raw_reply_fmt(Client* dst, const char* fmt, ...) {
msg_t* msg = messages.pop();
char* buf = (char*)msg;
size_t len = messages.len();
va_list args;
va_start(args, fmt);
if (!setstr(buf, len, fmt, args)) {
va_end(args);
messages.push(msg);
return dst->send(NULL, 0);
}
va_end(args);
return dst->send(msg, buf-(char*)msg);
}
static bool reply_splittable(Client* dst, msg_t* prefix, size_t prefix_len, const char* suffix, size_t suffix_len) {
if (prefix_len + 2 >= IRC_MAX_MSG_LEN) {
messages.push(prefix);
return dst->send(NULL, 0);
}
while (prefix_len + suffix_len + 2 > IRC_MAX_MSG_LEN) {
msg_t* msg = messages.pop();
memcpy(msg, prefix, prefix_len);
size_t l = IRC_MAX_MSG_LEN - prefix_len - 2;
assert(l);
assert(l < suffix_len);
memcpy((char*)msg + prefix_len, suffix, l);
suffix += l;
suffix_len -= l;
if (!dst->send(msg, prefix_len + l)) {
messages.push(prefix);
return dst->send(NULL, 0);
}
}
memcpy((char*)prefix + prefix_len, suffix, suffix_len);
return dst->send(prefix, prefix_len + suffix_len);
}
bool raw_reply_fmt_long(Client* dst, const char* str, const char* fmt, ...) {
msg_t* msg = messages.pop();
char* buf = (char*)msg;
size_t len = messages.len();
va_list args;
va_start(args, fmt);
if (!setstr(buf, len, fmt, args)) {
va_end(args);
messages.push(msg);
return dst->send(NULL, 0);
}
va_end(args);
if (str) {
if (!setstr(buf, len, " :", 2)) {
messages.push(msg);
return dst->send(NULL, 0);
}
return reply_splittable(dst, msg, buf-(char*)msg, str, strlen(str));
}
return dst->send(msg, buf-(char*)msg);
}
static char* srv_reply_pre(char* buf, size_t& len, const char* prefix, const char* code, const Client* nick) {
if (!setstr(buf, len, ':') || !setstr(buf, len, prefix)) {
return NULL;
}
if (code) {
if (!setstr(buf, len, ' ') || !setstr(buf, len, code)) {
return NULL;
}
}
if (!setstr(buf, len, ' ') || !setstr(buf, len, nick->irc_state->nick ?: "*")) {
return NULL;
}
return buf;
}
bool srv_reply_msg(Client* dst, const char* code, const char* str, size_t slen) {
msg_t* msg = messages.pop();
char* buf = (char*)msg;
size_t len = messages.len();
buf = srv_reply_pre(buf, len, config.server_name.str, code, dst);
if (!buf) {
messages.push(msg);
return dst->send(NULL, 0);
}
if (str) {
if (!setstr(buf, len, ' ') || !setstr(buf, len, str, slen)) {
messages.push(msg);
return dst->send(NULL, 0);
}
}
return dst->send(msg, buf-(char*)msg);
}
bool srv_reply_fmt(Client* dst, const char* code, const char* fmt, ...) {
msg_t* msg = messages.pop();
char* buf = (char*)msg;
size_t len = messages.len();
buf = srv_reply_pre(buf, len, config.server_name.str, code, dst);
if (!buf) {
messages.push(msg);
return dst->send(NULL, 0);
}
if (fmt) {
va_list args;
va_start(args, fmt);
if (!setstr(buf, len, ' ') || !setstr(buf, len, fmt, args)) {
va_end(args);
messages.push(msg);
return dst->send(NULL, 0);
}
va_end(args);
}
return dst->send(msg, buf-(char*)msg);
}
bool cli_reply_msg(Client* dst, const Client* nick, const char* str, size_t slen) {
msg_t* msg = messages.pop();
char* buf = (char*)msg;
size_t len = messages.len();
if (!setstr(buf, len, ':') || !setstr(buf, len, nick->irc_state->prefix_get(true, dst == nick || dst->irc_state->is_oper))) { // e.g. pidgin segfaults in irc_msg_join() when only using the nick
messages.push(msg);
return dst->send(NULL, 0);
}
if (str) {
if (!setstr(buf, len, ' ') || !setstr(buf, len, str, slen)) {
messages.push(msg);
return dst->send(NULL, 0);
}
}
return dst->send(msg, buf-(char*)msg);
}
bool cli_reply_fmt(Client* dst, const Client* nick, const char* fmt, ...) {
msg_t* msg = messages.pop();
char* buf = (char*)msg;
size_t len = messages.len();
if (!setstr(buf, len, ':') || !setstr(buf, len, nick->irc_state->prefix_get(true, dst == nick || dst->irc_state->is_oper))) {
messages.push(msg);
return dst->send(NULL, 0);
}
if (fmt) {
va_list args;
va_start(args, fmt);
if (!setstr(buf, len, ' ') || !setstr(buf, len, fmt, args)) {
va_end(args);
messages.push(msg);
return dst->send(NULL, 0);
}
va_end(args);
}
return dst->send(msg, buf-(char*)msg);
}