/* 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 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); }