]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Merge RFC 2231 header continuations in BODY/BODYSTRUCTURE replies. Also use
authorTimo Sirainen <tss@iki.fi>
Tue, 24 Jun 2008 13:49:36 +0000 (16:49 +0300)
committerTimo Sirainen <tss@iki.fi>
Tue, 24 Jun 2008 13:49:36 +0000 (16:49 +0300)
them internally while parsing messages.

--HG--
branch : HEAD

src/lib-imap/imap-bodystructure.c
src/lib-mail/Makefile.am
src/lib-mail/message-decoder.c
src/lib-mail/message-parser.c
src/lib-mail/rfc2231-parser.c [new file with mode: 0644]
src/lib-mail/rfc2231-parser.h [new file with mode: 0644]
src/tests/test-mail.c

index f4066f12025d9f405b2ac12188aba5cebe21f6da..3ce764f3667aa698316d633a6c78b9e91e6ba480 100644 (file)
@@ -6,6 +6,7 @@
 #include "str.h"
 #include "message-parser.h"
 #include "rfc822-parser.h"
+#include "rfc2231-parser.h"
 #include "imap-parser.h"
 #include "imap-quote.h"
 #include "imap-envelope.h"
@@ -37,7 +38,7 @@ static void parse_content_type(struct message_part_body_data *data,
                               struct message_header_line *hdr)
 {
        struct rfc822_parser_context parser;
-       const char *key, *value;
+       const char *value, *const *results;
        string_t *str;
        unsigned int i;
        bool charset_found = FALSE;
@@ -64,14 +65,15 @@ static void parse_content_type(struct message_part_body_data *data,
 
        /* parse parameters and save them */
        str_truncate(str, 0);
-       while (rfc822_parse_content_param(&parser, &key, &value) > 0) {
-               if (strcasecmp(key, "charset") == 0)
+       (void)rfc2231_parse(&parser, &results);
+       for (; *results != NULL; results += 2) {
+               if (strcasecmp(results[0], "charset") == 0)
                        charset_found = TRUE;
 
                str_append_c(str, ' ');
-               imap_quote_append_string(str, key, TRUE);
+               imap_quote_append_string(str, results[0], TRUE);
                str_append_c(str, ' ');
-               imap_quote_append_string(str, value, TRUE);
+               imap_quote_append_string(str, results[1], TRUE);
        }
 
        if (!charset_found &&
@@ -106,7 +108,7 @@ static void parse_content_disposition(struct message_part_body_data *data,
                                      struct message_header_line *hdr)
 {
        struct rfc822_parser_context parser;
-       const char *key, *value;
+       const char *const *results;
        string_t *str;
 
        rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
@@ -120,11 +122,12 @@ static void parse_content_disposition(struct message_part_body_data *data,
 
        /* parse parameters and save them */
        str_truncate(str, 0);
-       while (rfc822_parse_content_param(&parser, &key, &value) > 0) {
+       (void)rfc2231_parse(&parser, &results);
+       for (; *results != NULL; results += 2) {
                str_append_c(str, ' ');
-               imap_quote_append_string(str, key, TRUE);
+               imap_quote_append_string(str, results[0], TRUE);
                str_append_c(str, ' ');
-               imap_quote_append_string(str, value, TRUE);
+               imap_quote_append_string(str, results[1], TRUE);
        }
        if (str_len(str) > 0) {
                data->content_disposition_params =
index 8655df167122e0fc31a03ff1852af55e6916663d..0f1b651c5e64e810c57891246a6ef0ab59bbb8d0 100644 (file)
@@ -19,6 +19,7 @@ libmail_a_SOURCES = \
        message-send.c \
        message-size.c \
        quoted-printable.c \
+       rfc2231-parser.c \
        rfc822-parser.c
 
 headers = \
@@ -37,6 +38,7 @@ headers = \
        message-send.h \
        message-size.h \
        quoted-printable.h \
+       rfc2231-parser.h \
        rfc822-parser.h
 
 if INSTALL_HEADERS
index 8d4e770b261df92a658a4b45c5b139d233863244..3c28111c8656fbd043115f870b5058a97ca279fe 100644 (file)
@@ -8,6 +8,7 @@
 #include "charset-utf8.h"
 #include "quoted-printable.h"
 #include "rfc822-parser.h"
+#include "rfc2231-parser.h"
 #include "message-parser.h"
 #include "message-header-decode.h"
 #include "message-decoder.h"
@@ -112,7 +113,7 @@ parse_content_type(struct message_decoder_context *ctx,
                   struct message_header_line *hdr)
 {
        struct rfc822_parser_context parser;
-       const char *key, *value;
+       const char *const *results;
        string_t *str;
 
        if (ctx->content_charset != NULL)
@@ -124,10 +125,11 @@ parse_content_type(struct message_decoder_context *ctx,
        if (rfc822_parse_content_type(&parser, str) <= 0)
                return;
 
-       while (rfc822_parse_content_param(&parser, &key, &value) > 0) {
-               if (strcasecmp(key, "charset") == 0) {
-                       ctx->content_charset = i_strdup(value);
-                       ctx->charset_utf8 = charset_is_utf8(value);
+       (void)rfc2231_parse(&parser, &results);
+       for (; *results != NULL; results += 2) {
+               if (strcasecmp(results[0], "charset") == 0) {
+                       ctx->content_charset = i_strdup(results[1]);
+                       ctx->charset_utf8 = charset_is_utf8(results[1]);
                        break;
                }
        }
index 057a6b7bc7a25265ec4e1d77c0e2a853b0948a3f..bef73345f25878fed0380d1203c67434ea89eb1a 100644 (file)
@@ -4,6 +4,7 @@
 #include "str.h"
 #include "istream.h"
 #include "rfc822-parser.h"
+#include "rfc2231-parser.h"
 #include "message-parser.h"
 
 /* RFC-2046 requires boundaries are max. 70 chars + "--" prefix + "--" suffix.
@@ -410,7 +411,7 @@ static void parse_content_type(struct message_parser_ctx *ctx,
                               struct message_header_line *hdr)
 {
        struct rfc822_parser_context parser;
-       const char *key, *value;
+       const char *const *results;
        string_t *content_type;
 
        if (ctx->part_seen_content_type)
@@ -441,9 +442,11 @@ static void parse_content_type(struct message_parser_ctx *ctx,
            ctx->last_boundary != NULL)
                return;
 
-       while (rfc822_parse_content_param(&parser, &key, &value) > 0) {
-               if (strcasecmp(key, "boundary") == 0) {
-                       ctx->last_boundary = p_strdup(ctx->parser_pool, value);
+       (void)rfc2231_parse(&parser, &results);
+       for (; *results != NULL; results += 2) {
+               if (strcasecmp(results[0], "boundary") == 0) {
+                       ctx->last_boundary =
+                               p_strdup(ctx->parser_pool, results[1]);
                        break;
                }
        }
diff --git a/src/lib-mail/rfc2231-parser.c b/src/lib-mail/rfc2231-parser.c
new file mode 100644 (file)
index 0000000..aacd058
--- /dev/null
@@ -0,0 +1,161 @@
+/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "rfc822-parser.h"
+#include "rfc2231-parser.h"
+
+#include <stdlib.h>
+
+struct rfc2231_parameter {
+       const char *key, *value;
+       unsigned int idx;
+       bool extended;
+};
+
+static int rfc2231_parameter_cmp(const void *p1, const void *p2)
+{
+       const struct rfc2231_parameter *r1 = p1, *r2 = p2;
+       int ret;
+
+       ret = strcmp(r1->key, r2->key);
+       if (ret != 0)
+               return ret;
+
+       return r1->idx < r2->idx ? -1 :
+               (r1-> idx > r2->idx ? 1 : 0);
+}
+
+static void rfc2231_escape(string_t *dest, const char *src)
+{
+       for (; *src != '\0'; src++) {
+               if (*src == '%')
+                       str_append(dest, "%25");
+               else
+                       str_append_c(dest, *src);
+       }
+}
+
+int rfc2231_parse(struct rfc822_parser_context *ctx,
+                 const char *const **result_r)
+{
+       ARRAY_TYPE(const_string) result;
+       ARRAY_DEFINE(rfc2231_params_arr, struct rfc2231_parameter);
+       struct rfc2231_parameter rfc2231_param, *rfc2231_params;
+       const char *key, *value, *p, *p2;
+       string_t *str;
+       unsigned int i, j, count, next, next_idx;
+       bool ok, have_extended;
+       int ret;
+
+       /* Get a list of all parameters. RFC 2231 uses key*<n>[*]=value pairs,
+          which we want to merge to a key[*]=value pair. Save them to a
+          separate array. */
+       memset(&rfc2231_param, 0, sizeof(rfc2231_param));
+       t_array_init(&result, 8);
+       t_array_init(&rfc2231_params_arr, 8);
+       while ((ret = rfc822_parse_content_param(ctx, &key, &value)) > 0) {
+               p = strchr(key, '*');
+               if (p != NULL) {
+                       p2 = p++;
+                       rfc2231_param.idx = 0;
+                       for (; *p >= '0' && *p <= '9'; p++) {
+                               rfc2231_param.idx =
+                                       rfc2231_param.idx*10 + *p - '0';
+                       }
+                       if (*p != '*')
+                               rfc2231_param.extended = FALSE;
+                       else {
+                               rfc2231_param.extended = TRUE;
+                               p++;
+                       }
+                       if (*p != '\0')
+                               p = NULL;
+                       else {
+                               rfc2231_param.key = t_strdup_until(key, p2);
+                               rfc2231_param.value = value;
+                               array_append(&rfc2231_params_arr,
+                                            &rfc2231_param, 1);
+                       }
+               }
+               if (p == NULL) {
+                       array_append(&result, &key, 1);
+                       array_append(&result, &value, 1);
+               }
+       }
+
+       if (array_count(&rfc2231_params_arr) == 0) {
+               /* No RFC 2231 parameters */
+               (void)array_append_space(&result); /* NULL-terminate */
+               *result_r = array_idx(&result, 0);
+               return ret;
+       }
+
+       /* Merge the RFC 2231 parameters. Since their order isn't guaranteed to
+          be ascending, start by sorting them. */
+       rfc2231_params = array_get_modifiable(&rfc2231_params_arr, &count);
+       qsort(rfc2231_params, count, sizeof(*rfc2231_params),
+             rfc2231_parameter_cmp);
+
+       /* keys are now sorted primarily by their name and secondarily by
+          their index. If any indexes are missing, fallback to assuming
+          these aren't RFC 2231 encoded parameters. */
+       str = t_str_new(64);
+       for (i = 0; i < count; i = next) {
+               ok = TRUE;
+               have_extended = FALSE;
+               next_idx = 0;
+               for (j = i; j < count; j++) {
+                       if (strcasecmp(rfc2231_params[i].key,
+                                      rfc2231_params[j].key) != 0)
+                               break;
+                       if (rfc2231_params[j].idx != next_idx) {
+                               /* missing indexes */
+                               ok = FALSE;
+                       }
+                       if (rfc2231_params[j].extended)
+                               have_extended = TRUE;
+                       next_idx++;
+               }
+               next = j;
+
+               if (!ok) {
+                       /* missing indexes */
+                       for (j = i; j < next; j++) {
+                               key = t_strdup_printf(
+                                       rfc2231_params[j].extended ?
+                                       "%s*%u*" : "%s*%u",
+                                       rfc2231_params[j].key,
+                                       rfc2231_params[j].idx);
+                               array_append(&result, &key, 1);
+                               array_append(&result,
+                                            &rfc2231_params[j].value, 1);
+                       }
+               } else {
+                       /* everything was successful */
+                       str_truncate(str, 0);
+                       if (!rfc2231_params[i].extended && have_extended)
+                               str_append(str, "''");
+                       for (j = i; j < next; j++) {
+                               if (!rfc2231_params[j].extended &&
+                                   have_extended) {
+                                       rfc2231_escape(str,
+                                                      rfc2231_params[j].value);
+                               } else {
+                                       str_append(str,
+                                                  rfc2231_params[j].value);
+                               }
+                       }
+                       key = rfc2231_params[i].key;
+                       if (have_extended)
+                               key = t_strconcat(key, "*", NULL);
+                       value = t_strdup(str_c(str));
+                       array_append(&result, &key, 1);
+                       array_append(&result, &value, 1);
+               }
+       }
+       (void)array_append_space(&result); /* NULL-terminate */
+       *result_r = array_idx(&result, 0);
+       return ret;
+}
diff --git a/src/lib-mail/rfc2231-parser.h b/src/lib-mail/rfc2231-parser.h
new file mode 100644 (file)
index 0000000..08f53e5
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef RFC2231_PARSER_H
+#define RFC2231_PARSER_H
+
+/* Parse all content parameters using rfc822_parse_content_param() and return
+   them as a NULL-terminated [key, value] array. RFC 2231-style continuations
+   are merged to a single key. Returns -1 if some of the input was invalid
+   (but valid key/value pairs are still returned), 0 if everything looked ok. */
+int rfc2231_parse(struct rfc822_parser_context *ctx,
+                 const char *const **result_r);
+
+#endif
index 04b65102d761f4fed437005ba497f8e312f2c50d..fcac7c79bda31a55278861736450718afb198294 100644 (file)
@@ -3,6 +3,8 @@
 #include "lib.h"
 #include "str.h"
 #include "istream.h"
+#include "rfc822-parser.h"
+#include "rfc2231-parser.h"
 #include "message-address.h"
 #include "message-date.h"
 #include "message-parser.h"
@@ -249,6 +251,45 @@ static void test_message_parser(void)
        test_out("message_parser()", success);
 }
 
+static void test_rfc2231_parser(void)
+{
+       const char *input =
+               "; key*2=ba%"
+               "; key2*0=a"
+               "; key3*0*=us-ascii'en'xyz"
+               "; key*0=\"foo\""
+               "; key2*1*=b%25"
+               "; key3*1=plop%"
+               "; key*1=baz";
+       const char *output[] = {
+               "key",
+               "foobazba%",
+               "key2*",
+               "''ab%25",
+               "key3*",
+               "us-ascii'en'xyzplop%25",
+               NULL
+       };
+       struct rfc822_parser_context parser;
+       const char *const *result;
+       unsigned int i;
+       bool success;
+
+       rfc822_parser_init(&parser, (const void *)input, strlen(input), NULL);
+       if (rfc2231_parse(&parser, &result) < 0)
+               success = FALSE;
+       else {
+               success = TRUE;
+               for (i = 0; output[i] != NULL && result[i] != NULL; i++) {
+                       if (strcmp(output[i], result[i]) != 0)
+                               break;
+               }
+               if (output[i] != NULL || result[i] != NULL)
+                       success = FALSE;
+       }
+       test_out("rfc2231_parse()", success);
+}
+
 static void filter_callback(struct message_header_line *hdr,
                            bool *matched, void *context ATTR_UNUSED)
 {
@@ -309,6 +350,7 @@ int main(void)
        test_message_address();
        test_message_date_parse();
        test_message_parser();
+       test_rfc2231_parser();
        test_istream_filter();
        return test_deinit();
 }