rfc822: new module.
authorRusty Russell <rusty@rustcorp.com.au>
Tue, 26 Jun 2012 06:59:31 +0000 (16:29 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Tue, 26 Jun 2012 06:59:31 +0000 (16:29 +0930)
15 files changed:
ccan/rfc822/LICENSE [new symlink]
ccan/rfc822/_info [new file with mode: 0644]
ccan/rfc822/rfc822.c [new file with mode: 0644]
ccan/rfc822/rfc822.h [new file with mode: 0644]
ccan/rfc822/test/helper.c [new file with mode: 0644]
ccan/rfc822/test/helper.h [new file with mode: 0644]
ccan/rfc822/test/run-bad-header-name.c [new file with mode: 0644]
ccan/rfc822/test/run-bad-header.c [new file with mode: 0644]
ccan/rfc822/test/run-check-check.c [new file with mode: 0644]
ccan/rfc822/test/run-default-alloc-failure.c [new file with mode: 0644]
ccan/rfc822/test/run-hdr-and-body.c [new file with mode: 0644]
ccan/rfc822/test/run-no-body.c [new file with mode: 0644]
ccan/rfc822/test/run-testdata.c [new file with mode: 0644]
ccan/rfc822/test/run-unfold.c [new file with mode: 0644]
ccan/rfc822/test/testdata.h [new file with mode: 0644]

diff --git a/ccan/rfc822/LICENSE b/ccan/rfc822/LICENSE
new file mode 120000 (symlink)
index 0000000..dc314ec
--- /dev/null
@@ -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 (file)
index 0000000..560812d
--- /dev/null
@@ -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
+ *   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: <from@example.com>\n"
+ *                  "To: <to@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 (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 (file)
index 0000000..3bd3dd7
--- /dev/null
@@ -0,0 +1,348 @@
+/* Licensed under LGPLv2.1+ - see LICENSE file for details */
+
+#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>
+
+#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, "<rfc22_start");
+
+       return msg;
+}
+
+void rfc822_free(struct rfc822_msg *msg)
+{
+       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_header_cached");
+
+       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 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, "<next_header_parse");
+
+       return hi;
+}
+
+struct rfc822_header *rfc822_next_header(struct rfc822_msg *msg,
+                                        struct rfc822_header *hdr)
+{
+       struct rfc822_header *h;
+
+       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, "<rfc822_body");
+
+       if (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 (file)
index 0000000..2beae41
--- /dev/null
@@ -0,0 +1,171 @@
+/* Licensed under LGPLv2.1+ - see LICENSE file for details */
+#ifndef CCAN_RFC822_H_
+#define CCAN_RFC822_H_
+
+#include <stdlib.h>
+
+#include <ccan/bytestring/bytestring.h>
+
+/* #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 (file)
index 0000000..36a2a06
--- /dev/null
@@ -0,0 +1,58 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <ccan/talloc/talloc.h>
+#include <ccan/failtest/failtest_override.h>
+#include <ccan/failtest/failtest.h>
+
+#include <ccan/rfc822/rfc822.h>
+
+#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 (file)
index 0000000..7e1e67b
--- /dev/null
@@ -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 (file)
index 0000000..7b4eac1
--- /dev/null
@@ -0,0 +1,95 @@
+#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"
+#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 (file)
index 0000000..27d76ec
--- /dev/null
@@ -0,0 +1,68 @@
+#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"
+#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 <from@example.com>\n"
+       "To: Mizz To <to@example.org>\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 (file)
index 0000000..6ef7a10
--- /dev/null
@@ -0,0 +1,53 @@
+#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 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 (file)
index 0000000..7fc0393
--- /dev/null
@@ -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)
+{
+       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 (file)
index 0000000..d3f563f
--- /dev/null
@@ -0,0 +1,185 @@
+#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"
+#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 (file)
index 0000000..54ff4db
--- /dev/null
@@ -0,0 +1,86 @@
+#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"
+#include "helper.h"
+
+const char no_body[] = 
+       "Date: Tue, 22 Feb 2011 00:15:59 +1100\n"
+       "From: Mister From <from@example.com>\n"
+       "To: Mizz To <to@example.org>\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 <from@example.com>\n"
+       "To: Mizz To <to@";
+
+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);
+       allocation_failure_check();
+
+       body = rfc822_body(msg);
+       allocation_failure_check();
+       if (body.ptr)
+               ok = 0;
+
+       rfc822_free(msg);
+       allocation_failure_check();
+       return ok;
+}
+
+static int test_truncated(const char *buf, size_t len)
+{
+       struct rfc822_msg *msg;
+       struct rfc822_header *h = NULL;
+       struct bytestring body;
+       int ok = 1;
+
+       msg = rfc822_start(NULL, buf, len);
+       allocation_failure_check();
+
+       do {
+               h = rfc822_next_header(msg, h);
+               allocation_failure_check();
+       } while (h);
+
+       body = rfc822_body(msg);
+       allocation_failure_check();
+       if (body.ptr)
+               ok = 0;
+
+       rfc822_free(msg);
+       allocation_failure_check();
+       return ok;
+}
+
+int main(int argc, char *argv[])
+{
+       failtest_setup(argc, argv);
+
+       /* This is how many tests you plan to run */
+       plan_tests(3);
+
+       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 (file)
index 0000000..dbe2848
--- /dev/null
@@ -0,0 +1,51 @@
+#include <config.h>
+
+#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 <ccan/rfc822/rfc822.c>
+
+#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@example.com>\n"
+       "To:Mizz To <to@example.org>\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 (file)
index 0000000..b952926
--- /dev/null
@@ -0,0 +1,121 @@
+#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"
+#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 <from@example.com>\n" \
+       "To: Mizz To <to@example.org>\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 (file)
index 0000000..ceb3bcd
--- /dev/null
@@ -0,0 +1,124 @@
+#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@example.com>"},
+       {"To", "Mizz To <to@example.org>"},
+       {"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 */