[gmime] Rewrote address parser
- From: Jeffrey Stedfast <fejj src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gmime] Rewrote address parser
- Date: Wed, 8 Feb 2017 16:03:56 +0000 (UTC)
commit 752a0135252efe61f2f4ab58b61ae08fff6c92b4
Author: Jeffrey Stedfast <jestedfa microsoft com>
Date: Wed Feb 8 11:02:19 2017 -0500
Rewrote address parser
Some unit tests are failing so there's still improvements to be made...
gmime/gmime-content-type.c | 2 +-
gmime/gmime-message.c | 107 ++-----
gmime/gmime-param.c | 24 +-
gmime/gmime-parse-utils.c | 204 +++++++++--
gmime/gmime-parse-utils.h | 20 +-
gmime/gmime-utils.c | 14 +-
gmime/internet-address.c | 882 +++++++++++++++++++++++++++++---------------
tests/test-mime.c | 15 +-
8 files changed, 814 insertions(+), 454 deletions(-)
---
diff --git a/gmime/gmime-content-type.c b/gmime/gmime-content-type.c
index 022bfdf..dc182f1 100644
--- a/gmime/gmime-content-type.c
+++ b/gmime/gmime-content-type.c
@@ -192,7 +192,7 @@ g_mime_content_type_parse (GMimeParserOptions *options, const char *str)
mime_type->type = type;
/* skip past any remaining junk that shouldn't be here... */
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
while (*inptr && *inptr != ';')
inptr++;
diff --git a/gmime/gmime-message.c b/gmime/gmime-message.c
index 04d418c..ffa3f76 100644
--- a/gmime/gmime-message.c
+++ b/gmime/gmime-message.c
@@ -265,87 +265,25 @@ struct _received_token {
token_skip_t skip;
};
-static void skip_atom (const char **in);
-static void skip_domain (const char **in);
-static void skip_addr (const char **in);
-static void skip_msgid (const char **in);
+static void skip_cfws_atom (const char **in);
+static void skip_domain (const char **in);
+static void skip_addr (const char **in);
+static void skip_msgid (const char **in);
static struct _received_token received_tokens[] = {
- { "from ", 5, skip_domain },
- { "by ", 3, skip_domain },
- { "via ", 4, skip_atom },
- { "with ", 5, skip_atom },
- { "id ", 3, skip_msgid },
- { "for ", 4, skip_addr }
+ { "from ", 5, skip_domain },
+ { "by ", 3, skip_domain },
+ { "via ", 4, skip_cfws_atom },
+ { "with ", 5, skip_cfws_atom },
+ { "id ", 3, skip_msgid },
+ { "for ", 4, skip_addr }
};
static void
-skip_atom (const char **in)
+skip_cfws_atom (const char **in)
{
- register const char *inptr;
-
- decode_lwsp (in);
- inptr = *in;
- while (is_atom (*inptr))
- inptr++;
- *in = inptr;
-}
-
-static void
-skip_comment (const char **in)
-{
- register const char *inptr = *in;
- int depth = 1;
-
- if (*inptr == '(')
- inptr++;
-
- while (*inptr && depth > 0) {
- if (*inptr == '(')
- depth++;
- else if (*inptr == ')')
- depth--;
- inptr++;
- }
-
- if (*inptr == ')')
- inptr++;
-
- *in = inptr;
-}
-
-static void
-skip_quoted_string (const char **in)
-{
- const char *inptr = *in;
-
- decode_lwsp (&inptr);
- if (*inptr == '"') {
- inptr++;
- while (*inptr && *inptr != '"') {
- if (*inptr == '\\')
- inptr++;
-
- if (*inptr)
- inptr++;
- }
-
- if (*inptr == '"')
- inptr++;
- }
-
- *in = inptr;
-}
-
-static void
-skip_word (const char **in)
-{
- decode_lwsp (in);
- if (**in == '"') {
- skip_quoted_string (in);
- } else {
- skip_atom (in);
- }
+ skip_cfws (in);
+ skip_atom (in);
}
static void
@@ -357,7 +295,7 @@ skip_domain_subliteral (const char **in)
if (is_dtext (*inptr)) {
inptr++;
} else if (is_lwsp (*inptr)) {
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
} else {
break;
}
@@ -371,7 +309,7 @@ skip_domain_literal (const char **in)
{
const char *inptr = *in;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
while (*inptr && *inptr != ']') {
skip_domain_subliteral (&inptr);
if (*inptr && *inptr != ']')
@@ -387,7 +325,7 @@ skip_domain (const char **in)
const char *save, *inptr = *in;
while (inptr && *inptr) {
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr == '[') {
/* domain literal */
inptr++;
@@ -399,7 +337,7 @@ skip_domain (const char **in)
}
save = inptr;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr != '.') {
inptr = save;
break;
@@ -416,14 +354,15 @@ skip_addrspec (const char **in)
{
const char *inptr = *in;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
skip_word (&inptr);
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
while (*inptr == '.') {
inptr++;
+ skip_cfws (&inptr);
skip_word (&inptr);
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
}
if (*inptr == '@') {
@@ -439,7 +378,7 @@ skip_addr (const char **in)
{
const char *inptr = *in;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr == '<') {
inptr++;
skip_addrspec (&inptr);
@@ -457,7 +396,7 @@ skip_msgid (const char **in)
{
const char *inptr = *in;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr == '<') {
inptr++;
skip_addrspec (&inptr);
diff --git a/gmime/gmime-param.c b/gmime/gmime-param.c
index 3f357dd..d495566 100644
--- a/gmime/gmime-param.c
+++ b/gmime/gmime-param.c
@@ -94,7 +94,7 @@ decode_int (const char **in)
const unsigned char *inptr;
int digit, n = 0;
- decode_lwsp (in);
+ skip_cfws (in);
inptr = (const unsigned char *) *in;
while (isdigit ((int) *inptr)) {
@@ -122,7 +122,7 @@ decode_quoted_string (const char **in)
char *outptr, *out = NULL;
gboolean unescape = FALSE;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr != '"') {
*in = inptr;
@@ -169,7 +169,7 @@ decode_token (const char **in)
const char *inptr = *in;
const char *start;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
start = inptr;
#ifdef STRICT_PARSER
@@ -200,7 +200,7 @@ decode_value (const char **in)
{
const char *inptr = *in;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
*in = inptr;
if (*inptr == '"') {
@@ -225,7 +225,7 @@ decode_param_token (const char **in)
const char *inptr = *in;
const char *start;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
start = inptr;
while (is_ttoken (*inptr) && *inptr != '*')
@@ -250,13 +250,13 @@ decode_rfc2184_param (const char **in, char **paramp, int *part, gboolean *encod
param = decode_param_token (&inptr);
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr == '*') {
is_rfc2184 = TRUE;
inptr++;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr == '=') {
/* form := param*=value */
*encoded = TRUE;
@@ -264,12 +264,12 @@ decode_rfc2184_param (const char **in, char **paramp, int *part, gboolean *encod
/* form := param*#=value or param*#*=value */
*part = decode_int (&inptr);
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr == '*') {
/* form := param*#*=value */
inptr++;
*encoded = TRUE;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
}
}
}
@@ -549,12 +549,12 @@ decode_param_list (GMimeParserOptions *options, const char *in)
t = (struct _rfc2184_param *) &list;
rfc2184_hash = g_hash_table_new (g_mime_strcase_hash, g_mime_strcase_equal);
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
do {
/* invalid format? */
if (!decode_param (options, &inptr, &name, &value, &id, &encoded)) {
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr == ';')
continue;
@@ -596,7 +596,7 @@ decode_param_list (GMimeParserOptions *options, const char *in)
tail = param;
}
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
} while (*inptr++ == ';');
g_hash_table_destroy (rfc2184_hash);
diff --git a/gmime/gmime-parse-utils.c b/gmime/gmime-parse-utils.c
index ccb9e4e..230bb4d 100644
--- a/gmime/gmime-parse-utils.c
+++ b/gmime/gmime-parse-utils.c
@@ -56,7 +56,7 @@ g_mime_parse_content_type (const char **in, char **type, char **subtype)
register const char *inptr;
const char *start = *in;
- decode_lwsp (&start);
+ skip_cfws (&start);
inptr = start;
/* decode the type */
@@ -66,7 +66,7 @@ g_mime_parse_content_type (const char **in, char **type, char **subtype)
*type = g_strndup (start, (size_t) (inptr - start));
start = inptr;
- decode_lwsp (&start);
+ skip_cfws (&start);
/* check for type/subtype delimeter */
if (*start++ != '/') {
@@ -76,7 +76,7 @@ g_mime_parse_content_type (const char **in, char **type, char **subtype)
return FALSE;
}
- decode_lwsp (&start);
+ skip_cfws (&start);
inptr = start;
/* decode the subtype */
@@ -101,41 +101,178 @@ g_mime_parse_content_type (const char **in, char **type, char **subtype)
/**
- * g_mime_decode_lwsp:
- * @in: address of input text string
+ * g_mime_skip_comment:
+ * @in: address of input string
+ *
+ * Skips a comment.
*
- * Skips past any LWSP or rfc822 comments in *@in and updates @in.
+ * Returns: %TRUE on success or %FALSE otherwise.
**/
-void
-g_mime_decode_lwsp (const char **in)
+gboolean
+g_mime_skip_comment (const char **in)
{
- const char *inptr = *in;
+ register const char *inptr = *in;
+ int depth = 1;
- while (*inptr && (*inptr == '(' || is_lwsp (*inptr))) {
- while (*inptr && is_lwsp (*inptr))
+ /* skip over the '(' */
+ inptr++;
+ while (*inptr && depth) {
+ if (*inptr == '\\' && *(inptr + 1))
inptr++;
+ else if (*inptr == '(')
+ depth++;
+ else if (*inptr == ')')
+ depth--;
- /* skip over any comments */
- if (*inptr == '(') {
- int depth = 1;
-
- inptr++;
- while (*inptr && depth) {
- if (*inptr == '\\' && *(inptr + 1))
- inptr++;
- else if (*inptr == '(')
- depth++;
- else if (*inptr == ')')
- depth--;
-
- inptr++;
- }
+ inptr++;
+ }
+
+ *in = inptr;
+
+ return depth == 0;
+}
+
+
+/**
+ * g_mime_skip_lwsp:
+ * @in: address of input string
+ *
+ * Skips whitespace.
+ *
+ * Returns: %TRUE if any input was skipped or %FALSE otherwise.
+ **/
+gboolean
+g_mime_skip_lwsp (const char **in)
+{
+ register const char *inptr = *in;
+ const char *start = inptr;
+
+ while (is_lwsp (*inptr))
+ inptr++;
+
+ *in = inptr;
+
+ return inptr > start;
+}
+
+
+/**
+ * g_mime_skip_cfws:
+ * @in: address of input string
+ *
+ * Skips comments and whitespace.
+ *
+ * Returns: %TRUE on success or %FALSE on error.
+ **/
+gboolean
+g_mime_skip_cfws (const char **in)
+{
+ const char *inptr = *in;
+
+ skip_lwsp (&inptr);
+
+ while (*inptr == '(') {
+ if (!skip_comment (&inptr))
+ return FALSE;
+
+ skip_lwsp (&inptr);
+ }
+
+ *in = inptr;
+
+ return TRUE;
+}
+
+
+/**
+ * g_mime_skip_quoted:
+ * @in: address of input string
+ *
+ * Skips a quoted string.
+ *
+ * Returns: %TRUE on success or %FALSE on error.
+ **/
+gboolean
+g_mime_skip_quoted (const char **in)
+{
+ register const char *inptr = *in;
+ gboolean escaped = FALSE;
+
+ /* skip over leading '"' */
+ inptr++;
+
+ while (*inptr) {
+ if (*inptr == '\\') {
+ escaped = !escaped;
+ } else if (!escaped) {
+ if (*inptr == '"')
+ break;
+ } else {
+ escaped = FALSE;
}
+
+ inptr++;
}
+ if (*inptr == '\0') {
+ *in = inptr;
+
+ return FALSE;
+ }
+
+ /* skip over the closing '"' */
+ inptr++;
+
+ *in = inptr;
+
+ return TRUE;
+}
+
+
+/**
+ * g_mime_skip_atom:
+ * @in: address of input string
+ *
+ * Skips an atom.
+ *
+ * Returns: %TRUE if any input was skipped or %FALSE otherwise.
+ **/
+gboolean
+g_mime_skip_atom (const char **in)
+{
+ register const char *inptr = *in;
+ const char *start = inptr;
+
+ while (is_atom (*inptr))
+ inptr++;
+
*in = inptr;
+
+ return inptr > start;
}
+
+/**
+ * g_mime_skip_word:
+ * @in: address of input string
+ *
+ * Skips a word token.
+ *
+ * Returns: %TRUE on success or %FALSE otherwise.
+ **/
+gboolean
+g_mime_skip_word (const char **in)
+{
+ if (**in == '"')
+ return skip_quoted (in);
+
+ if (is_atom (**in))
+ return skip_atom (in);
+
+ return FALSE;
+}
+
+
static const char *
decode_quoted_string (const char **in)
{
@@ -195,7 +332,7 @@ g_mime_decode_word (const char **in)
{
const char *inptr = *in;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr == '"') {
*in = inptr;
return decode_quoted_string (in);
@@ -216,10 +353,11 @@ decode_subliteral (const char **in, GString *domain)
g_string_append_c (domain, *inptr);
inptr++;
got = TRUE;
- } else if (is_lwsp (*inptr))
- decode_lwsp (&inptr);
- else
+ } else if (is_lwsp (*inptr)) {
+ skip_cfws (&inptr);
+ } else {
break;
+ }
}
*in = inptr;
@@ -232,7 +370,7 @@ decode_domain_literal (const char **in, GString *domain)
{
const char *inptr = *in;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
while (*inptr && *inptr != ']') {
if (decode_subliteral (&inptr, domain) && *inptr == '.') {
g_string_append_c (domain, *inptr);
@@ -268,7 +406,7 @@ g_mime_decode_domain (const char **in, GString *domain)
inptr = *in;
while (inptr && *inptr) {
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr == '[') {
/* domain literal */
g_string_append_c (domain, '[');
@@ -294,7 +432,7 @@ g_mime_decode_domain (const char **in, GString *domain)
}
save = inptr;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr != '.') {
inptr = save;
break;
diff --git a/gmime/gmime-parse-utils.h b/gmime/gmime-parse-utils.h
index 0a56f56..ebc2a5e 100644
--- a/gmime/gmime-parse-utils.h
+++ b/gmime/gmime-parse-utils.h
@@ -26,8 +26,24 @@ G_BEGIN_DECLS
G_GNUC_INTERNAL gboolean g_mime_parse_content_type (const char **in, char **type, char **subtype);
-G_GNUC_INTERNAL void g_mime_decode_lwsp (const char **in);
-#define decode_lwsp(in) g_mime_decode_lwsp (in)
+G_GNUC_INTERNAL gboolean g_mime_skip_comment (const char **in);
+#define skip_comment(in) g_mime_skip_comment (in)
+
+G_GNUC_INTERNAL gboolean g_mime_skip_lwsp (const char **in);
+#define skip_lwsp(in) g_mime_skip_lwsp (in)
+
+G_GNUC_INTERNAL gboolean g_mime_skip_cfws (const char **in);
+#define skip_cfws(in) g_mime_skip_cfws (in)
+
+G_GNUC_INTERNAL gboolean g_mime_skip_quoted (const char **in);
+#define skip_quoted(in) g_mime_skip_quoted (in)
+
+G_GNUC_INTERNAL gboolean g_mime_skip_atom (const char **in);
+#define skip_atom(in) g_mime_skip_atom (in)
+
+G_GNUC_INTERNAL gboolean g_mime_skip_word (const char **in);
+#define skip_word(in) g_mime_skip_word (in)
+
G_GNUC_INTERNAL const char *g_mime_decode_word (const char **in);
#define decode_word(in) g_mime_decode_word (in)
diff --git a/gmime/gmime-utils.c b/gmime/gmime-utils.c
index a77e8b9..b7f413d 100644
--- a/gmime/gmime-utils.c
+++ b/gmime/gmime-utils.c
@@ -902,7 +902,7 @@ decode_addrspec (const char **in)
GString *addrspec;
char *str;
- decode_lwsp (in);
+ skip_cfws (in);
inptr = *in;
if (!(word = decode_word (&inptr))) {
@@ -914,12 +914,12 @@ decode_addrspec (const char **in)
g_string_append_len (addrspec, word, (size_t) (inptr - word));
/* get the rest of the local-part */
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
while (*inptr == '.') {
g_string_append_c (addrspec, *inptr++);
if ((word = decode_word (&inptr))) {
g_string_append_len (addrspec, word, (size_t) (inptr - word));
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
} else {
w(g_warning ("Invalid local-part in addr-spec: %s", *in));
goto exception;
@@ -958,16 +958,16 @@ decode_msgid (const char **in)
const char *inptr = *in;
char *msgid = NULL;
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr != '<') {
w(g_warning ("Invalid msg-id; missing '<': %s", *in));
} else {
inptr++;
}
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if ((msgid = decode_addrspec (&inptr))) {
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr != '>') {
w(g_warning ("Invalid msg-id; missing '>': %s", *in));
} else {
@@ -1028,7 +1028,7 @@ g_mime_references_decode (const char *text)
refs.next = NULL;
while (*inptr) {
- decode_lwsp (&inptr);
+ skip_cfws (&inptr);
if (*inptr == '<') {
/* looks like a msg-id */
if ((msgid = decode_msgid (&inptr))) {
diff --git a/gmime/internet-address.c b/gmime/internet-address.c
index 940d1d4..44ee15a 100644
--- a/gmime/internet-address.c
+++ b/gmime/internet-address.c
@@ -1276,384 +1276,683 @@ internet_address_list_writer (InternetAddressList *list, GString *str)
_internet_address_list_to_string (list, flags, &linelen, str);
}
-static void
-_internet_address_decode_name (GMimeParserOptions *options, InternetAddress *ia, GString *name)
+
+static char *
+decode_name (GMimeParserOptions *options, const char *name, size_t len)
{
char *value, *buf = NULL;
- char *phrase;
- if (!g_utf8_validate (name->str, name->len, NULL)) {
+ if (!g_utf8_validate (name, len, NULL)) {
/* A (broken) mailer has sent us raw 8bit/multibyte text data... */
- buf = g_mime_utils_decode_8bit (options, name->str, name->len);
- phrase = buf;
+ buf = g_mime_utils_decode_8bit (options, name, len);
} else {
- phrase = name->str;
+ buf = g_strndup (name, len);
}
/* decode the phrase */
- g_mime_utils_unquote_string (phrase);
- value = g_mime_utils_header_decode_phrase (options, phrase);
- g_free (ia->name);
- ia->name = value;
+ g_mime_utils_unquote_string (buf);
+ value = g_mime_utils_header_decode_phrase (options, buf);
+ g_strstrip (value);
g_free (buf);
+
+ return value;
}
-static InternetAddress *decode_address (GMimeParserOptions *options, const char **in);
-static void
-skip_lwsp (const char **in)
+typedef enum {
+ ALLOW_MAILBOX = 1 << 0,
+ ALLOW_GROUP = 1 << 1,
+ ALLOW_ANY = ALLOW_MAILBOX | ALLOW_GROUP
+} AddressParserFlags;
+
+static gboolean
+decode_route (const char **in)
{
- register const char *inptr = *in;
+ const char *start = *in;
+ const char *inptr = *in;
+ GString *route;
- while (*inptr && is_lwsp (*inptr))
+ route = g_string_new ("");
+
+ do {
+ inptr++;
+
+ g_string_append_c (route, '@');
+ if (!decode_domain (&inptr, route)) {
+ g_string_free (route, TRUE);
+ goto error;
+ }
+
+ skip_cfws (&inptr);
+ if (*inptr == ',') {
+ g_string_append_c (route, ',');
+ inptr++;
+ skip_cfws (&inptr);
+
+ /* obs-domain-lists allow commas with nothing between them... */
+ while (*inptr == ',') {
+ inptr++;
+ skip_cfws (&inptr);
+ }
+ }
+ } while (*inptr == '@');
+
+ g_string_free (route, TRUE);
+ skip_cfws (&inptr);
+
+ if (*inptr != ':') {
+ w(g_warning ("Invalid route domain-list, missing ':': %.*s", inptr - start, start));
+ goto error;
+ }
+
+ *in = inptr;
+
+ return TRUE;
+
+ error:
+
+ while (*inptr && *inptr != ':' && *inptr != '>')
inptr++;
*in = inptr;
+
+ return FALSE;
}
-static InternetAddress *
-decode_addrspec (const char **in)
+static gboolean
+localpart_parse (GString *localpart, const char **in)
{
- InternetAddress *mailbox = NULL;
- const char *start, *inptr, *word;
- gboolean got_local = FALSE;
- GString *addr;
- size_t len;
+ const char *inptr = *in;
+ const char *start = *in;
+ const char *word;
- addr = g_string_new ("");
- inptr = *in;
+ do {
+ word = inptr;
+
+ if (!skip_word (&inptr))
+ goto error;
+
+ if (!g_utf8_validate (word, (size_t) (inptr - word), NULL))
+ goto error;
+
+ g_string_append_len (localpart, word, (size_t) (inptr - word));
+
+ if (!skip_cfws (&inptr))
+ goto error;
+
+ if (*inptr != '.')
+ break;
+
+ g_string_append_c (localpart, *inptr++);
+
+ if (!skip_cfws (&inptr))
+ goto error;
+
+ if (*inptr == '\0')
+ goto error;
+ } while (TRUE);
- decode_lwsp (&inptr);
+ *in = inptr;
- /* some spam bots set their addresses to stuff like: ).ORHH em ca */
- while (*inptr && !(*inptr == '"' || is_atom (*inptr)))
- inptr++;
+ return TRUE;
- start = inptr;
+ error:
+ *in = inptr;
- /* extract the first word of the local-part */
- if ((word = decode_word (&inptr))) {
- g_string_append_len (addr, word, (size_t) (inptr - word));
- decode_lwsp (&inptr);
- got_local = TRUE;
- }
+ return FALSE;
+}
+
+#define COMMA_GREATER_THAN_OR_SEMICOLON ",>;"
+
+static gboolean
+dotatom_parse (GString *str, const char **in, const char *sentinels)
+{
+ const char *atom, *comment;
+ const char *inptr = *in;
+ const char *start = *in;
- /* extract the rest of the local-part */
- while (word && *inptr == '.') {
- /* Note: According to the spec, only a single '.' is
- * allowed between word tokens in the local-part of an
- * addr-spec token, but according to Evolution bug
- * #547969, some Japanese cellphones have email
- * addresses that look like x y somewhere jp */
- do {
+ do {
+ if (!is_atom (*inptr))
+ goto error;
+
+ atom = inptr;
+ while (is_atom (*inptr))
inptr++;
- decode_lwsp (&inptr);
- g_string_append_c (addr, '.');
- } while (*inptr == '.');
- if ((word = decode_word (&inptr)))
- g_string_append_len (addr, word, (size_t) (inptr - word));
+ if (!g_utf8_validate (atom, (size_t) (inptr - atom), NULL))
+ goto error;
- decode_lwsp (&inptr);
- }
-
- if (*inptr == '@') {
- len = addr->len;
+ g_string_append_len (str, atom, (size_t) (inptr - atom));
- g_string_append_c (addr, '@');
- inptr++;
+ comment = inptr;
+ if (!skip_cfws (&inptr))
+ goto error;
- if (!decode_domain (&inptr, addr)) {
- /* drop the @domain and continue as if it weren't there */
- w(g_warning ("Missing domain in addr-spec: %.*s",
- inptr - start, start));
- g_string_truncate (addr, len);
+ if (*inptr != '.') {
+ inptr = comment;
+ break;
}
- } else if (got_local) {
- w(g_warning ("Missing '@' and domain in addr-spec: %.*s",
- inptr - start, start));
- }
+
+ /* skip over the '.' */
+ inptr++;
+
+ if (!skip_cfws (&inptr))
+ goto error;
+
+ /* allow domains to end with a '.', but strip it off */
+ if (*inptr == '\0' || strchr (sentinels, *inptr))
+ break;
+
+ g_string_append_c (str, '.');
+ } while (TRUE);
*in = inptr;
- if (!got_local) {
- w(g_warning ("Invalid addr-spec, missing local-part: %.*s",
- inptr - start, start));
- g_string_free (addr, TRUE);
- return NULL;
- }
+ return TRUE;
- mailbox = g_object_newv (INTERNET_ADDRESS_TYPE_MAILBOX, 0, NULL);
- ((InternetAddressMailbox *) mailbox)->addr = addr->str;
- g_string_free (addr, FALSE);
+ error:
+ *in = inptr;
- return mailbox;
+ return FALSE;
}
-static InternetAddress *
-decode_group (GMimeParserOptions *options, const char **in)
+static gboolean
+domain_literal_parse (GString *str, const char **in)
{
- InternetAddressGroup *group;
- InternetAddress *addr;
- const char *inptr;
+ const char *inptr = *in;
- inptr = *in;
+ g_string_append_c (str, '[');
+ inptr++;
- addr = internet_address_group_new (NULL);
- group = (InternetAddressGroup *) addr;
+ skip_lwsp (&inptr);
- decode_lwsp (&inptr);
- while (*inptr && *inptr != ';') {
- InternetAddress *member;
+ do {
+ while (is_dtext (*inptr))
+ g_string_append_c (str, *inptr++);
- if ((member = decode_address (options, &inptr)))
- _internet_address_group_add_member (group, member);
+ skip_lwsp (&inptr);
- decode_lwsp (&inptr);
- while (*inptr == ',') {
- inptr++;
- decode_lwsp (&inptr);
- if ((member = decode_address (options, &inptr)))
- _internet_address_group_add_member (group, member);
-
- decode_lwsp (&inptr);
- }
- }
+ if (*inptr == '\0')
+ goto error;
+
+ if (*inptr == ']')
+ break;
+
+ if (!is_dtext (*inptr))
+ goto error;
+ } while (TRUE);
+
+ g_string_append_c (str, ']');
+ *in = inptr + 1;
+
+ return TRUE;
+ error:
*in = inptr;
- return addr;
+ return FALSE;
}
static gboolean
-decode_route (const char **in)
+domain_parse (GString *str, const char **in, const char *sentinels)
{
+ if (**in == '[')
+ return domain_literal_parse (str, in);
+
+ return dotatom_parse (str, in, sentinels);
+}
+
+static gboolean
+addrspec_parse (const char **in, const char *sentinels, char **addrspec)
+{
+ const char *inptr = *in;
const char *start = *in;
+ GString *str;
+
+ str = g_string_new ("");
+
+ if (!localpart_parse (str, &inptr))
+ goto error;
+
+ if (*inptr == '\0' || strchr (sentinels, *inptr)) {
+ *addrspec = g_string_free (str, FALSE);
+ *in = inptr;
+ return TRUE;
+ }
+
+ if (*inptr != '@')
+ goto error;
+
+ g_string_append_c (str, *inptr++);
+
+ if (*inptr == '\0')
+ goto error;
+
+ if (!skip_cfws (&inptr))
+ goto error;
+
+ if (*inptr == '\0')
+ goto error;
+
+ if (!domain_parse (str, &inptr, sentinels))
+ goto error;
+
+ *addrspec = g_string_free (str, FALSE);
+ *in = inptr;
+
+ return TRUE;
+
+ error:
+ g_string_free (str, TRUE);
+ *addrspec = NULL;
+ *in = inptr;
+
+ return FALSE;
+}
+
+// TODO: rename to angleaddr_parse??
+static gboolean
+mailbox_parse (GMimeParserOptions *options, const char **in, const char *name, InternetAddress **address)
+{
const char *inptr = *in;
- GString *route;
+ char *addrspec = NULL;
- route = g_string_new ("");
+ /* skip over the '<' */
+ inptr++;
- do {
+ /* Note: check for excessive angle brackets like the example described in section 7.1.2 of rfc7103...
*/
+ if (*inptr == '<') {
+ if (options->addresses != GMIME_RFC_COMPLIANCE_LOOSE)
+ goto error;
+
+ do {
+ inptr++;
+ } while (*inptr == '<');
+ }
+
+ if (*inptr == '\0')
+ goto error;
+
+ if (!skip_cfws (&inptr))
+ goto error;
+
+ if (*inptr == '@') {
+ if (!decode_route (&inptr))
+ goto error;
+
+ if (*inptr != ':')
+ goto error;
+
inptr++;
- g_string_append_c (route, '@');
- if (!decode_domain (&inptr, route)) {
- g_string_free (route, TRUE);
+ if (!skip_cfws (&inptr))
goto error;
- }
+ }
+
+ // Note: The only syntactically correct sentinel token here is the '>', but alas... to deal with the
first example
+ // in section 7.1.5 of rfc7103, we need to at least handle ',' as a sentinel and might as well handle
';' as well
+ // in case the mailbox is within a group address.
+ //
+ // Example: <third example net, fourth example net>
+ if (!addrspec_parse (&inptr, COMMA_GREATER_THAN_OR_SEMICOLON, &addrspec))
+ goto error;
+
+ if (!skip_cfws (&inptr))
+ goto error;
+
+ if (*inptr != '>') {
+ if (options->addresses != GMIME_RFC_COMPLIANCE_LOOSE)
+ goto error;
+ } else {
+ /* skip over the '>' */
+ inptr++;
- decode_lwsp (&inptr);
- if (*inptr == ',') {
- g_string_append_c (route, ',');
- inptr++;
- decode_lwsp (&inptr);
+ /* Note: check for excessive angle brackets like the example described in section 7.1.2 of
rfc7103... */
+ if (*inptr == '>') {
+ if (options->addresses != GMIME_RFC_COMPLIANCE_LOOSE)
+ goto error;
- /* obs-domain-lists allow commas with nothing between them... */
- while (*inptr == ',') {
+ do {
inptr++;
- decode_lwsp (&inptr);
- }
+ } while (*inptr == '>');
}
- } while (*inptr == '@');
-
- g_string_free (route, TRUE);
- decode_lwsp (&inptr);
-
- if (*inptr != ':') {
- w(g_warning ("Invalid route domain-list, missing ':': %.*s", inptr - start, start));
- goto error;
}
- /* eat the ':' */
- *in = inptr + 1;
+ *address = internet_address_mailbox_new (name, addrspec);
+ g_free (addrspec);
+ *in = inptr;
return TRUE;
error:
+ g_free (addrspec);
+ *address = NULL;
+ *in = inptr;
- while (*inptr && *inptr != ':' && *inptr != '>')
- inptr++;
+ return FALSE;
+}
+
+static gboolean address_list_parse (InternetAddressList *list, GMimeParserOptions *options, const char **in,
gboolean is_group);
+
+static gboolean
+group_parse (GMimeParserOptions *options, const char **in, const char *name, InternetAddress **address)
+{
+ InternetAddressGroup *group;
+ const char *inptr = *in;
+
+ /* skip over the ':' */
+ inptr++;
+
+ if (*inptr == '\0') {
+ *address = NULL;
+ *in = inptr;
+
+ return FALSE;
+ }
- if (*inptr == ':')
+ group = (InternetAddressGroup *) internet_address_group_new (name);
+ address_list_parse (group->members, options, &inptr, TRUE);
+
+ if (*inptr != ';') {
+ while (*inptr && *inptr != ';')
+ inptr++;
+ } else {
inptr++;
+ }
+ *address = (InternetAddress *) group;
*in = inptr;
- return FALSE;
+ return TRUE;
}
-static InternetAddress *
-decode_address (GMimeParserOptions *options, const char **in)
+static gboolean
+address_parse (GMimeParserOptions *options, AddressParserFlags flags, const char **in, InternetAddress
**address)
{
- const char *inptr, *start, *word, *comment = NULL;
- InternetAddress *addr = NULL;
- gboolean has_lwsp = FALSE;
- gboolean is_word;
- GString *name;
+ gboolean strict = options->addresses != GMIME_RFC_COMPLIANCE_LOOSE;
+ gboolean trim_leading_quote = FALSE;
+ const char *inptr = *in;
+ const char *start;
+ size_t length;
- decode_lwsp (in);
- start = inptr = *in;
+ if (!skip_cfws (&inptr) || *inptr == '\0')
+ goto error;
- name = g_string_new ("");
+ /* keep track of the start & length of the phrase */
+ start = inptr;
+ length = 0;
- /* Both groups and mailboxes can begin with a phrase (denoting
- * the display name for the address). Collect all of the
- * tokens that make up this name phrase.
- */
while (*inptr) {
- if ((word = decode_word (&inptr))) {
- g_string_append_len (name, word, (size_t) (inptr - word));
-
- check_lwsp:
- word = inptr;
- skip_lwsp (&inptr);
-
- /* is the next token a word token? */
- is_word = *inptr == '"' || is_atom (*inptr);
-
- if (inptr > word && is_word) {
- g_string_append_c (name, ' ');
- has_lwsp = TRUE;
- }
-
- if (is_word)
- continue;
- }
-
- /* specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted-
- * / "," / ";" / ":" / "\" / <"> ; string, to use
- * / "." / "[" / "]" ; within a word.
- */
- if (*inptr == ':') {
- /* rfc2822 group */
- inptr++;
- addr = decode_group (options, &inptr);
- decode_lwsp (&inptr);
- if (*inptr != ';')
- w(g_warning ("Invalid group spec, missing closing ';': %.*s",
- inptr - start, start));
- else
- inptr++;
- break;
- } else if (*inptr == '<') {
- /* rfc2822 angle-addr */
- inptr++;
-
- /* check for obsolete routing... */
- if (*inptr != '@' || decode_route (&inptr)) {
- /* rfc2822 addr-spec */
- addr = decode_addrspec (&inptr);
- }
+ if (strict) {
+ if (!skip_word (&inptr))
+ break;
+ } else if (*inptr == '"') {
+ const char *qstring = inptr;
- decode_lwsp (&inptr);
- if (*inptr != '>') {
- w(g_warning ("Invalid rfc2822 angle-addr, missing closing '>': %.*s",
- inptr - start, start));
+ if (!skip_quoted (&inptr)) {
+ inptr = qstring + 1;
- while (*inptr && *inptr != '>' && *inptr != ',')
- inptr++;
+ skip_lwsp (&inptr);
- if (*inptr == '>')
- inptr++;
- } else {
- inptr++;
+ if (!skip_atom (&inptr))
+ break;
+
+ if (start == qstring)
+ trim_leading_quote = TRUE;
}
+ } else {
+ if (!skip_atom (&inptr))
+ break;
+ }
+
+ length = (size_t) (inptr - start);
+
+ do {
+ if (!skip_cfws (&inptr))
+ goto error;
- /* if comment is non-NULL, we can check for a comment containing a name */
- comment = inptr;
- break;
- } else if (*inptr == '(') {
- /* beginning of a comment, use decode_lwsp() to skip past it */
- decode_lwsp (&inptr);
- } else if (*inptr && strchr ("@,;", *inptr)) {
- if (name->len == 0) {
- if (*inptr == '@') {
- GString *domain;
-
- w(g_warning ("Unexpected address: %s: skipping.", start));
-
- /* skip over @domain? */
- inptr++;
- domain = g_string_new ("");
- decode_domain (&inptr, domain);
- g_string_free (domain, TRUE);
- } else {
- /* empty address */
- }
+ /* Note: some clients don't quote dots in the name */
+ if (*inptr != '.')
break;
- } else if (has_lwsp) {
- /* assume this is just an unquoted special that we should
- treat as part of the name */
- w(g_warning ("Unquoted '%c' in address name: %s: ignoring.", *inptr, start));
- g_string_append_c (name, *inptr);
- inptr++;
-
- goto check_lwsp;
- }
- addrspec:
- /* what we thought was a name was actually an addrspec? */
- g_string_truncate (name, 0);
- inptr = start;
+ inptr++;
+ } while (TRUE);
+ }
+
+ if (!skip_cfws (&inptr))
+ goto error;
+
+ // specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted-
+ // / "," / ";" / ":" / "\" / <"> ; string, to use
+ // / "." / "[" / "]" ; within a word.
+
+ if (*inptr == '\0' || *inptr == ',' || *inptr == '>' || *inptr == ';') {
+ /* we've completely gobbled up an addr-spec w/o a domain */
+ char sentinel = *inptr != '\0' ? *inptr : ',';
+ char sentinels[2] = { sentinel, 0 };
+ char *name, *addrspec;
+
+ /* rewind back to the beginning of the local-part */
+ inptr = start;
+
+ if (!(flags & ALLOW_MAILBOX))
+ goto error;
+
+ if (!addrspec_parse (&inptr, sentinels, &addrspec))
+ goto error;
+
+ skip_lwsp (&inptr);
+
+ if (*inptr == '(') {
+ const char *comment = inptr;
+ char *buf;
+
+ if (!skip_comment (&inptr))
+ goto error;
- addr = decode_addrspec (&inptr);
+ comment++;
- /* if comment is non-NULL, we can check for a comment containing a name */
- comment = inptr;
- break;
- } else if (*inptr == '.') {
- /* This would normally signify that we are
- * decoding the local-part of an addr-spec,
- * but sadly, it is common for broken mailers
- * to forget to quote/encode .'s in the name
- * phrase. */
- g_string_append_c (name, *inptr);
- inptr++;
+ name = decode_name (options, comment, (size_t) ((inptr - 1) - comment));
+ } else {
+ name = g_strdup ("");
+ }
+
+ if (*inptr == '>') {
+ if (strict)
+ goto error;
- goto check_lwsp;
- } else if (*inptr) {
- /* Technically, these are all invalid tokens
- * but in the interest of being liberal in
- * what we accept, we'll ignore them. */
- w(g_warning ("Unexpected char '%c' in address: %s: ignoring.", *inptr, start));
- g_string_append_c (name, *inptr);
inptr++;
-
- goto check_lwsp;
+ }
+
+ *address = internet_address_mailbox_new (name, addrspec);
+ g_free (addrspec);
+ g_free (name);
+ *in = inptr;
+
+ return TRUE;
+ }
+
+ if (*inptr == ':') {
+ /* rfc2822 group address */
+ const char *phrase = start;
+ gboolean retval;
+ char *name;
+
+ if (!(flags & ALLOW_GROUP))
+ goto error;
+
+ if (trim_leading_quote) {
+ phrase++;
+ length--;
+ }
+
+ if (length > 0) {
+ name = decode_name (options, phrase, length);
} else {
- goto addrspec;
+ name = g_strdup ("");
}
+
+ retval = group_parse (options, &inptr, name, address);
+ g_free (name);
+ *in = inptr;
+
+ return retval;
}
- /* Note: will also skip over any comments */
- decode_lwsp (&inptr);
+ if (!(flags & ALLOW_MAILBOX))
+ goto error;
- if (name->len == 0 && comment && inptr > comment) {
- /* missing a name, look for a trailing comment */
- if ((comment = memchr (comment, '(', inptr - comment))) {
- const char *cend;
+ if (*inptr == '@') {
+ /* we're either in the middle of an addr-spec token or we completely gobbled up an addr-spec
w/o a domain */
+ char *name, *addrspec;
+
+ /* rewind back to the beginning of the local-part */
+ inptr = start;
+
+ if (!addrspec_parse (&inptr, COMMA_GREATER_THAN_OR_SEMICOLON, &addrspec))
+ goto error;
+
+ skip_lwsp (&inptr);
+
+ if (*inptr == '(') {
+ const char *comment = inptr;
+ char *buf;
+
+ if (!skip_comment (&inptr))
+ goto error;
+
+ comment++;
+
+ name = decode_name (options, comment, (size_t) ((inptr - 1) - comment));
+ } else {
+ name = g_strdup ("");
+ }
+
+ if (!skip_cfws (&inptr)) {
+ g_free (addrspec);
+ g_free (name);
+ goto error;
+ }
+
+ if (*inptr == '\0') {
+ *address = internet_address_mailbox_new (name, addrspec);
+ g_free (addrspec);
+ g_free (name);
+ *in = inptr;
+
+ return TRUE;
+ }
+
+ if (*inptr == '<') {
+ /* We have an address like "user example com <user example com>"; i.e. the name is an
unquoted string with an '@'. */
+ const char *end;
+
+ if (strict)
+ goto error;
- /* find the end of the comment */
- cend = inptr - 1;
- while (cend > comment && is_lwsp (*cend))
- cend--;
+ end = inptr;
+ while (end > start && is_lwsp (*(end - 1)))
+ end--;
+
+ length = (size_t) (end - start);
+ g_free (addrspec);
+ g_free (name);
+
+ /* fall through to the rfc822 angle-addr token case... */
+ } else {
+ // Note: since there was no '<', there should not be a '>'... but we handle it anyway
in order to
+ // deal with the second Unbalanced Angle Brackets example in section 7.1.3: second
example org>
+ if (*inptr == '>') {
+ if (strict)
+ goto error;
+
+ inptr++;
+ }
- if (*cend == ')')
- cend--;
+ *address = internet_address_mailbox_new (name, addrspec);
+ g_free (addrspec);
+ g_free (name);
+ *in = inptr;
- g_string_append_len (name, comment + 1, (size_t) (cend - comment));
+ return TRUE;
+ }
+ }
+
+ if (*inptr == '<') {
+ /* rfc2822 angle-addr token */
+ const char *phrase = start;
+ gboolean retval;
+ char *name;
+
+ if (trim_leading_quote) {
+ phrase++;
+ length--;
+ }
+
+ if (length > 0) {
+ name = decode_name (options, phrase, length);
+ } else {
+ name = g_strdup ("");
}
+
+ retval = mailbox_parse (options, &inptr, name, address);
+ g_free (name);
+ *in = inptr;
+
+ return retval;
}
- if (addr && name->len > 0)
- _internet_address_decode_name (options, addr, name);
+ error:
+ *address = NULL;
+ *in = inptr;
+
+ return FALSE;
+}
+
+static gboolean
+address_list_parse (InternetAddressList *list, GMimeParserOptions *options, const char **in, gboolean
is_group)
+{
+ InternetAddress *address;
+ const char *inptr;
+
+ if (!skip_cfws (in))
+ return FALSE;
+
+ inptr = *in;
+
+ if (*inptr == '\0')
+ return FALSE;
- g_string_free (name, TRUE);
+ while (*inptr) {
+ if (is_group && *inptr == ';')
+ break;
+
+ if (!address_parse (options, ALLOW_ANY, &inptr, &address)) {
+ /* skip this address... */
+ while (*inptr && *inptr != ',' && (!is_group || *inptr != ';'))
+ inptr++;
+ } else {
+ _internet_address_list_add (list, address);
+ }
+
+ /* Note: we loop here in case there are any null addresses between commas */
+ do {
+ if (!skip_cfws (&inptr)) {
+ *in = inptr;
+
+ return FALSE;
+ }
+
+ if (*inptr != ',')
+ break;
+
+ inptr++;
+ } while (TRUE);
+ }
*in = inptr;
- return addr;
+ return TRUE;
}
@@ -1670,45 +1969,16 @@ decode_address (GMimeParserOptions *options, const char **in)
InternetAddressList *
internet_address_list_parse (GMimeParserOptions *options, const char *str)
{
- InternetAddressList *addrlist;
+ InternetAddressList *list;
const char *inptr = str;
- InternetAddress *addr;
- const char *start;
- addrlist = internet_address_list_new ();
-
- while (inptr && *inptr) {
- start = inptr;
-
- if ((addr = decode_address (options, &inptr))) {
- _internet_address_list_add (addrlist, addr);
- } else {
- w(g_warning ("Invalid or incomplete address: %.*s",
- inptr - start, start));
- }
-
- decode_lwsp (&inptr);
- if (*inptr == ',') {
- inptr++;
- decode_lwsp (&inptr);
-
- /* obs-mbox-list and obs-addr-list allow for empty members (commas with nothing
between them) */
- while (*inptr == ',') {
- inptr++;
- decode_lwsp (&inptr);
- }
- } else if (*inptr) {
- w(g_warning ("Parse error at '%s': expected ','", inptr));
- /* try skipping to the next address */
- if ((inptr = strchr (inptr, ',')))
- inptr++;
- }
- }
+ g_return_val_if_fail (str != NULL, NULL);
- if (addrlist->array->len == 0) {
- g_object_unref (addrlist);
- addrlist = NULL;
+ list = internet_address_list_new ();
+ if (!address_list_parse (list, options, &inptr, FALSE) || list->array->len == 0) {
+ g_object_unref (list);
+ return NULL;
}
- return addrlist;
+ return list;
}
diff --git a/tests/test-mime.c b/tests/test-mime.c
index 038cb61..7b2fa85 100644
--- a/tests/test-mime.c
+++ b/tests/test-mime.c
@@ -69,9 +69,6 @@ static struct {
{ "fejj helixcode com",
"fejj helixcode com",
"fejj helixcode com" },
- { "this is\n\ta folded name <folded name com>",
- "this is a folded name <folded name com>",
- "this is a folded name <folded name com>" },
{ "Jeffrey Stedfast <fejj helixcode com>",
"Jeffrey Stedfast <fejj helixcode com>",
"Jeffrey Stedfast <fejj helixcode com>" },
@@ -118,8 +115,8 @@ static struct {
"Charles Kerr <charles@[127.0.0.1]>",
"Charles Kerr <charles@[127.0.0.1]>" },
{ "Charles <charles@[127..0.1]>",
- "Charles <charles@[127.0.1]>",
- "Charles <charles@[127.0.1]>" },
+ "Charles <charles@[127..0.1]>",
+ "Charles <charles@[127..0.1]>" },
{ "Charles,, likes illegal commas <charles superpimp org>",
"Charles, likes illegal commas <charles superpimp org>",
"Charles, likes illegal commas <charles superpimp org>" },
@@ -192,8 +189,8 @@ static struct {
"TEST <p p org>",
"TEST <p p org>" },
{ "sdfasf wp pl,c tert wp pl,sffdg rtre op pl",
- "sdfasf wp pl, c, sffdg rtre op pl",
- "sdfasf wp pl, c, sffdg rtre op pl" },
+ "sdfasf wp pl, sffdg rtre op pl",
+ "sdfasf wp pl, sffdg rtre op pl" },
/* obsolete routing address syntax tests */
{ "<@route:user domain com>",
@@ -210,7 +207,7 @@ static struct {
const char *encoded;
} broken_addrspec[] = {
{ "\"Biznes=?ISO-8859-2?Q?_?=INTERIA.PL\"=?ISO-8859-2?Q?_?=<biuletyny firma interia pl>",
- "\"Biznes INTERIA.PL \" <biuletyny firma interia pl>",
+ "\"Biznes INTERIA.PL\" <biuletyny firma interia pl>",
"\"Biznes INTERIA.PL\" <biuletyny firma interia pl>", },
/* UTF-8 sequence split between multiple encoded-word tokens */
{ "=?utf-8?Q?{#D=C3=A8=C3=A9=C2=A3=C3=A5=C3=BD_M$=C3=A1=C3?= =?utf-8?Q?=AD.=C3=A7=C3=B8m@#}?= <user
domain com>",
@@ -240,7 +237,7 @@ test_addrspec (GMimeParserOptions *options, gboolean test_broken)
testsuite_check ("addrspec[%u]", i);
try {
if (!(addrlist = internet_address_list_parse (options, addrspec[i].input)))
- throw (exception_new ("could not parse addr-spec"));
+ throw (exception_new ("could not parse: %s", addrspec[i].input));
str = internet_address_list_to_string (addrlist, FALSE);
if (strcmp (addrspec[i].display, str) != 0)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]