Skip to content

Commit

Permalink
lib-mail: message-address: Add support for parsing RFC5322 "path" syn…
Browse files Browse the repository at this point in the history
…tax.

This is either a single angle-addr or just <>. This path syntax differs from the
RFC5321 "Path" syntax in that it allows whitespace, which is very important when
it is parsed from a header.
  • Loading branch information
stephanbosch authored and sirainen committed Feb 1, 2018
1 parent 421e784 commit 1558129
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 0 deletions.
47 changes: 47 additions & 0 deletions src/lib-mail/message-address.c
Expand Up @@ -406,6 +406,32 @@ message_address_parse_real(pool_t pool, const unsigned char *data, size_t size,
return ctx.first_addr;
}

static int
message_address_parse_path_real(pool_t pool, const unsigned char *data,
size_t size, struct message_address **addr_r)
{
struct message_address_parser_context ctx;
int ret;

i_zero(&ctx);
*addr_r = NULL;

rfc822_parser_init(&ctx.parser, data, size, NULL);
ctx.pool = pool;
ctx.str = t_str_new(128);

if (rfc822_skip_lwsp(&ctx.parser) <= 0)
return -1;
if ((ret=parse_angle_addr(&ctx)) < 0 ||
(ctx.addr.mailbox != NULL && ctx.addr.domain == NULL)) {
ctx.addr.invalid_syntax = TRUE;
ret = -1;
}
add_address(&ctx);
*addr_r = ctx.first_addr;
return (ret < 0 ? -1 : 0);
}

struct message_address *
message_address_parse(pool_t pool, const unsigned char *data, size_t size,
unsigned int max_addresses, bool fill_missing)
Expand All @@ -423,11 +449,32 @@ message_address_parse(pool_t pool, const unsigned char *data, size_t size,
return addr;
}

int message_address_parse_path(pool_t pool, const unsigned char *data,
size_t size, struct message_address **addr_r)
{
int ret;

if (pool->datastack_pool) {
return message_address_parse_path_real(pool, data, size, addr_r);
}
T_BEGIN {
ret = message_address_parse_path_real(pool, data, size, addr_r);
} T_END;
return ret;
}

void message_address_write(string_t *str, const struct message_address *addr)
{
const char *tmp;
bool first = TRUE, in_group = FALSE;

/* <> path */
if (addr->mailbox == NULL && addr->domain == NULL) {
i_assert(addr->next == NULL);
str_append(str, "<>");
return;
}

/* a) mailbox@domain
b) name <@route:mailbox@domain>
c) group: .. ; */
Expand Down
6 changes: 6 additions & 0 deletions src/lib-mail/message-address.h
Expand Up @@ -30,6 +30,12 @@ struct message_address *
message_address_parse(pool_t pool, const unsigned char *data, size_t size,
unsigned int max_addresses, bool fill_missing);

/* Parse RFC 5322 "path" (Return-Path header) from given data. Returns -1 if
the path is invalid and 0 otherwise.
*/
int message_address_parse_path(pool_t pool, const unsigned char *data,
size_t size, struct message_address **addr_r);

void message_address_init(struct message_address *addr,
const char *name, const char *mailbox, const char *domain)
ATTR_NULL(1);
Expand Down
76 changes: 76 additions & 0 deletions src/lib-mail/test-message-address.c
Expand Up @@ -295,10 +295,86 @@ static void test_message_address(void)
test_end();
}

static int
test_parse_path(const char *input, const struct message_address **addr_r)
{
struct message_address *addr;
int ret;

/* duplicate the input (without trailing NUL) so valgrind notices
if there's any out-of-bounds access */
size_t input_len = strlen(input);
unsigned char *input_dup = i_malloc(input_len);
memcpy(input_dup, input, input_len);
ret = message_address_parse_path(pool_datastack_create(),
input_dup, input_len, &addr);
i_free(input_dup);
*addr_r = addr;
return ret;
}

static void test_message_address_path(void)
{
static const struct test {
const char *input;
const char *wanted_output;
struct message_address addr;
} tests[] = {
{ "<>", NULL,
{ NULL, NULL, NULL, NULL, NULL, FALSE } },
{ " < > ", "<>",
{ NULL, NULL, NULL, NULL, NULL, FALSE } },
{ "<user@domain>", NULL,
{ NULL, NULL, NULL, "user", "domain", FALSE } },
{ " <user@domain> ", "<user@domain>",
{ NULL, NULL, NULL, "user", "domain", FALSE } },
{ "<\"user\"@domain>", "<user@domain>",
{ NULL, NULL, NULL, "user", "domain", FALSE } },
{ "<\"user name\"@domain>", NULL,
{ NULL, NULL, NULL, "user name", "domain", FALSE } },
{ "<\"user@na\\\\me\"@domain>", NULL,
{ NULL, NULL, NULL, "user@na\\me", "domain", FALSE } },
{ "<\"user\\\"name\"@domain>", NULL,
{ NULL, NULL, NULL, "user\"name", "domain", FALSE } },
{ "<\"\"@domain>", NULL,
{ NULL, NULL, NULL, "", "domain", FALSE } },
};
const struct message_address *addr;
string_t *str;
const char *wanted_string;
unsigned int i;

test_begin("message address path parsing");
str = t_str_new(128);

for (i = 0; i < N_ELEMENTS(tests); i++) {
const struct test *test = &tests[i];
const struct message_address *test_wanted_addr;
int ret;

test_wanted_addr = &test->addr;
ret = test_parse_path(test->input, &addr);
test_assert_idx(ret == 0, i);
test_assert_idx(addr != NULL && addr->next == NULL &&
cmp_addr(addr, test_wanted_addr), i);

/* test the address alone */
str_truncate(str, 0);
message_address_write(str, addr);
if (test->wanted_output != NULL)
wanted_string = test->wanted_output;
else
wanted_string = test->input;
test_assert_idx(strcmp(str_c(str), wanted_string) == 0, i);
}
test_end();
}

int main(void)
{
static void (*const test_functions[])(void) = {
test_message_address,
test_message_address_path,
NULL
};
return test_run(test_functions);
Expand Down

0 comments on commit 1558129

Please sign in to comment.