From de28abde1337e9255812ba720ffeef2c83a25dde Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 26 Jun 2012 16:29:31 +0930 Subject: [PATCH 1/1] rfc822: new module. --- ccan/rfc822/LICENSE | 1 + ccan/rfc822/_info | 54 +++ ccan/rfc822/rfc822.c | 348 +++++++++++++++++++ ccan/rfc822/rfc822.h | 171 +++++++++ ccan/rfc822/test/helper.c | 58 ++++ ccan/rfc822/test/helper.h | 2 + ccan/rfc822/test/run-bad-header-name.c | 95 +++++ ccan/rfc822/test/run-bad-header.c | 68 ++++ ccan/rfc822/test/run-check-check.c | 53 +++ ccan/rfc822/test/run-default-alloc-failure.c | 56 +++ ccan/rfc822/test/run-hdr-and-body.c | 185 ++++++++++ ccan/rfc822/test/run-no-body.c | 86 +++++ ccan/rfc822/test/run-testdata.c | 51 +++ ccan/rfc822/test/run-unfold.c | 121 +++++++ ccan/rfc822/test/testdata.h | 124 +++++++ 15 files changed, 1473 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/helper.c create mode 100644 ccan/rfc822/test/helper.h create mode 100644 ccan/rfc822/test/run-bad-header-name.c create mode 100644 ccan/rfc822/test/run-bad-header.c create mode 100644 ccan/rfc822/test/run-check-check.c create mode 100644 ccan/rfc822/test/run-default-alloc-failure.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/run-unfold.c create mode 100644 ccan/rfc822/test/testdata.h diff --git a/ccan/rfc822/LICENSE b/ccan/rfc822/LICENSE new file mode 120000 index 00000000..dc314eca --- /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 00000000..560812d4 --- /dev/null +++ b/ccan/rfc822/_info @@ -0,0 +1,54 @@ +#include "config.h" +#include +#include + +/** + * 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 + * message. Just reading messages into the system and 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: \n" + * "To: \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 (v2.1 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 00000000..3bd3dd74 --- /dev/null +++ b/ccan/rfc822/rfc822.c @@ -0,0 +1,348 @@ +/* Licensed under LGPLv2.1+ - see LICENSE file for details */ + +#include "config.h" + +#include + +#include +#include +#include + +#include + +#if !HAVE_MEMMEM +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 (void *)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 all, rawname, rawvalue; + struct bytestring unfolded; + struct list_node list; +}; + +struct rfc822_msg *rfc822_check(const struct rfc822_msg *msg, + const char *abortstr) +{ + assert(msg); + if (!list_check(&msg->headers, abortstr)) + return NULL; + return (struct rfc822_msg *)msg; +} + +#ifdef CCAN_RFC822_DEBUG +#define CHECK(msg, str) do { rfc822_check((msg), (str)); } while (0) +#else +#define CHECK(msg, str) 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); + ALLOC_CHECK(msg, NULL); + + msg->data = p; + msg->end = p + len; + + msg->remainder = msg->data; + msg->body = NULL; + + list_head_init(&msg->headers); + + CHECK(msg, "rfc822_free"); + talloc_free(msg); +} + +static struct rfc822_header *next_header_cached(struct rfc822_msg *msg, + struct rfc822_header *hdr) +{ + struct list_node *h = &msg->headers.n; + const struct list_node *n = h; + + CHECK(msg, ">next_header_cached"); + + if (hdr) + n = &hdr->list; + + if (n->next == h) + return NULL; + + CHECK(msg, "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 struct rfc822_header *next_header_parse(struct rfc822_msg *msg) +{ + const char *h, *eh, *ev, *colon; + struct rfc822_header *hi; + + CHECK(msg, ">next_header_parse"); + + 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) && rfc822_iswsp(*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_zero(msg, struct rfc822_header); + ALLOC_CHECK(hi, NULL); + + hi->all = bytestring(h, eh - h); + list_add_tail(&msg->headers, &hi->list); + + colon = memchr(h, ':', hi->all.len); + if (colon) { + hi->rawname = bytestring(h, colon - h); + hi->rawvalue = bytestring(colon + 1, eh - colon - 1); + } else { + hi->rawname = bytestring_NULL; + hi->rawvalue = bytestring_NULL; + } + + CHECK(msg, "rfc822_next_header"); + + h = next_header_cached(msg, hdr); + if (h) + return h; + + return next_header_parse(msg); +} + +struct bytestring rfc822_body(struct rfc822_msg *msg) +{ + CHECK(msg, ">rfc822_body"); + + if (!msg->body && msg->remainder) { + const char *p, *q; + + p = memmem(msg->remainder, msg->end - msg->remainder, + "\n\r\n", 3); + q = memmem(msg->remainder, msg->end - msg->remainder, + "\n\n", 2); + + if (p && (!q || (p < q))) + msg->body = p + 3; + else if (q && (!p || (q < p))) + msg->body = q + 2; + + if (msg->body >= msg->end) { + assert(msg->body == msg->end); + msg->body = NULL; + } + } + + CHECK(msg, "body) + return bytestring(msg->body, msg->end - msg->body); + else + return bytestring_NULL; +} + +enum rfc822_header_errors rfc822_header_errors(struct rfc822_msg *msg, + struct rfc822_header *hdr) +{ + enum rfc822_header_errors err = 0; + int i; + + if (!hdr->rawname.ptr) { + err |= RFC822_HDR_NO_COLON; + } else { + for (i = 0; i < hdr->rawname.len; i++) { + char c = hdr->rawname.ptr[i]; + + assert(c != ':'); + + if ((c < 33) || (c > 126)) { + err |= RFC822_HDR_BAD_NAME; + break; + } + } + } + return err; +} + +struct bytestring rfc822_header_raw_content(struct rfc822_msg *msg, + struct rfc822_header *hdr) +{ + return hdr->all; +} + +struct bytestring rfc822_header_raw_name(struct rfc822_msg *msg, + struct rfc822_header *hdr) +{ + return hdr->rawname; +} + +struct bytestring rfc822_header_raw_value(struct rfc822_msg *msg, + struct rfc822_header *hdr) +{ + return hdr->rawvalue; +} + +static void get_line(struct bytestring in, struct bytestring *first, + struct bytestring *rest) +{ + size_t rawlen, trimlen; + const char *inp = in.ptr; + const char *nl; + + nl = memchr(inp, '\n', in.len); + if (!nl) + rawlen = in.len; + else + rawlen = nl - inp + 1; + + trimlen = rawlen; + if ((trimlen > 0) && (inp[trimlen-1] == '\n')) { + trimlen--; + if ((trimlen > 0) && (inp[trimlen-1] == '\r')) + trimlen--; + } + + *first = bytestring(in.ptr, trimlen); + + if (rawlen < in.len) + *rest = bytestring(in.ptr + rawlen, in.len - rawlen); + else + *rest = bytestring_NULL; +} + + +struct bytestring rfc822_header_unfolded_value(struct rfc822_msg *msg, + struct rfc822_header *hdr) +{ + struct bytestring raw = rfc822_header_raw_value(msg, hdr); + struct bytestring next, rest; + int lines = 0; + size_t len = 0; + + if (!hdr->unfolded.ptr) { + rest = raw; + while (rest.ptr) { + get_line(rest, &next, &rest); + lines++; + len += next.len; + } + + if (lines <= 1) { + hdr->unfolded = bytestring(raw.ptr, len); + } else { + char *unfold = talloc_array(msg, char, len); + char *p = unfold; + + ALLOC_CHECK(unfold, bytestring_NULL); + + rest = raw; + while (rest.ptr) { + get_line(rest, &next, &rest); + memcpy(p, next.ptr, next.len); + p += next.len; + } + + assert(p == (unfold + len)); + hdr->unfolded = bytestring(unfold, len); + } + } + + return hdr->unfolded; +} diff --git a/ccan/rfc822/rfc822.h b/ccan/rfc822/rfc822.h new file mode 100644 index 00000000..2beae410 --- /dev/null +++ b/ccan/rfc822/rfc822.h @@ -0,0 +1,171 @@ +/* Licensed under LGPLv2.1+ - see LICENSE file for details */ +#ifndef CCAN_RFC822_H_ +#define CCAN_RFC822_H_ + +#include + +#include + +/* #define CCAN_RFC822_DEBUG 1 */ + +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: + * static void my_handler(const char *str) + * { + * fprintf(stderr, "Allocation failure: %s\n", str); + * exit(1); + * } + * + * static void do_rfc822_stuff(void *buf, size_t len) + * { + * rfc822_set_allocation_failure_handler(&my_handler); + * rfc822_start(NULL, buf, len); + * } + */ +void rfc822_set_allocation_failure_handler(void (*h)(const char *)); + + +static inline bool rfc822_iswsp(char c) +{ + return (c == ' ') || (c == '\t'); +} + +/** + * 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 @abortstr is + * non-NULL, that will be printed in a diagnostic if the state is + * inconsistent, and the function will abort. If the state of the + * structure is valid it returns it unchanged. + * + * Returns the list head if the list is consistent, NULL if not (it + * can never return NULL if @abortstr is set). + */ +struct rfc822_msg *rfc822_check(const struct rfc822_msg *msg, + const char *abortstr); + +/** + * 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 - free an rfc822 message + * @msg: message to free + * + * Frees an rfc822_msg context, including all subsiduary data + * structures. + */ +void rfc822_free(struct rfc822_msg *msg); + +/** + * rfc822_first_header - retrieve the first header of an rfc822 message + * @msg: message + * + * Finds the first header field of @msg and returns a struct + * rfc822_header pointer representing it. + */ +#define rfc822_first_header(msg) (rfc822_next_header((msg), NULL)) + +/** + * rfc822_next_header - retrieve the next header of an rfc822 message + * @msg: message + * @hdr: current header field + * + * Finds the header field of @msg which immediately follows @hdr and + * returns a struct rfc822_header pointer for it. If @hdr is NULL, + * returns the first header in the message. + */ +struct rfc822_header *rfc822_next_header(struct rfc822_msg *msg, + struct rfc822_header *hdr); + +#define rfc822_for_each_header(msg, hdr) \ + for ((hdr) = rfc822_first_header((msg)); \ + (hdr); \ + (hdr) = rfc822_next_header((msg), (hdr))) + +/** + * rfc822_body - retrieve the body of an rfc822 message + * @msg: message + * + * Finds the body of @msg and returns a bytestring containing its + * contents. + */ +struct bytestring rfc822_body(struct rfc822_msg *msg); + +enum rfc822_header_errors { + RFC822_HDR_NO_COLON = 1, + RFC822_HDR_BAD_NAME = 2, +}; + +enum rfc822_header_errors rfc822_header_errors(struct rfc822_msg *msg, + struct rfc822_header *hdr); + +/** + * rfc822_header_raw_content - retrieve the raw content of an rfc822 header + * @hdr: a header handle + * + * This returns a bytestring containing the complete contents (name + * and value) of @hdr. This will work even if the header is badly + * formatted and cannot otherwise be parsed. + */ +struct bytestring rfc822_header_raw_content(struct rfc822_msg *msg, + struct rfc822_header *hdr); + + +/** + * rfc822_header_raw_name - retrieve the name of an rfc822 header + * @hdr: a header handle + * + * This returns a bytestring containing the header name of @hdr. This + * could include any invalid characters, in the case of a badly + * formatted header. + */ +struct bytestring rfc822_header_raw_name(struct rfc822_msg *msg, + struct rfc822_header *hdr); + +/** + * rfc822_header_raw_value - retrieve the unprocessed value of an rfc822 header + * @hdr: a header handle + * + * This returns a bytestring containing the complete contents of + * @hdr's value. This includes the terminating and any internal + * (folded) newlines. + */ +struct bytestring rfc822_header_raw_value(struct rfc822_msg *msg, + struct rfc822_header *hdr); + +/** + * rfc822_header_unfolded_value - retrieve the unfolded value of an rfc822 header + * @hdr: a header handle + * + * This returns a bytestring containing the unfolded contents of + * @hdr's value. That is, the header value with any internal and the + * terminating newline removed. + */ +struct bytestring rfc822_header_unfolded_value(struct rfc822_msg *msg, + struct rfc822_header *hdr); + +#endif /* CCAN_RFC822_H_ */ diff --git a/ccan/rfc822/test/helper.c b/ccan/rfc822/test/helper.c new file mode 100644 index 00000000..36a2a065 --- /dev/null +++ b/ccan/rfc822/test/helper.c @@ -0,0 +1,58 @@ +#include +#include + +#include +#include +#include + +#include + +#include "helper.h" + +/* failtest limitations mean we need these wrappers to test talloc + * failure paths. */ +static void *malloc_wrapper(size_t size) +{ + return malloc(size); +} + +static void free_wrapper(void *ptr) +{ + free(ptr); +} + +static void *realloc_wrapper(void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +#if 0 +static void allocation_failure_exit(const char *s) +{ + fprintf(stderr, "Allocation failure: %s", s); + exit(0); +} +#endif + +static bool allocation_failed = false; + +static void allocation_failure_continue(const char *s) +{ + fprintf(stderr, "Allocation failure: %s", s); + allocation_failed = true; +} + +void allocation_failure_check(void) +{ + if (allocation_failed) { + fprintf(stderr, "Exiting due to earlier failed allocation\n"); + exit(0); + } +} + +void failtest_setup(int argc, char *argv[]) +{ + failtest_init(argc, argv); + rfc822_set_allocation_failure_handler(allocation_failure_continue); + talloc_set_allocator(malloc_wrapper, free_wrapper, realloc_wrapper); +} diff --git a/ccan/rfc822/test/helper.h b/ccan/rfc822/test/helper.h new file mode 100644 index 00000000..7e1e67b2 --- /dev/null +++ b/ccan/rfc822/test/helper.h @@ -0,0 +1,2 @@ +void failtest_setup(int argc, char *argv[]); +void allocation_failure_check(void); diff --git a/ccan/rfc822/test/run-bad-header-name.c b/ccan/rfc822/test/run-bad-header-name.c new file mode 100644 index 00000000..7b4eac19 --- /dev/null +++ b/ccan/rfc822/test/run-bad-header-name.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include + +#define CCAN_RFC822_DEBUG + +#include + +#include + +#include "testdata.h" +#include "helper.h" + +#define NAME_TEMPLATE "X-Bad-Header" +#define NAME_TEMPLATE_LEN (strlen(NAME_TEMPLATE)) + +const char bad_name_template[] = + NAME_TEMPLATE ": This is a good header that will become bad\n"; + +static bool bad_name(const char *buf, char c) +{ + struct rfc822_msg *msg; + struct rfc822_header *hdr; + enum rfc822_header_errors errs; + struct bytestring hname; + + msg = rfc822_start(NULL, buf, strlen(buf)); + + allocation_failure_check(); + + hdr = rfc822_first_header(msg); + ok1(hdr); + + allocation_failure_check(); + + errs = rfc822_header_errors(msg, hdr); + + allocation_failure_check(); + + ok1(!(errs & ~RFC822_HDR_BAD_NAME)); + + /* Check raw_name still works properly */ + hname = rfc822_header_raw_name(msg, hdr); + + allocation_failure_check(); + + ok1(hname.ptr && hname.len == NAME_TEMPLATE_LEN); + ok1((hname.ptr[0] == c) && + (memcmp(hname.ptr + 1, NAME_TEMPLATE + 1, NAME_TEMPLATE_LEN - 1) == 0)); + + rfc822_free(msg); + + allocation_failure_check(); + + return !!(errs & RFC822_HDR_BAD_NAME); +} + +int main(int argc, char *argv[]) +{ + char c; + + failtest_setup(argc, argv); + + plan_tests(5 * (1 + 3 + 4)); + + ok1(!bad_name(bad_name_template, bad_name_template[0])); + + /* Good characters */ + foreach_int(c, 'a', 'Z', '3') { + char *tmp = strdup(bad_name_template); + + tmp[0] = c; + + ok1(!bad_name(tmp, c)); + + free(tmp); + } + + /* Bad characters */ + foreach_int(c, ' ', '\t', '\b', '\x80') { + char *tmp = strdup(bad_name_template); + + tmp[0] = c; + + ok1(bad_name(tmp, c)); + + free(tmp); + } + + /* This exits depending on whether all tests passed */ + failtest_exit(exit_status()); +} diff --git a/ccan/rfc822/test/run-bad-header.c b/ccan/rfc822/test/run-bad-header.c new file mode 100644 index 00000000..27d76ec3 --- /dev/null +++ b/ccan/rfc822/test/run-bad-header.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include + +#define CCAN_RFC822_DEBUG + +#include + +#include + +#include "testdata.h" +#include "helper.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 \n" + "To: Mizz To \n" + "Subject: Some subject\n" + "Message-ID: <20110221131559.GA28327@example>\n"; + +static void test_bad_header(const char *buf, size_t len) +{ + struct rfc822_msg *msg; + struct rfc822_header *hdr; + struct bytestring hfull; + + plan_tests(3); + + msg = rfc822_start(NULL, buf, len); + + allocation_failure_check(); + + hdr = rfc822_first_header(msg); + allocation_failure_check(); + + ok(hdr && (rfc822_header_errors(msg, hdr) == 0), "First header valid"); + allocation_failure_check(); + + hdr = rfc822_next_header(msg, hdr); + allocation_failure_check(); + + ok(hdr && (rfc822_header_errors(msg, hdr) == RFC822_HDR_NO_COLON), + "Second header invalid"); + + hfull = rfc822_header_raw_content(msg, hdr); + allocation_failure_check(); + + ok(bytestring_eq(hfull, BYTESTRING(BAD_HEADER_STR)), + "Invalid header content"); + + rfc822_free(msg); + allocation_failure_check(); +} + +int main(int argc, char *argv[]) +{ + failtest_setup(argc, argv); + + 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-check-check.c b/ccan/rfc822/test/run-check-check.c new file mode 100644 index 00000000..6ef7a106 --- /dev/null +++ b/ccan/rfc822/test/run-check-check.c @@ -0,0 +1,53 @@ +#include +#include +#include + +#include +#include + +#define CCAN_RFC822_DEBUG + +#include + +#include + +#include "testdata.h" + +static void mangle_list(struct rfc822_msg *msg) +{ + msg->headers.n.prev = NULL; +} + +int main(int argc, char *argv[]) +{ + void (*mangler)(struct rfc822_msg *msg); + + plan_tests(3 * 1); + + foreach_ptr(mangler, mangle_list) { + const char *buf; + size_t len; + struct rfc822_msg *msg, *check; + + buf = assemble_msg(&test_msg_1, &len, 0); + + msg = rfc822_start(NULL, buf, len); + fprintf(stderr, "msg = %p\n", msg); + + ok1(msg != NULL); + + (void) rfc822_next_header(msg, NULL); + + check = rfc822_check(msg, NULL); + fprintf(stderr, "check = %p (1)\n", check); + ok1(check == msg); + + mangler(msg); + + check = rfc822_check(msg, NULL); + fprintf(stderr, "check = %p (2)\n", check); + ok1(check == NULL); + } + + exit(exit_status()); +} diff --git a/ccan/rfc822/test/run-default-alloc-failure.c b/ccan/rfc822/test/run-default-alloc-failure.c new file mode 100644 index 00000000..7fc0393e --- /dev/null +++ b/ccan/rfc822/test/run-default-alloc-failure.c @@ -0,0 +1,56 @@ +#include +#include +#include + +#include +#include + +#define CCAN_RFC822_DEBUG + +#include + +#include + +#include "testdata.h" + +static void *failing_malloc(size_t size) +{ + return NULL; +} + +static void abort_handler(int signum) +{ + ok(1, "Aborted"); + 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(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 00000000..d3f563ff --- /dev/null +++ b/ccan/rfc822/test/run-hdr-and-body.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include + +#define CCAN_RFC822_DEBUG + +#include + +#include + +#include "testdata.h" +#include "helper.h" + +#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((_msg), (_h), (_e)->hdrs[_i].name, \ + (_e)->hdrs[_i].val, crlf); \ + } \ + } while (0) + +static void check_header(struct rfc822_msg *msg, + 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 nln = crlf ? 2 : 1; + size_t fulllen = namelen + valuelen + 1 + nln; + + ok(rfc822_header_errors(msg, h) == 0, "Header valid"); + allocation_failure_check(); + + hname = rfc822_header_raw_name(msg, h); + allocation_failure_check(); + + ok(hname.ptr && bytestring_eq(hname, bytestring_from_string(name)), + "Header name \"%.*s\"", hname.len, hname.ptr); + + hvalue = rfc822_header_raw_value(msg, h); + allocation_failure_check(); + + ok(hvalue.ptr && ((valuelen + nln) == hvalue.len) + && (memcmp(val, hvalue.ptr, valuelen) == 0) + && (!crlf || (hvalue.ptr[hvalue.len - 2] == '\r')) + && (hvalue.ptr[hvalue.len - 1] == '\n'), + "Header value"); + + hfull = rfc822_header_raw_content(msg, h); + allocation_failure_check(); + + 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; + struct rfc822_header *h = NULL; + struct bytestring body; + + msg = rfc822_start(NULL, buf, len); + allocation_failure_check(); + + ok(msg, "opened %s", exname); + + body = rfc822_body(msg); + allocation_failure_check(); + 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); + allocation_failure_check(); + ok(!h, "Too many headers for %s", exname); + + rfc822_free(msg); + allocation_failure_check(); +} + +static void test_hdrbody(const struct aexample *e, const char *buf, size_t len, + const char *exname, int crlf) +{ + struct rfc822_msg *msg; + struct rfc822_header *h = NULL; + struct bytestring body; + + msg = rfc822_start(NULL, buf, len); + allocation_failure_check(); + ok(msg, "opened %s", exname); + + CHECK_HEADERS(e, msg, h, exname, crlf); + h = rfc822_next_header(msg, h); + allocation_failure_check(); + ok(!h, "Too many headers for %s", exname); + + body = rfc822_body(msg); + allocation_failure_check(); + ok(bytestring_eq(body, bytestring_from_string(e->body)), + "body content %s", exname); + + rfc822_free(msg); + allocation_failure_check(); +} + +static void test_hdrhdr(const struct aexample *e, const char *buf, size_t len, + const char *exname, int crlf) +{ + struct rfc822_msg *msg; + struct rfc822_header *h; + + msg = rfc822_start(NULL, buf, len); + allocation_failure_check(); + ok(msg, "opened %s", exname); + + h = NULL; + CHECK_HEADERS(e, msg, h, exname, crlf); + + h = rfc822_next_header(msg, h); + allocation_failure_check(); + 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); + allocation_failure_check(); + ok(!h, "Too many headers for %s", exname); + + rfc822_free(msg); + allocation_failure_check(); +} + +int main(int argc, char *argv[]) +{ + struct aexample *e; + + /* This is how many tests you plan to run */ + plan_tests(20*num_aexamples() + 40*num_aexample_hdrs()); + + failtest_setup(argc, argv); + + 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 00000000..54ff4dbc --- /dev/null +++ b/ccan/rfc822/test/run-no-body.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include + +#define CCAN_RFC822_DEBUG + +#include + +#include + +#include "testdata.h" +#include "helper.h" + +const char no_body[] = + "Date: Tue, 22 Feb 2011 00:15:59 +1100\n" + "From: Mister From \n" + "To: Mizz To \n" + "Subject: Some subject\n" + "Message-ID: <20110221131559.GA28327@example>\n"; + +const char truncated[] = + "Date: Tue, 22 Feb 2011 00:15:59 +1100\n" + "From: Mister From \n" + "To: Mizz To + +#include +#include + +#include +#include + +#define CCAN_RFC822_DEBUG + +#include +#include + +#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 \n" + "To:Mizz To \n" + "Subject:Some subject\n" + "Message-ID:<20110221131559.GA28327@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); + fprintf(stderr, "Assembled message %zd bytes (versus %zd bytes)\n", + len, cmplen); + ok1(len == cmplen); + ok1(memcmp(msg, cmp, cmplen) == 0); + talloc_free(msg); +} + +int main(int argc, char *argv[]) +{ + plan_tests(3); + + test_assemble(&test_msg_1, 0, test_msg_1_cmp, sizeof(test_msg_1_cmp) - 1); + exit(exit_status()); +} diff --git a/ccan/rfc822/test/run-unfold.c b/ccan/rfc822/test/run-unfold.c new file mode 100644 index 00000000..b9529269 --- /dev/null +++ b/ccan/rfc822/test/run-unfold.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include +#include + +#define CCAN_RFC822_DEBUG + +#include + +#include + +#include "testdata.h" +#include "helper.h" + +#define UNFOLDED " This is a string with\tlots of \tplaces to fold" +#define FOLD_POINTS 11 + +#define BEFORE "Date: Tue, 22 Feb 2011 00:15:59 +1100\n" \ + "From: Mister From \n" \ + "To: Mizz To \n" \ + "Subject:" + +#define AFTER "Message-ID: <20110221131559.GA28327@example>\n" \ + "\n" \ + "body" + +static struct bytestring fold_and_assemble(int foldat, int crlf, int truncated) +{ + char *buf, *p; + int i, n = 0; + + buf = talloc_array(NULL, char, strlen(BEFORE) + strlen(AFTER) + 3*strlen(UNFOLDED) + 2); + if (!buf) + exit(0); + + memcpy(buf, BEFORE, strlen(BEFORE)); + + p = buf + strlen(BEFORE); + + for (i = 0; i < strlen(UNFOLDED); i++) { + if (rfc822_iswsp(UNFOLDED[i])) { + n++; + if ((foldat == -1) || (foldat == n)) { + if (crlf) + *p++ = '\r'; + *p++ = '\n'; + } + } + *p++ = UNFOLDED[i]; + } + + if (!truncated) { + if (crlf) + *p++ = '\r'; + *p++ = '\n'; + + memcpy(p, AFTER, strlen(AFTER)); + p += strlen(AFTER); + } + + return bytestring(buf, p - buf); +} + +static void check_folded_header(const char *buf, size_t len) +{ + struct rfc822_msg *msg; + struct rfc822_header *hdr; + struct bytestring hunfold; + + msg = rfc822_start(NULL, buf, len); + allocation_failure_check(); + + hdr = rfc822_first_header(msg); + allocation_failure_check(); + hdr = rfc822_next_header(msg, hdr); + allocation_failure_check(); + hdr = rfc822_next_header(msg, hdr); + allocation_failure_check(); + + /* This is the one we care about */ + hdr = rfc822_next_header(msg, hdr); + allocation_failure_check(); + + ok(hdr && (rfc822_header_errors(msg, hdr) == 0), "Folded header valid"); + allocation_failure_check(); + + hunfold = rfc822_header_unfolded_value(msg, hdr); + allocation_failure_check(); + + ok(hunfold.len == strlen(UNFOLDED), "Unfolded length %d, should be %d", + hunfold.len, strlen(UNFOLDED)); + ok1(memcmp(hunfold.ptr, UNFOLDED, hunfold.len) == 0); + + rfc822_free(msg); + allocation_failure_check(); +} + +int main(int argc, char *argv[]) +{ + struct bytestring msgbuf; + int crlf, truncated, i; + + failtest_setup(argc, argv); + + plan_tests(3 * 2 * 2 * (FOLD_POINTS + 2)); + + foreach_int(crlf, 0, 1) { + foreach_int(truncated, 0, 1) { + for (i = -1; i <= FOLD_POINTS; i++) { + msgbuf = fold_and_assemble(i, crlf, truncated); + check_folded_header(msgbuf.ptr, msgbuf.len); + talloc_free(msgbuf.ptr); + } + } + } + + /* This exits depending on whether all tests passed */ + failtest_exit(exit_status()); +} diff --git a/ccan/rfc822/test/testdata.h b/ccan/rfc822/test/testdata.h new file mode 100644 index 00000000..ceb3bcd8 --- /dev/null +++ b/ccan/rfc822/test/testdata.h @@ -0,0 +1,124 @@ +#ifndef RFC822_TESTDATA_H +#define RFC822_TESTDATA_H + +#include +#include +#include + +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 "}, + {"To", "Mizz To "}, + {"Subject", "Some subject"}, + {"Message-ID", "<20110221131559.GA28327@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 test_msg_empty_body_hdrs test_msg_1_hdrs +const char test_msg_empty_body_body[] = ""; +AEXAMPLE(test_msg_empty_body); + +#define test_msg_nlnl_lf_hdrs test_msg_1_hdrs +const char test_msg_nlnl_lf_body[] = "Message containing \n\n inside body\n"; +AEXAMPLE(test_msg_nlnl_lf); + +#define test_msg_nlnl_crlf_hdrs test_msg_1_hdrs +const char test_msg_nlnl_crlf_body[] = "Message containing \r\n\r\n inside body\r\n"; +AEXAMPLE(test_msg_nlnl_crlf); + +#define test_msg_nlnl_mixed_hdrs test_msg_1_hdrs +const char test_msg_nlnl_mixed_body[] = "Message containing both \n\n and \r\n\r\n inside body\n\r\n"; +AEXAMPLE(test_msg_nlnl_mixed); + + +#define for_each_aexample(_e) \ + foreach_ptr((_e), &test_msg_1, &test_msg_empty_body, \ + &test_msg_nlnl_lf, &test_msg_nlnl_crlf, \ + &test_msg_nlnl_mixed) + +#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 */ -- 2.39.2