| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdint.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <inttypes.h> |
| |
| #include "htparse.h" |
| |
| #define ADD_DATA_BUF(buf, name, data, len) do { \ |
| strcat(buf, name ": '"); \ |
| strncat(buf, data, len); \ |
| strcat(buf, "'\n"); \ |
| } while (0) |
| |
| struct testobj { |
| char * name; |
| char * data; |
| htp_type type; |
| }; |
| |
| |
| static int |
| _msg_begin(htparser * p) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| strcat(buf, "START\n"); |
| |
| return 0; |
| } |
| |
| static int |
| _method(htparser * p, const char * b, size_t s) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| if (htparser_get_method(p) == htp_method_UNKNOWN) { |
| ADD_DATA_BUF(buf, "METHOD_UNKNOWN", b, s); |
| return htp_method_UNKNOWN; |
| } else { |
| ADD_DATA_BUF(buf, "METHOD", b, s); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| _scheme(htparser * p, const char * b, size_t s) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| ADD_DATA_BUF(buf, "SCHEME", b, s); |
| |
| return 0; |
| } |
| |
| static int |
| _host(htparser * p, const char * b, size_t s) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| ADD_DATA_BUF(buf, "HOST", b, s); |
| |
| return 0; |
| } |
| |
| static int |
| _port(htparser * p, const char * b, size_t s) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| ADD_DATA_BUF(buf, "PORT", b, s); |
| |
| return 0; |
| } |
| |
| static int |
| _path(htparser * p, const char * b, size_t s) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| ADD_DATA_BUF(buf, "PATH", b, s); |
| |
| return 0; |
| } |
| |
| static int |
| _args(htparser * p, const char * b, size_t s) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| ADD_DATA_BUF(buf, "ARGS", b, s); |
| |
| return 0; |
| } |
| |
| static int |
| _uri(htparser * p, const char * b, size_t s) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| ADD_DATA_BUF(buf, "URI", b, s); |
| |
| return 0; |
| } |
| |
| static int |
| _hdrs_begin(htparser * p) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| strcat(buf, "HDRS_BEGIN\n"); |
| |
| return 0; |
| } |
| |
| static int |
| _hdr_key(htparser * p, const char * b, size_t s) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| ADD_DATA_BUF(buf, "HDR_KEY", b, s); |
| |
| return 0; |
| } |
| |
| static int |
| _hdr_val(htparser * p, const char * b, size_t s) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| ADD_DATA_BUF(buf, "HDR_VAL", b, s); |
| |
| return 0; |
| } |
| |
| static int |
| _hostname(htparser * p, const char * b, size_t s) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| ADD_DATA_BUF(buf, "HOSTNAME", b, s); |
| |
| return 0; |
| } |
| |
| static int |
| _hdrs_complete(htparser * p) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| strcat(buf, "HDRS_COMPLETE\n"); |
| |
| return 0; |
| } |
| |
| static int |
| _new_chunk(htparser * p) { |
| char * buf = (char *)htparser_get_userdata(p); |
| char tbuf[1024] = { 0 }; |
| |
| sprintf(tbuf, "NEW_CHUNK: %" PRIu64 "\n", htparser_get_content_length(p)); |
| strcat(buf, tbuf); |
| |
| return 0; |
| } |
| |
| static int |
| _chunk_complete(htparser * p) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| strcat(buf, "END_CHUNK\n"); |
| |
| return 0; |
| } |
| |
| static int |
| _chunks_complete(htparser * p) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| strcat(buf, "END_CHUNKS\n"); |
| |
| return 0; |
| } |
| |
| static int |
| _body(htparser * p, const char * b, size_t s) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| ADD_DATA_BUF(buf, "BODY", b, s); |
| |
| return 0; |
| } |
| |
| static int |
| _msg_complete(htparser * p) { |
| char * buf = (char *)htparser_get_userdata(p); |
| |
| strcat(buf, "MSG_COMPLETE\n"); |
| |
| return 0; |
| } |
| |
| htparse_hooks hooks = { |
| .on_msg_begin = _msg_begin, |
| .method = _method, |
| .scheme = _scheme, |
| .host = _host, |
| .port = _port, |
| .path = _path, |
| .args = _args, |
| .uri = _uri, |
| .on_hdrs_begin = _hdrs_begin, |
| .hdr_key = _hdr_key, |
| .hdr_val = _hdr_val, |
| .hostname = _hostname, |
| .on_hdrs_complete = _hdrs_complete, |
| .on_new_chunk = _new_chunk, |
| .on_chunk_complete = _chunk_complete, |
| .on_chunks_complete = _chunks_complete, |
| .body = _body, |
| .on_msg_complete = _msg_complete |
| }; |
| |
| |
| struct testobj t1 = { |
| .name = "small GET request", |
| .type = htp_type_request, |
| .data = "GET / HTTP/1.0\r\n\r\n" |
| }; |
| |
| struct testobj t2 = { |
| .name = "GET request with arguments", |
| .type = htp_type_request, |
| .data = "GET /test?a=b&c=d HTTP/1.1\r\n\r\n" |
| }; |
| |
| struct testobj t3 = { |
| .name = "POST request with 4 bytes of data", |
| .type = htp_type_request, |
| .data = "POST /foo/bar HTTP/1.0\r\n" |
| "Content-Length: 4\r\n\r\n" |
| "abcd" |
| }; |
| |
| struct testobj t4 = { |
| .name = "Simple POST with chunked data", |
| .type = htp_type_request, |
| .data = "POST /test/ HTTP/1.1\r\n" |
| "Transfer-Encoding: chunked\r\n\r\n" |
| "1e\r\nall your base are belong to us\r\n" |
| "0\r\n" |
| "\r\n" |
| }; |
| |
| struct testobj t5 = { |
| .name = "POST request with multiple chunks", |
| .type = htp_type_request, |
| .data = "POST /test/ HTTP/1.1\r\n" |
| "Connection: Keep-Alive\r\n" |
| "Transfer-Encoding: chunked\r\n\r\n" |
| "23\r\n" |
| "This is the data in the first chunk" |
| "\r\n" |
| "1A\r\n" |
| "and this is the second one" |
| "\r\n" |
| "3\r\n" |
| "foo\r\n" |
| "6\r\n" |
| "barbaz\r\n" |
| "0\r\n\r\n" |
| }; |
| |
| struct testobj t6 = { |
| .name = "GET request with a host header", |
| .type = htp_type_request, |
| .data = "GET /test/ HTTP/1.0\r\n" |
| "Host: ieatfood.net\r\n\r\n" |
| }; |
| |
| struct testobj t7 = { |
| .name = "GET request with an empty header value", |
| .type = htp_type_request, |
| .data = "GET /test/ HTTP/1.0\r\n" |
| "Header1: value1\r\n" |
| "Header2: \r\n" |
| "Header3: value3\r\n\r\n" |
| }; |
| |
| struct testobj t8 = { |
| .name = "GET request with a multi-line header value", |
| .type = htp_type_request, |
| .data = "GET /test/ HTTP/1.1\r\n" |
| "Header1: value1\r\n" |
| "Header2: val\r\n" |
| "\tue\r\n" |
| "\t2\r\n" |
| "Header3: value3\r\n\r\n" |
| }; |
| |
| struct testobj t9 = { |
| .name = "[FAILURE TEST] GET REQUEST with LF instead of CRLF on header value", |
| .type = htp_type_request, |
| .data = "GET /test/ HTTP/1.1\r\n" |
| "Header: value\n\n" |
| }; |
| |
| struct testobj t10 = { |
| .name = "[FAILURE TEST] GET request with invalid protocol", |
| .type = htp_type_request, |
| .data = "GET /test/ fdasfs\r\n\r\n" |
| }; |
| |
| struct testobj t11 = { |
| .name = "[FALURE TEST] POST request with invalid chunk length", |
| .type = htp_type_request, |
| .data = "POST /test/ HTTP/1.1\r\n" |
| "Transfer-Encoding: chunked\r\n\r\n" |
| "3\r\n" |
| "foo" |
| "\r\n" |
| "A\r\n" |
| "foobar\r\n" |
| "3\r\n" |
| "baz\r\n" |
| "0\r\n\r\n" |
| }; |
| |
| struct testobj t12 = { |
| .name = "Simple GET on a FTP scheme", |
| .type = htp_type_request, |
| .data = "GET ftp://test.com/foo/bar HTTP/1.1\r\n\r\n" |
| }; |
| |
| struct testobj t13 = { |
| .name = "Multiple GET requests in HTTP/1.1 request", |
| .type = htp_type_request, |
| .data = "GET /request1 HTTP/1.1\r\n\r\n" |
| "GET /request2 HTTP/1.0\r\n" |
| "Connection: close\r\n\r\n" |
| }; |
| |
| struct testobj t14 = { |
| .name = "[FAILURE TEST] invalid request type", |
| .type = htp_type_request, |
| .data = "DERP /test HTTP/1.1\r\n\r\n" |
| }; |
| |
| struct testobj t15 = { |
| .name = "http SCHEME request with port / args / headers", |
| .type = htp_type_request, |
| .data = "GET http://ieatfood.net:80/index.html?foo=bar&baz=buz HTTP/1.0\r\n" |
| "Host: ieatfood.net\r\n" |
| "Header: value\r\n\r\n" |
| }; |
| |
| struct testobj t16 = { |
| .name = "GET request which should run all callbacks minus scheme stuff, this includes multiple requests", |
| .type = htp_type_request, |
| .data = "GET /test1?a=b&c=d&e=f HTTP/1.1\r\n" |
| "Content-Length: 6\r\n\r\n" |
| "foobar" |
| "GET /test2 HTTP/1.1\r\n" |
| "Header: test2\r\n\r\n" |
| "POST /test/ HTTP/1.1\r\n" |
| "Transfer-Encoding: chunked\r\n\r\n" |
| "23\r\n" |
| "This is the data in the first chunk" |
| "\r\n" |
| "1A\r\n" |
| "and this is the second one" |
| "\r\n" |
| "3\r\n" |
| "foo\r\n" |
| "6\r\n" |
| "barbaz\r\n" |
| "0\r\n\r\n" |
| "GET /test/ HTTP/1.1\r\n" |
| "Host: ieatfood.net\r\n" |
| "Connection: close\r\n\r\n" |
| }; |
| |
| struct testobj t17 = { |
| .name = "Like the last test, but with scheme requests", |
| .type = htp_type_request, |
| .data = "GET http://ieatfood.net/test1?a=b&c=d&e=f HTTP/1.1\r\n" |
| "Content-Length: 6\r\n\r\n" |
| "foobar" |
| "GET https://ieatfood.net:443/test2 HTTP/1.1\r\n" |
| "Header: test2\r\n\r\n" |
| "POST /test/ HTTP/1.1\r\n" |
| "Transfer-Encoding: chunked\r\n\r\n" |
| "23\r\n" |
| "This is the data in the first chunk" |
| "\r\n" |
| "1A\r\n" |
| "and this is the second one" |
| "\r\n" |
| "3\r\n" |
| "foo\r\n" |
| "6\r\n" |
| "barbaz\r\n" |
| "0\r\n\r\n" |
| "GET ftp://ackers.net:21/test/ HTTP/1.1\r\n" |
| "Host: ackers.net\r\n" |
| "Connection: close\r\n\r\n" |
| }; |
| |
| struct testobj t18 = { |
| .name = "scheme request with empty path", |
| .type = htp_type_request, |
| .data = "GET http://ackers.net HTTP/1.0\r\n\r\n" |
| }; |
| |
| struct testobj t19 = { |
| .name = "basic HTTP RESPONSE", |
| .type = htp_type_response, |
| .data = "HTTP/1.1 200 OK\r\n\r\n" |
| }; |
| |
| struct testobj t20 = { |
| .name = "HTTP RESPONSE with body", |
| .type = htp_type_response, |
| .data = "HTTP/1.1 200 OK\r\n" |
| "Content-Length: 6\r\n\r\n" |
| "foobar" |
| }; |
| |
| struct testobj t21 = { |
| .name = "HTTP RESPONSE with chunked data", |
| .type = htp_type_response, |
| .data = "HTTP/1.1 200 OK\r\n" |
| "Transfer-Encoding: chunked\r\n\r\n" |
| "23\r\n" |
| "This is the data in the first chunk" |
| "\r\n" |
| "1A\r\n" |
| "and this is the second one" |
| "\r\n" |
| "3\r\n" |
| "foo\r\n" |
| "6\r\n" |
| "barbaz\r\n" |
| "0\r\n\r\n" |
| }; |
| |
| struct testobj t22 = { |
| .name = "Header key with no value", |
| .type = htp_type_request, |
| .data = "GET / HTTP/1.1\r\n" |
| "Accept\r\n\r\n" |
| }; |
| |
| |
| static int |
| _run_test(htparser * p, struct testobj * obj) { |
| size_t data_sz; |
| size_t parsed_sz; |
| char result_buf[5000] = { 0 }; |
| |
| htparser_init(p, obj->type); |
| htparser_set_userdata(p, result_buf); |
| |
| data_sz = strlen(obj->data); |
| parsed_sz = htparser_run(p, &hooks, obj->data, data_sz); |
| |
| strcat(result_buf, "ERROR_STR: "); |
| strcat(result_buf, htparser_get_strerror(p)); |
| strcat(result_buf, "\n"); |
| |
| printf("%s\n", obj->name); |
| printf("-----------------\n"); |
| printf("%s", result_buf); |
| printf("\n"); |
| |
| return 0; |
| } |
| |
| int |
| main(int argc, char **argv) { |
| htparser * parser; |
| |
| parser = htparser_new(); |
| assert(parser != NULL); |
| |
| _run_test(parser, &t1); |
| _run_test(parser, &t2); |
| _run_test(parser, &t3); |
| _run_test(parser, &t4); |
| _run_test(parser, &t5); |
| _run_test(parser, &t6); |
| _run_test(parser, &t7); |
| _run_test(parser, &t8); |
| _run_test(parser, &t9); |
| _run_test(parser, &t10); |
| _run_test(parser, &t11); |
| _run_test(parser, &t12); |
| _run_test(parser, &t13); |
| _run_test(parser, &t14); |
| _run_test(parser, &t15); |
| _run_test(parser, &t16); |
| _run_test(parser, &t17); |
| _run_test(parser, &t18); |
| _run_test(parser, &t19); |
| _run_test(parser, &t20); |
| _run_test(parser, &t21); |
| _run_test(parser, &t22); |
| |
| return 0; |
| } |
| |