summaryrefslogtreecommitdiffstats
path: root/src/lib-http/test-http-response-parser.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-http/test-http-response-parser.c406
1 files changed, 406 insertions, 0 deletions
diff --git a/src/lib-http/test-http-response-parser.c b/src/lib-http/test-http-response-parser.c
new file mode 100644
index 0000000..3964a32
--- /dev/null
+++ b/src/lib-http/test-http-response-parser.c
@@ -0,0 +1,406 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "buffer.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "ostream.h"
+#include "test-common.h"
+#include "http-response-parser.h"
+
+#include <time.h>
+
+struct valid_parse_test_response {
+ unsigned char version_major;
+ unsigned char version_minor;
+ unsigned int status;
+ uoff_t content_length;
+ const char *payload;
+};
+
+struct valid_parse_test {
+ const char *input;
+ enum http_response_parse_flags flags;
+
+ const struct valid_parse_test_response *responses;
+ unsigned int responses_count;
+};
+
+/* Valid response tests */
+
+static const struct valid_parse_test_response valid_responses1[] = {
+ {
+ .status = 200,
+ .payload = "This is a piece of stupid text.\r\n"
+ }
+};
+
+static const struct valid_parse_test_response valid_responses2[] = {
+ {
+ .status = 200,
+ .payload = "This is a piece of stupid text.\r\n"
+ },{
+ .status = 200,
+ .payload = "This is a piece of even more stupid text.\r\n"
+ }
+};
+
+static const struct valid_parse_test_response valid_responses3[] = {
+ {
+ .status = 401,
+ .payload = "Frop!"
+ }
+};
+
+static const struct valid_parse_test_response valid_responses4[] = {
+ {
+ .status = 200,
+ .payload = "Invalid date header"
+ }
+};
+
+static const struct valid_parse_test_response valid_responses5[] = {
+ {
+ .status = 200,
+ .payload = "Duplicate headers"
+ }
+};
+
+static const struct valid_parse_test
+valid_response_parse_tests[] = {
+ { .input =
+ "HTTP/1.1 200 OK\r\n"
+ "Date: Sun, 07 Oct 2012 13:02:27 GMT\r\n"
+ "Server: Apache/2.2.16 (Debian)\r\n"
+ "Last-Modified: Tue, 18 Sep 2012 19:31:41 GMT\r\n"
+ "Etag: \"2a8400c-10751f-4c9fef0858140\"\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "Content-Length: 33\r\n"
+ "Keep-Alive: timeout=15, max=100\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "This is a piece of stupid text.\r\n",
+ .responses = valid_responses1,
+ .responses_count = N_ELEMENTS(valid_responses1)
+ },{
+ .input =
+ "HTTP/1.1 200 OK\r\n"
+ "Date: Sun, 07 Oct 2012 13:02:27 GMT\r\n"
+ "Server: Apache/2.2.16 (Debian)\r\n"
+ "Last-Modified: Tue, 18 Sep 2012 19:31:41 GMT\r\n"
+ "Etag: \"2a8400c-10751f-4c9fef0858140\"\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "Content-Length: 33\r\n"
+ "Keep-Alive: timeout=15, max=100\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "This is a piece of stupid text.\r\n"
+ "HTTP/1.1 200 OK\r\n"
+ "Date: Sun, 07 Oct 2012 13:02:27 GMT\r\n"
+ "Server: Apache/2.2.16 (Debian)\r\n"
+ "Last-Modified: Tue, 18 Sep 2012 19:31:41 GMT\r\n"
+ "Etag: \"2a8400c-10751f-4c9fef0858140\"\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "Content-Length: 43\r\n"
+ "Keep-Alive: timeout=15, max=100\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "This is a piece of even more stupid text.\r\n",
+ .responses = valid_responses2,
+ .responses_count = N_ELEMENTS(valid_responses2)
+ },{
+ .input =
+ "HTTP/1.1 401 Authorization Required\r\n"
+ "Date: Sun, 07 Oct 2012 19:52:03 GMT\r\n"
+ "Server: Apache/2.2.16 (Debian) PHP/5.3.3-7+squeeze14\r\n"
+ "WWW-Authenticate: Basic realm=\"Munin\"\r\n"
+ "Vary: Accept-Encoding\r\n"
+ "Content-Encoding: gzip\r\n"
+ "Content-Length: 5\r\n"
+ "Keep-Alive: timeout=15, max=99\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "\r\n"
+ "Frop!",
+ .responses = valid_responses3,
+ .responses_count = N_ELEMENTS(valid_responses3)
+ },{
+ .input =
+ "HTTP/1.1 200 OK\r\n"
+ "Date: Sun, 07 Ocu 2012 19:52:03 GMT\r\n"
+ "Content-Length: 19\r\n"
+ "Keep-Alive: timeout=15, max=99\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Date: Sun, 13 Oct 2013 13:13:13 GMT\r\n"
+ "\r\n"
+ "Invalid date header",
+ .responses = valid_responses4,
+ .responses_count = N_ELEMENTS(valid_responses4)
+ },{
+ .input =
+ "HTTP/1.1 200 OK\r\n"
+ "Date: Sun, 07 Oct 2012 19:52:03 GMT\r\n"
+ "Server: Apache/2.2.16 (Debian) PHP/5.3.3-7+squeeze14\r\n"
+ "Content-Length: 17\r\n"
+ "Keep-Alive: timeout=15, max=99\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Date: Sun, 13 Oct 2013 13:13:13 GMT\r\n"
+ "\r\n"
+ "Duplicate headers",
+ .responses = valid_responses5,
+ .responses_count = N_ELEMENTS(valid_responses5)
+ }
+};
+
+static const unsigned int valid_response_parse_test_count =
+ N_ELEMENTS(valid_response_parse_tests);
+
+static void test_http_response_parse_valid(void)
+{
+ unsigned int i;
+ buffer_t *payload_buffer = buffer_create_dynamic(default_pool, 1024);
+
+ for (i = 0; i < valid_response_parse_test_count; i++) T_BEGIN {
+ struct istream *input;
+ struct ostream *output;
+ const struct valid_parse_test *test;
+ const struct valid_parse_test_response *tresponse;
+ struct http_response_parser *parser;
+ struct http_response presponse;
+ const char *input_text, *payload, *error;
+ unsigned int j, pos, input_text_len;
+ int ret = 0;
+
+ i_zero(&presponse);
+ test = &valid_response_parse_tests[i];
+ input_text = test->input;
+ input_text_len = strlen(input_text);
+ input = test_istream_create_data(input_text, input_text_len);
+ parser = http_response_parser_init(input, NULL,
+ valid_response_parse_tests[i].flags);
+
+ test_begin(t_strdup_printf("http response valid [%d]", i));
+
+ payload = NULL;
+ for (pos = 0; pos < input_text_len && ret == 0; pos++) {
+ test_istream_set_size(input, pos);
+ ret = http_response_parse_next(parser,
+ HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED, &presponse, &error);
+ }
+ test_istream_set_size(input, input_text_len);
+ i_stream_unref(&input);
+
+ j = 0;
+ test_out("parse success", ret > 0);
+ while (ret > 0) {
+ if (presponse.payload != NULL) {
+ buffer_set_used_size(payload_buffer, 0);
+ output = o_stream_create_buffer(payload_buffer);
+ test_out("payload receive",
+ o_stream_send_istream(output, presponse.payload)
+ == OSTREAM_SEND_ISTREAM_RESULT_FINISHED);
+ o_stream_destroy(&output);
+ payload = str_c(payload_buffer);
+ } else {
+ payload = NULL;
+ }
+
+ test_assert(j < test->responses_count);
+ if (j >= test->responses_count)
+ break;
+ tresponse = &test->responses[j];
+
+ /* verify last response only */
+ test_out(t_strdup_printf("response->status = %d",
+ tresponse->status),
+ presponse.status == tresponse->status);
+ if (payload == NULL || tresponse->payload == NULL) {
+ test_out(t_strdup_printf("response->payload = %s",
+ str_sanitize(payload, 80)),
+ payload == tresponse->payload);
+ } else {
+ test_out(t_strdup_printf("response->payload = %s",
+ str_sanitize(payload, 80)),
+ strcmp(payload, tresponse->payload) == 0);
+ }
+
+ ret = http_response_parse_next(parser,
+ HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED, &presponse, &error);
+ if (++j == test->responses_count)
+ test_out("parse end", ret == 0);
+ else
+ test_out("parse success", ret > 0);
+ }
+
+ test_assert(ret == 0);
+ test_end();
+ http_response_parser_deinit(&parser);
+ } T_END;
+
+ buffer_free(&payload_buffer);
+}
+
+/*
+ * Invalid response tests
+ */
+
+struct invalid_parse_test {
+ const char *input;
+ enum http_response_parse_flags flags;
+};
+
+static struct invalid_parse_test invalid_response_parse_tests[] = {
+ {
+ .input =
+ "XMPP/1.0 302 Found\r\n"
+ "Location: http://www.example.nl/\r\n"
+ "Cache-Control: private\r\n"
+ },{
+ .input =
+ "HTTP/1.1 302 Found\r\n"
+ "Location: http://www.example.nl/\r\n"
+ "Cache-Control: private\r\n"
+ },{
+ .input =
+ "HTTP/1.1 ABC Found\r\n"
+ "Location: http://www.example.nl/\r\n"
+ "Cache-Control: private\r\n"
+ },{
+ .input =
+ "HTTP/1.1 302 \177\r\n"
+ "Location: http://www.example.nl/\r\n"
+ "Cache-Control: private\r\n"
+ },{
+ .input =
+ "HTTP/1.1 302 Found\n\r"
+ "Location: http://www.example.nl/\n\r"
+ "Cache-Control: private\n\r"
+ },{
+ .input =
+ "HTTP/1.1 200 OK\r\n"
+ "Date: Sun, 07 Ocu 2012 19:52:03 GMT\r\n"
+ "Content-Length: 19\r\n"
+ "Keep-Alive: timeout=15, max=99\r\n"
+ "Connection: Keep-Alive\r\n"
+ "\r\n"
+ "Invalid date header",
+ .flags = HTTP_RESPONSE_PARSE_FLAG_STRICT
+ },{
+ .input =
+ "HTTP/1.1 200 OK\r\n"
+ "Date: Sun, 07 Oct 2012 19:52:03 GMT\r\n"
+ "Server: Apache/2.2.16 (Debian) PHP/5.3.3-7+squeeze14\r\n"
+ "Content-Length: 17\r\n"
+ "Keep-Alive: timeout=15, max=99\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Date: Sun, 13 Oct 2013 13:13:13 GMT\r\n"
+ "\r\n"
+ "Duplicate headers",
+ .flags = HTTP_RESPONSE_PARSE_FLAG_STRICT
+ }
+};
+
+static const unsigned int invalid_response_parse_test_count =
+ N_ELEMENTS(invalid_response_parse_tests);
+
+static void test_http_response_parse_invalid(void)
+{
+ struct http_response_parser *parser;
+ struct http_response response;
+ const char *response_text, *error;
+ struct istream *input;
+ int ret;
+ unsigned int i;
+
+ for (i = 0; i < invalid_response_parse_test_count; i++) T_BEGIN {
+ const char *test;
+
+ test = invalid_response_parse_tests[i].input;
+ response_text = test;
+ input = i_stream_create_from_data(response_text, strlen(response_text));
+ parser = http_response_parser_init(input, NULL,
+ invalid_response_parse_tests[i].flags);
+ i_stream_unref(&input);
+
+ test_begin(t_strdup_printf("http response invalid [%d]", i));
+
+ while ((ret=http_response_parse_next(parser, HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED, &response, &error)) > 0);
+
+ test_out_reason("parse failure", ret < 0, error);
+ test_end();
+ http_response_parser_deinit(&parser);
+ } T_END;
+}
+
+/*
+ * Bad response tests
+ */
+
+static const unsigned char bad_response_with_nuls[] =
+ "HTTP/1.1 200 OK\r\n"
+ "Server: text\0server\r\n"
+ "\r\n";
+
+static void test_http_response_parse_bad(void)
+{
+ struct http_response_parser *parser;
+ struct http_response response;
+ const char *header, *error;
+ struct istream *input;
+ int ret;
+
+ /* parse failure guarantees http_response_header.size equals
+ strlen(http_response_header.value) */
+
+ test_begin("http response with NULs (strict)");
+ input = i_stream_create_from_data(bad_response_with_nuls,
+ sizeof(bad_response_with_nuls)-1);
+ parser = http_response_parser_init(input, NULL,
+ HTTP_RESPONSE_PARSE_FLAG_STRICT);
+ i_stream_unref(&input);
+
+ while ((ret=http_response_parse_next(parser,
+ HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED, &response, &error)) > 0);
+ test_assert(ret < 0);
+ http_response_parser_deinit(&parser);
+ test_end();
+
+ /* even when lenient, bad characters like NUL must not be returned */
+ test_begin("http response with NULs (lenient)");
+ input = i_stream_create_from_data(bad_response_with_nuls,
+ sizeof(bad_response_with_nuls)-1);
+ parser = http_response_parser_init(input, NULL, 0);
+ i_stream_unref(&input);
+
+ ret = http_response_parse_next(parser,
+ HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED, &response, &error);
+ test_out("parse success", ret > 0);
+ header = http_response_header_get(&response, "server");
+ test_out("header present", header != NULL);
+ if (header != NULL) {
+ test_out(t_strdup_printf("header Server: %s", header),
+ strcmp(header, "textserver") == 0);
+ }
+ ret = http_response_parse_next(parser,
+ HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED, &response, &error);
+ test_out("parse end", ret == 0);
+ http_response_parser_deinit(&parser);
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_http_response_parse_valid,
+ test_http_response_parse_invalid,
+ test_http_response_parse_bad,
+ NULL
+ };
+ return test_run(test_functions);
+}