[ccan] RFC: draft rfc822 module

David Gibson david at gibson.dropbear.id.au
Mon May 21 11:12:37 EST 2012


Here's a draft of a module for parsing RFC822(/RFC2822/RFC5322)
messages.  It doesn't do a heap at the moment, and it needs some work
on stuff to match the RFC better, but here's what I have now for
comment.

---
 ccan/rfc822/LICENSE                       |    1 +
 ccan/rfc822/_info                         |   54 ++++++
 ccan/rfc822/rfc822.c                      |  257 +++++++++++++++++++++++++++++
 ccan/rfc822/rfc822.h                      |   81 +++++++++
 ccan/rfc822/test/run-allocation-failure.c |   56 +++++++
 ccan/rfc822/test/run-bad-header.c         |   82 +++++++++
 ccan/rfc822/test/run-hdr-and-body.c       |  190 +++++++++++++++++++++
 ccan/rfc822/test/run-no-body.c            |  107 ++++++++++++
 ccan/rfc822/test/run-testdata.c           |   45 +++++
 ccan/rfc822/test/testdata.h               |  105 ++++++++++++
 10 files changed, 978 insertions(+)
 create mode 120000 ccan/rfc822/LICENSE
 create mode 100644 ccan/rfc822/_info
 create mode 100644 ccan/rfc822/rfc822.c
 create mode 100644 ccan/rfc822/rfc822.h
 create mode 100644 ccan/rfc822/test/run-allocation-failure.c
 create mode 100644 ccan/rfc822/test/run-bad-header.c
 create mode 100644 ccan/rfc822/test/run-hdr-and-body.c
 create mode 100644 ccan/rfc822/test/run-no-body.c
 create mode 100644 ccan/rfc822/test/run-testdata.c
 create mode 100644 ccan/rfc822/test/testdata.h

diff --git a/ccan/rfc822/LICENSE b/ccan/rfc822/LICENSE
new file mode 120000
index 0000000..dc314ec
--- /dev/null
+++ b/ccan/rfc822/LICENSE
@@ -0,0 +1 @@
+../../licenses/LGPL-2.1
\ No newline at end of file
diff --git a/ccan/rfc822/_info b/ccan/rfc822/_info
new file mode 100644
index 0000000..b8a5dce
--- /dev/null
+++ b/ccan/rfc822/_info
@@ -0,0 +1,54 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * rfc822 - Parsing of RFC822 emails
+ *
+ * This code allows easy processing of RFC822/RFC2822/RFC5322
+ * formatted email messages.  For now only read-only operation is
+ * supported.
+ *
+ * The important design goals are these:
+ * - Be lazy.  Don't compute immediately compute fancy indexes for the
+ *   mesage.  Just reading messages into the system, then sending
+ *   them out again should not incur a serious performance hit.
+ * - But cache.  Once the user does request data that needs parsing,
+ *   cache the results in suitable data structures so that if lots
+ *   more lookups are done they're then fast.
+ * - Cope with ill-formatted messages.  Even if the input is not
+ *   RFC822 compliant, don't SEGV and try to return as much useful
+ *   data as possible.
+ *
+ * Example:
+ *	char buf[] = "From: <from at example.com>\n"
+ *		     "To: <to at example.com>\n\n"
+ *                   "body\n";
+ *	struct rfc822_msg *msg;
+ *	struct bytestring body;
+ *
+ *	msg = rfc822_start(NULL, buf, sizeof(buf));
+ *	body = rfc822_body(msg);
+ *	fwrite(body.ptr, 1, body.len, stdout);
+ *
+ * License: LGPL (2 or any later version)
+ */
+int main(int argc, char *argv[])
+{
+	/* Expect exactly one argument */
+	if (argc != 2)
+		return 1;
+
+	if (strcmp(argv[1], "depends") == 0) {
+		printf("ccan/array_size\n");
+		printf("ccan/talloc\n");
+		printf("ccan/list\n");
+		printf("ccan/foreach\n");
+		printf("ccan/failtest\n");
+		printf("ccan/str\n");
+		printf("ccan/bytestring\n");
+		return 0;
+	}
+
+	return 1;
+}
diff --git a/ccan/rfc822/rfc822.c b/ccan/rfc822/rfc822.c
new file mode 100644
index 0000000..d6312af
--- /dev/null
+++ b/ccan/rfc822/rfc822.c
@@ -0,0 +1,257 @@
+#include "config.h"
+
+#include <string.h>
+
+#include <ccan/str/str.h>
+#include <ccan/talloc/talloc.h>
+#include <ccan/list/list.h>
+
+#include <ccan/rfc822/rfc822.h>
+
+#ifndef HAVE_MEMMEM
+static const void *memmem(const void *haystack, size_t haystacklen,
+			  const void *needle, size_t needlelen)
+{
+	const char *p, *last;
+
+	p = haystack;
+	last = p + haystacklen - needlelen;
+
+	do {
+		if (memcmp(p, needle, needlelen) == 0)
+			return p;
+	} while (p++ <= last);
+
+	return NULL;
+}
+#endif
+
+static void (*allocation_failure_hook)(const char *);
+
+static void NORETURN default_allocation_failure(const char *s)
+{
+	fprintf(stderr, "ccan/rfc822: Allocation failure: %s", s);
+	abort();
+}
+
+static void allocation_failure(const char *s)
+{
+	if (allocation_failure_hook)
+		(*allocation_failure_hook)(s);
+	else
+		default_allocation_failure(s);
+}
+
+void rfc822_set_allocation_failure_handler(void (*h)(const char *))
+{
+	allocation_failure_hook = h;
+}
+
+#define ALLOC_CHECK(p, r) \
+	do { \
+		if (!(p)) { \
+			allocation_failure(__FILE__ ":" stringify(__LINE__)); \
+			return (r); \
+		} \
+	} while (0)
+
+struct rfc822_msg {
+	const char *data, *end;
+
+	const char *remainder;
+
+	struct list_head headers;
+
+	const char *body;
+};
+
+struct rfc822_header {
+	struct bytestring full, name, value;
+	struct list_node list;
+};
+
+void rfc822_check(struct rfc822_msg *msg)
+{
+	assert(msg);
+	list_check(&msg->headers, "bad header list");
+}
+
+#ifdef CCAN_RFC822_DEBUG
+#define CHECK(msg)	rfc822_check((msg))
+#else
+#define CHECK(msg)	do { } while (0)
+#endif
+
+struct rfc822_msg *rfc822_start(const void *ctx, const char *p, size_t len)
+{
+	struct rfc822_msg *msg;
+
+	msg = talloc(ctx, struct rfc822_msg);
+	if (!msg)
+		return NULL;
+
+	msg->data = p;
+	msg->end = p + len;
+
+	msg->remainder = msg->data;
+	msg->body = NULL;
+
+	list_head_init(&msg->headers);
+
+	CHECK(msg);
+
+	return msg;
+}
+
+void rfc822_free(struct rfc822_msg *msg)
+{
+	talloc_free(msg);
+}
+
+static const struct rfc822_header *next_header_cached(struct rfc822_msg *msg,
+						      const struct rfc822_header *hdr)
+{
+	struct list_node *h = &msg->headers.n;
+	const struct list_node *n = h;
+
+	CHECK(msg);
+
+	if (hdr)
+		n = &hdr->list;
+
+	if (n->next == h)
+		return NULL;
+
+	CHECK(msg);
+
+	return list_entry(n->next, struct rfc822_header, list);
+}
+
+static const char *next_line(const char *start, const char *end)
+{
+	const char *p = memchr(start, '\n', end - start);
+
+	return p ? (p + 1) : end;
+}
+
+static int is_lwsp(char c){
+	return (c == ' ') || (c == '\t');
+}
+
+static const struct rfc822_header *next_header_parse(struct rfc822_msg *msg)
+{
+	const char *h, *eh, *ev, *colon;
+	struct rfc822_header *hi;
+
+	CHECK(msg);
+
+	if (!msg->remainder)
+		return NULL;
+
+	if (msg->body && (msg->remainder >= msg->body))
+		return NULL;
+
+	eh = h = msg->remainder;
+	do {
+		eh = next_line(eh, msg->end);
+	} while ((eh < msg->end) && is_lwsp(*eh));
+
+	if (eh >= msg->end)
+		msg->remainder = NULL;
+	else
+		msg->remainder = eh;
+
+	ev = eh;
+	if ((ev > h) && (ev[-1] == '\n'))
+		ev--;
+	if ((ev > h) && (ev[-1] == '\r'))
+		ev--;
+
+	if (ev == h) {
+		/* Found the end of the headers */
+		if (eh < msg->end)
+			msg->body = eh;
+		return NULL;
+	}
+
+	hi = talloc(msg, struct rfc822_header);
+	ALLOC_CHECK(hi, NULL);
+
+	hi->full = bytestring(h, eh - h);
+	list_add_tail(&msg->headers, &hi->list);
+
+	colon = memchr(h, ':', hi->full.len);
+	if (colon) {
+		hi->name = bytestring(h, colon - h);
+		hi->value = bytestring(colon + 1, ev - colon - 1);
+	} else {
+		hi->name = bytestring_NULL;
+		hi->value = bytestring_NULL;
+	}
+
+	CHECK(msg);
+
+	return hi;
+}
+
+const struct rfc822_header *rfc822_next_header(struct rfc822_msg *msg,
+					       const struct rfc822_header *hdr)
+{
+	const struct rfc822_header *h;
+
+	CHECK(msg);
+
+	h = next_header_cached(msg, hdr);
+	if (h)
+		return h;
+
+	return next_header_parse(msg);
+}
+
+struct bytestring rfc822_body(struct rfc822_msg *msg)
+{
+	const char *p = msg->body;
+
+	CHECK(msg);
+
+	if (!msg->body && msg->remainder) {
+		p = memmem(msg->remainder, msg->end - msg->remainder,
+			   "\r\n\r\n", 4);
+		if (p) {
+			p += 4;
+		} else {
+			p = memmem(msg->remainder, msg->end - msg->remainder,
+				   "\n\n", 2);
+			if (p) {
+				p += 2;
+			}
+		}
+
+		if (p && (p < msg->end))
+			msg->body = p;
+	}
+
+	CHECK(msg);
+
+	return bytestring(p, msg->end - p);
+}
+
+int rfc822_header_valid(const struct rfc822_header *hdr)
+{
+	return (hdr->name.ptr && hdr->value.ptr);
+}
+
+struct bytestring rfc822_header_full(const struct rfc822_header *hdr)
+{
+	return hdr->full;
+}
+
+struct bytestring rfc822_header_name(const struct rfc822_header *hdr)
+{
+	return hdr->name;
+}
+
+struct bytestring rfc822_header_value(const struct rfc822_header *hdr)
+{
+	return hdr->value;
+}
diff --git a/ccan/rfc822/rfc822.h b/ccan/rfc822/rfc822.h
new file mode 100644
index 0000000..271fc0c
--- /dev/null
+++ b/ccan/rfc822/rfc822.h
@@ -0,0 +1,81 @@
+#ifndef CCAN_RFC822_H_
+#define CCAN_RFC822_H_
+
+#include <stdlib.h>
+
+#include <ccan/bytestring/bytestring.h>
+
+struct rfc822_header;
+struct rfc822_msg;
+
+/**
+ * rfc822_set_allocation_failure_handler - set function to call on allocation
+ *					   failure
+ * @h: failure handler function pointer
+ *
+ * Normally functions in this module abort() on allocation failure for
+ * simplicity.  You can change this behaviour by calling this function
+ * to set an alternative callback for allocation failures.  The
+ * callback is called with a string describing where the failure
+ * occurred, which can be used to log a more useful error message.
+ *
+ * Example:
+ *	void my_handler(const char *str)
+ *	{
+ *		fprintf(stderr, "Allocation failure: %s\n", str);
+ *		exit(1);
+ *	}
+ *
+ *	rfc822_set_allocation_failure_handler(&my_handler);
+ */
+void rfc822_set_allocation_failure_handler(void (*h)(const char *));
+
+/**
+ * rfc822_check - check validity of an rfc822_msg context
+ * @msg: message to validate
+ *
+ * This debugging function checks the validity of the internal data
+ * structures in an active rfc822_msg context.  If the state of the
+ * structure is valid, it is a no-op, if an error is found it will
+ * abort().
+ */
+void rfc822_check(struct rfc822_msg *msg);
+
+/**
+ * rfc822_start - start parsing a new rfc822 message
+ * @ctx: talloc context to make allocations in
+ * @p: pointer to a buffer containing the message text
+ * @len: length of the message text
+ *
+ * This function creates a new rfc822_msg context for parsing an
+ * rfc822 message, initialized based on the message text given by the
+ * pointer.
+ */
+struct rfc822_msg *rfc822_start(const void *ctx, const char *p, size_t len);
+
+/**
+ * rfc822_free - get pointer to enclosing structure
+ * @msg:
+ *
+ * Given a pointer to a member of a structure, this macro does pointer
+ * subtraction to return the pointer to the enclosing type.
+ */
+void rfc822_free(struct rfc822_msg *msg);
+
+const struct rfc822_header *rfc822_next_header(struct rfc822_msg *msg,
+					       const struct rfc822_header *hdr);
+#define rfc822_first_header(msg) (rfc822_next_header((msg), NULL))
+
+#define rfc822_for_each_header(msg, hdr) \
+	for ((hdr) = rfc822_first_header((msg)); \
+	     (hdr);					\
+	     (hdr) = rfc822_next_header((msg), (hdr)))
+
+struct bytestring rfc822_body(struct rfc822_msg *msg);
+
+int rfc822_header_valid(const struct rfc822_header *hdr);
+struct bytestring rfc822_header_full(const struct rfc822_header *hdr);
+struct bytestring rfc822_header_name(const struct rfc822_header *hdr);
+struct bytestring rfc822_header_value(const struct rfc822_header *hdr);
+
+#endif /* CCAN_RFC822_H_ */
diff --git a/ccan/rfc822/test/run-allocation-failure.c b/ccan/rfc822/test/run-allocation-failure.c
new file mode 100644
index 0000000..9427111
--- /dev/null
+++ b/ccan/rfc822/test/run-allocation-failure.c
@@ -0,0 +1,56 @@
+#include <ccan/tap/tap.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <signal.h>
+#include <errno.h>
+
+#define CCAN_RFC822_DEBUG
+
+#include <ccan/rfc822/rfc822.h>
+
+#include <ccan/rfc822/rfc822.c>
+
+#include "testdata.h"
+
+static void *failing_malloc(size_t size)
+{
+	return NULL;
+}
+
+static void abort_handler(int signum)
+{
+	ok1(1);
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+	const char *buf;
+	size_t len;
+	struct rfc822_msg *msg;
+	struct sigaction sa = {
+		.sa_handler = abort_handler,
+	};
+	int ret;
+
+	plan_tests(2);
+
+	ret = sigaction(SIGABRT, &sa, NULL);
+	ok(ret, "Couldn't install signal handler: %s", strerror(errno));
+
+	buf = assemble_msg(&test_msg_1, &len, 0);
+
+	msg = rfc822_start(NULL, buf, len);
+
+	talloc_set_allocator(failing_malloc, free, realloc);
+
+	(void) rfc822_next_header(msg, NULL);
+
+	ok(0, "Didn't get SIGABRT");
+
+	rfc822_free(msg);
+	talloc_free(buf);
+
+	exit(0);
+}
diff --git a/ccan/rfc822/test/run-bad-header.c b/ccan/rfc822/test/run-bad-header.c
new file mode 100644
index 0000000..1a7f01b
--- /dev/null
+++ b/ccan/rfc822/test/run-bad-header.c
@@ -0,0 +1,82 @@
+#include <ccan/foreach/foreach.h>
+#include <ccan/failtest/failtest_override.h>
+#include <ccan/failtest/failtest.h>
+#include <ccan/tap/tap.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CCAN_RFC822_DEBUG
+
+#include <ccan/rfc822/rfc822.h>
+
+#include <ccan/rfc822/rfc822.c>
+
+#include "testdata.h"
+
+#define BAD_HEADER_STR "This is a bad header\n"
+const char bad_header[] = 
+	"Date: Tue, 22 Feb 2011 00:15:59 +1100\n"
+	BAD_HEADER_STR
+	"From: Mister From <from at example.com>\n"
+	"To: Mizz To <to at example.org>\n"
+	"Subject: Some subject\n"
+	"Message-ID: <20110221131559.GA28327 at example>\n";
+
+/* We want to test talloc failure paths. */
+static void *my_malloc(size_t size)
+{
+	return malloc(size);
+}
+
+static void my_free(void *ptr)
+{
+	free(ptr);
+}
+
+static void *my_realloc(void *ptr, size_t size)
+{
+	return realloc(ptr, size);
+}
+
+static void failtest_abort(const char *s)
+{
+	fprintf(stderr, "Allocation failure: %s", s);
+	exit(0);
+}
+
+static void test_bad_header(const char *buf, size_t len)
+{
+	struct rfc822_msg *msg;
+	const struct rfc822_header *hdr;
+	struct bytestring hfull;
+
+	plan_tests(3);
+
+	msg = rfc822_start(NULL, buf, len);
+	if (!msg)
+		return;
+
+	hdr = rfc822_first_header(msg);
+	ok(hdr && rfc822_header_valid(hdr), "First header valid");
+
+	hdr = rfc822_next_header(msg, hdr);
+	ok(hdr && !rfc822_header_valid(hdr), "Second header invalid");
+
+	hfull = rfc822_header_full(hdr);
+	ok(bytestring_eq(hfull, BYTESTRING(BAD_HEADER_STR)),
+		"Invalid header content");
+
+	rfc822_free(msg);
+}
+
+int main(int argc, char *argv[])
+{
+	failtest_init(argc, argv);
+	rfc822_set_allocation_failure_handler(failtest_abort);
+	talloc_set_allocator(my_malloc, my_free, my_realloc);
+
+	test_bad_header(bad_header, sizeof(bad_header));
+
+	/* This exits depending on whether all tests passed */
+	failtest_exit(exit_status());
+}
diff --git a/ccan/rfc822/test/run-hdr-and-body.c b/ccan/rfc822/test/run-hdr-and-body.c
new file mode 100644
index 0000000..316044f
--- /dev/null
+++ b/ccan/rfc822/test/run-hdr-and-body.c
@@ -0,0 +1,190 @@
+#include <ccan/foreach/foreach.h>
+#include <ccan/failtest/failtest_override.h>
+#include <ccan/failtest/failtest.h>
+#include <ccan/tap/tap.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CCAN_RFC822_DEBUG
+
+#include <ccan/rfc822/rfc822.h>
+
+#include <ccan/rfc822/rfc822.c>
+
+#include "testdata.h"
+
+/* We want to test talloc failure paths. */
+static void *my_malloc(size_t size)
+{
+	return malloc(size);
+}
+
+static void my_free(void *ptr)
+{
+	free(ptr);
+}
+
+static void *my_realloc(void *ptr, size_t size)
+{
+	return realloc(ptr, size);
+}
+
+static void failtest_abort(const char *s)
+{
+	fprintf(stderr, "Allocation failure: %s", s);
+	exit(0);
+}
+
+#define CHECK_HEADERS(_e, _msg, _h, _n, _crlf)	\
+	do { \
+		int _i; \
+		for (_i = 0; _i < (_e)->nhdrs; _i++) {	\
+			(_h) = rfc822_next_header((_msg), (_h)); \
+			ok((_h), "header %d exists %s", _i, (_n)); \
+			if (!(_h)) \
+				break; \
+			check_header((_h), (_e)->hdrs[_i].name, \
+				     (_e)->hdrs[_i].val, crlf); \
+		} \
+	} while (0)
+
+static void check_header(const struct rfc822_header *h,
+			 const char *name, const char *val,
+			 int crlf)
+{
+	struct bytestring hname, hvalue, hfull;
+	size_t namelen = strlen(name);
+	size_t valuelen = strlen(val);
+	size_t fulllen = namelen + valuelen + 1 + (crlf ? 2 : 1);
+
+	ok(rfc822_header_valid(h), "Header valid");
+
+	hname = rfc822_header_name(h);
+	ok(hname.ptr && bytestring_eq(hname, bytestring_from_string(name)),
+	   "Header name \"%.*s\"", hname.len, hname.ptr);
+
+	hvalue = rfc822_header_value(h);
+	ok(hvalue.ptr && bytestring_eq(hvalue, bytestring_from_string(val)),
+	   "Header value \"%.*s\"", hvalue.len, hvalue.ptr);
+
+	hfull = rfc822_header_full(h);
+	ok(hfull.ptr && (fulllen == hfull.len)
+	   && (memcmp(name, hfull.ptr, namelen) == 0)
+	   && (hfull.ptr[namelen] == ':')
+	   && (memcmp(val, hfull.ptr + namelen + 1, valuelen) == 0)
+	   && (!crlf || (hfull.ptr[fulllen-2] == '\r'))
+	   && (hfull.ptr[fulllen-1] == '\n'),
+	   "Full header");
+}
+
+static void test_bodyhdr(const struct aexample *e, const char *buf, size_t len,
+			 const char *exname, int crlf)
+{
+	struct rfc822_msg *msg;
+	const struct rfc822_header *h = NULL;
+	struct bytestring body;
+
+	msg = rfc822_start(NULL, buf, len);
+	ok(msg, "opened %s", exname);
+	if (!msg)
+		failtest_abort("test_bodyhdr");
+
+	body = rfc822_body(msg);
+	ok(bytestring_eq(body, bytestring_from_string(e->body)),
+	   "body content %s", exname);
+
+	CHECK_HEADERS(e, msg, h, exname, crlf);
+	h = rfc822_next_header(msg, h);
+	ok(!h, "Too many headers for %s", exname);
+
+	rfc822_free(msg);
+}
+
+static void test_hdrbody(const struct aexample *e, const char *buf, size_t len,
+			 const char *exname, int crlf)
+{
+	struct rfc822_msg *msg;
+	const struct rfc822_header *h = NULL;
+	struct bytestring body;
+
+	msg = rfc822_start(NULL, buf, len);
+	ok(msg, "opened %s", exname);
+	if (!msg)
+		failtest_abort("test_hdrbody");
+
+	CHECK_HEADERS(e, msg, h, exname, crlf);
+	h = rfc822_next_header(msg, h);
+	ok(!h, "Too many headers for %s", exname);
+
+	body = rfc822_body(msg);
+	ok(bytestring_eq(body, bytestring_from_string(e->body)),
+	   "body content %s", exname);
+
+	rfc822_free(msg);
+}
+
+static void test_hdrhdr(const struct aexample *e, const char *buf, size_t len,
+			const char *exname, int crlf)
+{
+	struct rfc822_msg *msg;
+	const struct rfc822_header *h;
+
+	msg = rfc822_start(NULL, buf, len);
+	ok(msg, "opened %s", exname);
+	if (!msg)
+		failtest_abort("test_hdrhdr");
+
+	h = NULL;
+	CHECK_HEADERS(e, msg, h, exname, crlf);
+
+	h = rfc822_next_header(msg, h);
+	ok(!h, "Too many headers for %s", exname);
+
+	/* And again, this time it should be cached */
+	h = NULL;
+	CHECK_HEADERS(e, msg, h, exname, crlf);
+
+	h = rfc822_next_header(msg, h);
+	ok(!h, "Too many headers for %s", exname);
+
+	rfc822_free(msg);
+}
+
+int main(int argc, char *argv[])
+{
+	struct aexample *e;
+
+	failtest_init(argc, argv);
+	rfc822_set_allocation_failure_handler(failtest_abort);
+
+	/* This is how many tests you plan to run */
+	plan_tests(20*num_aexamples() + 40*num_aexample_hdrs());
+
+	talloc_set_allocator(my_malloc, my_free, my_realloc);
+
+	for_each_aexample(e) {
+		int crlf;
+
+		foreach_int(crlf, 0, 1) {
+			const char *buf;
+			size_t len;
+			char exname[256];
+
+			sprintf(exname, "%s[%s]", e->name, NLT(crlf));
+
+			buf = assemble_msg(e, &len, crlf);
+			ok((buf), "assembled %s", exname);
+			if (!buf)
+				continue;
+
+			test_bodyhdr(e, buf, len, exname, crlf);
+			test_hdrbody(e, buf, len, exname, crlf);
+			test_hdrhdr(e, buf, len, exname, crlf);
+
+			talloc_free(buf);
+		}
+	}
+
+	/* This exits depending on whether all tests passed */
+	failtest_exit(exit_status());
+}
diff --git a/ccan/rfc822/test/run-no-body.c b/ccan/rfc822/test/run-no-body.c
new file mode 100644
index 0000000..d7c65e4
--- /dev/null
+++ b/ccan/rfc822/test/run-no-body.c
@@ -0,0 +1,107 @@
+#include <ccan/foreach/foreach.h>
+#include <ccan/failtest/failtest_override.h>
+#include <ccan/failtest/failtest.h>
+#include <ccan/tap/tap.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CCAN_RFC822_DEBUG
+
+#include <ccan/rfc822/rfc822.h>
+
+#include <ccan/rfc822/rfc822.c>
+
+#include "testdata.h"
+
+const char no_body[] = 
+	"Date: Tue, 22 Feb 2011 00:15:59 +1100\n"
+	"From: Mister From <from at example.com>\n"
+	"To: Mizz To <to at example.org>\n"
+	"Subject: Some subject\n"
+	"Message-ID: <20110221131559.GA28327 at example>\n";
+
+const char truncated[] = 
+	"Date: Tue, 22 Feb 2011 00:15:59 +1100\n"
+	"From: Mister From <from at example.com>\n"
+	"To: Mizz To <to@";
+
+/* We want to test talloc failure paths. */
+static void *my_malloc(size_t size)
+{
+	return malloc(size);
+}
+
+static void my_free(void *ptr)
+{
+	free(ptr);
+}
+
+static void *my_realloc(void *ptr, size_t size)
+{
+	return realloc(ptr, size);
+}
+
+static void failtest_abort(const char *s)
+{
+	fprintf(stderr, "Allocation failure: %s", s);
+	exit(0);
+}
+
+static int test_no_body(const char *buf, size_t len)
+{
+	struct rfc822_msg *msg;
+	struct bytestring body;
+	int ok = 1;
+
+	msg = rfc822_start(NULL, buf, len);
+	if (!msg)
+		return 0;
+
+	body = rfc822_body(msg);
+	if (body.ptr)
+		ok = 0;
+
+	rfc822_free(msg);
+	return ok;
+}
+
+static int test_truncated(const char *buf, size_t len)
+{
+	struct rfc822_msg *msg;
+	const struct rfc822_header *h = NULL;
+	struct bytestring body;
+	int ok = 1;
+
+	msg = rfc822_start(NULL, buf, len);
+	if (!msg)
+		return 0;
+
+	do {
+		h = rfc822_next_header(msg, h);
+	} while (h);
+
+	body = rfc822_body(msg);
+	if (body.ptr)
+		ok = 0;
+
+	rfc822_free(msg);
+	return ok;
+}
+
+int main(int argc, char *argv[])
+{
+	failtest_init(argc, argv);
+	rfc822_set_allocation_failure_handler(failtest_abort);
+
+	/* This is how many tests you plan to run */
+	plan_tests(3);
+
+	talloc_set_allocator(my_malloc, my_free, my_realloc);
+
+	ok1(test_no_body(no_body, sizeof(no_body)));
+	ok1(test_no_body(truncated, sizeof(truncated)));
+	ok1(test_truncated(truncated, sizeof(truncated)));
+
+	/* This exits depending on whether all tests passed */
+	failtest_exit(exit_status());
+}
diff --git a/ccan/rfc822/test/run-testdata.c b/ccan/rfc822/test/run-testdata.c
new file mode 100644
index 0000000..883bf65
--- /dev/null
+++ b/ccan/rfc822/test/run-testdata.c
@@ -0,0 +1,45 @@
+#include <stdlib.h>
+#include <string.h>
+#include <ccan/tap/tap.h>
+#include <ccan/array_size/array_size.h>
+
+#define CCAN_RFC822_DEBUG
+
+#include <ccan/rfc822/rfc822.h>
+
+#include "testdata.h"
+
+/* Test some of the test infrastructure */
+
+static const char test_msg_1_cmp[] = 
+	"Date: Tue, 22 Feb 2011 00:15:59 +1100\n"
+	"From: Mister From <from at example.com>\n"
+	"To: Mizz To <to at example.org>\n"
+	"Subject: Some subject\n"
+	"Message-ID: <20110221131559.GA28327 at example>\n"
+	"MIME-Version: 1.0\n"
+	"Content-Type: text/plain; charset=us-ascii\n"
+	"Content-Disposition: inline\n"
+	"\n"
+	"Test message\n";
+
+static void test_assemble(const struct aexample *e, int crlf,
+			  const char *cmp, size_t cmplen)
+{
+	const char *msg;
+	size_t len;
+
+	msg = assemble_msg(e, &len, crlf);
+	ok1(msg != NULL);
+	ok1(len == cmplen);
+	ok1(memcmp(msg, cmp, cmplen) == 0);
+	talloc_free(msg);
+}
+
+int main(int argc, char *argv[])
+{
+	plan_tests(2);
+
+	test_assemble(&test_msg_1, 0, test_msg_1_cmp, sizeof(test_msg_1_cmp));
+	exit(0);
+}
diff --git a/ccan/rfc822/test/testdata.h b/ccan/rfc822/test/testdata.h
new file mode 100644
index 0000000..a4f42f0
--- /dev/null
+++ b/ccan/rfc822/test/testdata.h
@@ -0,0 +1,105 @@
+#ifndef RFC822_TESTDATA_H
+#define RFC822_TESTDATA_H
+
+#include <ccan/talloc/talloc.h>
+#include <ccan/array_size/array_size.h>
+#include <ccan/foreach/foreach.h>
+
+struct testhdr {
+	const char *name, *val;
+};
+
+struct aexample {
+	const char *name;
+	struct testhdr *hdrs;
+	size_t nhdrs;
+	const char *body;
+};
+
+#define AEXAMPLE(s)				\
+	struct aexample s = {		\
+		.name = #s,			\
+		.hdrs = s##_hdrs,		\
+		.nhdrs = ARRAY_SIZE(s##_hdrs),	\
+		.body = s##_body,		\
+	};
+
+struct testhdr test_msg_1_hdrs[] = {
+	{"Date", "Tue, 22 Feb 2011 00:15:59 +1100"},
+	{"From", "Mister From <from at example.com>"},
+	{"To", "Mizz To <to at example.org>"},
+	{"Subject", "Some subject"},
+	{"Message-ID", "<20110221131559.GA28327 at example>"},
+	{"MIME-Version", "1.0"},
+	{"Content-Type", "text/plain; charset=us-ascii"},
+	{"Content-Disposition", "inline"},
+};
+const char test_msg_1_body[] = "Test message\n";
+AEXAMPLE(test_msg_1);
+
+#define for_each_aexample(_e) \
+	foreach_ptr((_e), &test_msg_1)
+
+#define for_each_aexample_buf(_e, _buf, _len)	\
+	for_each_aexample((_e)) 		\
+	if (((_buf) = assemble_msg((_e), &(_len))) != NULL)
+
+static inline int num_aexamples(void)
+{
+	const struct aexample *e;
+	int n = 0;
+
+	for_each_aexample(e)
+		n++;
+
+	return n;
+}
+
+static inline int num_aexample_hdrs(void)
+{
+	const struct aexample *e;
+	int n = 0;
+
+	for_each_aexample(e)
+		n += e->nhdrs;
+
+	return n;
+}
+
+static inline const char *assemble_msg(const struct aexample *e,
+				       size_t *len, int crlf)
+{
+	const char *nl = crlf ? "\r\n" : "\n";
+	int nln = crlf ? 2 : 1;
+	char *msg, *amsg;
+	size_t n = 0;
+	int i;
+
+	msg = talloc_strdup(NULL, "");
+	if (!msg)
+		return NULL;
+
+	for (i = 0; i < e->nhdrs; i++) {
+		amsg = talloc_asprintf_append(msg, "%s:%s%s", e->hdrs[i].name,
+					      e->hdrs[i].val, nl);
+		if (!amsg) {
+			talloc_free(msg);
+			return NULL;
+		}
+		msg = amsg;
+		n += strlen(e->hdrs[i].name) + strlen(e->hdrs[i].val) + 1 + nln;
+	}
+	amsg = talloc_asprintf_append(msg, "%s%s", nl, e->body);
+	if (!amsg) {
+		talloc_free(msg);
+		return NULL;
+	}
+	msg = amsg;
+	n += strlen(e->body) + nln;
+	*len = n;
+	return msg;
+}
+
+#define NLT(crlf)	((crlf) ? "CRLF" : "LF")
+
+#endif /* RFC822_TESTDATA_H */
-- 
1.7.10


-- 
David Gibson			| I'll have my music baroque, and my code
david AT gibson.dropbear.id.au	| minimalist, thank you.  NOT _the_ _other_
				| _way_ _around_!
http://www.ozlabs.org/~dgibson


More information about the ccan mailing list