1
0
mirror of https://github.com/stefanocasazza/ULib.git synced 2025-09-28 19:05:55 +08:00
ULib/tests/examples/test_http_parser.cpp
stefanocasazza 1b6ce8e697 fix
2017-08-08 18:38:15 +02:00

1202 lines
32 KiB
C++

// test_http_parser.cpp
#include "test_http_parser.h"
#include <ulib/url.h>
#include <ulib/utility/uhttp.h>
static int num_messages;
static struct message _messages[5];
/**
strnlen() is a POSIX.2008 addition. Can't rely on it being available so define it ourselves
static size_t strnlen(const char* s, size_t maxlen)
{
const char* p = (const char*) memchr(s, '\0', maxlen);
if (p == U_NULLPTR) return maxlen;
return p - s;
}
static size_t strlncat(char* dst, size_t len, const char* src, size_t n)
{
size_t rlen;
size_t ncpy;
size_t slen = strnlen(src, n);
size_t dlen = strnlen(dst, len);
if (dlen < len)
{
rlen = len - dlen;
ncpy = slen < rlen ? slen : (rlen - 1);
(void) memcpy(dst + dlen, src, ncpy);
dst[dlen + ncpy] = '\0';
}
// assert(len > slen + dlen);
return slen + dlen;
}
*/
static size_t strlncpy(char* dst, size_t len, const char* src, size_t n)
{
size_t ncpy;
size_t slen = strnlen(src, n);
if (len > 0)
{
ncpy = slen < len ? slen : (len - 1);
(void) memcpy(dst, src, ncpy);
dst[ncpy] = '\0';
}
/* assert(len > slen); */
return slen;
}
/*
static size_t strlcat(char* dst, const char* src, size_t len) { return strlncat(dst, len, src, (size_t) -1); }
static size_t strlcpy(char* dst, const char* src, size_t len) { return strlncpy(dst, len, src, (size_t) -1); }
*/
static size_t parse(const char* buf, size_t len, int req = HTTP_REQUEST)
{
U_TRACE(5, "::parse(%.*S,%u,%d)", len, buf, len, req)
return UHTTP::parserExecute(buf, len, req == HTTP_RESPONSE);
}
static inline int check_str_eq(const struct message* m, const char* prop, const char* expected, const char* found)
{
U_TRACE(5, "::check_str_eq(%p,%p,%S,%S)", m, prop, expected, found)
if ((expected == U_NULLPTR) != (found == U_NULLPTR))
{
printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
printf("expected %s\n", (expected == U_NULLPTR) ? "NULL" : expected);
printf(" found %s\n", (found == U_NULLPTR) ? "NULL" : found);
return 0;
}
if (expected != U_NULLPTR && 0 != strcmp(expected, found))
{
printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
printf("expected '%s'\n", expected);
printf(" found '%s'\n", found);
return 0;
}
return 1;
}
static inline int check_num_eq(const struct message* m, const char* prop, int expected, int found)
{
U_TRACE(5, "::check_num_eq(%p,%p,%u,%u)", m, prop, expected, found)
if (expected != found)
{
printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
printf("expected %d\n", expected);
printf(" found %d\n", found);
return 0;
}
return 1;
}
#define MESSAGE_CHECK_STR_EQ(expected, found, prop) \
if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0
#define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \
if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0
#define MESSAGE_CHECK_URL_EQ(u, expected, found, prop, fn) \
do { \
check_str_eq(expected, #prop, expected->prop, u.getFieldValue(fn).data()); \
} while(0)
static int message_eq(int index, int connect, const struct message* expected)
{
U_TRACE(5, "::message_eq(%d,%d,%p)", index, connect, expected)
int i;
struct message* m = _messages+index;
MESSAGE_CHECK_NUM_EQ(expected, m, http_major);
MESSAGE_CHECK_NUM_EQ(expected, m, http_minor);
if (expected->type == HTTP_REQUEST)
{
MESSAGE_CHECK_NUM_EQ(expected, m, method);
}
else
{
MESSAGE_CHECK_NUM_EQ(expected, m, status_code);
MESSAGE_CHECK_STR_EQ(expected, m, response_status);
}
if (!connect)
{
MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive);
MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof);
}
/*
assert(m->message_begin_cb_called);
assert(m->headers_complete_cb_called);
assert(m->message_complete_cb_called);
*/
MESSAGE_CHECK_STR_EQ(expected, m, request_url);
/*
* Check URL components; we can't do this w/ CONNECT since it doesn't send us a well-formed URL.
*/
if (*m->request_url && m->method != HTTP_CONNECT)
{
Url u(m->request_url, strlen(m->request_url));
if (expected->host)
{
MESSAGE_CHECK_URL_EQ(u, expected, m, host, Url::U_HOST);
}
if (expected->userinfo)
{
MESSAGE_CHECK_URL_EQ(u, expected, m, userinfo, Url::U_USERINFO);
}
m->port = u.getPortNumber();
MESSAGE_CHECK_URL_EQ(u, expected, m, query_string, Url::U_QUERY);
MESSAGE_CHECK_URL_EQ(u, expected, m, fragment, Url::U_FRAGMENT);
MESSAGE_CHECK_URL_EQ(u, expected, m, request_path, Url::U_PATH);
MESSAGE_CHECK_NUM_EQ(expected, m, port);
}
if (connect)
{
check_num_eq(m, "body_size", 0, m->body_size);
}
else if (expected->body_size)
{
MESSAGE_CHECK_NUM_EQ(expected, m, body_size);
}
else
{
MESSAGE_CHECK_STR_EQ(expected, m, body);
}
if (connect)
{
check_num_eq(m, "num_chunks_complete", 0, m->num_chunks_complete);
}
else
{
/* assert(m->num_chunks == m->num_chunks_complete); */
MESSAGE_CHECK_NUM_EQ(expected, m, num_chunks_complete);
for (i = 0; i < m->num_chunks && i < MAX_CHUNKS; i++)
{
MESSAGE_CHECK_NUM_EQ(expected, m, chunk_lengths[i]);
}
}
MESSAGE_CHECK_NUM_EQ(expected, m, num_headers);
int r;
for (i = 0; i < m->num_headers; i++)
{
r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]);
if (!r) return 0;
r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]);
if (!r) return 0;
}
MESSAGE_CHECK_STR_EQ(expected, m, upgrade);
return 1;
}
/**
* Given a sequence of varargs messages, return the number of them that the
* parser should successfully parse, taking into account that upgraded
* messages prevent all subsequent messages from being parsed.
*/
static size_t count_parsed_messages(const size_t nmsgs, ...)
{
U_TRACE(5, "::count_parsed_messages(%u)", nmsgs)
size_t i;
va_list ap;
va_start(ap, nmsgs);
for (i = 0; i < nmsgs; i++)
{
struct message *m = va_arg(ap, struct message *);
if (m->upgrade)
{
va_end(ap);
return i + 1;
}
}
va_end(ap);
return nmsgs;
}
/**
* Given a sequence of bytes and the number of these that we were able to
* parse, verify that upgrade bodies are correct.
static void upgrade_message_fix(char* body, const size_t nread, const size_t nmsgs, ...)
{
va_list ap;
size_t i;
size_t off = 0;
va_start(ap, nmsgs);
for (i = 0; i < nmsgs; i++)
{
struct message* m = va_arg(ap, struct message *);
off += strlen(m->raw);
if (m->upgrade)
{
off -= strlen(m->upgrade);
// Check the portion of the response after its specified upgrade
if (!check_str_eq(m, "upgrade", body + off, body + nread)) abort();
// Fix up the response so that message_eq() will verify the beginning of the upgrade
*(body + nread + strlen(m->upgrade)) = '\0';
_messages[num_messages -1 ].upgrade = body + nread;
va_end(ap);
return;
}
}
va_end(ap);
printf("\n\n*** Error: expected a message with upgrade ***\n");
abort();
}
*/
static void print_error(const char* raw, size_t error_location)
{
U_TRACE(5, "::print_error(%S,%u)", raw, error_location)
int this_line = 0, char_len = 0;
size_t i, j, len = strlen(raw), error_location_line = 0;
for (i = 0; i < len; i++)
{
if (i == error_location) this_line = 1;
switch (raw[i])
{
case '\r':
char_len = 2;
fprintf(stderr, "\\r");
break;
case '\n':
fprintf(stderr, "\\n\n");
if (this_line) goto print;
error_location_line = 0;
continue;
default:
char_len = 1;
fputc(raw[i], stderr);
break;
}
if (!this_line) error_location_line += char_len;
}
fprintf(stderr, "[eof]\n");
print:
for (j = 0; j < error_location_line; j++)
{
fputc(' ', stderr);
}
fprintf(stderr, "^\n\nerror location: %u\n", (unsigned int)error_location);
}
static void test_message(const struct message* message)
{
U_TRACE(5, "::test_message(%p)", message)
size_t raw_len = strlen(message->raw);
size_t msg1len;
for (msg1len = 0; msg1len < raw_len; msg1len++)
{
size_t read;
const char* msg1 = message->raw;
const char* msg2 = msg1 + msg1len;
size_t msg2len = raw_len - msg1len;
if (msg1len)
{
read = parse(msg1, msg1len, message->type);
if (message->upgrade && num_messages > 0)
{
_messages[num_messages - 1].upgrade = msg1 + read;
goto test;
}
if (read != msg1len)
{
print_error(msg1, read);
abort();
}
}
read = parse(msg2, msg2len);
if (message->upgrade)
{
_messages[num_messages - 1].upgrade = msg2 + read;
goto test;
}
if (read != msg2len)
{
print_error(msg2, read);
abort();
}
test: if (num_messages != 1)
{
printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name);
abort();
}
if (!message_eq(0, 0, message)) abort();
}
}
static void test_simple(const char* buf, int err_expected)
{
U_TRACE(5, "::test_simple(%S,%d)", buf, err_expected)
int err = 0;
parse(buf, strlen(buf));
if (err_expected != err)
{
fprintf(stderr, "\n*** test_simple expected %d, but saw %d ***\n\n%s\n", err_expected, err, buf);
abort();
}
}
static void test_invalid_header_content(int req, const char* str)
{
U_TRACE(5, "::test_invalid_header_content(%d,%S)", req, str)
const char* buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n";
parse(buf, strlen(buf));
buf = str;
size_t buflen = strlen(buf);
if (parse(buf, buflen)) fprintf(stderr, "\n*** Error expected but none in invalid header content test ***\n");
}
static void test_invalid_header_field_content_error(int req)
{
U_TRACE(5, "::test_invalid_header_field_content_error(%d)", req)
test_invalid_header_content(req, "Foo: F\01ailure");
test_invalid_header_content(req, "Foo: B\02ar");
}
static void test_invalid_header_field(int req, const char* str)
{
U_TRACE(5, "::test_invalid_header_field(%d,%S)", req, str)
const char* buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n";
parse(buf, strlen(buf));
buf = str;
size_t buflen = strlen(buf);
if (parse(buf, buflen)) fprintf(stderr, "\n*** Error expected but none in invalid header token test ***\n");
}
static void test_invalid_header_field_token_error(int req)
{
U_TRACE(5, "::test_invalid_header_field_token_error(%d)", req)
test_invalid_header_field(req, "Fo@: Failure");
test_invalid_header_field(req, "Foo\01\test: Bar");
}
static void test_double_content_length_error(int req)
{
U_TRACE(5, "::test_double_content_length_error(%d)", req)
const char* buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n";
parse(buf, strlen(buf));
buf = "Content-Length: 0\r\nContent-Length: 1\r\n\r\n";
size_t buflen = strlen(buf);
if (parse(buf, buflen)) fprintf(stderr, "\n*** Error expected but none in double content-length test ***\n");
}
static void test_chunked_content_length_error(int req)
{
U_TRACE(5, "::test_chunked_content_length_error(%d)", req)
const char* buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n";
parse(buf, strlen(buf));
buf = "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n";
size_t buflen = strlen(buf);
if (parse(buf, buflen)) fprintf(stderr, "\n*** Error expected but none in chunked content-length test ***\n");
}
static void test_header_cr_no_lf_error(int req)
{
U_TRACE(5, "::test_header_cr_no_lf_error(%d)", req)
const char* buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n";
parse(buf, strlen(buf));
buf = "Foo: 1\rBar: 1\r\n\r\n";
size_t buflen = strlen(buf);
if (parse(buf, buflen)) fprintf(stderr, "\n*** Error expected but none in header whitespace test ***\n");
}
static void test_header_overflow_error(int req)
{
U_TRACE(5+256, "::test_header_overflow_error(%d)", req)
const char* buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.0 200 OK\r\n";
parse(buf, strlen(buf), req);
buf = "header-key: header-value\r\n";
size_t buflen = strlen(buf);
if (parse(buf, buflen)) fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n");
}
static void test_header_nread_value()
{
U_TRACE_NO_PARAM(5+256, "::test_header_nread_value()")
bool result = parse(U_CONSTANT_TO_PARAM("GET / HTTP/1.1\r\nheader: value\nhdr: value\r\n"));
U_INTERNAL_ASSERT_EQUALS(result, false)
U_INTERNAL_ASSERT(U_ClientImage_data_missing)
}
static void test_content_length_overflow(const char* buf, size_t buflen, int req, int expect_ok)
{
U_TRACE(5, "::test_content_length_overflow(%.*S,%u,%d,%d)", buflen, buf, buflen, req, expect_ok)
bool result = (expect_ok == (parse(buf, buflen, req) != 0));
U_INTERNAL_ASSERT(result)
}
static void test_header_content_length_overflow_error(void)
{
U_TRACE_NO_PARAM(5, "::test_header_content_length_overflow_error()")
#define X(size) \
"HTTP/1.1 200 OK\r\n" \
"Content-Length: " #size "\r\n" \
"\r\n"
const char a[] = X(1844674407370955160); /* 2^64 / 10 - 1 */
const char b[] = X(18446744073709551615); /* 2^64-1 */
const char c[] = X(18446744073709551616); /* 2^64 */
#undef X
test_content_length_overflow(a, sizeof(a)-1, HTTP_RESPONSE, 1); /* expect ok */
test_content_length_overflow(b, sizeof(b)-1, HTTP_RESPONSE, 0); /* expect failure */
test_content_length_overflow(c, sizeof(c)-1, HTTP_RESPONSE, 0); /* expect failure */
}
static void test_chunk_content_length_overflow_error(void)
{
U_TRACE_NO_PARAM(5, "::test_chunk_content_length_overflow_error()")
#define X(size) \
"HTTP/1.1 200 OK\r\n" \
"Transfer-Encoding: chunked\r\n" \
"\r\n" \
#size "\r\n" \
"..."
const char a[] = X(FFFFFFFFFFFFFFE); /* 2^64 / 16 - 1 */
const char b[] = X(FFFFFFFFFFFFFFFF); /* 2^64-1 */
const char c[] = X(10000000000000000); /* 2^64 */
#undef X
test_content_length_overflow(a, sizeof(a)-1, HTTP_RESPONSE, 1); /* expect ok */
test_content_length_overflow(b, sizeof(b)-1, HTTP_RESPONSE, 0); /* expect failure */
test_content_length_overflow(c, sizeof(c)-1, HTTP_RESPONSE, 0); /* expect failure */
}
static void test_no_overflow_long_body(int req, size_t length)
{
U_TRACE(5+256, "::test_no_overflow_long_body(%d,%u)", req, length)
size_t i;
char buf1[3000];
size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %lu\r\n\r\n", req == HTTP_REQUEST ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", (unsigned long)length);
for (i = 0; i < length; i++) buf1[buf1len+i] = 'a';
buf1[buf1len+i] = '\0';
bool result = parse(buf1, buf1len+i, req);
U_INTERNAL_ASSERT(result)
}
static void test_multiple3(const struct message* r1, const struct message* r2, const struct message* r3)
{
U_TRACE(5, "::test_multiple3(%p,%p,%p)", r1, r2, r3)
int message_count = count_parsed_messages(3, r1, r2, r3);
char total[ strlen(r1->raw)
+ strlen(r2->raw)
+ strlen(r3->raw)
+ 1
];
total[0] = '\0';
strcat(total, r1->raw);
strcat(total, r2->raw);
strcat(total, r3->raw);
parse(total, strlen(total));
if (!message_eq(0, 0, r1)) abort();
if (message_count > 1 && !message_eq(1, 0, r2)) abort();
if (message_count > 2 && !message_eq(2, 0, r3)) abort();
}
/*
* SCAN through every possible breaking to make sure the
* parser can handle getting the content in any chunks that
* might come from the socket
*/
static void test_scan(const struct message* r1, const struct message* r2, const struct message* r3)
{
U_TRACE(5, "::test_scan(%p,%p,%p)", r1, r2, r3)
char total[80*1024] = "\0";
char buf1[80*1024] = "\0";
char buf2[80*1024] = "\0";
char buf3[80*1024] = "\0";
strcat(total, r1->raw);
strcat(total, r2->raw);
strcat(total, r3->raw);
int total_len = strlen(total);
int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2;
int ops = 0 ;
size_t buf1_len, buf2_len, buf3_len;
int i, j, message_count = count_parsed_messages(3, r1, r2, r3);
for (int type_both = 0; type_both < 2; type_both++)
{
for (j = 2; j < total_len; j++)
{
for (i = 1; i < j; i++)
{
if (ops % 1000 == 0)
{
printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops);
fflush(stdout);
}
ops += 1;
buf1_len = i;
strlncpy(buf1, sizeof(buf1), total, buf1_len);
buf1[buf1_len] = 0;
buf2_len = j - i;
strlncpy(buf2, sizeof(buf1), total+i, buf2_len);
buf2[buf2_len] = 0;
buf3_len = total_len - j;
strlncpy(buf3, sizeof(buf1), total+j, buf3_len);
buf3[buf3_len] = 0;
parse(buf1, buf1_len);
parse(buf2, buf2_len);
parse(buf3, buf3_len);
if (message_count != num_messages)
{
fprintf(stderr, "\n\nParser didn't see %d messages only %d\n", message_count, num_messages);
goto error;
}
if (!message_eq(0, 0, r1))
{
fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n");
goto error;
}
if (message_count > 1 && !message_eq(1, 0, r2))
{
fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n");
goto error;
}
if (message_count > 2 && !message_eq(2, 0, r3))
{
fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n");
goto error;
}
}
}
}
puts("\b\b\b\b100%");
return;
error:
fprintf(stderr, "i=%d j=%d\n", i, j);
fprintf(stderr, "buf1 (%u) %s\n\n", (unsigned int)buf1_len, buf1);
fprintf(stderr, "buf2 (%u) %s\n\n", (unsigned int)buf2_len , buf2);
fprintf(stderr, "buf3 (%u) %s\n", (unsigned int)buf3_len, buf3);
abort();
}
// user required to free the result string terminated by \0
static void create_large_chunked_message(int body_size_in_kb, const char* headers)
{
size_t wrote = 0;
size_t headers_len = strlen(headers);
memcpy(large_chunked_message, headers, headers_len);
wrote += headers_len;
for (int i = 0; i < body_size_in_kb; i++)
{
// write 1kb chunk into the body.
memcpy(large_chunked_message + wrote, "400\r\n", 5);
wrote += 5;
memset(large_chunked_message + wrote, 'C', 1024);
wrote += 1024;
strcpy(large_chunked_message + wrote, "\r\n");
wrote += 2;
}
memcpy(large_chunked_message + wrote, "0\r\n\r\n", 6);
}
/* Verify that body and next message won't be parsed in responses to CONNECT */
static void test_message_connect(const struct message* msg)
{
U_TRACE(5, "::test_message_connect(%p)", msg)
char* buf = (char*) msg->raw;
size_t buflen = strlen(msg->raw);
parse(buf, buflen);
if (num_messages != 1)
{
printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name);
abort();
}
if (!message_eq(0, 1, msg)) abort();
}
static size_t parse_count_body(const char* buf, size_t len)
{
size_t nparsed = parse(buf, len);
return nparsed;
}
static void test_message_count_body(const struct message* message)
{
U_TRACE(5, "::test_message_count_body(%p)", message)
size_t read;
size_t l = strlen(message->raw);
size_t i, toread;
size_t chunk = 4024;
for (i = 0; i < l; i+= chunk)
{
toread = U_min(l-i, chunk);
read = parse_count_body(message->raw + i, toread);
if (read != toread)
{
print_error(message->raw, read);
abort();
}
}
if (num_messages != 1)
{
printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name);
abort();
}
if (!message_eq(0, 0, message)) abort();
}
static int http_parser_parse_url(const struct url_test* test, struct http_parser_url* u)
{
U_TRACE(5, "::http_parser_parse_url(%p,%p)", test, u)
bool result = parse(test->url, strlen(test->url));
(void) memcpy(u, &test->u, sizeof(struct http_parser_url));
return (test->rv ? result == false : result);
}
static void dump_url(const char* url, const struct http_parser_url* u)
{
U_TRACE(5, "::dump_url(%S,%p)", url, u)
unsigned int i;
printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port);
for (i = 0; i < UF_MAX; i++)
{
if ((u->field_set & (1 << i)) == 0)
{
printf("\tfield_data[%u]: unset\n", i);
continue;
}
printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n\"", i, u->field_data[i].off, u->field_data[i].len, u->field_data[i].len, url + u->field_data[i].off);
}
}
static void test_parse_url(void)
{
U_TRACE_NO_PARAM(5+256, "::test_parse_url()")
unsigned int i, rv;
struct http_parser_url u;
const struct url_test* test;
for (i = 0; i < getNumUrlTests(); i++)
{
test = url_tests+i;
memset(&u, 0, sizeof(u));
rv = http_parser_parse_url(test, &u);
if (test->rv == 0)
{
if (rv != 0)
{
printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " "unexpected rv %d ***\n\n", test->url, test->name, rv);
abort();
}
if (memcmp(&u, &test->u, sizeof(u)) != 0)
{
printf("\n*** http_parser_parse_url(\"%s\") \"%s\" failed ***\n", test->url, test->name);
printf("target http_parser_url:\n");
dump_url(test->url, &test->u);
printf("result http_parser_url:\n");
dump_url(test->url, &u);
abort();
}
}
else
{
/* test->rv != 0 */
if (rv == 0)
{
printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, unexpected rv %d ***\n\n", test->url, test->name, rv);
abort();
}
}
}
}
int main(int argc, char** argv, char** env)
{
U_ULIB_INIT(argv);
U_TRACE(5, "main(%d)", argc)
int i, j, k, request_count, response_count;
u_init_ulib_hostname();
UClientImage_Base::init();
UString::str_allocate(STR_ALLOCATE_HTTP);
for ( request_count = 0; requests[ request_count].name; request_count++);
for (response_count = 0; responses[response_count].name; response_count++);
//// API
test_parse_url();
//// NREAD
test_header_nread_value();
//// OVERFLOW CONDITIONS
test_header_overflow_error(HTTP_REQUEST);
test_header_overflow_error(HTTP_RESPONSE);
test_no_overflow_long_body(HTTP_REQUEST, 1000);
test_no_overflow_long_body(HTTP_RESPONSE, 1000);
test_header_content_length_overflow_error();
test_chunk_content_length_overflow_error();
//// HEADER FIELD CONDITIONS
test_double_content_length_error(HTTP_REQUEST);
test_chunked_content_length_error(HTTP_REQUEST);
test_header_cr_no_lf_error(HTTP_REQUEST);
test_invalid_header_field_token_error(HTTP_REQUEST);
test_invalid_header_field_content_error(HTTP_REQUEST);
test_double_content_length_error(HTTP_RESPONSE);
test_chunked_content_length_error(HTTP_RESPONSE);
test_header_cr_no_lf_error(HTTP_RESPONSE);
test_invalid_header_field_token_error(HTTP_RESPONSE);
test_invalid_header_field_content_error(HTTP_RESPONSE);
//// RESPONSES
for (i = 0; i < response_count; i++) test_message(&responses[i]);
for (i = 0; i < response_count; i++) test_message_connect(&responses[i]);
for (i = 0; i < response_count; i++)
{
if (!responses[i].should_keep_alive) continue;
for (j = 0; j < response_count; j++)
{
if (!responses[j].should_keep_alive) continue;
for (k = 0; k < response_count; k++) test_multiple3(&responses[i], &responses[j], &responses[k]);
}
}
test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]);
test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]);
// test very large chunked response
{
create_large_chunked_message(31337,
"HTTP/1.0 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"Content-Type: text/plain\r\n"
"\r\n");
for (i = 0; i < MAX_CHUNKS; i++) large_chunked.chunk_lengths[i] = 1024;
test_message_count_body(&large_chunked);
}
printf("response scan 1/2 ");
test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY]
, &responses[NO_BODY_HTTP10_KA_204]
, &responses[NO_REASON_PHRASE]
);
printf("response scan 2/2 ");
test_scan( &responses[BONJOUR_MADAME_FR]
, &responses[UNDERSTORE_HEADER_KEY]
, &responses[NO_CARRIAGE_RET]
);
puts("responses okay");
/// REQUESTS
test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION);
// Extended characters - see nodejs/test/parallel/test-http-headers-obstext.js
test_simple("GET / HTTP/1.1\r\n" "Test: Düsseldorf\r\n", HPE_OK);
// Well-formed but incomplete
test_simple("GET / HTTP/1.1\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: 6\r\n"
"\r\n"
"fooba",
HPE_OK);
static const char* all_methods[] = {
"DELETE",
"GET",
"HEAD",
"POST",
"PUT",
//"CONNECT", //CONNECT can't be tested like other methods, it's a tunnel
"OPTIONS",
"TRACE",
"COPY",
"LOCK",
"MKCOL",
"MOVE",
"PROPFIND",
"PROPPATCH",
"SEARCH",
"UNLOCK",
"BIND",
"REBIND",
"UNBIND",
"ACL",
"REPORT",
"MKACTIVITY",
"CHECKOUT",
"MERGE",
"M-SEARCH",
"NOTIFY",
"SUBSCRIBE",
"UNSUBSCRIBE",
"PATCH",
"PURGE",
"MKCALENDAR",
"LINK",
"UNLINK",
U_NULLPTR };
const char** this_method;
for (this_method = all_methods; *this_method; this_method++)
{
char buf[200];
sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method);
test_simple(buf, HPE_OK);
}
static const char* bad_methods[] = {
"ASDF",
"C******",
"COLA",
"GEM",
"GETA",
"M****",
"MKCOLA",
"PROPPATCHA",
"PUN",
"PX",
"SA",
"hello world",
U_NULLPTR };
for (this_method = bad_methods; *this_method; this_method++)
{
char buf[200];
sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method);
test_simple(buf, HPE_INVALID_METHOD);
}
// illegal header field name line folding
test_simple("GET / HTTP/1.1\r\n" "name\r\n" " : value\r\n" "\r\n", HPE_INVALID_HEADER_TOKEN);
const char* dumbfuck2 =
"GET / HTTP/1.1\r\n"
"X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n"
"\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n"
"\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n"
"\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n"
"\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n"
"\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n"
"\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n"
"\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n"
"\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n"
"\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n"
"\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n"
"\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n"
"\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n"
"\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n"
"\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n"
"\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n"
"\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n"
"\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n"
"\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n"
"\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n"
"\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n"
"\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n"
"\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n"
"\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n"
"\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n"
"\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n"
"\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n"
"\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n"
"\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n"
"\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n"
"\tRA==\r\n"
"\t-----END CERTIFICATE-----\r\n"
"\r\n";
test_simple(dumbfuck2, HPE_OK);
const char* corrupted_connection =
"GET / HTTP/1.1\r\n"
"Host: www.example.com\r\n"
"Connection\r\033\065\325eep-Alive\r\n"
"Accept-Encoding: gzip\r\n"
"\r\n";
test_simple(corrupted_connection, HPE_INVALID_HEADER_TOKEN);
const char* corrupted_header_name =
"GET / HTTP/1.1\r\n"
"Host: www.example.com\r\n"
"X-Some-Header\r\033\065\325eep-Alive\r\n"
"Accept-Encoding: gzip\r\n"
"\r\n";
test_simple(corrupted_header_name, HPE_INVALID_HEADER_TOKEN);
#if 0
// NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body until EOF.
// no content-length error if there is a body without content length
const char* bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n"
"Accept: */*\r\n"
"\r\n"
"HELLO";
test_simple(bad_get_no_headers_no_body, 0);
#endif
/* TODO sending junk and large headers gets rejected */
/* check to make sure our predefined requests are okay */
for (i = 0; requests[i].name; i++) test_message(&requests[i]);
for (i = 0; i < request_count; i++)
{
if (!requests[i].should_keep_alive) continue;
for (j = 0; j < request_count; j++)
{
if (!requests[j].should_keep_alive) continue;
for (k = 0; k < request_count; k++) test_multiple3(&requests[i], &requests[j], &requests[k]);
}
}
printf("request scan 1/4 ");
test_scan( &requests[GET_NO_HEADERS_NO_BODY]
, &requests[GET_ONE_HEADER_NO_BODY]
, &requests[GET_NO_HEADERS_NO_BODY]
);
printf("request scan 2/4 ");
test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE]
, &requests[POST_IDENTITY_BODY_WORLD]
, &requests[GET_FUNKY_CONTENT_LENGTH]
);
printf("request scan 3/4 ");
test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END]
, &requests[CHUNKED_W_TRAILING_HEADERS]
, &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH]
);
printf("request scan 4/4 ");
test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET]
, &requests[PREFIX_NEWLINE_GET ]
, &requests[CONNECT_REQUEST]
);
puts("requests okay");
}