diff --git a/cassandane/tiny-tests/JMAPEmail/email_import_zerobyte b/cassandane/tiny-tests/JMAPEmail/email_import_zerobyte index 050e0aee1a..f0b8c5381e 100644 --- a/cassandane/tiny-tests/JMAPEmail/email_import_zerobyte +++ b/cassandane/tiny-tests/JMAPEmail/email_import_zerobyte @@ -2,13 +2,14 @@ use Cassandane::Tiny; sub test_email_import_zerobyte - :min_version_3_1 :needs_component_sieve + :needs_component_sieve { my ($self) = @_; my $jmap = $self->{jmap}; - # A bogus email with an unencoded zero byte - my $email = <<"EOF"; + my @testCases = ({ + desc => "unencoded zero byte in MIME body", + mime => <<"EOF", From: \"Some Example Sender\" \r\n To: baseball\@local\r\n Subject: test email\r\n @@ -18,32 +19,38 @@ Content-Type: text/plain; charset="UTF-8"\r\n \r\n This is a test email with a \x{0}-byte.\r\n EOF + }, { + desc => "unencoded zero byte in MIME headers", + mime => <<"EOF", +From: from\@local\r\n +To: to\@local\r\n +Subject: subject with \x{0}-byte\r\n +Date: Wed, 7 Dec 2016 22:11:11 +1100\r\n +MIME-Version: 1.0\r\n +Content-Type: text/plain; charset="UTF-8"\r\n +\r\n +This is a test email.\r\n +EOF + }); - my $data = $jmap->Upload($email, "message/rfc822"); - my $blobid = $data->{blobId}; - - xlog $self, "create drafts mailbox"; - my $res = $jmap->CallMethods([ - ['Mailbox/set', { create => { "1" => { - name => "drafts", - parentId => undef, - role => "drafts" - }}}, "R1"] - ]); - my $draftsmbox = $res->[0][1]{created}{"1"}{id}; - $self->assert_not_null($draftsmbox); + foreach (@testCases) { + xlog $self, "Upload MIME blob for test '$_->{desc}'"; + my $blobId = $jmap->Upload($_->{mime}, "message/rfc822")->{blobId}; - xlog $self, "import email from blob $blobid"; - $res = $jmap->CallMethods([['Email/import', { - emails => { - "1" => { - blobId => $blobid, - mailboxIds => {$draftsmbox => JSON::true}, - keywords => { - '$draft' => JSON::true, + xlog $self, "Import email from blob $blobId"; + my $res = $jmap->CallMethods([ [ + 'Email/import', + { + emails => { + "1" => { + blobId => $blobId, + mailboxIds => { '$inbox' => JSON::true }, }, }, }, - }, "R1"]]); - $self->assert_str_equals("invalidEmail", $res->[0][1]{notCreated}{1}{type}); + "R1" + ] ]); + $self->assert_str_equals("invalidEmail", + $res->[0][1]{notCreated}{1}{type}); + } } diff --git a/imap/message.c b/imap/message.c index 5328b59d4a..b33b125894 100644 --- a/imap/message.c +++ b/imap/message.c @@ -115,7 +115,8 @@ static int message_parse_headers(struct msg *msg, struct body *body, const char *defaultContentType, strarray_t *boundaries, - const char *efname); + const char *efname, + int *sawboundaryp); static void message_parse_address(const char *hdr, struct address **addrp); static void message_parse_encoding(const char *hdr, char **hdrp); @@ -522,13 +523,15 @@ EXPORTED int message_parse_mapped(const char *msg_base, unsigned long msg_len, struct body *body, const char *efname) { struct msg msg; + int r = 0; msg.base = msg_base; msg.len = msg_len; msg.offset = 0; msg.encode = 0; - message_parse_body(&msg, body, DEFAULT_CONTENT_TYPE, NULL, efname); + r = message_parse_body(&msg, body, DEFAULT_CONTENT_TYPE, NULL, efname); + if (r) goto done; body->filesize = msg_len; @@ -536,22 +539,22 @@ EXPORTED int message_parse_mapped(const char *msg_base, unsigned long msg_len, if (body->filesize != body->header_size + body->content_size) { if (efname) - /* XXX IOERROR but only LOG_NOTICE?? */ - xsyslog(LOG_NOTICE, "IOERROR: size mismatch on parse", - "guid=<%s> filename=<%s> " - "filesize=<%" PRIu32 "> bodysize=<%" PRIu32 ">", - message_guid_encode(&body->guid), efname, - body->filesize, - body->header_size + body->content_size); + xsyslog(LOG_ERR, "IOERROR: size mismatch on parse", + "guid=<%s> filename=<%s>" + " filesize=<%" PRIu32 "> bodysize=<%" PRIu32 ">", + message_guid_encode(&body->guid), efname, + body->filesize, + body->header_size + body->content_size); else - xsyslog(LOG_NOTICE, "IOERROR: size mismatch on parse", - "guid=<%s> " - "filesize=<%" PRIu32 "> bodysize=<%" PRIu32 ">", - message_guid_encode(&body->guid), body->filesize, - body->header_size + body->content_size); + xsyslog(LOG_ERR, "IOERROR: size mismatch on parse", + "guid=<%s>" + " filesize=<%" PRIu32 "> bodysize=<%" PRIu32 ">", + message_guid_encode(&body->guid), body->filesize, + body->header_size + body->content_size); } - return 0; +done: + return r; } /* @@ -777,7 +780,8 @@ static int message_parse_body(struct msg *msg, struct body *body, const char *efname) { strarray_t newboundaries = STRARRAY_INITIALIZER; - int sawboundary; + int sawboundary = 0; + int r = 0; memset(body, 0, sizeof(struct body)); @@ -788,9 +792,9 @@ static int message_parse_body(struct msg *msg, struct body *body, buf_ensure(&body->cacheheaders, 1024); } - - sawboundary = message_parse_headers(msg, body, defaultContentType, - boundaries, efname); + r = message_parse_headers(msg, body, defaultContentType, boundaries, + efname, &sawboundary); + if (r) goto done; /* Charset id and encoding id are stored in the binary * bodystructure, but we don't have that one here. */ @@ -846,10 +850,10 @@ static int message_parse_body(struct msg *msg, struct body *body, } } +done: /* Free up boundary storage if necessary */ strarray_fini(&newboundaries); - - return 0; + return r; } /* @@ -858,15 +862,17 @@ static int message_parse_body(struct msg *msg, struct body *body, static int message_parse_headers(struct msg *msg, struct body *body, const char *defaultContentType, strarray_t *boundaries, - const char *efname) + const char *efname, + int *sawboundaryp) { struct buf headers = BUF_INITIALIZER; char *next; - int len; - int sawboundary = 0; + size_t len; + if (sawboundaryp) *sawboundaryp = 0; uint32_t maxlines = config_getint(IMAPOPT_MAXHEADERLINES); int have_max = 0; const char *value; + int r = 0; body->header_offset = msg->offset; @@ -877,7 +883,12 @@ static int message_parse_headers(struct msg *msg, struct body *body, (next[-1] != '\n' || (*next != '\r' || next[1] != '\n'))) { - len = strlen(next); + len = buf_len(&headers) - (next - buf_base(&headers)); + if (len != strlen(next)) { + // NUL in the MIME headers is a fatal error + r = IMAP_MESSAGE_CONTAINSNULL; + goto done; + } if (next[-1] == '\n' && *next == '-' && message_pendingboundary(next, len, boundaries)) { @@ -891,7 +902,7 @@ static int message_parse_headers(struct msg *msg, struct body *body, else { *next = '\0'; } - sawboundary = 1; + if (sawboundaryp) *sawboundaryp = 1; break; } } @@ -1038,8 +1049,10 @@ static int message_parse_headers(struct msg *msg, struct body *body, if (!body->type) { message_parse_bodytype(defaultContentType, body); } + +done: buf_free(&headers); - return sawboundary; + return r; } /* @@ -4956,7 +4969,8 @@ static int body_foreach_section(struct body *body, struct message *message, msg.len = body->header_size; msg.offset = 0; msg.encode = 0; - message_parse_headers(&msg, tmpbody, "text/plain", &boundaries, NULL); + message_parse_headers(&msg, tmpbody, "text/plain", &boundaries, + NULL, NULL); disposition = tmpbody->disposition; disposition_params = tmpbody->disposition_params;