diff options
Diffstat (limited to 'src/lib-http/test-http-response-parser.c')
-rw-r--r-- | src/lib-http/test-http-response-parser.c | 406 |
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); +} |