summaryrefslogtreecommitdiffstats
path: root/test/test_multipart.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'test/test_multipart.cpp')
-rw-r--r--test/test_multipart.cpp1940
1 files changed, 1940 insertions, 0 deletions
diff --git a/test/test_multipart.cpp b/test/test_multipart.cpp
new file mode 100644
index 0000000..e423d2d
--- /dev/null
+++ b/test/test_multipart.cpp
@@ -0,0 +1,1940 @@
+/***************************************************************************
+ * Copyright (c) 2009-2010 Open Information Security Foundation
+ * Copyright (c) 2010-2013 Qualys, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * - Neither the name of the Qualys, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ***************************************************************************/
+
+/**
+ * @file
+ * @author Ivan Ristic <ivanr@webkreator.com>
+ */
+
+#include <iostream>
+#include <gtest/gtest.h>
+#include <htp/htp_private.h>
+#include "test.h"
+
+#include <htp/htp_multipart_private.h>
+
+class Multipart : public testing::Test {
+protected:
+
+ void parseRequest(char *headers[], char *data[]) {
+ size_t i;
+
+ // Calculate body length.
+ size_t bodyLen = 0;
+ for (i = 0; data[i] != NULL; i++) {
+ bodyLen += strlen(data[i]);
+ }
+
+ // Open connection
+ connp = htp_connp_create(cfg);
+ htp_connp_open(connp, "127.0.0.1", 32768, "127.0.0.1", 80, NULL);
+
+ // Send headers.
+
+ for (i = 0; headers[i] != NULL; i++) {
+ htp_connp_req_data(connp, NULL, headers[i], strlen(headers[i]));
+ }
+
+ char buf[32];
+ snprintf(buf, sizeof (buf), "Content-Length: %zu\r\n", bodyLen);
+ htp_connp_req_data(connp, NULL, buf, strlen(buf));
+
+ htp_connp_req_data(connp, NULL, (void *) "\r\n", 2);
+
+ // Send data.
+ for (i = 0; data[i] != NULL; i++) {
+ htp_connp_req_data(connp, NULL, data[i], strlen(data[i]));
+ }
+
+ ASSERT_EQ(1, htp_list_size(connp->conn->transactions));
+
+ tx = (htp_tx_t *) htp_list_get(connp->conn->transactions, 0);
+ ASSERT_TRUE(tx != NULL);
+
+ ASSERT_TRUE(tx->request_mpartp != NULL);
+ mpartp = tx->request_mpartp;
+ body = htp_mpartp_get_multipart(mpartp);
+ ASSERT_TRUE(body != NULL);
+ }
+
+ void parseRequestThenVerify(char *headers[], char *data[]) {
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_TRUE(htp_list_size(body->parts) == 3);
+
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_INCOMPLETE);
+
+ // Field 1
+ htp_multipart_part_t *field1 = (htp_multipart_part_t *) htp_list_get(body->parts, 0);
+ ASSERT_TRUE(field1 != NULL);
+ ASSERT_EQ(MULTIPART_PART_TEXT, field1->type);
+ ASSERT_TRUE(field1->name != NULL);
+ ASSERT_TRUE(bstr_cmp_c(field1->name, "field1") == 0);
+ ASSERT_TRUE(field1->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(field1->value, "ABCDEF") == 0);
+
+ // File 1
+ htp_multipart_part_t *file1 = (htp_multipart_part_t *) htp_list_get(body->parts, 1);
+ ASSERT_TRUE(file1 != NULL);
+ ASSERT_EQ(MULTIPART_PART_FILE, file1->type);
+ ASSERT_TRUE(file1->name != NULL);
+ ASSERT_TRUE(bstr_cmp_c(file1->name, "file1") == 0);
+ ASSERT_TRUE(file1->file->filename != NULL);
+ ASSERT_TRUE(bstr_cmp_c(file1->file->filename, "file.bin") == 0);
+
+ // Field 2
+ htp_multipart_part_t *field2 = (htp_multipart_part_t *) htp_list_get(body->parts, 2);
+ ASSERT_TRUE(field2 != NULL);
+ ASSERT_EQ(MULTIPART_PART_TEXT, field2->type);
+ ASSERT_TRUE(field2->name != NULL);
+ ASSERT_TRUE(bstr_cmp_c(field2->name, "field2") == 0);
+ ASSERT_TRUE(field2->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(field2->value, "GHIJKL") == 0);
+ }
+
+ void parseParts(char *parts[]) {
+ mpartp = htp_mpartp_create(cfg, bstr_dup_c("0123456789"), 0 /* flags */);
+
+ size_t i = 0;
+ for (;;) {
+ if (parts[i] == NULL) break;
+ htp_mpartp_parse(mpartp, parts[i], strlen(parts[i]));
+ i++;
+ }
+
+ htp_mpartp_finalize(mpartp);
+
+ body = htp_mpartp_get_multipart(mpartp);
+ ASSERT_TRUE(body != NULL);
+ }
+
+ void parsePartsThenVerify(char *parts[]) {
+ parseParts(parts);
+
+ // Examine the result
+ body = htp_mpartp_get_multipart(mpartp);
+ ASSERT_TRUE(body != NULL);
+
+ ASSERT_TRUE(htp_list_size(body->parts) == 2);
+
+ for (size_t i = 0, n = htp_list_size(body->parts); i < n; i++) {
+ htp_multipart_part_t *part = (htp_multipart_part_t *) htp_list_get(body->parts, i);
+
+ switch (i) {
+ case 0:
+ ASSERT_EQ(MULTIPART_PART_TEXT, part->type);
+ ASSERT_TRUE(part->name != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->name, "field1") == 0);
+ ASSERT_TRUE(part->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->value, "ABCDEF") == 0);
+ break;
+ case 1:
+ ASSERT_EQ(MULTIPART_PART_TEXT, part->type);
+ ASSERT_TRUE(part->name != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->name, "field2") == 0);
+ ASSERT_TRUE(part->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->value, "GHIJKL") == 0);
+ break;
+ }
+ }
+ }
+
+ virtual void SetUp() {
+ cfg = htp_config_create();
+ htp_config_set_server_personality(cfg, HTP_SERVER_APACHE_2);
+ htp_config_register_multipart_parser(cfg);
+
+ connp = NULL;
+ mpartp = NULL;
+ body = NULL;
+ tx = NULL;
+ }
+
+ virtual void TearDown() {
+ if (connp != NULL) {
+ htp_connp_destroy_all(connp);
+ } else if (mpartp != NULL) {
+ htp_mpartp_destroy(mpartp);
+ }
+
+ if (cfg != NULL) {
+ htp_config_destroy(cfg);
+ }
+ }
+
+ htp_tx_t *tx;
+
+ htp_connp_t *connp;
+
+ htp_multipart_t *body;
+
+ htp_mpartp_t *mpartp;
+
+ htp_cfg_t *cfg;
+};
+
+TEST_F(Multipart, Test1) {
+ mpartp = htp_mpartp_create(cfg, bstr_dup_c("---------------------------41184676334"), 0 /* flags */);
+
+ char *parts[999];
+
+ size_t i = 0;
+ parts[i++] = (char *) "-----------------------------41184676334\r\n";
+ parts[i++] = (char *) "Content-Disposition: form-data;\n name=\"field1\"\r\n";
+ parts[i++] = (char *) "\r\n";
+ parts[i++] = (char *) "0123456789\r\n-";
+ parts[i++] = (char *) "-------------";
+ parts[i++] = (char *) "---------------41184676334\r\n";
+ parts[i++] = (char *) "Content-Disposition: form-data;\n name=\"field2\"\r\n";
+ parts[i++] = (char *) "\r\n";
+ parts[i++] = (char *) "0123456789\r\n-";
+ parts[i++] = (char *) "-------------";
+ parts[i++] = (char *) "--------------X\r\n";
+ parts[i++] = (char *) "-----------------------------41184676334\r\n";
+ parts[i++] = (char *) "Content-Disposition: form-data;\n";
+ parts[i++] = (char *) " ";
+ parts[i++] = (char *) "name=\"field3\"\r\n";
+ parts[i++] = (char *) "\r\n";
+ parts[i++] = (char *) "9876543210\r\n";
+ parts[i++] = (char *) "-----------------------------41184676334\r\n";
+ parts[i++] = (char *) "Content-Disposition: form-data; name=\"file1\"; filename=\"New Text Document.txt\"\r\nContent-Type: text/plain\r\n\r\n";
+ parts[i++] = (char *) "1FFFFFFFFFFFFFFFFFFFFFFFFFFF\r\n";
+ parts[i++] = (char *) "2FFFFFFFFFFFFFFFFFFFFFFFFFFE\r";
+ parts[i++] = (char *) "3FFFFFFFFFFFFFFFFFFFFFFFFFFF\r\n4FFFFFFFFFFFFFFFFFFFFFFFFF123456789";
+ parts[i++] = (char *) "\r\n";
+ parts[i++] = (char *) "-----------------------------41184676334\r\n";
+ parts[i++] = (char *) "Content-Disposition: form-data; name=\"file2\"; filename=\"New Text Document.txt\"\r\n";
+ parts[i++] = (char *) "Content-Type: text/plain\r\n";
+ parts[i++] = (char *) "\r\n";
+ parts[i++] = (char *) "FFFFFFFFFFFFFFFFFFFFFFFFFFFZ";
+ parts[i++] = (char *) "\r\n-----------------------------41184676334--";
+ parts[i++] = NULL;
+
+ i = 0;
+ for (;;) {
+ if (parts[i] == NULL) break;
+ htp_mpartp_parse(mpartp, parts[i], strlen(parts[i]));
+ i++;
+ }
+
+ htp_mpartp_finalize(mpartp);
+
+ // Examine the result
+ htp_multipart_t *body = htp_mpartp_get_multipart(mpartp);
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(5, htp_list_size(body->parts));
+
+ for (size_t i = 0, n = htp_list_size(body->parts); i < n; i++) {
+ htp_multipart_part_t *part = (htp_multipart_part_t *) htp_list_get(body->parts, i);
+
+ switch (i) {
+ case 0:
+ ASSERT_TRUE(part->name != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->name, "field1") == 0);
+ ASSERT_EQ(MULTIPART_PART_TEXT, part->type);
+ ASSERT_TRUE(part->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->value, "0123456789") == 0);
+ break;
+ case 1:
+ ASSERT_TRUE(part->name != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->name, "field2") == 0);
+ ASSERT_EQ(MULTIPART_PART_TEXT, part->type);
+ ASSERT_TRUE(bstr_cmp_c(part->value, "0123456789\r\n----------------------------X") == 0);
+ break;
+ case 2:
+ ASSERT_TRUE(part->name != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->name, "field3") == 0);
+ ASSERT_EQ(MULTIPART_PART_TEXT, part->type);
+ ASSERT_TRUE(part->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->value, "9876543210") == 0);
+ break;
+ case 3:
+ ASSERT_TRUE(part->name != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->name, "file1") == 0);
+ ASSERT_EQ(MULTIPART_PART_FILE, part->type);
+ break;
+ case 4:
+ ASSERT_TRUE(part->name != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->name, "file2") == 0);
+ ASSERT_EQ(MULTIPART_PART_FILE, part->type);
+ break;
+ default:
+ FAIL() << "More parts than expected";
+ break;
+ }
+ }
+
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_PART_INCOMPLETE);
+
+ htp_mpartp_destroy(mpartp);
+ mpartp = NULL;
+}
+
+TEST_F(Multipart, Test2) {
+ mpartp = htp_mpartp_create(cfg, bstr_dup_c("BBB"), 0 /* flags */);
+
+ const char *i1 = "x0000x\n--BBB\n\nx1111x\n--\nx2222x\n--";
+ const char *i2 = "BBB\n\nx3333x\n--B";
+ const char *i3 = "B\n\nx4444x\n--BB\r";
+ const char *i4 = "\n--B";
+ const char *i5 = "B";
+ const char *i6 = "B\n\nx5555x\r";
+ const char *i7 = "\n--x6666x\r";
+ const char *i8 = "-";
+ const char *i9 = "-";
+
+ htp_mpartp_parse(mpartp, i1, strlen(i1));
+ htp_mpartp_parse(mpartp, i2, strlen(i2));
+ htp_mpartp_parse(mpartp, i3, strlen(i3));
+ htp_mpartp_parse(mpartp, i4, strlen(i4));
+ htp_mpartp_parse(mpartp, i5, strlen(i5));
+ htp_mpartp_parse(mpartp, i6, strlen(i6));
+ htp_mpartp_parse(mpartp, i7, strlen(i7));
+ htp_mpartp_parse(mpartp, i8, strlen(i8));
+ htp_mpartp_parse(mpartp, i9, strlen(i9));
+ htp_mpartp_finalize(mpartp);
+
+ htp_multipart_t *body = htp_mpartp_get_multipart(mpartp);
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(4, htp_list_size(body->parts));
+
+ for (size_t i = 0, n = htp_list_size(body->parts); i < n; i++) {
+ htp_multipart_part_t *part = (htp_multipart_part_t *) htp_list_get(body->parts, i);
+
+ switch (i) {
+ case 0:
+ ASSERT_EQ(MULTIPART_PART_PREAMBLE, part->type);
+
+ ASSERT_TRUE(bstr_cmp_c(part->value, "x0000x") == 0);
+ break;
+ case 1:
+ ASSERT_EQ(MULTIPART_PART_UNKNOWN, part->type);
+ ASSERT_TRUE(part->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->value, "x1111x\n--\nx2222x") == 0);
+ break;
+ case 2:
+ ASSERT_EQ(MULTIPART_PART_UNKNOWN, part->type);
+ ASSERT_TRUE(part->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->value, "x3333x\n--BB\n\nx4444x\n--BB") == 0);
+ break;
+ case 3:
+ ASSERT_EQ(MULTIPART_PART_UNKNOWN, part->type);
+ ASSERT_TRUE(part->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->value, "x5555x\r\n--x6666x\r--") == 0);
+ break;
+ default:
+ FAIL();
+
+ }
+ }
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_INCOMPLETE);
+
+ htp_mpartp_destroy(mpartp);
+ mpartp = NULL;
+}
+
+TEST_F(Multipart, Test3) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n",
+ "--0",
+ "1",
+ "2",
+ "4: Value\r\n",
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+}
+
+TEST_F(Multipart, BeginsWithoutLine) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+}
+
+TEST_F(Multipart, BeginsWithCrLf) {
+ char *parts[] = {
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+}
+
+TEST_F(Multipart, BeginsWithLf) {
+ char *parts[] = {
+ "\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+}
+
+TEST_F(Multipart, CrLfLineEndings) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_LF_LINE);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CRLF_LINE);
+}
+
+TEST_F(Multipart, LfLineEndings) {
+ char *parts[] = {
+ "--0123456789\n"
+ "Content-Disposition: form-data; name=\"field1\"\n"
+ "\n"
+ "ABCDEF"
+ "\n--0123456789\n"
+ "Content-Disposition: form-data; name=\"field2\"\n"
+ "\n"
+ "GHIJKL"
+ "\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_LF_LINE);
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_CRLF_LINE);
+}
+
+TEST_F(Multipart, CrAndLfLineEndings1) {
+ char *parts[] = {
+ "--0123456789\n"
+ "Content-Disposition: form-data; name=\"field1\"\n"
+ "\n"
+ "ABCDEF"
+ "\r\n--0123456789\n"
+ "Content-Disposition: form-data; name=\"field2\"\n"
+ "\n"
+ "GHIJKL"
+ "\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_LF_LINE);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CRLF_LINE);
+}
+
+TEST_F(Multipart, CrAndLfLineEndings2) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\n"
+ "\n"
+ "ABCDEF"
+ "\n--0123456789\n"
+ "Content-Disposition: form-data; name=\"field2\"\n"
+ "\n"
+ "GHIJKL"
+ "\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_LF_LINE);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CRLF_LINE);
+}
+
+TEST_F(Multipart, CrAndLfLineEndings3) {
+ char *parts[] = {
+ "--0123456789\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_LF_LINE);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CRLF_LINE);
+}
+
+TEST_F(Multipart, CrAndLfLineEndings4) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_LF_LINE);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CRLF_LINE);
+}
+
+TEST_F(Multipart, BoundaryInstanceWithLwsAfter) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\n--0123456789 \r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_BBOUNDARY_LWS_AFTER);
+}
+
+TEST_F(Multipart, BoundaryInstanceWithNonLwsAfter1) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\n--0123456789 X \r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_BBOUNDARY_NLWS_AFTER);
+}
+
+TEST_F(Multipart, BoundaryInstanceWithNonLwsAfter2) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\n--0123456789-\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_BBOUNDARY_NLWS_AFTER);
+}
+
+TEST_F(Multipart, BoundaryInstanceWithNonLwsAfter3) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\n--0123456789\r\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_BBOUNDARY_NLWS_AFTER);
+}
+
+TEST_F(Multipart, WithPreamble) {
+ char *parts[] = {
+ "Preamble"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\n--0123456789 X \r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseParts(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_HAS_PREAMBLE);
+
+ htp_multipart_part_t *part = (htp_multipart_part_t *) htp_list_get(body->parts, 0);
+ ASSERT_TRUE(part != NULL);
+ ASSERT_EQ(MULTIPART_PART_PREAMBLE, part->type);
+ ASSERT_TRUE(part->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->value, "Preamble") == 0);
+}
+
+TEST_F(Multipart, WithEpilogue1) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--\r\n"
+ "Epilogue",
+ NULL
+ };
+
+ parseParts(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_HAS_EPILOGUE);
+
+ htp_multipart_part_t *part = (htp_multipart_part_t *) htp_list_get(body->parts, 2);
+ ASSERT_TRUE(part != NULL);
+ ASSERT_EQ(MULTIPART_PART_EPILOGUE, part->type);
+ ASSERT_TRUE(part->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->value, "Epilogue") == 0);
+
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_INCOMPLETE);
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_PART_INCOMPLETE);
+}
+
+TEST_F(Multipart, WithEpilogue2) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--\r\n"
+ "Epi\nlogue",
+ NULL
+ };
+
+ parseParts(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_HAS_EPILOGUE);
+
+ htp_multipart_part_t *part = (htp_multipart_part_t *) htp_list_get(body->parts, 2);
+ ASSERT_TRUE(part != NULL);
+ ASSERT_EQ(MULTIPART_PART_EPILOGUE, part->type);
+ ASSERT_TRUE(part->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->value, "Epi\nlogue") == 0);
+
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_INCOMPLETE);
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_PART_INCOMPLETE);
+}
+
+TEST_F(Multipart, WithEpilogue3) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--\r\n"
+ "Epi\r",
+ "\n--logue",
+ NULL
+ };
+
+ parseParts(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_HAS_EPILOGUE);
+
+ htp_multipart_part_t *part = (htp_multipart_part_t *) htp_list_get(body->parts, 2);
+ ASSERT_TRUE(part != NULL);
+ ASSERT_EQ(MULTIPART_PART_EPILOGUE, part->type);
+ ASSERT_TRUE(part->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->value, "Epi\r\n--logue") == 0);
+
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_INCOMPLETE);
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_PART_INCOMPLETE);
+}
+
+TEST_F(Multipart, WithEpilogue4) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--\r\n"
+ "Epilogue1"
+ "\r\n--0123456789--\r\n"
+ "Epilogue2",
+ NULL
+ };
+
+ parseParts(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(4, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_HAS_EPILOGUE);
+
+ htp_multipart_part_t *ep1 = (htp_multipart_part_t *) htp_list_get(body->parts, 2);
+ ASSERT_TRUE(ep1 != NULL);
+ ASSERT_EQ(MULTIPART_PART_EPILOGUE, ep1->type);
+ ASSERT_TRUE(ep1->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(ep1->value, "Epilogue1") == 0);
+
+ htp_multipart_part_t *ep2 = (htp_multipart_part_t *) htp_list_get(body->parts, 3);
+ ASSERT_TRUE(ep2 != NULL);
+ ASSERT_EQ(MULTIPART_PART_EPILOGUE, ep2->type);
+ ASSERT_TRUE(ep2->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(ep2->value, "Epilogue2") == 0);
+
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_INCOMPLETE);
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_PART_INCOMPLETE);
+}
+
+TEST_F(Multipart, HasLastBoundary) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseParts(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(2, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_SEEN_LAST_BOUNDARY);
+}
+
+TEST_F(Multipart, DoesNotHaveLastBoundary) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_SEEN_LAST_BOUNDARY);
+}
+
+TEST_F(Multipart, PartAfterLastBoundary) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789--\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_SEEN_LAST_BOUNDARY);
+}
+
+TEST_F(Multipart, UnknownPart) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseParts(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(1, htp_list_size(body->parts));
+
+ htp_multipart_part_t *part = (htp_multipart_part_t *) htp_list_get(body->parts, 0);
+ ASSERT_EQ(MULTIPART_PART_UNKNOWN, part->type);
+}
+
+TEST_F(Multipart, WithFile) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"; filename=\"test.bin\"\r\n"
+ "Content-Type: application/octet-stream \r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseParts(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(2, htp_list_size(body->parts));
+
+ htp_multipart_part_t *part = (htp_multipart_part_t *) htp_list_get(body->parts, 1);
+ ASSERT_EQ(MULTIPART_PART_FILE, part->type);
+ ASSERT_TRUE(part->content_type != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->content_type, "application/octet-stream") == 0);
+ ASSERT_TRUE(part->file != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->file->filename, "test.bin") == 0);
+ ASSERT_EQ(6, part->file->len);
+}
+
+TEST_F(Multipart, WithFileExternallyStored) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"; filename=\"test.bin\"\r\n"
+ "Content-Type: application/octet-stream \r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ cfg->extract_request_files = 1;
+ cfg->tmpdir = "/tmp";
+
+ parseParts(parts);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(2, htp_list_size(body->parts));
+
+ htp_multipart_part_t *part = (htp_multipart_part_t *) htp_list_get(body->parts, 1);
+ ASSERT_EQ(MULTIPART_PART_FILE, part->type);
+ ASSERT_TRUE(part->content_type != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->content_type, "application/octet-stream") == 0);
+ ASSERT_TRUE(part->file != NULL);
+ ASSERT_TRUE(bstr_cmp_c(part->file->filename, "test.bin") == 0);
+ ASSERT_EQ(6, part->file->len);
+
+ ASSERT_TRUE(part->file->tmpname != NULL);
+
+ int fd = open(part->file->tmpname, O_RDONLY | O_BINARY);
+ ASSERT_TRUE(fd >= 0);
+
+ struct stat statbuf;
+ ASSERT_TRUE((fstat(fd, &statbuf) >= 0));
+ ASSERT_EQ(6, statbuf.st_size);
+
+ char buf[7];
+ ssize_t result = read(fd, buf, 6);
+ ASSERT_EQ(6, result);
+ buf[6] = '\0';
+
+ ASSERT_STREQ("GHIJKL", buf);
+
+ close(fd);
+}
+
+TEST_F(Multipart, PartHeadersEmptyLineBug) {
+ char *parts[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r",
+ "\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parsePartsThenVerify(parts);
+}
+
+TEST_F(Multipart, CompleteRequest) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequestThenVerify(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_PART_HEADER_FOLDING);
+}
+
+TEST_F(Multipart, InvalidHeader1) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // Colon missing.
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_HEADER_INVALID);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_INVALID);
+}
+
+TEST_F(Multipart, InvalidHeader2) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // Whitespace after header name.
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition : form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_HEADER_INVALID);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_INVALID);
+}
+
+TEST_F(Multipart, InvalidHeader3) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // Whitespace before header name.
+
+ char *data[] = {
+ "--0123456789\r\n"
+ " Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_HEADER_INVALID);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_INVALID);
+}
+
+TEST_F(Multipart, InvalidHeader4) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // Invalid header name; contains a space.
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_HEADER_INVALID);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_INVALID);
+}
+
+TEST_F(Multipart, InvalidHeader5) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // No header name.
+
+ char *data[] = {
+ "--0123456789\r\n"
+ ": form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_HEADER_INVALID);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_INVALID);
+}
+
+TEST_F(Multipart, InvalidHeader6) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // No header name.
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: \r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_HEADER_INVALID);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_INVALID);
+}
+
+TEST_F(Multipart, NulByte) {
+ mpartp = htp_mpartp_create(cfg, bstr_dup_c("0123456789"), 0 /* flags */);
+
+ // NUL byte in the part header.
+
+ char i1[] = "--0123456789\r\n"
+ "Content-Disposition: form-data; ";
+ char i2[] = "";
+ char i3[] =
+ "name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--";
+
+ htp_mpartp_parse(mpartp, i1, strlen(i1));
+ htp_mpartp_parse(mpartp, i2, 1);
+ htp_mpartp_parse(mpartp, i3, strlen(i3));
+ htp_mpartp_finalize(mpartp);
+
+ htp_multipart_t *body = htp_mpartp_get_multipart(mpartp);
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_NUL_BYTE);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_INVALID);
+}
+
+TEST_F(Multipart, MultipleContentTypeHeadersEvasion) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data\r\n"
+ "Content-Type: boundary=0123456789\r\n",
+ NULL
+ };
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequestThenVerify(headers, data);
+
+ ASSERT_TRUE(tx->request_content_type != NULL);
+ ASSERT_TRUE(bstr_cmp_c(tx->request_content_type, "multipart/form-data") == 0);
+}
+
+TEST_F(Multipart, BoundaryNormal) {
+ char *inputs[] = {
+ "multipart/form-data; boundary=----WebKitFormBoundaryT4AfwQCOgIxNVwlD",
+ "multipart/form-data; boundary=---------------------------21071316483088",
+ "multipart/form-data; boundary=---------------------------7dd13e11c0452",
+ "multipart/form-data; boundary=----------2JL5oh7QWEDwyBllIRc7fh",
+ "multipart/form-data; boundary=----WebKitFormBoundaryre6zL3b0BelnTY5S",
+ NULL
+ };
+
+ char *outputs[] = {
+ "----WebKitFormBoundaryT4AfwQCOgIxNVwlD",
+ "---------------------------21071316483088",
+ "---------------------------7dd13e11c0452",
+ "----------2JL5oh7QWEDwyBllIRc7fh",
+ "----WebKitFormBoundaryre6zL3b0BelnTY5S",
+ NULL
+ };
+
+ for (size_t i = 0; inputs[i] != NULL; i++) {
+ bstr *input = bstr_dup_c(inputs[i]);
+ bstr *boundary = NULL;
+ uint64_t flags = 0;
+
+ SCOPED_TRACE(inputs[i]);
+
+ htp_status_t rc = htp_mpartp_find_boundary(input, &boundary, &flags);
+ ASSERT_EQ(HTP_OK, rc);
+
+ ASSERT_TRUE(boundary != NULL);
+ ASSERT_TRUE(bstr_cmp_c(boundary, outputs[i]) == 0);
+ ASSERT_EQ(0, flags);
+
+ bstr_free(boundary);
+ bstr_free(input);
+ }
+}
+
+TEST_F(Multipart, BoundaryParsing) {
+ char *inputs[] = {
+ "multipart/form-data; boundary=1 ",
+ "multipart/form-data; boundary=1, boundary=2",
+ "multipart/form-data; boundary=\"1\"",
+ "multipart/form-data; boundary=\"1\" ",
+ "multipart/form-data; boundary=\"1",
+ NULL
+ };
+
+ char *outputs[] = {
+ "1",
+ "1",
+ "1",
+ "1",
+ "\"1",
+ NULL
+ };
+
+ for (size_t i = 0; inputs[i] != NULL; i++) {
+ bstr *input = bstr_dup_c(inputs[i]);
+ bstr *boundary = NULL;
+ uint64_t flags = 0;
+
+ SCOPED_TRACE(inputs[i]);
+
+ htp_status_t rc = htp_mpartp_find_boundary(input, &boundary, &flags);
+ ASSERT_EQ(HTP_OK, rc);
+
+ ASSERT_TRUE(boundary != NULL);
+ ASSERT_TRUE(bstr_cmp_c(boundary, outputs[i]) == 0);
+
+ bstr_free(boundary);
+ bstr_free(input);
+ }
+}
+
+TEST_F(Multipart, BoundaryInvalid) {
+ char *inputs[] = {
+ "multipart/form-data boundary=1",
+ "multipart/form-data ; boundary=1",
+ "multipart/form-data, boundary=1",
+ "multipart/form-data , boundary=1",
+ "multipart/form-datax; boundary=1",
+ "multipart/; boundary=1",
+ "multipart; boundary=1",
+ "application/octet-stream; boundary=1",
+ "boundary=1",
+ "multipart/form-data; boundary",
+ "multipart/form-data; boundary=",
+ "multipart/form-data; boundaryX=",
+ "multipart/form-data; boundary=\"\"",
+ "multipart/form-data; bounDary=1",
+ "multipart/form-data; boundary=1; boundary=2",
+ "multipart/form-data; boundary=1 2",
+ "multipart/form-data boundary=01234567890123456789012345678901234567890123456789012345678901234567890123456789",
+ NULL
+ };
+
+ for (size_t i = 0; inputs[i] != NULL; i++) {
+ bstr *input = bstr_dup_c(inputs[i]);
+ bstr *boundary = NULL;
+ uint64_t flags = 0;
+
+ SCOPED_TRACE(inputs[i]);
+
+ htp_status_t rc = htp_mpartp_find_boundary(input, &boundary, &flags);
+ ASSERT_TRUE(rc != HTP_ERROR);
+
+ ASSERT_TRUE(flags & HTP_MULTIPART_HBOUNDARY_INVALID);
+
+ bstr_free(boundary);
+ bstr_free(input);
+ }
+}
+
+TEST_F(Multipart, BoundaryUnusual) {
+ char *inputs[] = {
+ "multipart/form-data; boundary=1 ",
+ "multipart/form-data; boundary =1",
+ "multipart/form-data; boundary= 1",
+ "multipart/form-data; boundary=\"1\"",
+ "multipart/form-data; boundary=\" 1 \"",
+ //"multipart/form-data; boundary=1-2",
+ "multipart/form-data; boundary=\"1?2\"",
+ NULL
+ };
+
+ for (size_t i = 0; inputs[i] != NULL; i++) {
+ bstr *input = bstr_dup_c(inputs[i]);
+ bstr *boundary = NULL;
+ uint64_t flags = 0;
+
+ SCOPED_TRACE(inputs[i]);
+
+ htp_status_t rc = htp_mpartp_find_boundary(input, &boundary, &flags);
+ ASSERT_EQ(HTP_OK, rc);
+
+ ASSERT_TRUE(boundary != NULL);
+ ASSERT_TRUE(flags & HTP_MULTIPART_HBOUNDARY_UNUSUAL);
+
+ bstr_free(boundary);
+ bstr_free(input);
+ }
+}
+
+TEST_F(Multipart, CaseInsitiveBoundaryMatching) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=grumpyWizards\r\n",
+ NULL
+ };
+
+ // The second boundary is all-lowercase and shouldn't be matched on.
+ char *data[] = {
+ "--grumpyWizards\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n-grumpywizards\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--grumpyWizards\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--grumpyWizards--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(2, htp_list_size(body->parts));
+}
+
+TEST_F(Multipart, FoldedContentDisposition) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\";\r\n"
+ " filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequestThenVerify(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_HEADER_FOLDING);
+}
+
+TEST_F(Multipart, FoldedContentDisposition2) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\";\r\n"
+ "\rfilename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequestThenVerify(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_HEADER_FOLDING);
+}
+
+TEST_F(Multipart, InvalidPartNoData) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // The first part terminates abruptly by the next boundary. This
+ // actually works in PHP because its part header parser will
+ // consume everything (even boundaries) until the next empty line.
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ htp_multipart_part_t *field1 = (htp_multipart_part_t *) htp_list_get(body->parts, 0);
+ ASSERT_TRUE(field1 != NULL);
+ ASSERT_EQ(MULTIPART_PART_UNKNOWN, field1->type);
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_INCOMPLETE);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_INVALID);
+}
+
+TEST_F(Multipart, InvalidPartNoContentDisposition) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // A part without a Content-Disposition header.
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_UNKNOWN);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_INVALID);
+}
+
+TEST_F(Multipart, InvalidPartMultipleCD) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // When we encounter a part with more than one C-D header, we
+ // don't know which one the backend will use. Thus, we raise
+ // HTP_MULTIPART_PART_INVALID.
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "Content-Disposition: form-data; name=\"field3\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequestThenVerify(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_HEADER_REPEATED);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_INVALID);
+}
+
+TEST_F(Multipart, InvalidPartUnknownHeader) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // Unknown C-D header "Unknown".
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "Unknown: Header\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequestThenVerify(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_HEADER_UNKNOWN);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_PART_INVALID);
+}
+
+TEST_F(Multipart, InvalidContentDispositionMultipleParams1) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // Two "name" parameters in a C-D header.
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"; name=\"field3\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CD_PARAM_REPEATED);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CD_INVALID);
+}
+
+TEST_F(Multipart, InvalidContentDispositionMultipleParams2) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // Two "filename" parameters in a C-D header.
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"; filename=\"file2.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CD_PARAM_REPEATED);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CD_INVALID);
+}
+
+TEST_F(Multipart, InvalidContentDispositionUnknownParam) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ // Unknown C-D parameter "test".
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\"; test=\"param\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CD_PARAM_UNKNOWN);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CD_INVALID);
+}
+
+TEST_F(Multipart, InvalidContentDispositionSyntax) {
+ char *inputs[] = {
+ // Parameter value not quoted.
+ "form-data; name=field1",
+ // Using single quotes around parameter value.
+ "form-data; name='field1'",
+ // No semicolon after form-data in the C-D header.
+ "form-data name=\"field1\"",
+ // No semicolon after C-D parameter.
+ "form-data; name=\"file1\" filename=\"file.bin\"",
+ // Missing terminating quote in C-D parameter value.
+ "form-data; name=\"field1",
+ // Backslash as the last character in parameter value
+ "form-data; name=\"field1\\",
+ // C-D header does not begin with "form-data".
+ "invalid-syntax; name=\"field1",
+ // Escape the terminating double quote.
+ "name=\"field1\\\"",
+ // Incomplete header.
+ "form-data; ",
+ // Incomplete header.
+ "form-data; name",
+ // Incomplete header.
+ "form-data; name ",
+ // Incomplete header.
+ "form-data; name ?",
+ // Incomplete header.
+ "form-data; name=",
+ // Incomplete header.
+ "form-data; name= ",
+ NULL
+ };
+
+ for (size_t i = 0; inputs[i] != NULL; i++) {
+ SCOPED_TRACE(inputs[i]);
+
+ mpartp = htp_mpartp_create(cfg, bstr_dup_c("123"), 0 /* flags */);
+
+ htp_multipart_part_t *part = (htp_multipart_part_t *) calloc(1, sizeof (htp_multipart_part_t));
+ part->headers = htp_table_create(4);
+ part->parser = mpartp;
+
+ htp_header_t *h = (htp_header_t *) calloc(1, sizeof (htp_header_t));
+ h->name = bstr_dup_c("Content-Disposition");
+ h->value = bstr_dup_c(inputs[i]);
+
+ htp_table_add(part->headers, h->name, h);
+
+ htp_status_t rc = htp_mpart_part_parse_c_d(part);
+ ASSERT_EQ(HTP_DECLINED, rc);
+
+ body = htp_mpartp_get_multipart(mpartp);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CD_SYNTAX_INVALID);
+ ASSERT_TRUE(body->flags & HTP_MULTIPART_CD_INVALID);
+
+ htp_mpart_part_destroy(part, 0);
+ htp_mpartp_destroy(mpartp);
+ mpartp = NULL;
+ }
+}
+
+TEST_F(Multipart, ParamValueEscaping) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"---\\\"---\\\\---\"\r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequest(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+ ASSERT_EQ(3, htp_list_size(body->parts));
+
+ ASSERT_FALSE(body->flags & HTP_MULTIPART_CD_INVALID);
+
+ htp_multipart_part_t *field1 = (htp_multipart_part_t *) htp_list_get(body->parts, 0);
+ ASSERT_TRUE(field1 != NULL);
+ ASSERT_EQ(MULTIPART_PART_TEXT, field1->type);
+ ASSERT_TRUE(field1->name != NULL);
+ ASSERT_TRUE(bstr_cmp_c(field1->name, "---\"---\\---") == 0);
+ ASSERT_TRUE(field1->value != NULL);
+ ASSERT_TRUE(bstr_cmp_c(field1->value, "ABCDEF") == 0);
+}
+
+TEST_F(Multipart, HeaderValueTrim) {
+ char *headers[] = {
+ "POST / HTTP/1.0\r\n"
+ "Content-Type: multipart/form-data; boundary=0123456789\r\n",
+ NULL
+ };
+
+ char *data[] = {
+ "--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field1\" \r\n"
+ "\r\n"
+ "ABCDEF"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"file1\"; filename=\"file.bin\"\r\n"
+ "\r\n"
+ "FILEDATA"
+ "\r\n--0123456789\r\n"
+ "Content-Disposition: form-data; name=\"field2\"\r\n"
+ "\r\n"
+ "GHIJKL"
+ "\r\n--0123456789--",
+ NULL
+ };
+
+ parseRequestThenVerify(headers, data);
+
+ ASSERT_TRUE(body != NULL);
+ ASSERT_TRUE(body->parts != NULL);
+
+ htp_multipart_part_t *field1 = (htp_multipart_part_t *) htp_list_get(body->parts, 0);
+ ASSERT_TRUE(field1 != NULL);
+ htp_header_t *h = (htp_header_t *) htp_table_get_c(field1->headers, "content-disposition");
+ ASSERT_TRUE(h != NULL);
+ ASSERT_TRUE(bstr_cmp_c(h->value, "form-data; name=\"field1\" ") == 0);
+}