~rcr/rirc

cfe33d9d34a7adc69e3ed62d3f1b95b53dcc9775 — Richard Robbins 2 years ago 9be2278 + 56b22ba v0.1.2
Merge branch 'static_analysis'
M CHANGELOG => CHANGELOG +3 -0
@@ 2,6 2,8 @@
Summary of notable changes and features

## Unreleased (dev)
### Features
### Fixes

## [0.1.2]
### Features


@@ 25,6 27,7 @@ Summary of notable changes and features
 - add CTCP SOURCE
 - add CTCP USERINFO
 - add INVITE
 - add CASEMAPPING handling
### Refactor
 - generate gperf files at compile time
 - split mesg.c into:

M src/components/buffer.h => src/components/buffer.h +9 -9
@@ 16,15 16,15 @@
/* Buffer line types, in order of precedence */
enum buffer_line_t
{
	BUFFER_LINE_OTHER,      /* Default/all other lines */
	BUFFER_LINE_SERVER_MSG, /* Server info message */
	BUFFER_LINE_SERVER_ERR, /* Server error message */
	BUFFER_LINE_JOIN,       /* Irc JOIN message */
	BUFFER_LINE_NICK,       /* Irc NICK message */
	BUFFER_LINE_PART,       /* Irc PART message */
	BUFFER_LINE_QUIT,       /* Irc QUIT message */
	BUFFER_LINE_CHAT,       /* Line of text from another IRC user */
	BUFFER_LINE_PINGED,     /* Line of text from another IRC user containing current nick */
	BUFFER_LINE_OTHER,        /* Default/all other lines */
	BUFFER_LINE_SERVER_INFO,  /* Server info message */
	BUFFER_LINE_SERVER_ERROR, /* Server error message */
	BUFFER_LINE_JOIN,         /* Irc JOIN message */
	BUFFER_LINE_NICK,         /* Irc NICK message */
	BUFFER_LINE_PART,         /* Irc PART message */
	BUFFER_LINE_QUIT,         /* Irc QUIT message */
	BUFFER_LINE_CHAT,         /* Line of text from another IRC user */
	BUFFER_LINE_PINGED,       /* Line of text from another IRC user containing current nick */
	BUFFER_LINE_T_SIZE
};


M src/components/channel.c => src/components/channel.c +9 -42
@@ 5,15 5,6 @@
#include "src/components/channel.h"
#include "src/utils/utils.h"

static inline int channel_cmp(struct channel*, const char*);

static inline int
channel_cmp(struct channel *c, const char *name)
{
	/* TODO: CASEMAPPING, as ftpr held by the server */
	return irc_strcmp(c->name, name);
}

struct channel*
channel(const char *name, enum channel_t type)
{


@@ 26,9 17,8 @@ channel(const char *name, enum channel_t type)

	c->chanmodes_str.type = MODE_STR_CHANMODE;
	c->name_len = len;
	c->type = type;

	c->name = memcpy(c->_, name, len + 1);
	c->type = type;

	buffer(&c->buffer);
	input_init(&c->input);


@@ 59,14 49,9 @@ channel_list_free(struct channel_list *cl)
	} while (c1 != cl->head);
}

struct channel*
void
channel_list_add(struct channel_list *cl, struct channel *c)
{
	struct channel *tmp;

	if ((tmp = channel_list_get(cl, c->name)) != NULL)
		return tmp;

	if (cl->head == NULL) {
		cl->head = c->next = c;
		cl->tail = c->prev = c;


@@ 77,60 62,42 @@ channel_list_add(struct channel_list *cl, struct channel *c)
		cl->tail->next = c;
		cl->tail = c;
	}

	return NULL;
}

struct channel*
void
channel_list_del(struct channel_list *cl, struct channel *c)
{
	struct channel *tmp_h,
	               *tmp_t;

	if (cl->head == NULL) {
		return NULL;
	} else if (cl->head == c && cl->tail == c) {
		/* Removing tail */
	if (cl->head == c && cl->tail == c) {
		cl->head = NULL;
		cl->tail = NULL;
	} else if ((tmp_h = cl->head) == c) {
		/* Removing head */
	} else if (cl->head == c) {
		cl->head = cl->tail->next = cl->head->next;
		cl->head->prev = cl->tail;
	} else if ((tmp_t = cl->tail) == c) {
		/* Removing tail */
	} else if (cl->tail == c) {
		cl->tail = cl->head->prev = cl->tail->prev;
		cl->tail->next = cl->head;
	} else {
		/* Removing some channel (head, tail) */
		while ((tmp_h = tmp_h->next) != c) {
			if (tmp_h == tmp_t)
				return NULL;
		}
		c->next->prev = c->prev;
		c->prev->next = c->next;
	}

	c->next = NULL;
	c->prev = NULL;

	return c;
}

struct channel*
channel_list_get(struct channel_list *cl, const char *name)
channel_list_get(struct channel_list *cl, const char *name, enum casemapping_t cm)
{
	struct channel *tmp;

	if ((tmp = cl->head) == NULL)
		return NULL;

	if (!channel_cmp(cl->head, name))
	if (!irc_strcmp(cm, cl->head->name, name))
		return cl->head;

	while ((tmp = tmp->next) != cl->head) {

		if (!channel_cmp(tmp, name))
		if (!irc_strcmp(cm, tmp->name, name))
			return tmp;
	}


M src/components/channel.h => src/components/channel.h +6 -25
@@ 41,7 41,7 @@ struct channel
	struct server *server;
	struct user_list users;
	unsigned int parted : 1;
	unsigned int joined : 1; // TODO
	unsigned int joined : 1;
	char _[];
};



@@ 52,31 52,12 @@ struct channel_list
};

struct channel* channel(const char*, enum channel_t);

struct channel* channel_list_add(struct channel_list*, struct channel*);
struct channel* channel_list_del(struct channel_list*, struct channel*);
struct channel* channel_list_get(struct channel_list*, const char*);

/* TODO: `channel_tree_*` for fast retrieval
 *   struct channel_tree_node slist  -> for server channel list
 *   struct channel_list_node glist  -> unordered global channel list
 */

/* TODO: pointer from channel back to server can be eliminated which
 * simplifies architecture by having stateful functions aware of the
 * current context, and having input keybind functions, and send_mesg
 * returned from input() instead of calling them from input.c
 *
 * input shouldn't be a pointer, and thus shouldn't require being freed
 */

/* TODO: abstract list.h, test, add list foreach, replace
 * channel list iteration everywhere */

void channel_part(struct channel*);
void channel_reset(struct channel*);

struct channel* channel_list_get(struct channel_list*, const char*, enum casemapping_t);
void channel_free(struct channel*);
void channel_list_add(struct channel_list*, struct channel*);
void channel_list_del(struct channel_list*, struct channel*);
void channel_list_free(struct channel_list*);
void channel_part(struct channel*);
void channel_reset(struct channel*);

#endif

M src/components/server.c => src/components/server.c +20 -12
@@ 40,6 40,7 @@ server(const char *host, const char *port, const char *pass, const char *user, c
	s->username = strdup(user);
	s->realname = strdup(real);
	s->channel = channel(host, CHANNEL_T_SERVER);
	s->casemapping = CASEMAPPING_RFC1459;
	s->mode_str.type = MODE_STR_USERMODE;
	mode_cfg(&(s->mode_cfg), NULL, MODE_CFG_DEFAULTS);
	/* FIXME: remove server pointer from channel, remove


@@ 47,9 48,6 @@ server(const char *host, const char *port, const char *pass, const char *user, c
	s->channel->server = s;
	channel_list_add(&(s->clist), s->channel);

	/* TODO: CASEMAPPING */
	s->cmp = strcmp;

	return s;
}



@@ 167,21 165,22 @@ server_set_004(struct server *s, char *str)

	struct channel *c = s->channel;

	char *saveptr;
	char *server_name, /* Not used */
	     *version,     /* Not used */
	     *user_modes,  /* Configure server usermodes */
	     *chan_modes;  /* Configure server chanmodes */

	if (!(server_name = getarg(&str, " ")))
	if (!(server_name = strtok_r(str, " ", &saveptr)))
		newline(c, 0, "-!!-", "invalid numeric 004: server_name is null");

	if (!(version = getarg(&str, " ")))
	if (!(version = strtok_r(NULL, " ", &saveptr)))
		newline(c, 0, "-!!-", "invalid numeric 004: version is null");

	if (!(user_modes = getarg(&str, " ")))
	if (!(user_modes = strtok_r(NULL, " ", &saveptr)))
		newline(c, 0, "-!!-", "invalid numeric 004: user_modes is null");

	if (!(chan_modes = getarg(&str, " ")))
	if (!(chan_modes = strtok_r(NULL, " ", &saveptr)))
		newline(c, 0, "-!!-", "invalid numeric 004: chan_modes is null");

	if (user_modes) {


@@ 345,11 344,20 @@ parse_opt(struct opt *opt, char **str)
static int
server_set_CASEMAPPING(struct server *s, char *val)
{
	/* TODO: sets a function pointer to be used for
	 * nick/chan string cmps specific to this server */
	(void)(s);
	(void)(val);
	return 0;
	if (val == NULL)
		return 0;
	else if (!strcmp(val, "ascii"))
		s->casemapping = CASEMAPPING_ASCII;
	else if (!strcmp(val, "rfc1459"))
		s->casemapping = CASEMAPPING_RFC1459;
	else if (!strcmp(val, "strict-rfc1459"))
		s->casemapping = CASEMAPPING_STRICT_RFC1459;
	else
		return 0;

	debug("Setting numeric 005 CASEMAPPING: %s", val);

	return 1;
}

static int

M src/components/server.h => src/components/server.h +2 -2
@@ 4,6 4,7 @@
#include "src/components/buffer.h"
#include "src/components/channel.h"
#include "src/components/mode.h"
#include "src/utils/utils.h"

struct server
{


@@ 13,8 14,7 @@ struct server
	const char *username;
	const char *realname;
	const char *nick;
	int (*cmp) (const char*, const char*);
	// TODO: enum for cmp type
	enum casemapping_t casemapping;
	struct {
		size_t next;
		size_t size;

M src/components/user.c => src/components/user.c +25 -35
@@ 5,27 5,23 @@
#include "src/components/user.h"
#include "src/utils/utils.h"

static struct user* user(const char*);

static inline int user_cmp(struct user*, struct user*);
static inline int user_ncmp(struct user*, struct user*, size_t);

static struct user* user(const char*, struct mode);
static inline int user_cmp(struct user*, struct user*, void *arg);
static inline int user_ncmp(struct user*, struct user*, void *arg, size_t);
static inline void user_free(struct user*);

AVL_GENERATE(user_list, user, ul, user_cmp, user_ncmp)

static inline int
user_cmp(struct user *u1, struct user *u2)
user_cmp(struct user *u1, struct user *u2, void *arg)
{
	/* TODO: CASEMAPPING */
	return irc_strcmp(u1->nick, u2->nick);
	return irc_strcmp((enum casemapping_t)arg, u1->nick, u2->nick);
}

static inline int
user_ncmp(struct user *u1, struct user *u2, size_t n)
user_ncmp(struct user *u1, struct user *u2, void *arg, size_t n)
{
	/* TODO: CASEMAPPING */
	return irc_strncmp(u1->nick, u2->nick, n);
	return irc_strncmp((enum casemapping_t)arg, u1->nick, u2->nick, n);
}

static inline void


@@ 35,50 31,46 @@ user_free(struct user *u)
}

static struct user*
user(const char *nick)
user(const char *nick, struct mode prfxmodes)
{
	struct user *u;

	size_t len = strlen(nick);
	struct user *u;

	if ((u = calloc(1, sizeof(*u) + len + 1)) == NULL)
		fatal("calloc: %s", strerror(errno));

	u->nick_len = len;
	u->nick = memcpy(u->_, nick, len + 1);
	u->prfxmodes = prfxmodes;

	return u;
}

enum user_err
user_list_add(struct user_list *ul, const char *nick, struct mode prfxmodes)
user_list_add(struct user_list *ul, enum casemapping_t cm, const char *nick, struct mode prfxmodes)
{
	/* Create user and add to userlist */

	struct user *u;

	if (user_list_get(ul, nick, 0) != NULL)
	if (user_list_get(ul, cm, nick, 0) != NULL)
		return USER_ERR_DUPLICATE;

	u = AVL_ADD(user_list, ul, user(nick));
	AVL_ADD(user_list, ul, user(nick, prfxmodes), (void*)cm);
	ul->count++;

	u->prfxmodes = prfxmodes;

	return USER_ERR_NONE;
}

enum user_err
user_list_del(struct user_list *ul, const char *nick)
user_list_del(struct user_list *ul, enum casemapping_t cm, const char *nick)
{
	/* Delete user and remove from userlist */

	struct user *u;

	if ((u = user_list_get(ul, nick, 0)) == NULL)
	if ((u = user_list_get(ul, cm, nick, 0)) == NULL)
		return USER_ERR_NOT_FOUND;

	AVL_DEL(user_list, ul, u);
	AVL_DEL(user_list, ul, u, (void*)cm);
	ul->count--;

	user_free(u);


@@ 87,24 79,22 @@ user_list_del(struct user_list *ul, const char *nick)
}

enum user_err
user_list_rpl(struct user_list *ul, const char *nick_old, const char *nick_new)
user_list_rpl(struct user_list *ul, enum casemapping_t cm, const char *nick_old, const char *nick_new)
{
	/* Replace a user in a list by name, maintaining modes */

	struct user *old, *new;

	if ((old = user_list_get(ul, nick_old, 0)) == NULL)
	if ((old = user_list_get(ul, cm, nick_old, 0)) == NULL)
		return USER_ERR_NOT_FOUND;

	if ((new = user_list_get(ul, nick_new, 0)) != NULL)
	if ((new = user_list_get(ul, cm, nick_new, 0)) != NULL)
		return USER_ERR_DUPLICATE;

	new = user(nick_new);

	AVL_ADD(user_list, ul, new);
	AVL_DEL(user_list, ul, old);
	new = user(nick_new, old->prfxmodes);

	new->prfxmodes = old->prfxmodes;
	AVL_ADD(user_list, ul, new, (void*)cm);
	AVL_DEL(user_list, ul, old, (void*)cm);

	user_free(old);



@@ 112,14 102,14 @@ user_list_rpl(struct user_list *ul, const char *nick_old, const char *nick_new)
}

struct user*
user_list_get(struct user_list *ul, const char *nick, size_t prefix_len)
user_list_get(struct user_list *ul, enum casemapping_t cm, const char *nick, size_t prefix_len)
{
	struct user u2 = { .nick = nick };

	if (prefix_len == 0)
		return AVL_GET(user_list, ul, &u2);
		return AVL_GET(user_list, ul, &u2, (void*)cm);
	else
		return AVL_NGET(user_list, ul, &u2, prefix_len);
		return AVL_NGET(user_list, ul, &u2, (void*)cm, prefix_len);
}

void

M src/components/user.h => src/components/user.h +6 -7
@@ 3,6 3,7 @@

#include "src/components/mode.h"
#include "src/utils/tree.h"
#include "src/utils/utils.h"

enum user_err
{


@@ 14,9 15,9 @@ enum user_err
struct user
{
	AVL_NODE(user) ul;
	const char *nick;
	size_t nick_len;
	struct mode prfxmodes;
	const char *nick;
	char _[];
};



@@ 26,12 27,10 @@ struct user_list
	unsigned int count;
};

enum user_err user_list_add(struct user_list*, const char*, struct mode);
enum user_err user_list_del(struct user_list*, const char*);
enum user_err user_list_rpl(struct user_list*, const char*, const char*);

struct user* user_list_get(struct user_list*, const char*, size_t);

enum user_err user_list_add(struct user_list*, enum casemapping_t, const char*, struct mode);
enum user_err user_list_del(struct user_list*, enum casemapping_t, const char*);
enum user_err user_list_rpl(struct user_list*, enum casemapping_t, const char*, const char*);
struct user* user_list_get(struct user_list*, enum casemapping_t, const char*, size_t);
void user_list_free(struct user_list*);

#endif

M src/draw.c => src/draw.c +2 -2
@@ 206,8 206,8 @@ _draw_buffer_line(

		switch (line->type) {
			case BUFFER_LINE_OTHER:
			case BUFFER_LINE_SERVER_MSG:
			case BUFFER_LINE_SERVER_ERR:
			case BUFFER_LINE_SERVER_INFO:
			case BUFFER_LINE_SERVER_ERROR:
			case BUFFER_LINE_JOIN:
			case BUFFER_LINE_NICK:
			case BUFFER_LINE_PART:

M src/handlers/irc_ctcp.c => src/handlers/irc_ctcp.c +45 -30
@@ 12,7 12,7 @@
#include "src/utils/utils.h"

#define failf(S, ...) \
	do { server_err((S), __VA_ARGS__); \
	do { server_error((S), __VA_ARGS__); \
	     return 1; \
	} while (0)



@@ 20,12 20,12 @@
	do { int ret; \
	     if ((ret = io_sendf((S)->connection, __VA_ARGS__))) \
	         failf((S), "Send fail: %s", io_err(ret)); \
	     return 0; \
	} while (0)

static int
parse_ctcp(struct server *s, const char *from, char **args, const char **cmd)
{
	char *saveptr;
	char *message = *args;
	char *command;
	char *p;


@@ 41,14 41,14 @@ parse_ctcp(struct server *s, const char *from, char **args, const char **cmd)

	*message++ = 0;

	if (!(command = getarg(&message, " ")))
	if (!(command = strtok_r(message, " ", &saveptr)))
		failf(s, "Received empty CTCP from %s", from);

	for (p = command; *p; p++)
		*p = toupper(*p);

	*cmd = command;
	*args = message;
	*args = saveptr;

	return 0;
}


@@ 93,14 93,14 @@ ctcp_request_action(struct server *s, const char *from, const char *targ, char *
	if (!targ)
		failf(s, "CTCP ACTION: target is NULL");

	if (!s->cmp(targ, s->nick)) {
		if ((c = channel_list_get(&s->clist, from)) == NULL) {
	if (!irc_strcmp(s->casemapping, targ, s->nick)) {
		if ((c = channel_list_get(&s->clist, from, s->casemapping)) == NULL) {
			c = channel(from, CHANNEL_T_PRIVATE);
			c->activity = ACTIVITY_PINGED;
			c->server = s;
			channel_list_add(&s->clist, c);
		}
	} else if ((c = channel_list_get(&s->clist, targ)) == NULL) {
	} else if ((c = channel_list_get(&s->clist, targ, s->casemapping)) == NULL) {
		failf(s, "CTCP ACTION: target '%s' not found", targ);
	}



@@ 118,11 118,13 @@ ctcp_request_clientinfo(struct server *s, const char *from, const char *targ, ch
	UNUSED(targ);

	if (str_trim(&m))
		server_msg(s, "CTCP CLIENTINFO from %s (%s)", from, m);
		server_info(s, "CTCP CLIENTINFO from %s (%s)", from, m);
	else
		server_msg(s, "CTCP CLIENTINFO from %s", from);
		server_info(s, "CTCP CLIENTINFO from %s", from);

	sendf(s, "NOTICE %s :\001CLIENTINFO ACTION CLIENTINFO PING SOURCE TIME VERSION\001", from);

	return 0;
}

static int


@@ 131,11 133,13 @@ ctcp_request_finger(struct server *s, const char *from, const char *targ, char *
	UNUSED(targ);

	if (str_trim(&m))
		server_msg(s, "CTCP FINGER from %s (%s)", from, m);
		server_info(s, "CTCP FINGER from %s (%s)", from, m);
	else
		server_msg(s, "CTCP FINGER from %s", from);
		server_info(s, "CTCP FINGER from %s", from);

	sendf(s, "NOTICE %s :\001FINGER rirc v"VERSION" ("__DATE__")\001", from);

	return 0;
}

static int


@@ 143,9 147,11 @@ ctcp_request_ping(struct server *s, const char *from, const char *targ, char *m)
{
	UNUSED(targ);

	server_msg(s, "CTCP PING from %s", from);
	server_info(s, "CTCP PING from %s", from);

	sendf(s, "NOTICE %s :\001PING %s\001", from, m);

	return 0;
}

static int


@@ 154,11 160,13 @@ ctcp_request_source(struct server *s, const char *from, const char *targ, char *
	UNUSED(targ);

	if (str_trim(&m))
		server_msg(s, "CTCP SOURCE from %s (%s)", from, m);
		server_info(s, "CTCP SOURCE from %s (%s)", from, m);
	else
		server_msg(s, "CTCP SOURCE from %s", from);
		server_info(s, "CTCP SOURCE from %s", from);

	sendf(s, "NOTICE %s :\001SOURCE rcr.io/rirc\001", from);

	return 0;
}

static int


@@ 169,12 177,12 @@ ctcp_request_time(struct server *s, const char *from, const char *targ, char *m)
	struct tm tm;
	time_t t = 0;

	(void) targ;
	UNUSED(targ);

	if (str_trim(&m))
		server_msg(s, "CTCP TIME from %s (%s)", from, m);
		server_info(s, "CTCP TIME from %s (%s)", from, m);
	else
		server_msg(s, "CTCP TIME from %s", from);
		server_info(s, "CTCP TIME from %s", from);

#ifdef TESTING
	if (gmtime_r(&t, &tm) == NULL)


@@ 190,6 198,8 @@ ctcp_request_time(struct server *s, const char *from, const char *targ, char *m)
		failf(s, "CTCP TIME: strftime error");

	sendf(s, "NOTICE %s :\001TIME %s\001", from, buf);

	return 0;
}

static int


@@ 198,11 208,13 @@ ctcp_request_userinfo(struct server *s, const char *from, const char *targ, char
	UNUSED(targ);

	if (str_trim(&m))
		server_msg(s, "CTCP USERINFO from %s (%s)", from, m);
		server_info(s, "CTCP USERINFO from %s (%s)", from, m);
	else
		server_msg(s, "CTCP USERINFO from %s", from);
		server_info(s, "CTCP USERINFO from %s", from);

	sendf(s, "NOTICE %s :\001USERINFO %s (%s)\001", from, s->nick, s->realname);

	return 0;
}

static int


@@ 211,11 223,13 @@ ctcp_request_version(struct server *s, const char *from, const char *targ, char 
	UNUSED(targ);

	if (str_trim(&m))
		server_msg(s, "CTCP VERSION from %s (%s)", from, m);
		server_info(s, "CTCP VERSION from %s (%s)", from, m);
	else
		server_msg(s, "CTCP VERSION from %s", from);
		server_info(s, "CTCP VERSION from %s", from);

	sendf(s, "NOTICE %s :\001VERSION rirc v"VERSION" ("__DATE__")\001", from);

	return 0;
}

static int


@@ 226,7 240,7 @@ ctcp_response_clientinfo(struct server *s, const char *from, const char *targ, c
	if (!str_trim(&m))
		failf(s, "CTCP CLIENTINFO response from %s: empty message", from);

	server_msg(s, "CTCP CLIENTINFO response from %s: %s", from, m);
	server_info(s, "CTCP CLIENTINFO response from %s: %s", from, m);

	return 0;
}


@@ 239,7 253,7 @@ ctcp_response_finger(struct server *s, const char *from, const char *targ, char 
	if (!str_trim(&m))
		failf(s, "CTCP FINGER response from %s: empty message", from);

	server_msg(s, "CTCP FINGER response from %s: %s", from, m);
	server_info(s, "CTCP FINGER response from %s: %s", from, m);

	return 0;
}


@@ 247,6 261,7 @@ ctcp_response_finger(struct server *s, const char *from, const char *targ, char 
static int
ctcp_response_ping(struct server *s, const char *from, const char *targ, char *m)
{
	char *saveptr;
	const char *sec;
	const char *usec;
	long long unsigned res;


@@ 259,10 274,10 @@ ctcp_response_ping(struct server *s, const char *from, const char *targ, char *m

	UNUSED(targ);

	if (!(sec = getarg(&m, " ")))
	if (!(sec = strtok_r(m, " ", &saveptr)))
		failf(s, "CTCP PING response from %s: sec is NULL", from);

	if (!(usec = getarg(&m, " ")))
	if (!(usec = strtok_r(NULL, " ", &saveptr)))
		failf(s, "CTCP PING response from %s: usec is NULL", from);

	for (const char *p = sec; *p; p++) {


@@ 302,7 317,7 @@ ctcp_response_ping(struct server *s, const char *from, const char *targ, char *m
	res_sec = res / 1000000;
	res_usec = res % 1000000;

	server_msg(s, "CTCP PING response from %s: %llu.%llus", from, res_sec, res_usec);
	server_info(s, "CTCP PING response from %s: %llu.%llus", from, res_sec, res_usec);

	return 0;
}


@@ 315,7 330,7 @@ ctcp_response_source(struct server *s, const char *from, const char *targ, char 
	if (!str_trim(&m))
		failf(s, "CTCP SOURCE response from %s: empty message", from);

	server_msg(s, "CTCP SOURCE response from %s: %s", from, m);
	server_info(s, "CTCP SOURCE response from %s: %s", from, m);

	return 0;
}


@@ 328,7 343,7 @@ ctcp_response_time(struct server *s, const char *from, const char *targ, char *m
	if (!str_trim(&m))
		failf(s, "CTCP TIME response from %s: empty message", from);

	server_msg(s, "CTCP TIME response from %s: %s", from, m);
	server_info(s, "CTCP TIME response from %s: %s", from, m);

	return 0;
}


@@ 341,7 356,7 @@ ctcp_response_userinfo(struct server *s, const char *from, const char *targ, cha
	if (!str_trim(&m))
		failf(s, "CTCP USERINFO response from %s: empty message", from);

	server_msg(s, "CTCP USERINFO response from %s: %s", from, m);
	server_info(s, "CTCP USERINFO response from %s: %s", from, m);

	return 0;
}


@@ 354,7 369,7 @@ ctcp_response_version(struct server *s, const char *from, const char *targ, char
	if (!str_trim(&m))
		failf(s, "CTCP VERSION response from %s: empty message", from);

	server_msg(s, "CTCP VERSION response from %s: %s", from, m);
	server_info(s, "CTCP VERSION response from %s: %s", from, m);

	return 0;
}

M src/handlers/irc_recv.c => src/handlers/irc_recv.c +58 -46
@@ 23,7 23,7 @@
#endif

#define failf(S, ...) \
	do { server_err((S), __VA_ARGS__); \
	do { server_error((S), __VA_ARGS__); \
	     return 1; \
	} while (0)



@@ 189,7 189,8 @@ static const irc_recv_f irc_numerics[] = {
	[464] = irc_error,  /* ERR_PASSWDMISMATCH */
	[465] = irc_error,  /* ERR_YOUREBANNEDCREEP */
	[466] = irc_error,  /* ERR_YOUWILLBEBANNED */
	[467] = irc_error,  /* ERR_KEYSET */ [471] = irc_error,  /* ERR_CHANNELISFULL */
	[467] = irc_error,  /* ERR_KEYSET */
	[471] = irc_error,  /* ERR_CHANNELISFULL */
	[472] = irc_error,  /* ERR_UNKNOWNMODE */
	[473] = irc_error,  /* ERR_INVITEONLYCHAN */
	[474] = irc_error,  /* ERR_BANNEDFROMCHAN */


@@ 234,11 235,11 @@ irc_message(struct server *s, struct irc_message *m, const char *from)

	if (irc_message_split(m, &trailing)) {
		if (m->params)
			server_message(s, from, "[%s] ~ %s", m->params, trailing);
			newlinef(s->channel, 0, from, "[%s] ~ %s", m->params, trailing);
		else
			server_message(s, from, "%s", trailing);
			newlinef(s->channel, 0, from, "%s", trailing);
	} else if (m->params) {
		server_message(s, from, "[%s]", m->params);
		newlinef(s->channel, 0, from, "[%s]", m->params);
	}

	return 0;


@@ 287,7 288,8 @@ irc_001(struct server *s, struct irc_message *m)
	if (irc_message_split(m, &trailing))
		newline(s->channel, 0, FROM_INFO, trailing);

	newlinef(s->channel, 0, FROM_INFO, "You are known as %s", s->nick);
	server_info(s, "You are known as %s", s->nick);

	return 0;
}



@@ 304,6 306,7 @@ irc_004(struct server *s, struct irc_message *m)
		newlinef(s->channel, 0, FROM_INFO, "%s", m->params);

	server_set_004(s, m->params);

	return 0;
}



@@ 320,6 323,7 @@ irc_005(struct server *s, struct irc_message *m)
		newlinef(s->channel, 0, FROM_INFO, "%s ~ are supported by this server", m->params);

	server_set_005(s, m->params);

	return 0;
}



@@ 342,7 346,7 @@ irc_324(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &chan))
		failf(s, "RPL_CHANNELMODEIS: channel is null");

	if ((c = channel_list_get(&s->clist, chan)) == NULL)
	if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
		failf(s, "RPL_CHANNELMODEIS: channel '%s' not found", chan);

	return recv_mode_chanmodes(m, &(s->mode_cfg), c);


@@ 363,10 367,11 @@ irc_328(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &url))
		failf(s, "RPL_CHANNEL_URL: url is null");

	if ((c = channel_list_get(&s->clist, chan)) == NULL)
	if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
		failf(s, "RPL_CHANNEL_URL: channel '%s' not found", chan);

	newlinef(c, 0, FROM_INFO, "URL for %s is: \"%s\"", chan, url);

	return 0;
}



@@ 386,7 391,7 @@ irc_329(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &time_str))
		failf(s, "RPL_CREATIONTIME: time is null");

	if ((c = channel_list_get(&s->clist, chan)) == NULL)
	if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
		failf(s, "RPL_CREATIONTIME: channel '%s' not found", chan);

	errno = 0;


@@ 402,6 407,7 @@ irc_329(struct server *s, struct irc_message *m)
		failf(s, "RPL_CREATIONTIME: strftime error");

	newlinef(c, 0, FROM_INFO, "Channel created %s", buf);

	return 0;
}



@@ 420,10 426,11 @@ irc_332(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &topic))
		failf(s, "RPL_TOPIC: topic is null");

	if ((c = channel_list_get(&s->clist, chan)) == NULL)
	if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
		failf(s, "RPL_TOPIC: channel '%s' not found", chan);

	newlinef(c, 0, FROM_INFO, "Topic for %s is \"%s\"", chan, topic);

	return 0;
}



@@ 449,7 456,7 @@ irc_333(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &time_str))
		failf(s, "RPL_TOPICWHOTIME: time is null");

	if ((c = channel_list_get(&s->clist, chan)) == NULL)
	if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
		failf(s, "RPL_TOPICWHOTIME: channel '%s' not found", chan);

	errno = 0;


@@ 465,6 472,7 @@ irc_333(struct server *s, struct irc_message *m)
		failf(s, "RPL_TOPICWHOTIME: strftime error");

	newlinef(c, 0, FROM_INFO, "Topic set by %s, %s", nick, buf);

	return 0;
}



@@ 473,6 481,7 @@ irc_353(struct server *s, struct irc_message *m)
{
	/* 353 ("="/"*"/"@") <channel> *([ "@" / "+" ]<nick>) */

	char *saveptr;
	char *chan;
	char *nick;
	char *nicks;


@@ 488,28 497,31 @@ irc_353(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &nicks))
		failf(s, "RPL_NAMEREPLY: nicks is null");

	if ((c = channel_list_get(&s->clist, chan)) == NULL)
	if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
		failf(s, "RPL_NAMEREPLY: channel '%s' not found", chan);

	if (mode_chanmode_prefix(&(c->chanmodes), &(s->mode_cfg), *type) != MODE_ERR_NONE)
		newlinef(c, 0, FROM_ERROR, "RPL_NAMEREPLY: invalid channel flag: '%c'", *type);

	while ((nick = getarg(&nicks, " "))) {
	if ((nick = strtok_r(nicks, " ", &saveptr))) {
		do {
			char prefix = 0;
			struct mode m = MODE_EMPTY;

		char prefix = 0;
		struct mode m = MODE_EMPTY;
			if (!irc_isnickchar(*nick, 1))
				prefix = *nick++;

		if (!irc_isnickchar(*nick, 1))
			prefix = *nick++;
			if (prefix && mode_prfxmode_prefix(&m, &(s->mode_cfg), prefix) != MODE_ERR_NONE)
				newlinef(c, 0, FROM_ERROR, "Invalid user prefix: '%c'", prefix);

		if (prefix && mode_prfxmode_prefix(&m, &(s->mode_cfg), prefix) != MODE_ERR_NONE)
			newlinef(c, 0, FROM_ERROR, "Invalid user prefix: '%c'", prefix);
			if (user_list_add(&(c->users), s->casemapping, nick, m) == USER_ERR_DUPLICATE)
				newlinef(c, 0, FROM_ERROR, "Duplicate nick: '%s'", nick);

		if (user_list_add(&(c->users), nick, m) == USER_ERR_DUPLICATE)
			newlinef(c, 0, FROM_ERROR, "Duplicate nick: '%s'", nick);
		} while ((nick = strtok_r(NULL, " ", &saveptr)));
	}

	draw_status();

	return 0;
}



@@ 525,7 537,7 @@ irc_433(struct server *s, struct irc_message *m)

	newlinef(s->channel, 0, FROM_ERROR, "Nick '%s' in use", nick);

	if (!strcmp(m->from, s->nick)) {
	if (!strcmp(nick, s->nick)) {
		server_nicks_next(s);
		newlinef(s->channel, 0, FROM_ERROR, "Trying again with '%s'", s->nick);
		sendf(s, "NICK %s", s->nick);


@@ 634,7 646,7 @@ recv_join(struct server *s, struct irc_message *m)
		failf(s, "JOIN: target channel is null");

	if (!strcmp(m->from, s->nick)) {
		if ((c = channel_list_get(&s->clist, chan)) == NULL) {
		if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL) {
			c = channel(chan, CHANNEL_T_CHANNEL);
			c->server = s;
			channel_list_add(&s->clist, c);


@@ 648,14 660,14 @@ recv_join(struct server *s, struct irc_message *m)
		return 0;
	}

	if ((c = channel_list_get(&s->clist, chan)) == NULL)
	if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
		failf(s, "JOIN: channel '%s' not found", chan);

	if (user_list_add(&(c->users), m->from, MODE_EMPTY) == USER_ERR_DUPLICATE)
	if (user_list_add(&(c->users), s->casemapping, m->from, MODE_EMPTY) == USER_ERR_DUPLICATE)
		failf(s, "JOIN: user '%s' alread on channel '%s'", m->from, chan);

	if (!join_threshold || c->users.count <= join_threshold)
		newlinef(c, BUFFER_LINE_JOIN, ">", "%s!%s has joined", m->from, m->host);
		newlinef(c, BUFFER_LINE_JOIN, FROM_JOIN, "%s!%s has joined", m->from, m->host);

	draw_status();



@@ 681,7 693,7 @@ recv_kick(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &user))
		failf(s, "KICK: user is null");

	if ((c = channel_list_get(&s->clist, chan)) == NULL)
	if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
		failf(s, "KICK: channel '%s' not found", chan);

	irc_message_param(m, &message);


@@ 704,7 716,7 @@ recv_kick(struct server *s, struct irc_message *m)

	} else {

		if (user_list_del(&(c->users), user) == USER_ERR_NOT_FOUND)
		if (user_list_del(&(c->users), s->casemapping, user) == USER_ERR_NOT_FOUND)
			failf(s, "KICK: nick '%s' not found in '%s'", user, chan);

		if (message)


@@ 752,7 764,7 @@ recv_mode(struct server *s, struct irc_message *m)
	if (!strcmp(targ, s->nick))
		return recv_mode_usermodes(m, &(s->mode_cfg), s);

	if ((c = channel_list_get(&s->clist, targ)))
	if ((c = channel_list_get(&s->clist, targ, s->casemapping)))
		return recv_mode_chanmodes(m, &(s->mode_cfg), c);

	failf(s, "MODE: target '%s' not found", targ);


@@ 838,7 850,7 @@ recv_mode_chanmodes(struct irc_message *m, const struct mode_cfg *cfg, struct ch
						continue;
					}

					if (!(user = user_list_get(&(c->users), modearg, 0))) {
					if (!(user = user_list_get(&(c->users), c->server->casemapping, modearg, 0))) {
						newlinef(c, 0, FROM_ERROR, "MODE: flag '%c' user '%s' not found", flag, modearg);
						continue;
					}


@@ 954,7 966,7 @@ recv_nick(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &nick))
		failf(s, "NICK: new nick is null");

	if (!strcmp(nick, s->nick)) {
	if (!strcmp(m->from, s->nick)) {
		server_nick_set(s, nick);
		newlinef(s->channel, BUFFER_LINE_NICK, FROM_NICK, "Youn nick is '%s'", nick);
	}


@@ 962,11 974,11 @@ recv_nick(struct server *s, struct irc_message *m)
	do {
		enum user_err ret;

		if ((ret = user_list_rpl(&(c->users), m->from, nick)) == USER_ERR_NONE)
		if ((ret = user_list_rpl(&(c->users), s->casemapping, m->from, nick)) == USER_ERR_NONE)
			newlinef(c, BUFFER_LINE_NICK, FROM_NICK, "%s  >>  %s", m->from, nick);

		else if (ret == USER_ERR_DUPLICATE)
			server_err(s, "NICK: user '%s' alread on channel '%s'", m->from, c->name);
			server_error(s, "NICK: user '%s' alread on channel '%s'", m->from, c->name);

	} while ((c = c->next) != s->channel);



@@ 992,7 1004,7 @@ recv_notice(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &message))
		failf(s, "NOTICE: message is null");

	if (user_list_get(&(s->ignore), m->from, 0))
	if (user_list_get(&(s->ignore), s->casemapping, m->from, 0))
		return 0;

	if (IS_CTCP(message))


@@ 1002,7 1014,7 @@ recv_notice(struct server *s, struct irc_message *m)
		c = s->channel;
	} else if (!strcmp(target, s->nick)) {

		if ((c = channel_list_get(&s->clist, m->from)) == NULL) {
		if ((c = channel_list_get(&s->clist, m->from, s->casemapping)) == NULL) {
			c = channel(m->from, CHANNEL_T_PRIVATE);
			c->server = s;
			channel_list_add(&s->clist, c);


@@ 1011,11 1023,11 @@ recv_notice(struct server *s, struct irc_message *m)
		if (c != current_channel())
			urgent = 1;

	} else if ((c = channel_list_get(&s->clist, target)) == NULL) {
	} else if ((c = channel_list_get(&s->clist, target, s->casemapping)) == NULL) {
		failf(s, "NOTICE: channel '%s' not found", target);
	}

	if (check_pinged(message, s->nick)) {
	if (irc_pinged(s->casemapping, message, s->nick)) {

		if (c != current_channel())
			urgent = 1;


@@ 1052,7 1064,7 @@ recv_part(struct server *s, struct irc_message *m)
	if (!strcmp(m->from, s->nick)) {

		/* If not found, assume channel was closed */
		if ((c = channel_list_get(&s->clist, chan)) != NULL) {
		if ((c = channel_list_get(&s->clist, chan, s->casemapping)) != NULL) {

			if (irc_message_param(m, &message))
				newlinef(c, BUFFER_LINE_PART, FROM_PART, "you have parted (%s)", message);


@@ 1063,10 1075,10 @@ recv_part(struct server *s, struct irc_message *m)
		}
	} else {

		if ((c = channel_list_get(&s->clist, chan)) == NULL)
		if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
			failf(s, "PART: channel '%s' not found", chan);

		if (user_list_del(&(c->users), m->from) == USER_ERR_NOT_FOUND)
		if (user_list_del(&(c->users), s->casemapping, m->from) == USER_ERR_NOT_FOUND)
			failf(s, "PART: nick '%s' not found in '%s'", m->from, chan);

		if (!part_threshold || c->users.count <= part_threshold) {


@@ 1127,7 1139,7 @@ recv_privmsg(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &message))
		failf(s, "PRIVMSG: message is null");

	if (user_list_get(&(s->ignore), m->from, 0))
	if (user_list_get(&(s->ignore), s->casemapping, m->from, 0))
		return 0;

	if (IS_CTCP(message))


@@ 1135,7 1147,7 @@ recv_privmsg(struct server *s, struct irc_message *m)

	if (!strcmp(target, s->nick)) {

		if ((c = channel_list_get(&s->clist, m->from)) == NULL) {
		if ((c = channel_list_get(&s->clist, m->from, s->casemapping)) == NULL) {
			c = channel(m->from, CHANNEL_T_PRIVATE);
			c->server = s;
			channel_list_add(&s->clist, c);


@@ 1144,11 1156,11 @@ recv_privmsg(struct server *s, struct irc_message *m)
		if (c != current_channel())
			urgent = 1;

	} else if ((c = channel_list_get(&s->clist, target)) == NULL) {
	} else if ((c = channel_list_get(&s->clist, target, s->casemapping)) == NULL) {
		failf(s, "PRIVMSG: channel '%s' not found", target);
	}

	if (check_pinged(message, s->nick)) {
	if (irc_pinged(s->casemapping, message, s->nick)) {

		if (c != current_channel())
			urgent = 1;


@@ 1185,7 1197,7 @@ recv_topic(struct server *s, struct irc_message *m)
	if (!irc_message_param(m, &topic))
		failf(s, "TOPIC: topic is null");

	if ((c = channel_list_get(&s->clist, chan)) == NULL)
	if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
		failf(s, "TOPIC: target channel '%s' not found", chan);

	if (*topic) {


@@ 1212,7 1224,7 @@ recv_quit(struct server *s, struct irc_message *m)
	irc_message_param(m, &message);

	do {
		if (user_list_del(&(c->users), m->from) == USER_ERR_NONE) {
		if (user_list_del(&(c->users), s->casemapping, m->from) == USER_ERR_NONE) {
			if (!quit_threshold || c->users.count <= quit_threshold) {
				if (message)
					newlinef(c, BUFFER_LINE_QUIT, FROM_QUIT, "%s!%s has quit (%s)", m->from, m->host, message);

M src/handlers/irc_send.c => src/handlers/irc_send.c +51 -16
@@ 14,7 14,7 @@
// TODO: should privmsg/notice open a PRIVATE/CHANNEL buffer for the target?

#define failf(C, ...) \
	do { newlinef((C), 0, "-!!-", __VA_ARGS__); \
	do { newlinef((C), 0, FROM_ERROR, __VA_ARGS__); \
	     return 1; \
	} while (0)



@@ 22,7 22,6 @@
	do { int ret; \
	     if ((ret = io_sendf((S)->connection, __VA_ARGS__))) \
	         failf((C), "Send fail: %s", io_err(ret)); \
	     return 0; \
	} while (0)

static const char* targ_or_type(struct channel*, char*, enum channel_t type);


@@ 30,25 29,28 @@ static const char* targ_or_type(struct channel*, char*, enum channel_t type);
int
irc_send_command(struct server *s, struct channel *c, char *m)
{
	char *saveptr;
	char *command, *p;
	const struct send_handler *send;

	if (!s)
		failf(c, "This is not a server");

	if (*m == ' ' || !(command = getarg(&m, " ")))
	if (*m == ' ' || !(command = strtok_r(m, " ", &saveptr)))
		failf(c, "Messages beginning with '/' require a command");

	for (p = command; *p; p++)
		*p = toupper(*p);

	if ((send = send_handler_lookup(command, strlen(command))))
		return send->f(s, c, m);
		return send->f(s, c, saveptr);

	if (str_trim(&m))
		sendf(s, c, "%s %s", command, m);
	if (str_trim(&saveptr))
		sendf(s, c, "%s %s", command, saveptr);
	else
		sendf(s, c, "%s", command);

	return 0;
}

int


@@ 66,18 68,20 @@ irc_send_privmsg(struct server *s, struct channel *c, char *m)
	if (*m == 0)
		failf(c, "Message is empty");

	// FIXME: move this to state.c?
	sendf(s, c, "PRIVMSG %s :%s", c->name, m);

	newline(c, BUFFER_LINE_CHAT, s->nick, m);

	sendf(s, c, "PRIVMSG %s :%s", c->name, m);
	return 0;
}

static const char*
targ_or_type(struct channel *c, char *m, enum channel_t type)
{
	char *saveptr;
	const char *targ;

	if ((targ = getarg(&m, " ")))
	if ((targ = strtok_r(m, " ", &saveptr)))
		return targ;

	if (c->type == type)


@@ 93,6 97,8 @@ send_ctcp_action(struct server *s, struct channel *c, char *m)
		failf(c, "This is not a channel");

	sendf(s, c, "PRIVMSG %s :\001ACTION %s\001", c->name, m);

	return 0;
}

static int


@@ 104,6 110,8 @@ send_ctcp_clientinfo(struct server *s, struct channel *c, char *m)
		failf(c, "Usage: /ctcp-clientinfo <target>");

	sendf(s, c, "PRIVMSG %s :\001CLIENTINFO\001", targ);

	return 0;
}

static int


@@ 115,6 123,8 @@ send_ctcp_finger(struct server *s, struct channel *c, char *m)
		failf(c, "Usage: /ctcp-finger <target>");

	sendf(s, c, "PRIVMSG %s :\001FINGER\001", targ);

	return 0;
}

static int


@@ 129,6 139,8 @@ send_ctcp_ping(struct server *s, struct channel *c, char *m)
	(void) gettimeofday(&t, NULL);

	sendf(s, c, "PRIVMSG %s :\001PING %llu %llu\001", targ, t.tv_sec, t.tv_usec);

	return 0;
}

static int


@@ 140,6 152,8 @@ send_ctcp_source(struct server *s, struct channel *c, char *m)
		failf(c, "Usage: /ctcp-source <target>");

	sendf(s, c, "PRIVMSG %s :\001SOURCE\001", targ);

	return 0;
}

static int


@@ 151,6 165,8 @@ send_ctcp_time(struct server *s, struct channel *c, char *m)
		failf(c, "Usage: /ctcp-time <target>");

	sendf(s, c, "PRIVMSG %s :\001TIME\001", targ);

	return 0;
}

static int


@@ 162,6 178,8 @@ send_ctcp_userinfo(struct server *s, struct channel *c, char *m)
		failf(c, "Usage: /ctcp-userinfo <target>");

	sendf(s, c, "PRIVMSG %s :\001USERINFO\001", targ);

	return 0;
}

static int


@@ 173,20 191,25 @@ send_ctcp_version(struct server *s, struct channel *c, char *m)
		failf(c, "Usage: /ctcp-version <target>");

	sendf(s, c, "PRIVMSG %s :\001VERSION\001", targ);

	return 0;
}

static int
send_notice(struct server *s, struct channel *c, char *m)
{
	char *saveptr;
	const char *targ;

	if (!(targ = getarg(&m, " ")))
	if (!(targ = strtok_r(m, " ", &saveptr)))
		failf(c, "Usage: /notice <target> <message>");

	if (*m == 0)
	if (*saveptr == 0)
		failf(c, "Usage: /notice <target> <message>");

	sendf(s, c, "NOTICE %s :%s", targ, m);
	sendf(s, c, "NOTICE %s :%s", targ, saveptr);

	return 0;
}

static int


@@ 196,23 219,28 @@ send_part(struct server *s, struct channel *c, char *m)
		failf(c, "This is not a channel");

	if (str_trim(&m))
		sendf(s, c, "PART %s :%s",c->name, m);
		sendf(s, c, "PART %s :%s", c->name, m);
	else
		sendf(s, c, "PART %s :%s", c->name, DEFAULT_PART_MESG);

	return 0;
}

static int
send_privmsg(struct server *s, struct channel *c, char *m)
{
	char *saveptr;
	const char *targ;

	if (!(targ = getarg(&m, " ")))
	if (!(targ = strtok_r(m, " ", &saveptr)))
		failf(c, "Usage: /privmsg <target> <message>");

	if (*m == 0)
	if (*saveptr == 0)
		failf(c, "Usage: /privmsg <target> <message>");

	sendf(s, c, "PRIVMSG %s :%s", targ, m);
	sendf(s, c, "PRIVMSG %s :%s", targ, saveptr);

	return 0;
}

static int


@@ 224,6 252,8 @@ send_quit(struct server *s, struct channel *c, char *m)
		sendf(s, c, "QUIT :%s", m);
	else
		sendf(s, c, "QUIT :%s", DEFAULT_PART_MESG);

	return 0;
}

static int


@@ 236,4 266,9 @@ send_topic(struct server *s, struct channel *c, char *m)
		sendf(s, c, "TOPIC %s :%s", c->name, m);
	else
		sendf(s, c, "TOPIC %s", c->name);

	return 0;
}

#undef failf
#undef sendf

M src/state.c => src/state.c +44 -40
@@ 161,44 161,48 @@ newlinef(struct channel *c, enum buffer_line_t type, const char *from, const cha
static void
_newline(struct channel *c, enum buffer_line_t type, const char *from, const char *fmt, va_list ap)
{
	/* Static function for handling inserting new lines into buffers */

	char buf[TEXT_LENGTH_MAX];

	char prefix = 0;
	const char *from_str;
	const char *text_str;
	int len;

	const char *_from_str;
	const char *_text_str;
	size_t _from_len;
	size_t _text_len;

	struct user *u = NULL;
	size_t from_len;
	size_t text_len;

	if ((len = vsnprintf(buf, sizeof(buf), fmt, ap)) < 0) {
		_text_str = "newlinef error: vsprintf failure";
		_text_len = strlen(_text_str);
		_from_str = "-!!-";
		_from_len = strlen(_from_str);
		text_str = "newlinef error: vsprintf failure";
		text_len = strlen(text_str);
		from_str = "-!!-";
		from_len = strlen(from_str);
	} else {
		_text_str = buf;
		_text_len = len;
		_from_str = from;
		text_str = buf;
		text_len = len;
		from_str = from;

		// FIXME: don't need to get user for many non-user message types
		if ((u = user_list_get(&(c->users), from, 0)) != NULL)
			_from_len = u->nick_len;
		else
			_from_len = strlen(from);
		const struct user *u = NULL;

		if (type == BUFFER_LINE_CHAT) {
			u = user_list_get(&(c->users), c->server->casemapping, from, 0);
		}

		if (u) {
			prefix = u->prfxmodes.prefix;
			from_len = u->nick_len;
		} else {
			from_len = strlen(from);
		}
	}

	// TODO: preformat the time string here

	buffer_newline(
		&(c->buffer),
		type,
		_from_str,
		_text_str,
		_from_len,
		_text_len,
		((u == NULL) ? 0 : u->prfxmodes.prefix));
		from_str,
		text_str,
		from_len,
		text_len,
		prefix);

	if (c == current_channel()) {
		draw_buffer();


@@ 673,8 677,12 @@ static uint16_t
state_complete_user(char *str, uint16_t len, uint16_t max, int first)
{
	struct user *u;
	struct channel *c = current_channel();

	if ((u = user_list_get(&(current_channel()->users), str, len)) == NULL)
	if (c->server == NULL)
		return 0;

	if ((u = user_list_get(&(c->users), c->server->casemapping, str, len)) == NULL)
		return 0;

	if ((u->nick_len + (first != 0)) > max)


@@ 803,9 811,10 @@ static void
command(struct channel *c, char *buf)
{
	const char *cmnd;
	char *saveptr;
	int err;

	if (!(cmnd = getarg(&buf, " "))) {
	if (!(cmnd = strtok_r(buf, " ", &saveptr))) {
		newline(c, 0, "-!!-", "Messages beginning with ':' require a command");
		return;
	}


@@ 816,13 825,12 @@ command(struct channel *c, char *buf)

	if (!strcasecmp(cmnd, "connect")) {

		const char *host = getarg(&buf, " "),
		           *port = getarg(&buf, " "),
		           *pass = getarg(&buf, " "),
		           *user = getarg(&buf, " "),
		           *real = getarg(&buf, " "),
		           *help = ":connect [host [port] [pass] [user] [real]]";

		const char *host = strtok_r(NULL, " ", &saveptr);
		const char *port = strtok_r(NULL, " ", &saveptr);
		const char *pass = strtok_r(NULL, " ", &saveptr);
		const char *user = strtok_r(NULL, " ", &saveptr);
		const char *real = strtok_r(NULL, " ", &saveptr);
		const char *help = ":connect [host [port] [pass] [user] [real]]";
		struct server *s;

		if (host == NULL) {


@@ 1043,9 1051,5 @@ io_cb_read_soc(char *buf, size_t len, const void *cb_obj)
	else
		irc_recv((struct server *)cb_obj, &m);

	// FIXME: from ignored user?
	// if (user_list_get(&(s->ignore), p->from, 0))
	// 	return 0;

	redraw();
}

M src/state.h => src/state.h +16 -29
@@ 7,14 7,25 @@
#include "src/components/server.h"
#include "src/draw.h"

/* state.h
 *
 * Interface for retrieving and altering global state of the program */
#define FROM_INFO "--"
#define FROM_ERROR "-!!-"
#define FROM_UNKNOWN "-\?\?-"
#define FROM_JOIN ">>"
#define FROM_NICK "--"
#define FROM_PART "<<"
#define FROM_QUIT "<<"

int state_server_set_chans(struct server*, const char*);
#define server_info(S, ...) \
	do { newlinef((S)->channel, BUFFER_LINE_SERVER_INFO, FROM_INFO, __VA_ARGS__); } while (0)

#define server_error(S, ...) \
	do { newlinef((S)->channel, BUFFER_LINE_SERVER_ERROR, FROM_ERROR, __VA_ARGS__); } while (0)

#define server_unknown(S, ...) \
	do { newlinef((S)->channel, BUFFER_LINE_SERVER_ERROR, FROM_UNKNOWN, __VA_ARGS__); } while (0)

int state_server_set_chans(struct server*, const char*);

/* state.c */
struct channel* current_channel(void);

struct server_list* state_server_list(void);


@@ 40,30 51,6 @@ void channel_move_prev(void);
void channel_move_next(void);
void channel_set_current(struct channel*);

void free_channel(struct channel*);

#define FROM_ERROR "-!!-"
#define FROM_INFO "--"
#define FROM_UNKNOWN "-\?\?-"
#define FROM_JOIN ">>"
#define FROM_NICK "--"
#define FROM_PART "<<"
#define FROM_QUIT "<<"

// info / err
#define server_msg(S, ...) \
	do { newlinef((S)->channel, BUFFER_LINE_SERVER_MSG, FROM_INFO, __VA_ARGS__); } while (0)

#define server_err(S, ...) \
	do { newlinef((S)->channel, BUFFER_LINE_SERVER_ERR, FROM_ERROR, __VA_ARGS__); } while (0)

#define server_unknown(S, M, ...) \
	do { newlinef((S)->channel, BUFFER_LINE_SERVER_ERR, FROM_UNKNOWN, (M), __VA_ARGS__); } while (0)

// TODO: replace above macros
#define server_message(S, F, M, ...) \
	do { newlinef((S)->channel, BUFFER_LINE_SERVER_ERR, (F), (M), __VA_ARGS__); } while (0)

void newlinef(struct channel*, enum buffer_line_t, const char*, const char*, ...);
void newline(struct channel*, enum buffer_line_t, const char*, const char*);


M src/utils/tree.h => src/utils/tree.h +53 -62
@@ 16,12 16,10 @@

#define AVL_HEIGHT(elm, field) (elm)->field.height

#define AVL_NEW(name, x, y) name##_AVL_NEW(x, y)
#define AVL_ADD(name, x, y) name##_AVL_ADD(x, y)
#define AVL_DEL(name, x, y) name##_AVL_DEL(x, y)

#define AVL_GET(name, x, y)     name##_AVL_GET(x, y)
#define AVL_NGET(name, x, y, z) name##_AVL_NGET(x, y, z)
#define AVL_ADD(name, x, y, z)     name##_AVL_ADD(x, y, z)
#define AVL_DEL(name, x, y, z)     name##_AVL_DEL(x, y, z)
#define AVL_GET(name, x, y, z)     name##_AVL_GET(x, y, z)
#define AVL_NGET(name, x, y, z, n) name##_AVL_NGET(x, y, z, n)

#define AVL_FOREACH(name, x, y) name##_AVL_FOREACH(x, y)



@@ 38,11 36,11 @@

/* FIXME: scan-build showing NULL pointer dereferences on add */

#define AVL_GENERATE(name, type, field, cmp, cmp_n)                               \
    static struct type* name##_AVL_ADD(struct name*, struct type*);               \
    static struct type* name##_AVL_DEL(struct name*, struct type*);               \
    static struct type* name##_AVL_ADD_REC(struct type*, struct type*);           \
    static struct type* name##_AVL_DEL_REC(struct type**, struct type*);          \
#define AVL_GENERATE(name, type, field, cmp, ncmp) \
    static struct type* name##_AVL_ADD(struct name*, struct type*, void*);        \
    static struct type* name##_AVL_DEL(struct name*, struct type*, void*);        \
    static struct type* name##_AVL_ADD_REC(struct type*, struct type*, void*);    \
    static struct type* name##_AVL_DEL_REC(struct type**, struct type*, void*);   \
                                                                                  \
static inline void                                                                \
name##_AVL_INIT(struct type *elm)                                                 \


@@ 124,37 122,35 @@ name##_AVL_FOREACH(struct name *head, void (*f)(struct type*))                  
}                                                                                 \
                                                                                  \
static struct type*                                                               \
name##_AVL_GET(struct name *head, struct type *elm)                               \
name##_AVL_GET(struct name *head, struct type *elm, void *arg)                    \
{                                                                                 \
    struct type *tmp = TREE_ROOT(head);                                           \
                                                                                  \
    int comp;                                                                     \
    struct type *tmp = TREE_ROOT(head);                                           \
                                                                                  \
    while (tmp && (comp = (cmp)(elm, tmp)))                                       \
    while (tmp && (comp = cmp(elm, tmp, arg)))                                    \
        tmp = (comp > 0) ? TREE_RIGHT(tmp, field) : TREE_LEFT(tmp, field);        \
                                                                                  \
    return tmp;                                                                   \
}                                                                                 \
                                                                                  \
static struct type*                                                               \
name##_AVL_NGET(struct name *head, struct type *elm, size_t n)                    \
name##_AVL_NGET(struct name *head, struct type *elm, void *arg, size_t n)         \
{                                                                                 \
    struct type *tmp = TREE_ROOT(head);                                           \
                                                                                  \
    int comp;                                                                     \
    struct type *tmp = TREE_ROOT(head);                                           \
                                                                                  \
    while (tmp && (comp = (cmp_n)(elm, tmp, n)))                                  \
    while (tmp && (comp = ncmp(elm, tmp, arg, n)))                                \
        tmp = (comp > 0) ? TREE_RIGHT(tmp, field) : TREE_LEFT(tmp, field);        \
                                                                                  \
    return tmp;                                                                   \
}                                                                                 \
                                                                                  \
static struct type*                                                               \
name##_AVL_ADD(struct name *head, struct type *elm)                               \
name##_AVL_ADD(struct name *head, struct type *elm, void *arg)                    \
{                                                                                 \
    name##_AVL_INIT(elm);                                                         \
                                                                                  \
    struct type *r = name##_AVL_ADD_REC(TREE_ROOT(head), elm);                    \
    struct type *r = name##_AVL_ADD_REC(TREE_ROOT(head), elm, arg);               \
                                                                                  \
    if (r == NULL)                                                                \
        return NULL;                                                              \


@@ 165,29 161,26 @@ name##_AVL_ADD(struct name *head, struct type *elm)                             
}                                                                                 \
                                                                                  \
static struct type*                                                               \
name##_AVL_ADD_REC(struct type *n, struct type *elm)                              \
name##_AVL_ADD_REC(struct type *n, struct type *elm, void *arg)                   \
{                                                                                 \
    struct type *tmp;                                                             \
                                                                                  \
    int comp, balance;                                                            \
    struct type *tmp;                                                             \
                                                                                  \
    if (n == NULL)                                                                \
        return elm;                                                               \
                                                                                  \
    if ((comp = (cmp)(elm, n)) == 0)                                              \
    if ((comp = cmp(elm, n, arg)) == 0) {                                         \
        return NULL;                                                              \
    } else if (comp > 0) {                                                        \
                                                                                  \
    else if (comp > 0) {                                                          \
                                                                                  \
        if ((tmp = name##_AVL_ADD_REC(TREE_RIGHT(n, field), elm)) == NULL)        \
        if ((tmp = name##_AVL_ADD_REC(TREE_RIGHT(n, field), elm, arg)) == NULL)   \
            return NULL;                                                          \
                                                                                  \
        TREE_RIGHT(n, field) = tmp;                                               \
    }                                                                             \
                                                                                  \
    else if (comp < 0) {                                                          \
    } else if (comp < 0) {                                                        \
                                                                                  \
        if ((tmp = name##_AVL_ADD_REC(TREE_LEFT(n, field), elm)) == NULL)         \
        if ((tmp = name##_AVL_ADD_REC(TREE_LEFT(n, field), elm, arg)) == NULL)    \
            return NULL;                                                          \
                                                                                  \
        TREE_LEFT(n, field) = tmp;                                                \


@@ 197,7 190,7 @@ name##_AVL_ADD_REC(struct type *n, struct type *elm)                            
                                                                                  \
    if (balance > 1) {                                                            \
                                                                                  \
        if (((cmp)(elm, TREE_RIGHT(n, field))) < 0)                               \
        if ((cmp(elm, TREE_RIGHT(n, field), arg)) < 0)                            \
            TREE_RIGHT(n, field) = name##_AVL_ROTATE_RIGHT(TREE_RIGHT(n, field)); \
                                                                                  \
        return name##_AVL_ROTATE_LEFT(n);                                         \


@@ 205,7 198,7 @@ name##_AVL_ADD_REC(struct type *n, struct type *elm)                            
                                                                                  \
    if (balance < -1) {                                                           \
                                                                                  \
        if (((cmp)(elm, TREE_LEFT(n, field))) > 0)                                \
        if ((cmp(elm, TREE_LEFT(n, field), arg)) > 0)                             \
            TREE_LEFT(n, field) = name##_AVL_ROTATE_LEFT(TREE_LEFT(n, field));    \
                                                                                  \
        return name##_AVL_ROTATE_RIGHT(n);                                        \


@@ 215,22 208,22 @@ name##_AVL_ADD_REC(struct type *n, struct type *elm)                            
}                                                                                 \
                                                                                  \
static struct type*                                                               \
name##_AVL_DEL(struct name *head, struct type *elm)                               \
name##_AVL_DEL(struct name *head, struct type *elm, void *arg)                    \
{                                                                                 \
    return name##_AVL_DEL_REC(&TREE_ROOT(head), elm);                             \
    return name##_AVL_DEL_REC(&TREE_ROOT(head), elm, arg);                        \
}                                                                                 \
                                                                                  \
static struct type*                                                               \
name##_AVL_DEL_REC(struct type **p, struct type *elm)                             \
name##_AVL_DEL_REC(struct type **p, struct type *elm, void *arg)                  \
{                                                                                 \
    struct type *ret = NULL, *n = *p;                                             \
                                                                                  \
    int comp, balance;                                                            \
    struct type *n = *p;                                                          \
    struct type *ret;                                                             \
                                                                                  \
    if (n == NULL)                                                                \
        return NULL;                                                              \
                                                                                  \
    if ((comp = (cmp)(elm, n)) == 0) {                                            \
    if ((comp = cmp(elm, n, arg)) == 0) {                                         \
                                                                                  \
        ret = n;                                                                  \
                                                                                  \


@@ 244,9 237,9 @@ name##_AVL_DEL_REC(struct type **p, struct type *elm)                           
                swap = TREE_LEFT(swap, field);                                    \
            }                                                                     \
                                                                                  \
            if (swap_p == swap)                                                   \
            if (swap_p == swap) {                                                 \
                TREE_LEFT(swap, field) = TREE_LEFT(n, field);                     \
            else {                                                                \
            } else {                                                              \
                struct type *swap_l = TREE_LEFT(swap, field),                     \
                            *swap_r = TREE_RIGHT(swap, field);                    \
                                                                                  \


@@ 258,48 251,46 @@ name##_AVL_DEL_REC(struct type **p, struct type *elm)                           
                                                                                  \
                TREE_LEFT(swap_p, field) = n;                                     \
                                                                                  \
                name##_AVL_DEL_REC(&TREE_RIGHT(swap, field), elm);                \
                name##_AVL_DEL_REC(&TREE_RIGHT(swap, field), elm, arg);           \
            }                                                                     \
                                                                                  \
            *p = n = swap;                                                        \
        }                                                                         \
        else if (TREE_LEFT(n, field))                                             \
        } else if (TREE_LEFT(n, field))  {                                        \
            *p = TREE_LEFT(n, field);                                             \
        else if (TREE_RIGHT(n, field))                                            \
        } else if (TREE_RIGHT(n, field)) {                                        \
            *p = TREE_RIGHT(n, field);                                            \
        else                                                                      \
        } else {                                                                  \
            *p = NULL;                                                            \
        }                                                                         \
    } else if (comp < 0) {                                                        \
        ret = name##_AVL_DEL_REC(&TREE_LEFT(n, field), elm, arg);                 \
    } else if (comp > 0) {                                                        \
        ret = name##_AVL_DEL_REC(&TREE_RIGHT(n, field), elm, arg);                \
    }                                                                             \
                                                                                  \
    else if (comp < 0)                                                            \
        ret = name##_AVL_DEL_REC(&TREE_LEFT(n, field), elm);                      \
                                                                                  \
    else if (comp > 0)                                                            \
        ret = name##_AVL_DEL_REC(&TREE_RIGHT(n, field), elm);                     \
                                                                                  \
    if (ret == NULL)                                                              \
        return NULL;                                                              \
                                                                                  \
    balance = name##_AVL_BALANCE(n);                                              \
                                                                                  \
    if (balance > 1) {                                                            \
                                                                                  \
        int hrl = name##_AVL_GET_HEIGHT(TREE_LEFT(TREE_RIGHT(n, field), field)),  \
            hrr = name##_AVL_GET_HEIGHT(TREE_RIGHT(TREE_RIGHT(n, field), field)); \
                                                                                  \
        if ((hrl - hrr) > 0)                                                      \
        if ((hrl - hrr) > 0) {                                                    \
            TREE_RIGHT(n, field) = name##_AVL_ROTATE_RIGHT(TREE_RIGHT(n, field)); \
        }                                                                         \
                                                                                  \
        *p = name##_AVL_ROTATE_LEFT(n);                                           \
    }                                                                             \
                                                                                  \
    if (balance < -1) {                                                           \
                                                                                  \
        int hll = name##_AVL_GET_HEIGHT(TREE_LEFT(TREE_LEFT(n, field), field)),   \
            hlr = name##_AVL_GET_HEIGHT(TREE_RIGHT(TREE_LEFT(n, field), field));  \
                                                                                  \
        if ((hll - hlr) < 0)                                                      \
        if ((hll - hlr) < 0) {                                                    \
            TREE_LEFT(n, field) = name##_AVL_ROTATE_LEFT(TREE_LEFT(n, field));    \
        }                                                                         \
                                                                                  \
        *p = name##_AVL_ROTATE_RIGHT(n);                                          \
    }                                                                             \


@@ 403,7 394,7 @@ name##_SPLAY_GET(struct name *head, struct type *elm)                    \
                                                                         \
    name##_SPLAY(head, elm);                                             \
                                                                         \
    if ((cmp)(elm, TREE_ROOT(head)) == 0)                                \
    if (cmp(elm, TREE_ROOT(head)) == 0)                                  \
        return TREE_ROOT(head);                                          \
                                                                         \
    return NULL;                                                         \


@@ 417,7 408,7 @@ name##_SPLAY_ADD(struct name *head, struct type *elm)                    \
    } else {                                                             \
        int comp;                                                        \
        name##_SPLAY(head, elm);                                         \
        comp = (cmp)(elm, TREE_ROOT(head));                              \
        comp = cmp(elm, TREE_ROOT(head));                                \
        if (comp < 0) {                                                  \
            TREE_LEFT(elm, field) = TREE_LEFT(TREE_ROOT(head), field);   \
            TREE_RIGHT(elm, field) = TREE_ROOT(head);                    \


@@ 440,7 431,7 @@ name##_SPLAY_DEL(struct name *head, struct type *elm)                    \
    if (TREE_EMPTY(head))                                                \
        return NULL;                                                     \
    name##_SPLAY(head, elm);                                             \
    if ((cmp)(elm, TREE_ROOT(head)) == 0) {                              \
    if (cmp(elm, TREE_ROOT(head)) == 0) {                                \
        if (TREE_LEFT(TREE_ROOT(head), field) == NULL) {                 \
            TREE_ROOT(head) = TREE_RIGHT(TREE_ROOT(head), field);        \
        } else {                                                         \


@@ 463,12 454,12 @@ name##_SPLAY(struct name *head, struct type *elm)                        \
    TREE_LEFT(&node, field) = TREE_RIGHT(&node, field) = NULL;           \
    left = right = &node;                                                \
                                                                         \
    while ((comp = (cmp)(elm, TREE_ROOT(head))) != 0) {                  \
    while ((comp = cmp(elm, TREE_ROOT(head))) != 0) {                    \
        if (comp < 0) {                                                  \
            tmp = TREE_LEFT(TREE_ROOT(head), field);                     \
            if (tmp == NULL)                                             \
                break;                                                   \
            if ((cmp)(elm, tmp) < 0){                                    \
            if (cmp(elm, tmp) < 0){                                      \
                SPLAY_ROTATE_RIGHT(head, tmp, field);                    \
                if (TREE_LEFT(TREE_ROOT(head), field) == NULL)           \
                    break;                                               \


@@ 478,7 469,7 @@ name##_SPLAY(struct name *head, struct type *elm)                        \
            tmp = TREE_RIGHT(TREE_ROOT(head), field);                    \
            if (tmp == NULL)                                             \
                break;                                                   \
            if ((cmp)(elm, tmp) > 0){                                    \
            if (cmp(elm, tmp) > 0){                                      \
                SPLAY_ROTATE_LEFT(head, tmp, field);                     \
                if (TREE_RIGHT(TREE_ROOT(head), field) == NULL)          \
                    break;                                               \

M src/utils/utils.c => src/utils/utils.c +48 -85
@@ 7,45 7,7 @@

#include "src/utils/utils.h"

static inline int irc_toupper(int);

char*
getarg(char **str, const char *sep)
{
	/* Return a token parsed from *str delimited by any character in sep.
	 *
	 * Consumes all sep characters preceding the token and null terminates it.
	 *
	 * Returns NULL if *str is NULL or contains only sep characters */

	char *ret, *ptr;

	if (str == NULL || (ptr = *str) == NULL)
		return NULL;

	while (*ptr && strchr(sep, *ptr))
		ptr++;

	if (*ptr == '\0')
		return NULL;

	ret = ptr;

	while (*ptr && !strchr(sep, *ptr))
		ptr++;

	/* If the string continues after the found arg, set the input to point
	 * one past the arg's null terminator.
	 *
	 * This might result in *str pointing to the original string's null
	 * terminator, in which case the next call to getarg will return NULL */

	*str = ptr + (*ptr && strchr(sep, *ptr));

	*ptr = '\0';

	return ret;
}
static inline int irc_toupper(enum casemapping_t, int);

char*
strdup(const char *str)


@@ 79,26 41,29 @@ str_trim(char **str)
	return !!*p;
}

//TODO: CASEMAPPING,
//        - if `ascii` only az->AZ is used for nick/channel comp
static inline int
irc_toupper(const int c)
irc_toupper(enum casemapping_t casemapping, int c)
{
	/* RFC 2812, section 2.2
	 *
	 * Because of IRC's Scandinavian origin, the characters {}|^ are
	 * considered to be the lower case equivalents of the characters []\~,
	 * respectively. This is a critical issue when determining the
	 * equivalence of two nicknames or channel names.
	 */

	switch (c) {
		case '{': return '[';
		case '}': return ']';
		case '|': return '\\';
		case '^': return '~';
		default:
	 * equivalence of two nicknames or channel names. */

	switch (casemapping) {
		case CASEMAPPING_RFC1459:
			if (c == '^') return '~';
			/* FALLTHROUGH */
		case CASEMAPPING_STRICT_RFC1459:
			if (c == '{') return '[';
			if (c == '}') return ']';
			if (c == '|') return '\\';
			/* FALLTHROUGH */
		case CASEMAPPING_ASCII:
			return (c >= 'a' && c <= 'z') ? (c + 'A' - 'a') : c;
		default:
			fatal("Unknown CASEMAPPING");
	}
}



@@ 129,7 94,7 @@ irc_ischanchar(char c, int first)
	 * channelid  = 5( %x41-5A / digit )   ; 5( A-Z / 0-9 )
	 */

	/* TODO: */
	/* TODO: CHANTYPES */
	(void)c;
	(void)first;



@@ 165,18 130,40 @@ irc_ischan(const char *str)
}

int
irc_strcmp(const char *s1, const char *s2)
irc_pinged(enum casemapping_t casemapping, const char *mesg, const char *nick)
{
	size_t len = strlen(nick);

	while (*mesg) {

		/* skip any prefixing characters that wouldn't match a valid nick */
		while (!(*mesg >= 0x41 && *mesg <= 0x7D))
			mesg++;

		/* nick prefixes the word, following character is space or symbol */
		if (!irc_strncmp(casemapping, mesg, nick, len) && !irc_isnickchar(*(mesg + len), 0))
			return 1;

		/* skip to end of word */
		while (*mesg && *mesg != ' ')
			mesg++;
	}

	return 0;
}

int
irc_strcmp(enum casemapping_t casemapping, const char *s1, const char *s2)
{
	/* Case insensitive comparison of strings s1, s2 in accordance
	 * with RFC 2812, section 2.2
	 */
	 * with RFC 2812, section 2.2 */

	int c1, c2;

	for (;;) {

		c1 = irc_toupper(*s1++);
		c2 = irc_toupper(*s2++);
		c1 = irc_toupper(casemapping, *s1++);
		c2 = irc_toupper(casemapping, *s2++);

		if ((c1 -= c2))
			return -c1;


@@ 189,18 176,17 @@ irc_strcmp(const char *s1, const char *s2)
}

int
irc_strncmp(const char *s1, const char *s2, size_t n)
irc_strncmp(enum casemapping_t casemapping, const char *s1, const char *s2, size_t n)
{
	/* Case insensitive comparison of strings s1, s2 in accordance
	 * with RFC 2812, section 2.2, up to n characters
	 */
	 * with RFC 2812, section 2.2, up to n characters */

	int c1, c2;

	while (n > 0) {

		c1 = irc_toupper(*s1++);
		c2 = irc_toupper(*s2++);
		c1 = irc_toupper(casemapping, *s1++);
		c2 = irc_toupper(casemapping, *s2++);

		if ((c1 -= c2))
			return -c1;


@@ 391,29 377,6 @@ irc_message_split(struct irc_message *m, char **trailing)
	return 0;
}

int
check_pinged(const char *mesg, const char *nick)
{
	int len = strlen(nick);

	while (*mesg) {

		/* skip any prefixing characters that wouldn't match a valid nick */
		while (!(*mesg >= 0x41 && *mesg <= 0x7D))
			mesg++;

		/* nick prefixes the word, following character is space or symbol */
		if (!irc_strncmp(mesg, nick, len) && !irc_isnickchar(*(mesg + len), 0))
			return 1;

		/* skip to end of word */
		while (*mesg && *mesg != ' ')
			mesg++;
	}

	return 0;
}

char*
word_wrap(int n, char **str, char *end)
{

M src/utils/utils.h => src/utils/utils.h +13 -13
@@ 35,6 35,14 @@
	do { MESSAGE("FATAL", __VA_ARGS__); } while (0)
#endif

enum casemapping_t
{
	CASEMAPPING_INVALID,
	CASEMAPPING_ASCII,
	CASEMAPPING_RFC1459,
	CASEMAPPING_STRICT_RFC1459
};

struct irc_message
{
	char *params;


@@ 52,25 60,17 @@ int irc_message_param(struct irc_message*, char**);
int irc_message_parse(struct irc_message*, char*, size_t);
int irc_message_split(struct irc_message*, char**);

//TODO: replace comps to channel / nicks
int irc_ischanchar(char, int);
int irc_isnickchar(char, int);
int irc_ischan(const char*);
int irc_ischanchar(char, int);
int irc_isnick(const char*);
//TODO: CASEMAPPING, ascii, rfc, strict
int irc_strcmp(const char*, const char*);
int irc_strncmp(const char*, const char*, size_t);
int irc_isnickchar(char, int);
int irc_pinged(enum casemapping_t, const char*, const char*);
int irc_strcmp(enum casemapping_t, const char*, const char*);
int irc_strncmp(enum casemapping_t, const char*, const char*, size_t);

int str_trim(char**);
int check_pinged(const char*, const char*);
char* getarg(char**, const char*);
char* word_wrap(int, char**, char*);

char* strdup(const char*);
void* memdup(const void*, size_t);

// TODO: replace check_pinged -> irc_pinged, account for casemapping
// TODO: replace word_wrap -> str_wrap... int(char**, char**, size_t)
// TODO: getarg -> simplify, str_token? tokens on ' ' only

#endif

M test/components/channel.c => test/components/channel.c +13 -20
@@ 21,30 21,23 @@ test_channel_list(void)

	c1 = channel("aaa", CHANNEL_T_OTHER);
	c2 = channel("bbb", CHANNEL_T_OTHER);
	c3 = channel("bbb", CHANNEL_T_OTHER);
	c3 = channel("ccc", CHANNEL_T_OTHER);

	/* Test adding channels to list */
	assert_ptr_eq(channel_list_add(&clist, c1), NULL);
	assert_ptr_eq(channel_list_add(&clist, c2), NULL);
	channel_list_add(&clist, c1);
	channel_list_add(&clist, c2);
	channel_list_add(&clist, c3);

	/* Test adding duplicate */
	assert_ptr_eq(channel_list_add(&clist, c2), c2);
	assert_ptr_eq(channel_list_get(&clist, "aaa", CASEMAPPING_ASCII), c1);
	assert_ptr_eq(channel_list_get(&clist, "bbb", CASEMAPPING_ASCII), c2);
	assert_ptr_eq(channel_list_get(&clist, "ccc", CASEMAPPING_ASCII), c3);

	/* Test adding duplicate by name */
	assert_ptr_eq(channel_list_add(&clist, c3), c2);
	channel_list_del(&clist, c2);
	channel_list_del(&clist, c1);
	channel_list_del(&clist, c3);

	/* Test retrieving by name */
	assert_ptr_eq(channel_list_get(&clist, "aaa"), c1);
	assert_ptr_eq(channel_list_get(&clist, "bbb"), c2);

	/* Test removing channels from list */
	assert_ptr_eq(channel_list_del(&clist, c1), c1);
	assert_ptr_eq(channel_list_del(&clist, c2), c2);

	/* Test removing channels not in list */
	assert_ptr_eq(channel_list_del(&clist, c1), NULL);
	assert_ptr_eq(channel_list_del(&clist, c2), NULL);
	assert_ptr_eq(channel_list_del(&clist, c3), NULL);
	assert_ptr_eq(channel_list_get(&clist, "aaa", CASEMAPPING_ASCII), NULL);
	assert_ptr_eq(channel_list_get(&clist, "bbb", CASEMAPPING_ASCII), NULL);
	assert_ptr_eq(channel_list_get(&clist, "ccc", CASEMAPPING_ASCII), NULL);

	channel_free(c1);
	channel_free(c2);

M test/components/mode.c => test/components/mode.c +1 -1
@@ 492,7 492,7 @@ test_chanmode_type(void)
	config_errs -= mode_cfg(&cfg, "(f)@",    MODE_CFG_PREFIX);

	if (config_errs != MODE_ERR_NONE)
		abort_test("Configuration error");
		test_abort("Configuration error");

	/* Test invalid '+'/'-' */
	assert_eq(chanmode_type(&cfg, MODE_SET_T_SIZE, 'a'), MODE_FLAG_INVALID_SET);

M test/components/user.c => test/components/user.c +62 -36
@@ 7,70 7,70 @@ test_user_list(void)
{
	/* Test add/del/get/rpl users */

	struct user_list ulist;
	struct user *u1, *u2, *u3, *u4;
	struct user_list ulist;

	memset(&ulist, 0, sizeof(ulist));

	/* Test adding users to list */
	assert_eq(user_list_add(&ulist, "aaa", MODE_EMPTY), USER_ERR_NONE);
	assert_eq(user_list_add(&ulist, "bbb", MODE_EMPTY), USER_ERR_NONE);
	assert_eq(user_list_add(&ulist, "ccc", MODE_EMPTY), USER_ERR_NONE);
	assert_eq(user_list_add(&ulist, CASEMAPPING_RFC1459, "aaa", MODE_EMPTY), USER_ERR_NONE);
	assert_eq(user_list_add(&ulist, CASEMAPPING_RFC1459, "bbb", MODE_EMPTY), USER_ERR_NONE);
	assert_eq(user_list_add(&ulist, CASEMAPPING_RFC1459, "ccc", MODE_EMPTY), USER_ERR_NONE);

	if (ulist.count != 3)
		abort_test("Failed to add users to list");
		test_abort("Failed to add users to list");

	/* Test adding duplicates */
	assert_eq(user_list_add(&ulist, "aaa", MODE_EMPTY), USER_ERR_DUPLICATE);
	assert_eq(user_list_add(&ulist, CASEMAPPING_RFC1459, "aaa", MODE_EMPTY), USER_ERR_DUPLICATE);

	/* Test retrieving by name, failure */
	assert_ptr_null(user_list_get(&ulist, "a", 0));
	assert_ptr_null(user_list_get(&ulist, "z", 0));
	assert_ptr_null(user_list_get(&ulist, CASEMAPPING_RFC1459, "a", 0));
	assert_ptr_null(user_list_get(&ulist, CASEMAPPING_RFC1459, "z", 0));

	/* Test retrieving by name, success */
	if ((u1 = user_list_get(&ulist, "aaa", 0)) == NULL)
		abort_test("Failed to retrieve u1");
	if ((u1 = user_list_get(&ulist, CASEMAPPING_RFC1459, "aaa", 0)) == NULL)
		test_abort("Failed to retrieve u1");

	if ((u2 = user_list_get(&ulist, "bbb", 0)) == NULL)
		abort_test("Failed to retrieve u2");
	if ((u2 = user_list_get(&ulist, CASEMAPPING_RFC1459, "bbb", 0)) == NULL)
		test_abort("Failed to retrieve u2");

	if ((u3 = user_list_get(&ulist, "ccc", 0)) == NULL)
		abort_test("Failed to retrieve u3");
	if ((u3 = user_list_get(&ulist, CASEMAPPING_RFC1459, "ccc", 0)) == NULL)
		test_abort("Failed to retrieve u3");

	assert_strcmp(u1->nick, "aaa");
	assert_strcmp(u2->nick, "bbb");
	assert_strcmp(u3->nick, "ccc");

	/* Test retrieving by name prefix, failure */
	assert_ptr_null(user_list_get(&ulist, "z",  1));
	assert_ptr_null(user_list_get(&ulist, "ab", 2));
	assert_ptr_null(user_list_get(&ulist, CASEMAPPING_RFC1459, "z",  1));
	assert_ptr_null(user_list_get(&ulist, CASEMAPPING_RFC1459, "ab", 2));

	if ((u1 = user_list_get(&ulist, "a", 1)) == NULL)
		abort_test("Failed to retrieve u1 by prefix");
	if ((u1 = user_list_get(&ulist, CASEMAPPING_RFC1459, "a", 1)) == NULL)
		test_abort("Failed to retrieve u1 by prefix");

	if ((u2 = user_list_get(&ulist, "bb", 2)) == NULL)
		abort_test("Failed to retrieve u2 by prefix");
	if ((u2 = user_list_get(&ulist, CASEMAPPING_RFC1459, "bb", 2)) == NULL)
		test_abort("Failed to retrieve u2 by prefix");

	if ((u3 = user_list_get(&ulist, "ccc", 3)) == NULL)
		abort_test("Failed to retrieve u3 by prefix");
	if ((u3 = user_list_get(&ulist, CASEMAPPING_RFC1459, "ccc", 3)) == NULL)
		test_abort("Failed to retrieve u3 by prefix");

	assert_strcmp(u1->nick, "aaa");
	assert_strcmp(u2->nick, "bbb");
	assert_strcmp(u3->nick, "ccc");

	/* Test replacing user in list, failure */
	assert_eq(user_list_rpl(&ulist, "zzz", "yyy"), USER_ERR_NOT_FOUND);
	assert_eq(user_list_rpl(&ulist, "bbb", "ccc"), USER_ERR_DUPLICATE);
	assert_eq(user_list_rpl(&ulist, CASEMAPPING_RFC1459, "zzz", "yyy"), USER_ERR_NOT_FOUND);
	assert_eq(user_list_rpl(&ulist, CASEMAPPING_RFC1459, "bbb", "ccc"), USER_ERR_DUPLICATE);

	/* Test replacing user in list, success */
	u3->prfxmodes.lower = 0x123;
	u3->prfxmodes.upper = 0x456;
	u3->prfxmodes.prefix = '*';

	assert_eq(user_list_rpl(&ulist, "ccc", "ddd"), USER_ERR_NONE);
	assert_eq(user_list_rpl(&ulist, CASEMAPPING_RFC1459, "ccc", "ddd"), USER_ERR_NONE);

	if ((u4 = user_list_get(&ulist, "ddd", 0)) == NULL)
		abort_test("Failed to retrieve u4 by prefix");
	if ((u4 = user_list_get(&ulist, CASEMAPPING_RFC1459, "ddd", 0)) == NULL)
		test_abort("Failed to retrieve u4 by prefix");

	assert_eq(u4->prfxmodes.lower, 0x123);
	assert_eq(u4->prfxmodes.upper, 0x456);


@@ 78,20 78,45 @@ test_user_list(void)

	assert_strcmp(u4->nick, "ddd");

	assert_ptr_null(user_list_get(&ulist, "ccc",  0));
	assert_ptr_null(user_list_get(&ulist, CASEMAPPING_RFC1459, "ccc",  0));

	/* Test removing users from list, failure */
	assert_eq(user_list_del(&ulist, "ccc"), USER_ERR_NOT_FOUND);
	assert_eq(user_list_del(&ulist, CASEMAPPING_RFC1459, "ccc"), USER_ERR_NOT_FOUND);

	/* Test removing users from list, success */
	assert_eq(user_list_del(&ulist, "aaa"), USER_ERR_NONE);
	assert_eq(user_list_del(&ulist, "bbb"), USER_ERR_NONE);
	assert_eq(user_list_del(&ulist, "ddd"), USER_ERR_NONE);
	assert_eq(user_list_del(&ulist, CASEMAPPING_RFC1459, "aaa"), USER_ERR_NONE);
	assert_eq(user_list_del(&ulist, CASEMAPPING_RFC1459, "bbb"), USER_ERR_NONE);
	assert_eq(user_list_del(&ulist, CASEMAPPING_RFC1459, "ddd"), USER_ERR_NONE);

	assert_eq(ulist.count, 0);
}

static void
test_user_list_casemapping(void)
{
	/* Test add/del/get/rpl casemapping */

	struct user *u;
	struct user_list ulist;

	memset(&ulist, 0, sizeof(ulist));

	assert_eq(user_list_add(&ulist, CASEMAPPING_RFC1459, "aaa", MODE_EMPTY), USER_ERR_NONE);
	assert_eq(user_list_add(&ulist, CASEMAPPING_RFC1459, "aAa", MODE_EMPTY), USER_ERR_DUPLICATE);

	if ((u = user_list_get(&ulist, CASEMAPPING_RFC1459, "a", 1)) == NULL)
		test_abort("Failed to retrieve u");

	assert_ptr_eq(user_list_get(&ulist, CASEMAPPING_RFC1459, "Aaa", 0), u);
	assert_ptr_eq(user_list_get(&ulist, CASEMAPPING_RFC1459, "A", 1), u);

	assert_eq(user_list_rpl(&ulist, CASEMAPPING_RFC1459, "aaa", "aAa"), USER_ERR_DUPLICATE);
	assert_eq(user_list_rpl(&ulist, CASEMAPPING_RFC1459, "aAa", "zzz"), USER_ERR_NONE);

	assert_eq(user_list_del(&ulist, CASEMAPPING_RFC1459, "ZZZ"), USER_ERR_NONE);
}

static void
test_user_list_free(void)
{
	/* Test userlist can be freed and used again */


@@ 109,15 134,15 @@ test_user_list_free(void)
	};

	for (p = users; *p; p++) {
		if (user_list_add(&ulist, *p, MODE_EMPTY) != USER_ERR_NONE)
			abort_test("Failed to add users to list");
		if (user_list_add(&ulist, CASEMAPPING_RFC1459, *p, MODE_EMPTY) != USER_ERR_NONE)
			fail_testf("Failed to add user to list: %s", *p);
	}

	user_list_free(&ulist);

	for (p = users; *p; p++) {
		if (user_list_add(&ulist, *p, MODE_EMPTY) != USER_ERR_NONE)
			fail_testf("Duplicate user: %s", *p);
		if (user_list_add(&ulist, CASEMAPPING_RFC1459, *p, MODE_EMPTY) != USER_ERR_NONE)
			fail_testf("Failed to remove user from list: %s", *p);
	}

	user_list_free(&ulist);


@@ 128,6 153,7 @@ main(void)
{
	struct testcase tests[] = {
		TESTCASE(test_user_list),
		TESTCASE(test_user_list_casemapping),
		TESTCASE(test_user_list_free)
	};


M test/draw.c => test/draw.c +4 -30
@@ 1,7 1,5 @@
#include "test/test.h"

/* FIXME: decouple draw.c and state.c, irc_send.c, irc_recv.c */

#include "src/components/buffer.c"
#include "src/components/channel.c"
#include "src/components/input.c"


@@ 12,34 10,10 @@
#include "src/state.c"
#include "src/utils/utils.c"

/* Mock rirc.c */
const char *default_username = "username";
const char *default_realname = "realname";

/* Mock io.c */
struct connection*
connection(const void *o, const char *h, const char *p)
{
	UNUSED(o);
	UNUSED(h);
	UNUSED(p);
	return NULL;
}
const char* io_err(int err) { UNUSED(err); return "err"; }
int io_cx(struct connection *c) { UNUSED(c); return 0; }
int io_dx(struct connection *c) { UNUSED(c); return 0; }
int io_sendf(struct connection *c, const char *f, ...) { UNUSED(c); UNUSED(f); return 0; }
unsigned io_tty_cols(void) { return 0; }
unsigned io_tty_rows(void) { return 0; }
void io_free(struct connection *c) { UNUSED(c); }
void io_term(void) { ; }

/* Mock handlers/irc_send.c */
int irc_send_command(struct server *s, struct channel *c, char *m) { UNUSED(s); UNUSED(c); UNUSED(m); return 0; }
int irc_send_privmsg(struct server *s, struct channel *c, char *m) { UNUSED(s); UNUSED(c); UNUSED(m); return 0; }

/* Mock handlers/irc_recv.c */
int irc_recv(struct server *s, struct irc_message *m) { UNUSED(s); UNUSED(m); return 0; }
#include "test/io.c.mock"
#include "test/rirc.c.mock"
#include "test/handlers/irc_recv.c.mock"
#include "test/handlers/irc_send.c.mock"

static void
test_STUB(void)

A test/draw.c.mock => test/draw.c.mock +17 -0
@@ 0,0 1,17 @@
void draw(union draw d) { UNUSED(d); }
void draw_bell(void) { ; }
void draw_term(void) { ; }
void
split_buffer_cols(
	struct buffer_line *l,
	unsigned int *h,
	unsigned int *t,
	unsigned int c,
	unsigned int p)
{
	UNUSED(l);
	UNUSED(h);
	UNUSED(t);
	UNUSED(c);
	UNUSED(p);
}

M test/handlers/irc_ctcp.c => test/handlers/irc_ctcp.c +1 -1
@@ 32,7 32,7 @@ static struct channel *c_chan;
static struct channel *c_priv;
static struct server *s;

/* Mock stat.c */
/* Mock state.c */
void
newlinef(struct channel *c, enum buffer_line_t t, const char *f, const char *fmt, ...)
{

A test/handlers/irc_recv.c.mock => test/handlers/irc_recv.c.mock +7 -0
@@ 0,0 1,7 @@
int
irc_recv(struct server *s, struct irc_message *m)
{
	UNUSED(s);
	UNUSED(m);
	return 0;
}

M test/handlers/irc_send.c => test/handlers/irc_send.c +14 -9
@@ 42,7 42,7 @@ static struct channel *c_priv;
static struct channel *c_serv;
static struct server *s;

/* Mock stat.c */
/* Mock state.c */
void
newlinef(struct channel *c, enum buffer_line_t t, const char *f, const char *fmt, ...)
{


@@ 184,6 184,7 @@ test_send_ctcp_ping(void)
	char m3[] = "ctcp-ping";
	char m4[] = "ctcp-ping targ";

	char *saveptr;
	char *p1;
	char *p2;



@@ 195,14 196,16 @@ test_send_ctcp_ping(void)

	assert_eq(irc_send_command(s, c_priv, m3), 0);

	assert_true(((p1 = strchr(send_buf, '\001')) != NULL));
	assert_true(((p2 = strchr(p1 + 1, '\001')) != NULL));
	p1 = strchr(send_buf, '\001');
	p2 = strchr(p1 + 1, '\001');
	assert_true(p1 != NULL);
	assert_true(p2 != NULL);

	*p1++ = 0;
	*p2++ = 0;

	assert_true((getarg(&p1, " ") != NULL));
	assert_true((getarg(&p1, " ") != NULL));
	assert_true((strtok_r(p1, " ", &saveptr)));
	assert_true((strtok_r(NULL, " ", &saveptr)));

	assert_strcmp(fail_buf, "");
	assert_strcmp(send_buf, "PRIVMSG priv :");


@@ 212,14 215,16 @@ test_send_ctcp_ping(void)

	assert_eq(irc_send_command(s, c_priv, m4), 0);

	assert_true(((p1 = strchr(send_buf, '\001')) != NULL));
	assert_true(((p2 = strchr(p1 + 1, '\001')) != NULL));
	p1 = strchr(send_buf, '\001');
	p2 = strchr(p1 + 1, '\001');
	assert_true(p1 != NULL);
	assert_true(p2 != NULL);

	*p1++ = 0;
	*p2++ = 0;

	assert_true((getarg(&p1, " ") != NULL));
	assert_true((getarg(&p1, " ") != NULL));
	assert_true((strtok_r(p1, " ", &saveptr)));
	assert_true((strtok_r(NULL, " ", &saveptr)));

	assert_strcmp(fail_buf, "");
	assert_strcmp(send_buf, "PRIVMSG targ :");

A test/handlers/irc_send.c.mock => test/handlers/irc_send.c.mock +17 -0
@@ 0,0 1,17 @@
int
irc_send_command(struct server *s, struct channel *c, char *m)
{
	UNUSED(s);
	UNUSED(c);
	UNUSED(m);
	return 0;
}

int
irc_send_privmsg(struct server *s, struct channel *c, char *m)
{
	UNUSED(s);
	UNUSED(c);
	UNUSED(m);
	return 0;
}

A test/io.c.mock => test/io.c.mock +16 -0
@@ 0,0 1,16 @@
struct connection*
connection(const void *o, const char *h, const char *p)
{
	UNUSED(o);
	UNUSED(h);
	UNUSED(p);
	return NULL;
}
const char* io_err(int err) { UNUSED(err); return "err"; }
int io_cx(struct connection *c) { UNUSED(c); return 0; }
int io_dx(struct connection *c) { UNUSED(c); return 0; }
int io_sendf(struct connection *c, const char *f, ...) { UNUSED(c); UNUSED(f); return 0; }
unsigned io_tty_cols(void) { return 0; }
unsigned io_tty_rows(void) { return 0; }
void io_free(struct connection *c) { UNUSED(c); }
void io_term(void) { ; }

M test/rirc.c => test/rirc.c +4 -45
@@ 1,7 1,5 @@
#include "test/test.h"

/* FIXME: decouple rirc.c and state.c, irc_send.c, irc_recv.c */

#include "src/components/buffer.c"
#include "src/components/channel.c"
#include "src/components/input.c"


@@ 12,49 10,10 @@
#include "src/state.c"
#include "src/utils/utils.c"

/* Mock draw.c */
void draw(union draw d) { UNUSED(d); }
void draw_bell(void) { ; }
void draw_term(void) { ; }
void
split_buffer_cols(
	struct buffer_line *l,
	unsigned int *h,
	unsigned int *t,
	unsigned int c,
	unsigned int p)
{
	UNUSED(l);
	UNUSED(h);
	UNUSED(t);
	UNUSED(c);
	UNUSED(p);
}

/* Mock io.c */
struct connection*
connection(const void *o, const char *h, const char *p)
{
	UNUSED(o);
	UNUSED(h);
	UNUSED(p);
	return NULL;
}
const char* io_err(int err) { UNUSED(err); return "err"; }
int io_cx(struct connection *c) { UNUSED(c); return 0; }
int io_dx(struct connection *c) { UNUSED(c); return 0; }
int io_sendf(struct connection *c, const char *f, ...) { UNUSED(c); UNUSED(f); return 0; }
unsigned io_tty_cols(void) { return 0; }
unsigned io_tty_rows(void) { return 0; }
void io_free(struct connection *c) { UNUSED(c); }
void io_term(void) { ; }

/* Mock handlers/irc_send.c */
int irc_send_command(struct server *s, struct channel *c, char *m) { UNUSED(s); UNUSED(c); UNUSED(m); return 0; }
int irc_send_privmsg(struct server *s, struct channel *c, char *m) { UNUSED(s); UNUSED(c); UNUSED(m); return 0; }

/* Mock handlers/irc_recv.c */
int irc_recv(struct server *s, struct irc_message *m) { UNUSED(s); UNUSED(m); return 0; }
#include "test/draw.c.mock"
#include "test/io.c.mock"
#include "test/handlers/irc_recv.c.mock"
#include "test/handlers/irc_send.c.mock"

static void
test_STUB(void)

A test/rirc.c.mock => test/rirc.c.mock +2 -0
@@ 0,0 1,2 @@
const char *default_username = "username";
const char *default_realname = "realname";

M test/state.c => test/state.c +4 -43
@@ 10,49 10,10 @@
#include "src/state.c"
#include "src/utils/utils.c"

/* Mock rirc.c */
const char *default_username = "username";
const char *default_realname = "realname";

/* Mock draw.c */
void draw(union draw d) { UNUSED(d); }
void draw_bell(void) { ; }
void draw_term(void) { ; }
void
split_buffer_cols(
	struct buffer_line *l,
	unsigned int *h,
	unsigned int *t,
	unsigned int c,
	unsigned int p)
{
	UNUSED(l);
	UNUSED(h);
	UNUSED(t);
	UNUSED(c);
	UNUSED(p);
}

/* Mock io.c */
struct connection*
connection(const void *o, const char *h, const char *p)
{
	UNUSED(o);
	UNUSED(h);
	UNUSED(p);
	return NULL;
}
const char* io_err(int err) { UNUSED(err); return "err"; }
int io_cx(struct connection *c) { UNUSED(c); return 0; }
int io_dx(struct connection *c) { UNUSED(c); return 0; }
int io_sendf(struct connection *c, const char *f, ...) { UNUSED(c); UNUSED(f); return 0; }
unsigned io_tty_cols(void) { return 0; }
unsigned io_tty_rows(void) { return 0; }
void io_free(struct connection *c) { UNUSED(c); }
void io_term(void) { ; }

/* Mock handlers/irc_recv.c */
int irc_recv(struct server *s, struct irc_message *m) { UNUSED(s); UNUSED(m); return 0; }
#include "test/draw.c.mock"
#include "test/io.c.mock"
#include "test/rirc.c.mock"
#include "test/handlers/irc_recv.c.mock"

#define INP_S(S) io_cb_read_inp((S), strlen(S))
#define INP_C(C) io_cb_read_inp((char[]){(C)}, 1)

M test/test.h => test/test.h +6 -6
@@ 27,8 27,8 @@
 *   Explicitly fail or abort a test [with formated] message
 *    - fail_test(M)
 *    - fail_testf(M, ...)
 *    - abort_test(M)
 *    - abort_testf(M, ...)
 *    - test_abort(M)
 *    - test_abortf(M, ...)
 */

#define TESTING


@@ 81,7 81,7 @@ static void _print_testcase_name_(const char*);
		_failures_++; \
	} while (0)

#define abort_test(M) \
#define test_abort(M) \
	do { \
		_print_testcase_name_(__func__); \
		printf("    %d: " M "\n", __LINE__); \


@@ 90,7 90,7 @@ static void _print_testcase_name_(const char*);
		return; \
	} while (0)

#define abort_testf(M, ...) \
#define test_abortf(M, ...) \
	do { \
		_print_testcase_name_(__func__); \
		printf("    %d: ", __LINE__); \


@@ 122,13 122,13 @@ static void _print_testcase_name_(const char*);

#define assert_true(X) \
	do { \
		if ((X) == 0) \
		if (!(X)) \
			fail_test(#X " expected true"); \
	} while (0)

#define assert_false(X) \
	do { \
		if ((X) != 0) \
		if ((X)) \
			fail_test(#X " expected false"); \
	} while (0)


M test/utils/utils.c => test/utils/utils.c +71 -130
@@ 2,63 2,6 @@
#include "src/utils/utils.c"

static void
test_getarg(void)
{
	/* Test string token parsing */

	char *ptr;

	/* Test null pointer */
	assert_strcmp(getarg(NULL, " "), NULL);

	/* Test empty string */
	char str1[] = "";

	ptr = str1;
	assert_strcmp(getarg(&ptr, " "), NULL);

	/* Test only whitestapce */
	char str2[] = "   ";

	ptr = str2;
	assert_strcmp(getarg(&ptr, " "), NULL);

	/* Test single token */
	char str3[] = "arg1";

	ptr = str3;
	assert_strcmp(getarg(&ptr, " "), "arg1");
	assert_strcmp(getarg(&ptr, " "), NULL);

	/* Test multiple tokens */
	char str4[] = "arg2 arg3 arg4";

	ptr = str4;
	assert_strcmp(getarg(&ptr, " "), "arg2");
	assert_strcmp(getarg(&ptr, " "), "arg3");
	assert_strcmp(getarg(&ptr, " "), "arg4");
	assert_strcmp(getarg(&ptr, " "), NULL);

	/* Test multiple tokens with extraneous whitespace */
	char str5[] = "   arg5   arg6   arg7   ";

	ptr = str5;
	assert_strcmp(getarg(&ptr, " "), "arg5");
	assert_strcmp(getarg(&ptr, " "), "arg6");
	assert_strcmp(getarg(&ptr, " "), "arg7");
	assert_strcmp(getarg(&ptr, " "), NULL);

	/* Test multiple separator characters */
	char str6[] = "!!!arg8:!@#$arg9   :   arg10!@#";

	ptr = str6;
	assert_strcmp(getarg(&ptr, "!:"), "arg8");
	assert_strcmp(getarg(&ptr, ":$#@! "), "arg9");
	assert_strcmp(getarg(&ptr, " :"), "arg10!@#");
	assert_strcmp(getarg(&ptr, " "), NULL);
}

static void
test_irc_message_param(void)
{
	char *param;


@@ 407,52 350,90 @@ test_irc_message_split(void)
}

static void
test_irc_strcmp(void)
test_irc_pinged(void)
{
	/* Test case insensitive */
	assert_eq(irc_strcmp("abc123[]\\~`_", "ABC123{}|^`_"), 0);
	/* Test detecting user's nick in message */

	const char *nick = "testnick";

	/* Test message contains nick */
	const char *mesg1 = "testing testnick testing";
	assert_eq(irc_pinged(CASEMAPPING_RFC1459, mesg1, nick), 1);

	/* Test common way of addressing messages to nicks */
	const char *mesg2 = "testnick: testing";
	assert_eq(irc_pinged(CASEMAPPING_RFC1459, mesg2, nick), 1);

	/* Test non-nick char prefix */
	const char *mesg3 = "testing !@#testnick testing";
	assert_eq(irc_pinged(CASEMAPPING_RFC1459, mesg3, nick), 1);

	/* Test non-nick char suffix */
	const char *mesg4 = "testing testnick!@#$ testing";
	assert_eq(irc_pinged(CASEMAPPING_RFC1459, mesg4, nick), 1);

	/* Test non-nick char prefix and suffix */
	const char *mesg5 = "testing !testnick! testing";
	assert_eq(irc_pinged(CASEMAPPING_RFC1459, mesg5, nick), 1);

	/* Test case insensitive nick detection */
	const char *mesg6 = "testing TeStNiCk testing";
	assert_eq(irc_pinged(CASEMAPPING_RFC1459, mesg6, nick), 1);

	/* Error: message doesn't contain nick */
	const char *mesg7 = "testing testing";
	assert_eq(irc_pinged(CASEMAPPING_RFC1459, mesg7, nick), 0);

	/* Error: message contains nick prefix */
	const char *mesg8 = "testing testnickshouldfail testing";
	assert_eq(irc_pinged(CASEMAPPING_RFC1459, mesg8, nick), 0);
}

static void
test_irc_strcmp(void)
{
	/* Test lexicographic order
	 *
	 * The character '`' is permitted along with '{', but are disjoint
	 * in ascii, with lowercase letters between them. Ensure that in
	 * lexicographic order, irc_strmp ranks:
	 *  numeric > alpha > special
	 */

	assert_gt(irc_strcmp("0", "a"), 0);
	assert_gt(irc_strcmp("a", "`"), 0);
	assert_gt(irc_strcmp("a", "{"), 0);
	assert_gt(irc_strcmp("z", "{"), 0);
	assert_gt(irc_strcmp("Z", "`"), 0);
	assert_gt(irc_strcmp("a", "Z"), 0);
	assert_gt(irc_strcmp("A", "z"), 0);
	 *  numeric > alpha > special */

	assert_gt(irc_strcmp(CASEMAPPING_RFC1459, "0", "a"), 0);
	assert_gt(irc_strcmp(CASEMAPPING_RFC1459, "a", "`"), 0);
	assert_gt(irc_strcmp(CASEMAPPING_RFC1459, "a", "{"), 0);
	assert_gt(irc_strcmp(CASEMAPPING_RFC1459, "z", "{"), 0);
	assert_gt(irc_strcmp(CASEMAPPING_RFC1459, "Z", "`"), 0);
	assert_gt(irc_strcmp(CASEMAPPING_RFC1459, "a", "Z"), 0);
	assert_gt(irc_strcmp(CASEMAPPING_RFC1459, "A", "z"), 0);

	/* Test case insensitive */

	/* Passes for CASEMAPPING_RFC1459 */
	assert_eq(irc_strcmp(CASEMAPPING_RFC1459, "abc123[]\\~`_", "ABC123{}|^`_"), 0);
	assert_lt(irc_strcmp(CASEMAPPING_STRICT_RFC1459, "abc123[]\\~`_", "ABC123{}|^`_"), 0);
	assert_gt(irc_strcmp(CASEMAPPING_ASCII, "abc123[]\\~`_", "ABC123{}|^`_"), 0);

	/* Passes for CASEMAPPING_RFC1459, CASEMAPPING_STRICT_RFC1459 */
	assert_eq(irc_strcmp(CASEMAPPING_RFC1459, "abc123[]\\`_", "ABC123{}|`_"), 0);
	assert_eq(irc_strcmp(CASEMAPPING_STRICT_RFC1459, "abc123[]\\`_", "ABC123{}|`_"), 0);
	assert_gt(irc_strcmp(CASEMAPPING_ASCII, "abc123[]\\`_", "ABC123{}|`_"), 0);

	/* Passes for CASEMAPPING_RFC1459, CASEMAPPING_STRICT_RFC1459, CASEMAPPING_ASCII */
	assert_eq(irc_strcmp(CASEMAPPING_RFC1459, "abc123", "ABC123"), 0);
	assert_eq(irc_strcmp(CASEMAPPING_STRICT_RFC1459, "abc123", "ABC123"), 0);
	assert_eq(irc_strcmp(CASEMAPPING_ASCII, "abc123", "ABC123"), 0);
}

static void
test_irc_strncmp(void)
{
	/* Test case insensitive */
	assert_eq(irc_strncmp("abc123[]\\~`_", "ABC123{}|^`_", 100), 0);

	/* Test lexicographic order
	 *
	 * The character '`' is permitted along with '{', but are disjoint
	 * in ascii, with lowercase letters between them. Ensure that in
	 * lexicographic order, irc_strmp ranks:
	 *  numeric > alpha > special
	 */
	assert_gt(irc_strncmp("0", "a", 1), 0);
	assert_gt(irc_strncmp("a", "`", 1), 0);
	assert_gt(irc_strncmp("a", "{", 1), 0);
	assert_gt(irc_strncmp("z", "{", 1), 0);
	assert_gt(irc_strncmp("Z", "`", 1), 0);
	assert_gt(irc_strncmp("a", "Z", 1), 0);
	assert_gt(irc_strncmp("A", "z", 1), 0);
	assert_eq(irc_strncmp(CASEMAPPING_RFC1459, "abc123[]\\~`_", "ABC123{}|^`_", 100), 0);

	/* Test n */
	assert_eq(irc_strncmp("abcA", "abcZ", 3), 0);
	assert_gt(irc_strncmp("abcA", "abcZ", 4), 0);
	assert_eq(irc_strncmp(CASEMAPPING_RFC1459, "abcA", "abcZ", 3), 0);
	assert_gt(irc_strncmp(CASEMAPPING_RFC1459, "abcA", "abcZ", 4), 0);
}

static void


@@ 463,51 444,12 @@ test_irc_toupper(void)
	char *p, str[] = "*az{}|^[]\\~*";

	for (p = str; *p; p++)
		*p = irc_toupper(*p);
		*p = irc_toupper(CASEMAPPING_RFC1459, *p);

	assert_strcmp(str, "*AZ[]\\~[]\\~*");
}

static void
test_check_pinged(void)
{
	/* Test detecting user's nick in message */

	// FIXME: check_pinged should take server argument
	//        and use s->cmp to test nick case

	char *nick = "testnick";

	/* Test message contains username */
	char *mesg1 = "testing testnick testing";
	assert_eq(check_pinged(mesg1, nick), 1);

	/* Test common way of addressing messages to users */
	char *mesg2 = "testnick: testing";
	assert_eq(check_pinged(mesg2, nick), 1);

	/* Test non-nick char prefix */
	char *mesg3 = "testing !@#testnick testing";
	assert_eq(check_pinged(mesg3, nick), 1);

	/* Test non-nick char suffix */
	char *mesg4 = "testing testnick!@#$ testing";
	assert_eq(check_pinged(mesg4, nick), 1);

	/* Test non-nick char prefix and suffix */
	char *mesg5 = "testing !testnick! testing";
	assert_eq(check_pinged(mesg5, nick), 1);

	/* Error: message doesn't contain username */
	char *mesg6 = "testing testing";
	assert_eq(check_pinged(mesg6, nick), 0);

	/* Error: message contains username prefix */
	char *mesg7 = "testing testnickshouldfail testing";
	assert_eq(check_pinged(mesg7, nick), 0);
}

static void
test_str_trim(void)
{
	/* Test skipping space at the begging of a string pointer


@@ 647,11 589,10 @@ int
main(void)
{
	struct testcase tests[] = {
		TESTCASE(test_check_pinged),
		TESTCASE(test_getarg),
		TESTCASE(test_irc_message_param),
		TESTCASE(test_irc_message_parse),
		TESTCASE(test_irc_message_split),
		TESTCASE(test_irc_pinged),
		TESTCASE(test_irc_strcmp),
		TESTCASE(test_irc_strncmp),
		TESTCASE(test_irc_toupper),