]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-smtp: Implemented parsing, manipulation and encoding of SMTP addresses.
authorStephan Bosch <stephan.bosch@dovecot.fi>
Sun, 6 Nov 2016 20:15:41 +0000 (21:15 +0100)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Sun, 26 Nov 2017 20:30:01 +0000 (22:30 +0200)
src/lib-smtp/Makefile.am
src/lib-smtp/smtp-address.c [new file with mode: 0644]
src/lib-smtp/smtp-address.h [new file with mode: 0644]
src/lib-smtp/test-smtp-address.c [new file with mode: 0644]

index c7af84038c641c6b5bbfb8244308acd1564416e6..6c99e3f87d7e791faad580f131c38452258e92fb 100644 (file)
@@ -12,12 +12,14 @@ AM_CPPFLAGS = \
 
 libsmtp_la_SOURCES = \
        smtp-parser.c \
+       smtp-address.c \
        lmtp-client.c \
        smtp-submit-settings.c \
        smtp-submit.c
 
 headers = \
        smtp-parser.h \
+       smtp-address.h \
        lmtp-client.h \
        smtp-submit-settings.h \
        smtp-submit.h
@@ -25,7 +27,8 @@ headers = \
 pkginc_libdir=$(pkgincludedir)
 pkginc_lib_HEADERS = $(headers)
 
-test_programs =
+test_programs = \
+       test-smtp-address
 
 test_nocheck_programs = \
        test-smtp-submit
@@ -33,7 +36,7 @@ test_nocheck_programs = \
 noinst_PROGRAMS = $(test_programs) $(test_nocheck_programs)
 
 test_libs = \
-       libsmtp.la \
+       $(noinst_LTLIBRARIES) \
        ../lib-program-client/libprogram_client.la \
        ../lib-dns/libdns.la \
        ../lib-mail/libmail.la \
@@ -46,7 +49,7 @@ test_libs = \
        $(MODULE_LIBS)
 
 test_deps = \
-       libsmtp.la \
+       $(noinst_LTLIBRARIES) \
        ../lib-program-client/libprogram_client.la \
        ../lib-dns/libdns.la \
        ../lib-mail/libmail.la \
@@ -57,6 +60,11 @@ test_deps = \
        ../lib-test/libtest.la \
        ../lib/liblib.la
 
+test_smtp_address_SOURCES = test-smtp-address.c
+test_smtp_address_LDFLAGS = -export-dynamic
+test_smtp_address_LDADD = $(test_libs)
+test_smtp_address_DEPENDENCIES = $(test_deps)
+
 test_smtp_submit_SOURCES = test-smtp-submit.c
 test_smtp_submit_LDFLAGS = -export-dynamic
 test_smtp_submit_LDADD = $(test_libs)
diff --git a/src/lib-smtp/smtp-address.c b/src/lib-smtp/smtp-address.c
new file mode 100644 (file)
index 0000000..d29eb26
--- /dev/null
@@ -0,0 +1,674 @@
+/* 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);
+}
diff --git a/src/lib-smtp/smtp-address.h b/src/lib-smtp/smtp-address.h
new file mode 100644 (file)
index 0000000..01b499a
--- /dev/null
@@ -0,0 +1,115 @@
+#ifndef SMTP_ADDRESS_H
+#define SMTP_ADDRESS_H
+
+#include "array-decl.h"
+
+struct message_address;
+
+enum smtp_address_parse_flags {
+       /* Strictly enforce the RFC 5321 syntax */
+       SMTP_ADDRESS_PARSE_FLAG_STRICT            = BIT(0),
+       /* Allow an empty/NULL address */
+       SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY       = BIT(1),
+       /* Allow an address without a domain part */
+       SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART   = BIT(2),
+       /* Allow omission of the <...> brackets in a path */
+       SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL = BIT(3)
+};
+
+struct smtp_address {
+       const char *localpart;
+       const char *domain;
+};
+
+ARRAY_DEFINE_TYPE(smtp_address, struct smtp_address *);
+ARRAY_DEFINE_TYPE(smtp_address_const, const struct smtp_address *);
+
+/* Not const! Never return this as a result directly! */
+#define SMTP_ADDRESS_LITERAL(localpart, domain) \
+       &((struct smtp_address){ (localpart), (domain) })
+
+/*
+ * SMTP address parsing
+ */
+
+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)
+       ATTR_NULL(4, 5);
+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) ATTR_NULL(4, 5, 6);
+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)
+       ATTR_NULL(4, 5);
+int smtp_address_parse_username(pool_t pool, const char *username,
+       struct smtp_address **address_r, const char **error_r)
+       ATTR_NULL(3, 4);
+
+/*
+ * SMTP address construction
+ */
+
+void smtp_address_write(string_t *out,
+       const struct smtp_address *address) ATTR_NULL(2);
+void smtp_address_write_path(string_t *out,
+       const struct smtp_address *address) ATTR_NULL(2);
+
+const char *
+smtp_address_encode(const struct smtp_address *address)
+       ATTR_NULL(1);
+const char *
+smtp_address_encode_path(const struct smtp_address *address)
+       ATTR_NULL(1);
+
+/*
+ * SMTP address manipulation
+ */
+
+void smtp_address_init(struct smtp_address *address,
+       const char *localpart, const char *domain);
+void smtp_address_init_from_msg(struct smtp_address *address,
+       const struct message_address *msg_addr);
+
+struct smtp_address *
+smtp_address_clone(pool_t pool, const struct smtp_address *address);
+struct smtp_address *
+smtp_address_create(pool_t pool, const char *localpart, const char *domain);
+struct smtp_address *
+smtp_address_create_from_msg(pool_t pool,
+       const struct message_address *msg_addr);
+
+struct smtp_address *
+smtp_address_clone_temp(const struct smtp_address *address);
+struct smtp_address *
+smtp_address_create_temp(const char *localpart, const char *domain);
+struct smtp_address *
+smtp_address_create_from_msg_temp(const struct message_address *msg_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 *
+smtp_address_add_detail_temp(const struct smtp_address *address,
+       const char *detail, char delim_c);
+
+int smtp_address_cmp(const struct smtp_address *address1,
+       const struct smtp_address *address2);
+
+static inline bool
+smtp_address_equals(const struct smtp_address *address1,
+       const struct smtp_address *address2)
+{
+       return (smtp_address_cmp(address1, address2) == 0);
+}
+
+static inline bool ATTR_NULL(1) ATTR_PURE
+smtp_address_isnull(const struct smtp_address *address)
+{
+       return (address == NULL || address->localpart == NULL ||
+               *address->localpart == '\0');
+}
+
+#endif
diff --git a/src/lib-smtp/test-smtp-address.c b/src/lib-smtp/test-smtp-address.c
new file mode 100644 (file)
index 0000000..c0a22bd
--- /dev/null
@@ -0,0 +1,751 @@
+/* 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);
+}