Commit de28abde authored by Rusty Russell's avatar Rusty Russell

rfc822: new module.

parent a3158e79
../../licenses/LGPL-2.1
\ No newline at end of file
#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;
}
/* 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;
}
/* 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_ */
#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);
}
void failtest_setup(int argc, char *argv[]);
void allocation_failure_check(void);
#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());
}
#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());
}
#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());
}
#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());
}
#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());
}
#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());
}
#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());
}
#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());
}
#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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment