499 lines
12 KiB
C
499 lines
12 KiB
C
#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);
|
|
} 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\n0"
|
|
};
|
|
|
|
struct testobj t5 = {
|
|
.name = "POST request with multiple chunks",
|
|
.type = htp_type_request,
|
|
.data = "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"
|
|
};
|
|
|
|
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;
|
|
}
|
|
|