--- /dev/null
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "message-address.h"
+#include "smtp-parser.h"
+#include "smtp-address.h"
+
+/* From RFC 5321:
+
+ Reverse-path = Path / "<>"
+ Forward-path = Path
+
+ Path = "<" [ A-d-l ":" ] Mailbox ">"
+ A-d-l = At-domain *( "," At-domain )
+ ; Note that this form, the so-called "source
+ ; route", MUST BE accepted, SHOULD NOT be
+ ; generated, and SHOULD be ignored.
+ At-domain = "@" Domain
+
+ Domain = sub-domain *("." sub-domain)
+
+ sub-domain = Let-dig [Ldh-str]
+ Let-dig = ALPHA / DIGIT
+ Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
+
+ address-literal = "[" ( IPv4-address-literal /
+ IPv6-address-literal /
+ General-address-literal ) "]"
+ ; See Section 4.1.3
+
+ Mailbox = Local-part "@" ( Domain / address-literal )
+
+ Local-part = Dot-string / Quoted-string
+ ; MAY be case-sensitive
+ Dot-string = Atom *("." Atom)
+ Atom = 1*atext
+ */
+
+/*
+ * SMTP address parsing
+ */
+
+struct smtp_address_parser {
+ struct smtp_parser parser;
+
+ struct smtp_address address;
+
+ bool parse:1;
+ bool path:1;
+};
+
+static int
+smtp_parser_parse_dot_string(struct smtp_parser *parser,
+ const char **value_r)
+{
+ const unsigned char *pbegin = parser->cur;
+
+ /* Dot-string = Atom *("." Atom)
+ */
+
+ /* NOTE: this deviates from Dot-String syntax to allow some Japanese
+ mail addresses with dots at non-standard places to be accepted. */
+
+ if (parser->cur >= parser->end ||
+ (!smtp_char_is_atext(*parser->cur) && *parser->cur != '.'))
+ return 0;
+ parser->cur++;
+
+ while (parser->cur < parser->end &&
+ (smtp_char_is_atext(*parser->cur) || *parser->cur == '.'))
+ parser->cur++;
+
+ if (value_r != NULL)
+ *value_r = t_strndup(pbegin, parser->cur - pbegin);
+ return 1;
+}
+
+static int
+smtp_parse_localpart(struct smtp_parser *parser,
+ const char **localpart_r)
+{
+ int ret;
+
+ if ((ret=smtp_parser_parse_quoted_string(parser, localpart_r)) != 0)
+ return ret;
+
+ return smtp_parser_parse_dot_string(parser, localpart_r);
+}
+
+static int
+smtp_parse_mailbox(struct smtp_address_parser *aparser,
+ enum smtp_address_parse_flags flags)
+{
+ struct smtp_parser *parser = &aparser->parser;
+ const char **value = NULL;
+ int ret;
+
+ /* Mailbox = Local-part "@" ( Domain / address-literal )
+ */
+
+ value = (aparser->parse ? &aparser->address.localpart : NULL);
+ if ((ret=smtp_parse_localpart(parser, value)) <= 0)
+ return ret;
+
+ if ((parser->cur >= parser->end || *parser->cur != '@') &&
+ (flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART) == 0) {
+ if (parser->cur >= parser->end ||
+ (aparser->path && *parser->cur == '>'))
+ parser->error = "Missing domain";
+ else
+ parser->error = "Invalid character in localpart";
+ return -1;
+ }
+
+ if (parser->cur >= parser->end || *parser->cur != '@')
+ return 1;
+ parser->cur++;
+
+ value = (aparser->parse ? &aparser->address.domain : NULL);
+ if ((ret=smtp_parser_parse_domain(parser, value)) == 0 &&
+ (ret=smtp_parser_parse_address_literal(parser,
+ value, NULL)) == 0) {
+ if (parser->cur >= parser->end ||
+ (aparser->path && *parser->cur == '>')) {
+ parser->error = "Missing domain after '@'";
+ return -1;
+ } else {
+ parser->error = "Invalid domain";
+ return -1;
+ }
+ }
+ return ret;
+}
+
+static int
+smtp_parse_source_route(struct smtp_parser *parser)
+{
+ /* Source-route = [ A-d-l ":" ]
+ A-d-l = At-domain *( "," At-domain )
+ At-domain = "@" Domain
+ */
+
+ /* "@" Domain */
+ if (parser->cur >= parser->end || *parser->cur != '@')
+ return 0;
+ parser->cur++;
+
+ for (;;) {
+ /* Domain */
+ if (smtp_parser_parse_domain(parser, NULL) <= 0) {
+ parser->error =
+ "Missing domain after '@' in source route";
+ return -1;
+ }
+
+ /* *( "," At-domain ) */
+ if (parser->cur >= parser->end || *parser->cur != ',')
+ break;
+ parser->cur++;
+
+ /* "@" Domain */
+ if (parser->cur >= parser->end || *parser->cur != '@') {
+ parser->error = "Missing '@' after ',' in source route";
+ return -1;
+ }
+ parser->cur++;
+ }
+
+ /* ":" */
+ if (parser->cur >= parser->end || *parser->cur != ':') {
+ parser->error = "Missing ':' at end of source route";
+ return -1;
+ }
+ parser->cur++;
+ return 1;
+}
+
+static int
+smtp_parse_path(struct smtp_address_parser *aparser,
+ enum smtp_address_parse_flags flags)
+{
+ struct smtp_parser *parser = &aparser->parser;
+ int ret, sret;
+
+ /* Path = "<" [ A-d-l ":" ] Mailbox ">"
+ */
+
+ /* "<" */
+ if (parser->cur < parser->end && *parser->cur == '<') {
+ aparser->path = TRUE;
+ parser->cur++;
+ } else if ((flags & SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL) == 0) {
+ return 0;
+ }
+
+ /* [ A-d-l ":" ] */
+ if ((sret=smtp_parse_source_route(parser)) < 0)
+ return -1;
+
+ /* Mailbox */
+ if ((ret=smtp_parse_mailbox(aparser, flags)) < 0)
+ return -1;
+ if (ret == 0) {
+ if (parser->cur < parser->end && *parser->cur == '>') {
+ if (sret > 0) {
+ parser->error =
+ "Path only consists of source route";
+ return -1;
+ }
+ if ((flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY)
+ == 0) {
+ parser->error = "Null path not allowed";
+ return -1;
+ }
+ } else {
+ parser->error = "Invalid character in localpart";
+ return -1;
+ }
+ }
+
+ /* ">" */
+ if (aparser->path) {
+ if (parser->cur >= parser->end || *parser->cur != '>') {
+ parser->error = "Missing '>' at end of path";
+ return -1;
+ }
+ parser->cur++;
+ } else if (parser->cur < parser->end && *parser->cur == '>') {
+ parser->error = "Unmatched '>' at end of path";
+ return -1;
+ }
+ return 1;
+}
+
+static int
+smtp_parse_username(struct smtp_address_parser *aparser)
+{
+ struct smtp_parser *parser = &aparser->parser;
+ const char **value = NULL;
+ const unsigned char *p, *dp;
+ int ret;
+
+ /* Best-effort extraction of SMTP address from a user name.
+ */
+
+ value = (aparser->parse ? &aparser->address.localpart : NULL);
+ if (*parser->cur == '\"') {
+ /* if the local part is a quoted string, parse it as any other
+ SMTP address */
+ if ((ret=smtp_parse_localpart(parser, value)) <= 0)
+ return ret;
+ } else {
+ /* use the right-most '@' as separator */
+ dp = parser->end - 1;
+ while (dp > parser->cur && *dp != '@')
+ dp--;
+ if (dp == parser->cur)
+ dp = parser->end;
+ /* check whether the resulting localpart could be encoded as
+ quoted string */
+ for (p = parser->cur; p < dp; p++) {
+ if (!smtp_char_is_qtext(*p) || *p == ' ') {
+ parser->error =
+ "Invalid character in user name";
+ return -1;
+ }
+ }
+ if (aparser->parse) {
+ aparser->address.localpart =
+ p_strdup_until(parser->pool, parser->cur, dp);
+ }
+ parser->cur = dp;
+ }
+
+ if (parser->cur < parser->end && *parser->cur != '@') {
+ parser->error = "Invalid character in user name";
+ return -1;
+ }
+
+ if (parser->cur >= parser->end || *parser->cur != '@')
+ return 1;
+ parser->cur++;
+
+ value = (aparser->parse ? &aparser->address.domain : NULL);
+ if ((ret=smtp_parser_parse_domain(parser, value)) == 0 &&
+ (ret=smtp_parser_parse_address_literal(parser,
+ value, NULL)) == 0) {
+ if (parser->cur >= parser->end) {
+ parser->error = "Missing domain after '@'";
+ return -1;
+ } else {
+ parser->error = "Invalid domain";
+ return -1;
+ }
+ }
+ return ret;
+}
+
+int smtp_address_parse_mailbox(pool_t pool,
+ const char *mailbox, enum smtp_address_parse_flags flags,
+ struct smtp_address **address_r, const char **error_r)
+{
+ struct smtp_address_parser aparser;
+ int ret;
+
+ if (address_r != NULL)
+ *address_r = NULL;
+ if (error_r != NULL)
+ *error_r = NULL;
+
+ if ((mailbox == NULL || *mailbox == '\0')) {
+ if ((flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY) == 0) {
+ if (error_r != NULL)
+ *error_r = "Mailbox is empty string";
+ return -1;
+ }
+
+ if (address_r != NULL)
+ *address_r = p_new(pool, struct smtp_address, 1);
+ return 1;
+ }
+
+ i_zero(&aparser);
+ smtp_parser_init(&aparser.parser, pool_datastack_create(), mailbox);
+ aparser.parse = (address_r != NULL);
+
+ if ((ret=smtp_parse_mailbox(&aparser, flags)) <= 0) {
+ if (error_r != NULL)
+ *error_r = (ret < 0 ? aparser.parser.error :
+ "Invalid character in localpart");
+ return -1;
+ }
+ if (aparser.parser.cur != aparser.parser.end) {
+ if (error_r != NULL)
+ *error_r = "Invalid character in mailbox";
+ return -1;
+ }
+
+ if (address_r != NULL)
+ *address_r = smtp_address_clone(pool, &aparser.address);
+ return 1;
+}
+
+int smtp_address_parse_path_full(pool_t pool, const char *path,
+ enum smtp_address_parse_flags flags,
+ struct smtp_address **address_r, const char **error_r,
+ const char **endp_r)
+{
+ struct smtp_address_parser aparser;
+ int ret;
+
+ if (address_r != NULL)
+ *address_r = NULL;
+ if (error_r != NULL)
+ *error_r = NULL;
+
+ if (path == NULL || *path == '\0') {
+ if ((flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY) == 0 ||
+ (flags & SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL) == 0) {
+ if (error_r != NULL)
+ *error_r = "Path is empty string";
+ return -1;
+ }
+ if (address_r != NULL)
+ *address_r = p_new(pool, struct smtp_address, 1);
+ return 1;
+ }
+
+ i_zero(&aparser);
+ smtp_parser_init(&aparser.parser, pool_datastack_create(), path);
+ aparser.parse = (address_r != NULL);
+
+ if ((ret=smtp_parse_path(&aparser, flags)) <= 0) {
+ if (error_r != NULL) {
+ *error_r = (ret < 0 ? aparser.parser.error :
+ "Missing '<' at beginning of path");
+ }
+ return -1;
+ }
+ if (endp_r != NULL)
+ *endp_r = (const char *)aparser.parser.cur;
+ else if (aparser.parser.cur != aparser.parser.end) {
+ if (error_r != NULL)
+ *error_r = "Invalid character in path";
+ return -1;
+ }
+
+ if (address_r != NULL)
+ *address_r = smtp_address_clone(pool, &aparser.address);
+ return 1;
+}
+
+int smtp_address_parse_path(pool_t pool, const char *path,
+ enum smtp_address_parse_flags flags,
+ struct smtp_address **address_r, const char **error_r)
+{
+ return smtp_address_parse_path_full(pool, path, flags,
+ address_r, error_r, NULL);
+}
+
+int smtp_address_parse_username(pool_t pool, const char *username,
+ struct smtp_address **address_r, const char **error_r)
+{
+ struct smtp_address_parser aparser;
+ int ret;
+
+ if (address_r != NULL)
+ *address_r = NULL;
+ if (error_r != NULL)
+ *error_r = NULL;
+
+ if ((username == NULL || *username == '\0')) {
+ if (error_r != NULL)
+ *error_r = "Username is empty string";
+ return -1;
+ }
+
+ i_zero(&aparser);
+ smtp_parser_init(&aparser.parser, pool_datastack_create(), username);
+ aparser.parse = (address_r != NULL);
+
+ if ((ret=smtp_parse_username(&aparser)) <= 0) {
+ if (error_r != NULL) {
+ *error_r = (ret < 0 ? aparser.parser.error :
+ "Invalid character in user name");
+ }
+ return -1;
+ }
+ if (aparser.parser.cur != aparser.parser.end) {
+ if (error_r != NULL)
+ *error_r = "Invalid character in user name";
+ return -1;
+ }
+
+ if (address_r != NULL)
+ *address_r = smtp_address_clone(pool, &aparser.address);
+ return 1;
+}
+
+/*
+ * SMTP address construction
+ */
+
+void smtp_address_write(string_t *out,
+ const struct smtp_address *address) ATTR_NULL(2)
+{
+ bool quoted = FALSE;
+ const unsigned char *p, *pend, *pblock;
+ size_t begin;
+
+ if (address == NULL || address->localpart == NULL)
+ return;
+ begin = str_len(out);
+
+ /* encode localpart */
+ if (address->localpart == NULL)
+ return;
+ p = (const unsigned char *)address->localpart;
+ pend = p + strlen(address->localpart);
+ pblock = p;
+ while (p < pend) {
+ while (p < pend && smtp_char_is_atext(*p))
+ p++;
+
+ if (!quoted && p < pend && (*p != '.' || p == pblock)) {
+ quoted = TRUE;
+ str_insert(out, begin, "\"");
+ }
+
+ str_append_n(out, pblock, p - pblock);
+ if (p >= pend)
+ break;
+
+ if (!quoted) {
+ str_append_c(out, '.');
+ } else {
+ i_assert(smtp_char_is_qpair(*p));
+ if (!smtp_char_is_qtext(*p))
+ str_append_c(out, '\\');
+ str_append_c(out, *p);
+ }
+
+ p++;
+ pblock = p;
+ }
+
+ if (p == pblock) {
+ quoted = TRUE;
+ str_insert(out, begin, "\"");
+ }
+
+ if (quoted)
+ str_append_c(out, '\"');
+
+ if (address->domain == NULL)
+ return;
+
+ str_append_c(out, '@');
+ str_append(out, address->domain);
+}
+
+void smtp_address_write_path(string_t *out,
+ const struct smtp_address *address)
+{
+ str_append_c(out, '<');
+ smtp_address_write(out, address);
+ str_append_c(out, '>');
+}
+
+const char *
+smtp_address_encode(const struct smtp_address *address)
+{
+ string_t *str = t_str_new(256);
+ smtp_address_write(str, address);
+ return str_c(str);
+}
+
+const char *
+smtp_address_encode_path(const struct smtp_address *address)
+{
+ string_t *str = t_str_new(256);
+ smtp_address_write_path(str, address);
+ return str_c(str);
+}
+
+/*
+ * SMTP address manipulation
+ */
+
+void smtp_address_init(struct smtp_address *address,
+ const char *localpart, const char *domain)
+{
+ i_zero(address);
+ address->localpart = localpart;
+ address->domain = (localpart == NULL ? NULL : domain);
+}
+
+void smtp_address_init_from_msg(struct smtp_address *address,
+ const struct message_address *msg_addr)
+{
+ i_zero(address);
+ address->localpart = msg_addr->mailbox;
+ address->domain = (msg_addr->mailbox == NULL ? NULL : msg_addr->domain);
+}
+
+struct smtp_address *
+smtp_address_clone(pool_t pool, const struct smtp_address *src)
+{
+ struct smtp_address *new;
+ size_t size, lpsize, dsize;
+ char *data, *localpart, *domain;
+
+ if (src == NULL)
+ return NULL;
+
+ /* @UNSAFE */
+
+ lpsize = dsize = 0;
+ size = sizeof(struct smtp_address);
+ if (src->localpart != NULL) {
+ lpsize = strlen(src->localpart) + 1;
+ size = MALLOC_ADD(size, lpsize);
+ }
+ if (src->domain != NULL) {
+ dsize = strlen(src->domain) + 1;
+ size = MALLOC_ADD(size, dsize);
+ }
+
+ localpart = domain = NULL;
+ data = p_malloc(pool, size);
+ new = (struct smtp_address *)data;
+ if (lpsize > 0) {
+ localpart = PTR_OFFSET(data, sizeof(*new));
+ memcpy(localpart, src->localpart, lpsize);
+ }
+ if (dsize > 0) {
+ domain = PTR_OFFSET(data, sizeof(*new) + lpsize);
+ memcpy(domain, src->domain, dsize);
+ }
+ new->localpart = localpart;
+ new->domain = domain;
+
+ return new;
+}
+
+struct smtp_address *
+smtp_address_create(pool_t pool,
+ const char *localpart, const char *domain)
+{
+ struct smtp_address addr;
+
+ smtp_address_init(&addr, localpart, domain);
+ return smtp_address_clone(pool, &addr);
+}
+
+struct smtp_address *
+smtp_address_create_from_msg(pool_t pool,
+ const struct message_address *msg_addr)
+{
+ struct smtp_address addr;
+
+ smtp_address_init_from_msg(&addr, msg_addr);
+ return smtp_address_clone(pool, &addr);
+}
+
+struct smtp_address *
+smtp_address_clone_temp(const struct smtp_address *src)
+{
+ struct smtp_address *new;
+
+ new = t_new(struct smtp_address, 1);
+ new->localpart = t_strdup(src->localpart);
+ new->domain = t_strdup(src->domain);
+ return new;
+}
+
+struct smtp_address *
+smtp_address_create_temp(const char *localpart, const char *domain)
+{
+ struct smtp_address addr;
+
+ smtp_address_init(&addr, localpart, domain);
+ return smtp_address_clone_temp(&addr);
+}
+
+struct smtp_address *
+smtp_address_create_from_msg_temp(const struct message_address *msg_addr)
+{
+ struct smtp_address addr;
+
+ smtp_address_init_from_msg(&addr, msg_addr);
+ return smtp_address_clone_temp(&addr);
+}
+
+struct smtp_address *
+smtp_address_add_detail(pool_t pool, const struct smtp_address *address,
+ const char *detail, char delim_c)
+{
+ struct smtp_address *new_addr;
+ const char delim[] = {delim_c, '\0'};
+
+ new_addr = p_new(pool, struct smtp_address, 1);
+ new_addr->localpart = p_strconcat(pool,
+ address->localpart, delim, detail, NULL);
+ new_addr->domain = p_strdup(pool, address->domain);
+
+ return new_addr;
+}
+
+struct smtp_address *
+smtp_address_add_detail_temp(const struct smtp_address *address,
+ const char *detail, char delim_c)
+{
+ struct smtp_address *new_addr;
+ const char delim[] = {delim_c, '\0'};
+
+ new_addr = t_new(struct smtp_address, 1);
+ new_addr->localpart = t_strconcat(
+ address->localpart, delim, detail, NULL);
+ new_addr->domain = t_strdup(address->domain);
+
+ return new_addr;
+}
+
+int smtp_address_cmp(const struct smtp_address *address1,
+ const struct smtp_address *address2)
+{
+ int ret;
+
+ if ((ret=strcasecmp(address1->localpart, address2->localpart)) != 0)
+ return ret;
+ return strcasecmp(address1->domain, address2->domain);
+}
--- /dev/null
+/* Copyright (c) 2013-2017 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "str.h"
+#include "test-common.h"
+#include "smtp-address.h"
+
+/*
+ * Valid mailbox parse tests
+ */
+
+struct valid_mailbox_parse_test {
+ const char *input, *output;
+ enum smtp_address_parse_flags flags;
+
+ struct smtp_address address;
+};
+
+static const struct valid_mailbox_parse_test
+valid_mailbox_parse_tests[] = {
+ {
+ .input = "",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY,
+ .address = { .localpart = NULL, .domain = NULL },
+ },{
+ .input = "user",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART,
+ .address = { .localpart = "user", .domain = NULL },
+ },{
+ .input = "user@domain.tld",
+ .address = { .localpart = "user", .domain = "domain.tld" },
+ },{
+ .input = "1234567890@domain.tld",
+ .address = {
+ .localpart = "1234567890",
+ .domain = "domain.tld" },
+ },{
+ .input = "_______@domain.tld",
+ .address = {
+ .localpart = "_______",
+ .domain = "domain.tld" },
+ },{
+ .input = "firstname.lastname@domain.tld",
+ .address = {
+ .localpart = "firstname.lastname",
+ .domain = "domain.tld" },
+ },{
+ .input = "firstname+lastname@domain.tld",
+ .address = {
+ .localpart = "firstname+lastname",
+ .domain = "domain.tld" },
+ },{
+ .input = "firstname-lastname@domain.tld",
+ .address = {
+ .localpart = "firstname-lastname",
+ .domain = "domain.tld" },
+ },{
+ .input = "\"user\"@domain.tld",
+ .address = { .localpart = "user", .domain = "domain.tld" },
+ .output = "user@domain.tld"
+ },{
+ .input = "\"user@frop\"@domain.tld",
+ .address = { .localpart = "user@frop", .domain = "domain.tld" },
+ .output = "\"user@frop\"@domain.tld"
+ },{
+ .input = "user@127.0.0.1",
+ .address = { .localpart = "user", .domain = "127.0.0.1" },
+ },{
+ .input = "user@[127.0.0.1]",
+ .address = { .localpart = "user", .domain = "[127.0.0.1]" },
+ },{
+ .input = "user@[IPv6:::1]",
+ .address = { .localpart = "user", .domain = "[IPv6:::1]" },
+ },{
+ .input = "user@[IPv6:::127.0.0.1]",
+ .address = { .localpart = "user", .domain = "[IPv6:::127.0.0.1]" },
+ /* Japanese deviations */
+ }, {
+ .input = "email@-example.com",
+ .address = { .localpart = "email", .domain = "-example.com" },
+ }, {
+ .input = ".email@example.com",
+ .output = "\".email\"@example.com",
+ .address = { .localpart = ".email", .domain = "example.com" },
+ }, {
+ .input = "email.@example.com",
+ .output = "\"email.\"@example.com",
+ .address = { .localpart = "email.", .domain = "example.com" },
+ }, {
+ .input = "email..email@example.com",
+ .output = "\"email..email\"@example.com",
+ .address = { .localpart = "email..email", .domain = "example.com" },
+ }, {
+ .input = "Abc..123@example.com",
+ .output = "\"Abc..123\"@example.com",
+ .address = { .localpart = "Abc..123", .domain = "example.com" },
+ }
+};
+
+unsigned int valid_mailbox_parse_test_count =
+ N_ELEMENTS(valid_mailbox_parse_tests);
+
+static void test_smtp_mailbox_parse_valid(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < valid_mailbox_parse_test_count; i++) T_BEGIN {
+ const struct valid_mailbox_parse_test *test;
+ struct smtp_address *address;
+ const char *error = NULL, *output, *encoded;
+ int ret;
+
+ test = &valid_mailbox_parse_tests[i];
+ ret = smtp_address_parse_mailbox(pool_datastack_create(),
+ test->input, test->flags, &address, &error);
+
+ test_begin(t_strdup_printf("smtp mailbox valid [%d]", i));
+ test_out_reason(t_strdup_printf("parse(\"%s\")",
+ test->input), ret > 0, error);
+
+ if (ret > 0) {
+ if (address->localpart == NULL ||
+ test->address.localpart == NULL) {
+ test_out(t_strdup_printf
+ ("address->localpart = %s", address->localpart),
+ (address->localpart == test->address.localpart));
+ } else {
+ test_out(t_strdup_printf
+ ("address->localpart = \"%s\"", address->localpart),
+ strcmp(address->localpart, test->address.localpart) == 0);
+ }
+ if (address->domain == NULL ||
+ test->address.domain == NULL) {
+ test_out(t_strdup_printf
+ ("address->domain = %s", address->domain),
+ (address->domain == test->address.domain));
+ } else {
+ test_out(t_strdup_printf
+ ("address->domain = \"%s\"", address->domain),
+ strcmp(address->domain, test->address.domain) == 0);
+ }
+
+ encoded = smtp_address_encode(address);
+ output = (test->output == NULL ? test->input : test->output);
+ test_out(
+ t_strdup_printf("encode() = \"%s\"", encoded),
+ strcmp(encoded, output) == 0);
+ }
+ test_end();
+ } T_END;
+}
+
+/*
+ * Valid path parse tests
+ */
+
+struct valid_path_parse_test {
+ const char *input, *output;
+ enum smtp_address_parse_flags flags;
+
+ struct smtp_address address;
+};
+
+static const struct valid_path_parse_test
+valid_path_parse_tests[] = {
+ {
+ .input = "<>",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY,
+ .address = { .localpart = NULL, .domain = NULL }
+ },{
+ .input = "<user>",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART,
+ .address = { .localpart = "user", .domain = NULL }
+ },{
+ .input = "<user@domain.tld>",
+ .address = { .localpart = "user", .domain = "domain.tld" }
+ },{
+ .input = "<@otherdomain.tld,@yetanotherdomain.tld:user@domain.tld>",
+ .address = { .localpart = "user", .domain = "domain.tld" },
+ .output = "<user@domain.tld>"
+ }
+};
+
+unsigned int valid_path_parse_test_count =
+ N_ELEMENTS(valid_path_parse_tests);
+
+static void test_smtp_path_parse_valid(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < valid_path_parse_test_count; i++) T_BEGIN {
+ const struct valid_path_parse_test *test;
+ struct smtp_address *address;
+ const char *error = NULL, *output, *encoded;
+ int ret;
+
+ test = &valid_path_parse_tests[i];
+ ret = smtp_address_parse_path(pool_datastack_create(),
+ test->input, test->flags, &address, &error);
+
+ test_begin(t_strdup_printf("smtp path valid [%d]", i));
+ test_out_reason(t_strdup_printf("parse(\"%s\")",
+ test->input), ret > 0, error);
+
+ if (ret > 0) {
+ if (address->localpart == NULL ||
+ test->address.localpart == NULL) {
+ test_out(t_strdup_printf
+ ("address->localpart = %s", address->localpart),
+ (address->localpart == test->address.localpart));
+ } else {
+ test_out(t_strdup_printf
+ ("address->localpart = \"%s\"", address->localpart),
+ strcmp(address->localpart, test->address.localpart) == 0);
+ }
+ if (address->domain == NULL ||
+ test->address.domain == NULL) {
+ test_out(t_strdup_printf
+ ("address->domain = %s", address->domain),
+ (address->domain == test->address.domain));
+ } else {
+ test_out(t_strdup_printf
+ ("address->domain = \"%s\"", address->domain),
+ strcmp(address->domain, test->address.domain) == 0);
+ }
+
+ encoded = smtp_address_encode_path(address);
+ output = (test->output == NULL ? test->input : test->output);
+ test_out(
+ t_strdup_printf("encode() = \"%s\"", encoded),
+ strcmp(encoded, output) == 0);
+ }
+ test_end();
+ } T_END;
+}
+
+/*
+ * Valid username parse tests
+ */
+
+struct valid_username_parse_test {
+ const char *input, *output;
+
+ struct smtp_address address;
+};
+
+static const struct valid_username_parse_test
+valid_username_parse_tests[] = {
+ {
+ .input = "user",
+ .address = {
+ .localpart = "user",
+ .domain = NULL },
+ },{
+ .input = "user@domain.tld",
+ .address = {
+ .localpart = "user",
+ .domain = "domain.tld" },
+ },{
+ .input = "user@domain.tld",
+ .address = {
+ .localpart = "user",
+ .domain = "domain.tld" },
+ },{
+ .input = "1234567890@domain.tld",
+ .address = {
+ .localpart = "1234567890",
+ .domain = "domain.tld" },
+ },{
+ .input = "_______@domain.tld",
+ .address = {
+ .localpart = "_______",
+ .domain = "domain.tld" },
+ },{
+ .input = "firstname.lastname@domain.tld",
+ .address = {
+ .localpart = "firstname.lastname",
+ .domain = "domain.tld" },
+ },{
+ .input = "firstname+lastname@domain.tld",
+ .address = {
+ .localpart = "firstname+lastname",
+ .domain = "domain.tld" },
+ },{
+ .input = "firstname-lastname@domain.tld",
+ .address = {
+ .localpart = "firstname-lastname",
+ .domain = "domain.tld" },
+ },{
+ .input = "\"user\"@domain.tld",
+ .address = { .localpart = "user", .domain = "domain.tld" },
+ .output = "user@domain.tld"
+ },{
+ .input = "\"user@frop\"@domain.tld",
+ .address = { .localpart = "user@frop", .domain = "domain.tld" },
+ .output = "\"user@frop\"@domain.tld"
+ },{
+ .input = "user@frop@domain.tld",
+ .address = { .localpart = "user@frop", .domain = "domain.tld" },
+ .output = "\"user@frop\"@domain.tld"
+ },{
+ .input = "user@127.0.0.1",
+ .address = { .localpart = "user", .domain = "127.0.0.1" },
+ },{
+ .input = "user@[127.0.0.1]",
+ .address = { .localpart = "user", .domain = "[127.0.0.1]" },
+ },{
+ .input = "user@[IPv6:::1]",
+ .address = { .localpart = "user", .domain = "[IPv6:::1]" },
+ },{
+ .input = "user@[IPv6:::127.0.0.1]",
+ .address = { .localpart = "user", .domain = "[IPv6:::127.0.0.1]" },
+ }
+};
+
+unsigned int valid_username_parse_test_count =
+ N_ELEMENTS(valid_username_parse_tests);
+
+static void test_smtp_username_parse_valid(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < valid_username_parse_test_count; i++) T_BEGIN {
+ const struct valid_username_parse_test *test;
+ struct smtp_address *address;
+ const char *error = NULL, *output, *encoded;
+ int ret;
+
+ test = &valid_username_parse_tests[i];
+ ret = smtp_address_parse_username(pool_datastack_create(),
+ test->input, &address, &error);
+
+ test_begin(t_strdup_printf("smtp username valid [%d]", i));
+ test_out_reason(t_strdup_printf("parse(\"%s\")",
+ test->input), ret > 0, error);
+
+ if (ret > 0) {
+ if (address->localpart == NULL ||
+ test->address.localpart == NULL) {
+ test_out(t_strdup_printf
+ ("address->localpart = %s", address->localpart),
+ (address->localpart == test->address.localpart));
+ } else {
+ test_out(t_strdup_printf
+ ("address->localpart = \"%s\"", address->localpart),
+ strcmp(address->localpart, test->address.localpart) == 0);
+ }
+ if (address->domain == NULL ||
+ test->address.domain == NULL) {
+ test_out(t_strdup_printf
+ ("address->domain = %s", address->domain),
+ (address->domain == test->address.domain));
+ } else {
+ test_out(t_strdup_printf
+ ("address->domain = \"%s\"", address->domain),
+ strcmp(address->domain, test->address.domain) == 0);
+ }
+
+ encoded = smtp_address_encode(address);
+ output = (test->output == NULL ? test->input : test->output);
+ test_out(
+ t_strdup_printf("encode() = \"%s\"", encoded),
+ strcmp(encoded, output) == 0);
+ }
+ test_end();
+ } T_END;
+}
+
+/*
+ * Invalid mailbox parse tests
+ */
+
+struct invalid_mailbox_parse_test {
+ const char *input;
+ enum smtp_address_parse_flags flags;
+};
+
+static const struct invalid_mailbox_parse_test
+invalid_mailbox_parse_tests[] = {
+ {
+ .input = "",
+ }, {
+ .input = "user",
+ }, {
+ .input = "\"user@domain.tld",
+ }, {
+ .input = "us\"er@domain.tld",
+ }, {
+ .input = "user@frop@domain.tld",
+ }, {
+ .input = "user@.tld",
+ }, {
+ .input = "user@a$.tld",
+ }, {
+ .input = "user@a..tld",
+ }, {
+ .input = "user@[]",
+ }, {
+ .input = "user@[",
+ }, {
+ .input = "user@[AA]",
+ }, {
+ .input = "user@[AA",
+ }, {
+ .input = "user@[127.0.0]",
+ }, {
+ .input = "user@[256.256.256.256]",
+ }, {
+ .input = "user@[127.0.0.1",
+ }, {
+ .input = "user@[::1]",
+ }, {
+ .input = "user@[IPv6:flierp]",
+ }, {
+ .input = "user@[IPv6:aa:bb::cc::dd]",
+ }, {
+ .input = "user@[IPv6::1]",
+ }, {
+ .input = "user@[IPv6:::1",
+ }, {
+ .input = "user@[Gen:]",
+ }, {
+ .input = "user@[Gen:Hopsa",
+ }, {
+ .input = "user@[Gen-:Hopsa]",
+ }, {
+ .input = "#@%^%#$@#$@#.com",
+ }, {
+ .input = "@example.com",
+ }, {
+ .input = "Eric Mail <email@example.com>",
+ }, {
+ .input = "email.example.com",
+ }, {
+ .input = "email@example@example.com",
+ }, {
+ .input = "あいうえお@example.com",
+ }, {
+ .input = "email@example.com (Eric Mail)",
+ }, {
+ .input = "email@example..com",
+#if 0 /* These deviations are allowed (maybe implement strict mode) */
+ }, {
+ .input = "email@-example.com",
+ }, {
+ .input = ".email@example.com",
+ }, {
+ .input = "email.@example.com",
+ }, {
+ .input = "email..email@example.com",
+ }, {
+ .input = "Abc..123@example.com"
+#endif
+ }
+};
+
+unsigned int invalid_mailbox_parse_test_count =
+ N_ELEMENTS(invalid_mailbox_parse_tests);
+
+static void test_smtp_mailbox_parse_invalid(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < invalid_mailbox_parse_test_count; i++) T_BEGIN {
+ const struct invalid_mailbox_parse_test *test;
+ struct smtp_address *address;
+ const char *error = NULL;
+ int ret;
+
+ test = &invalid_mailbox_parse_tests[i];
+ ret = smtp_address_parse_mailbox(pool_datastack_create(),
+ test->input, test->flags, &address, &error);
+
+ test_begin(t_strdup_printf("smtp mailbox invalid [%d]", i));
+ test_out_reason(t_strdup_printf
+ ("parse(\"%s\")", test->input), ret < 0, error);
+ test_end();
+ } T_END;
+}
+
+/*
+ * Invalid path parse tests
+ */
+
+struct invalid_path_parse_test {
+ const char *input;
+ enum smtp_address_parse_flags flags;
+};
+
+static const struct invalid_path_parse_test
+invalid_path_parse_tests[] = {
+ {
+ .input = "",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "\"user@domain.tld",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "us\"er@domain.tld",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@frop@domain.tld",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@.tld",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@a$.tld",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@a..tld",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[]",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[AA]",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[AA",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[127.0.0]",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[256.256.256.256]",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[127.0.0.1",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[::1]",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[IPv6:flierp]",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[IPv6:aa:bb::cc::dd]",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[IPv6::1]",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[IPv6:::1",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[Gen:]",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[Gen:Hopsa",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "user@[Gen-:Hopsa]",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "#@%^%#$@#$@#.com",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "@example.com",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "Eric Mail <email@example.com>",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "email.example.com",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "email@example@example.com",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "あいうえお@example.com",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "email@example.com (Eric Mail)",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "email@example..com",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "<>",
+ }, {
+ .input = "<user>",
+ }, {
+ .input = "<\"user@domain.tld>",
+ }, {
+ .input = "<us\"er@domain.tld>",
+ }, {
+ .input = "<user@frop@domain.tld>",
+ }, {
+ .input = "<user@.tld>",
+ }, {
+ .input = "<user@a$.tld>",
+ }, {
+ .input = "<user@a..tld>",
+ }, {
+ .input = "<user@[]>",
+ }, {
+ .input = "<user@[>",
+ }, {
+ .input = "<user@[AA]>",
+ }, {
+ .input = "<user@[AA>",
+ }, {
+ .input = "<user@[127.0.0]>",
+ }, {
+ .input = "<user@[256.256.256.256]>",
+ }, {
+ .input = "<user@[127.0.0.1>",
+ }, {
+ .input = "<user@[::1]>",
+ }, {
+ .input = "<user@[IPv6:flierp]>",
+ }, {
+ .input = "<user@[IPv6:aa:bb::cc::dd]>",
+ }, {
+ .input = "<user@[IPv6::1]>",
+ }, {
+ .input = "<user@[IPv6:::1>",
+ }, {
+ .input = "<user@[Gen:]>",
+ }, {
+ .input = "<user@[Gen:Hopsa>",
+ }, {
+ .input = "<user@[Gen-:Hopsa]>",
+ }, {
+ .input = "<#@%^%#$@#$@#.com>",
+ }, {
+ .input = "<@example.com>",
+ }, {
+ .input = "Eric Mail <email@example.com>",
+ }, {
+ .input = "<email.example.com>",
+ }, {
+ .input = "<email@example@example.com>",
+ }, {
+ .input = "<あいうえお@example.com>",
+ }, {
+ .input = "<email@example.com> (Eric Mail)",
+ }, {
+ .input = "<email@example..com>",
+ }, {
+ .input = "<email@example.com",
+ }, {
+ .input = "email@example.com>",
+ }, {
+ .input = "email@example.com>",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL
+ }, {
+ .input = "<",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY,
+ },{
+ .input = "<user",
+ .flags = SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART,
+ },{
+ .input = "<@otherdomain.tld,@yetanotherdomain.tld.user@domain.tld>",
+ },{
+ .input = "<@###domain.tld,@yetanotherdomain.tld.user@domain.tld>",
+ }
+};
+
+unsigned int invalid_path_parse_test_count =
+ N_ELEMENTS(invalid_path_parse_tests);
+
+static void test_smtp_path_parse_invalid(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < invalid_path_parse_test_count; i++) T_BEGIN {
+ const struct invalid_path_parse_test *test;
+ struct smtp_address *address;
+ const char *error = NULL;
+ int ret;
+
+ test = &invalid_path_parse_tests[i];
+ ret = smtp_address_parse_path(pool_datastack_create(),
+ test->input, test->flags, &address, &error);
+
+ test_begin(t_strdup_printf("smtp path invalid [%d]", i));
+ test_out_reason(t_strdup_printf
+ ("parse(\"%s\")", test->input), ret < 0, error);
+ test_end();
+ } T_END;
+}
+
+/*
+ * Invalid username parse tests
+ */
+
+struct invalid_username_parse_test {
+ const char *input;
+ enum smtp_address_parse_flags flags;
+};
+
+static const struct invalid_username_parse_test
+invalid_username_parse_tests[] = {
+ {
+ .input = "frop@$%^$%^.tld",
+ }, {
+ .input = "fr op@domain.tld",
+ }, {
+ .input = "f r o p@domain.tld",
+ }
+};
+
+unsigned int invalid_username_parse_test_count =
+ N_ELEMENTS(invalid_username_parse_tests);
+
+static void test_smtp_username_parse_invalid(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < invalid_username_parse_test_count; i++) T_BEGIN {
+ const struct invalid_username_parse_test *test;
+ struct smtp_address *address;
+ const char *error = NULL;
+ int ret;
+
+ test = &invalid_username_parse_tests[i];
+ ret = smtp_address_parse_username(pool_datastack_create(),
+ test->input, &address, &error);
+
+ test_begin(t_strdup_printf("smtp username invalid [%d]", i));
+ test_out_reason(t_strdup_printf
+ ("parse(\"%s\")", test->input), ret < 0, error);
+ test_end();
+ } T_END;
+}
+
+/*
+ * Tests
+ */
+
+int main(void)
+{
+ static void (*test_functions[])(void) = {
+ test_smtp_mailbox_parse_valid,
+ test_smtp_path_parse_valid,
+ test_smtp_username_parse_valid,
+ test_smtp_mailbox_parse_invalid,
+ test_smtp_path_parse_invalid,
+ test_smtp_username_parse_invalid,
+ NULL
+ };
+ return test_run(test_functions);
+}