masscan-mark-ii/src/proto-http.c

953 lines
31 KiB
C

#include "proto-http.h"
#include "proto-banner1.h"
#include "stack-tcp-api.h"
#include "smack.h"
#include "unusedparm.h"
#include "util-safefunc.h"
#include "masscan-app.h"
#include "util-malloc.h"
#include "util-bool.h"
#include "stack-tcp-core.h"
#include <ctype.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
enum {
HTTPFIELD_INCOMPLETE,
HTTPFIELD_SERVER,
HTTPFIELD_CONTENT_LENGTH,
HTTPFIELD_CONTENT_TYPE,
HTTPFIELD_VIA,
HTTPFIELD_LOCATION,
HTTPFIELD_UNKNOWN,
HTTPFIELD_NEWLINE,
};
static struct Patterns http_fields[] = {
{"Server:", 7, HTTPFIELD_SERVER, SMACK_ANCHOR_BEGIN},
//{"Content-Length:", 15, HTTPFIELD_CONTENT_LENGTH, SMACK_ANCHOR_BEGIN},
//{"Content-Type:", 13, HTTPFIELD_CONTENT_TYPE, SMACK_ANCHOR_BEGIN},
{"Via:", 4, HTTPFIELD_VIA, SMACK_ANCHOR_BEGIN},
{"Location:", 9, HTTPFIELD_LOCATION, SMACK_ANCHOR_BEGIN},
{":", 1, HTTPFIELD_UNKNOWN, 0},
{"\n", 1, HTTPFIELD_NEWLINE, 0},
{0,0,0,0}
};
enum {
HTML_INCOMPLETE,
HTML_TITLE,
HTML_UNKNOWN,
};
static struct Patterns html_fields[] = {
{"<TiTle", 6, HTML_TITLE, 0},
{0,0,0,0}
};
extern struct ProtocolParserStream banner_http;
/**
* We might have an incomplete HTTP request header. Thus, as we insert
* fields into it, we'll add missing components onto the end.
*/
static size_t
_http_append(unsigned char **inout_header, size_t length1, size_t length2, const char *str)
{
size_t str_length = strlen(str);
*inout_header = REALLOC(*inout_header, length1 + length2 + str_length + 1);
memcpy(*inout_header + length1, str, str_length + 1);
return str_length;
}
enum What {spaces, notspaces, end_of_line, end_of_field};
static size_t
_skip(enum What what, const unsigned char *hdr, size_t offset, size_t header_length)
{
switch (what) {
case notspaces:
while (offset < header_length && !isspace(hdr[offset]&0xFF))
offset++;
break;
case spaces:
while (offset < header_length && hdr[offset] != '\n' && isspace(hdr[offset]&0xFF))
offset++;
if (offset < header_length && hdr[offset] == '\n') {
while (offset > 0 && hdr[offset-1] == '\r')
offset--;
}
break;
case end_of_field:
while (offset < header_length && hdr[offset] != '\n')
offset++;
if (offset < header_length && hdr[offset] == '\n') {
while (offset > 0 && hdr[offset-1] == '\r')
offset--;
}
break;
case end_of_line:
while (offset < header_length && hdr[offset] != '\n')
offset++;
if (offset < header_length && hdr[offset] == '\n')
offset++;
break;
}
return offset;
}
/**
* Used when editing our HTTP prototype request, it replaces the existing
* field (start..end) with the new field. The header is resized and data moved
* to accommodate this insertion.
*/
static size_t
_http_insert(unsigned char **r_hdr, size_t start, size_t end, size_t header_length, size_t field_length, const void *field)
{
size_t old_field_length = (end-start);
size_t new_header_length = header_length + field_length - old_field_length;
unsigned char *hdr;
*r_hdr = REALLOC(*r_hdr, new_header_length + 1);
hdr = *r_hdr;
/* Shrink/expand the field */
memmove(&hdr[start + field_length], &hdr[end], header_length - end + 1);
/* Insert the new header at this location */
memcpy(&hdr[start], field, field_length);
return new_header_length;
}
/***************************************************************************
***************************************************************************/
size_t
http_change_requestline(unsigned char **hdr, size_t header_length,
const void *field, size_t field_length, int item)
{
size_t offset;
size_t start;
/* If no length given, calculate length */
if (field_length == ~(size_t)0)
field_length = strlen((const char *)field);
/* GET /example.html HTTP/1.0
* 0111233333333333334
* #0 skip leading whitespace
* #1 skip past method
* #2 skip past space after method
* #3 skip past URL field
* #4 skip past space after URL
* #5 skip past version
*/
/* #0 Skip leading whitespace */
offset = 0;
offset = _skip(spaces, *hdr, offset, header_length);
/* #1 Method */
start = offset;
if (offset == header_length)
header_length += _http_append(hdr, header_length, field_length, "GET");
offset = _skip(notspaces, *hdr, offset, header_length);
if (item == 0) {
return _http_insert(hdr, start, offset, header_length, field_length, field);
}
/* #2 Method space */
if (offset == header_length)
header_length += _http_append(hdr, header_length, field_length, " ");
offset = _skip(spaces, *hdr, offset, header_length);
/* #3 URL */
start = offset;
if (offset == header_length)
header_length += _http_append(hdr, header_length, field_length, "/");
offset = _skip(notspaces, *hdr, offset, header_length);
if (item == 1) {
return _http_insert(hdr, start, offset, header_length, field_length, field);
}
/* #4 Space after URL */
if (offset == header_length)
header_length += _http_append(hdr, header_length, field_length, " ");
offset = _skip(spaces, *hdr, offset, header_length);
/* #5 version */
start = offset;
if (offset == header_length)
header_length += _http_append(hdr, header_length, field_length, "HTTP/1.0");
offset = _skip(notspaces, *hdr, offset, header_length);
if (item == 2) {
return _http_insert(hdr, start, offset, header_length, field_length, field);
}
/* ending line */
if (offset == header_length)
header_length += _http_append(hdr, header_length, field_length, "\r\n");
offset = _skip(spaces, *hdr, offset, header_length);
offset = _skip(end_of_line, *hdr, offset, header_length);
/* now find a blank line */
for (;;) {
/* make sure there's at least one line left */
if (offset == header_length)
header_length += _http_append(hdr, header_length, field_length, "\r\n");
if (offset + 1 == header_length && (*hdr)[offset] == '\r')
header_length += _http_append(hdr, header_length, field_length, "\n");
start = offset;
offset = _skip(end_of_field, *hdr, offset, header_length);
if (start == offset) {
/* We've reached the end of the header*/
offset = _skip(end_of_line, *hdr, offset, header_length);
break;
}
if (offset == header_length)
header_length += _http_append(hdr, header_length, field_length, "\r\n");
if (offset + 1 == header_length && (*hdr)[offset] == '\r')
header_length += _http_append(hdr, header_length, field_length, "\n");
offset = _skip(end_of_line, *hdr, offset, header_length);
}
start = offset;
offset = header_length;
if (item == 3) {
return _http_insert(hdr, start, offset, header_length, field_length, field);
}
return header_length;
}
static size_t
_field_length(const unsigned char *hdr, size_t offset, size_t hdr_length)
{
size_t original_offset = offset;
/* Find newline */
while (offset < hdr_length && hdr[offset] != '\n')
offset++;
/* Trim trailing whitespace */
while (offset > original_offset && isspace(hdr[offset-1]&0xFF))
offset--;
return offset - original_offset;
}
static size_t _next_field(const unsigned char *hdr, size_t offset, size_t hdr_length)
{
size_t original_offset = offset;
/* Find newline */
while (offset < hdr_length && hdr[offset] != '\n')
offset++;
/* Remove newline too*/
if (offset > original_offset && isspace(hdr[offset-1]&0xFF))
offset++;
return offset;
}
static bool
_has_field_name(const char *name, size_t name_length, const unsigned char *hdr, size_t offset, size_t hdr_length)
{
size_t x;
bool found_colon = false;
/* Trim leading whitespace */
while (offset < hdr_length && isspace(hdr[offset]&0xFF) && hdr[offset] != '\n')
offset++;
/* Make sure there's enough space left */
if (hdr_length - offset < name_length)
return false;
/* Make sure there's colon after */
for (x = offset + name_length; x<hdr_length; x++) {
unsigned char c = hdr[x] & 0xFF;
if (isspace(c))
continue;
else if (c == ':') {
found_colon = true;
break;
} else {
/* some unexpected character was found in the name */
return false;
}
}
if (!found_colon)
return false;
/* Compare the name (case insensitive) */
return memcasecmp(name, hdr + offset, name_length) == 0;
}
/***************************************************************************
***************************************************************************/
size_t
http_change_field(unsigned char **inout_header, size_t header_length,
const char *name,
const unsigned char *value, size_t value_length,
int what)
{
unsigned char *hdr = *inout_header;
size_t name_length = strlen(name);
size_t offset;
size_t next_offset;
/* If field 'name' ends in a colon, trim that. Also, trim whitespace */
while (name_length) {
unsigned char c = name[name_length-1];
if (c == ':' || isspace(c & 0xFF))
name_length--;
else
break;
}
/* If length of the fiend value not specified, then assume
* nul-terminated string */
if (value_length == ~(size_t)0)
value_length = strlen((const char *)value);
/* Find our field */
for (offset = _next_field(hdr, 0, header_length);
offset < header_length;
offset = _next_field(hdr, offset, header_length)) {
if (_has_field_name(name, name_length, hdr, offset, header_length)) {
break;
} else if (_field_length(hdr, offset, header_length) == 0) {
/* We reached end without finding field, so insert before end
* instead of replacing an existing header. */
if (what == http_field_remove)
return header_length;
what = http_field_add;
break;
}
}
/* Allocate a new header to replace the old one. We'll allocated
* more space than we actually need */
*inout_header = REALLOC(*inout_header, header_length + name_length + 2 + value_length + 2 + 1 + 2);
hdr = *inout_header;
/* If we reached the end without finding proper termination, then add
* it */
if (offset == header_length) {
if (offset == 0 || hdr[offset-1] != '\n') {
if (hdr[offset-1] == '\r')
header_length = _http_append(&hdr, header_length, value_length+2, "\n");
else
header_length = _http_append(&hdr, header_length, value_length+2, "\r\n");
}
}
/* Make room for the new header */
next_offset = _next_field(hdr, offset, header_length);
if (value == NULL || what == http_field_remove) {
memmove(&hdr[offset + 0],
&hdr[next_offset],
header_length - next_offset + 1);
header_length += 0 - (next_offset - offset);
return header_length;
} else if (what == http_field_replace) {
/* Replace existing field */
memmove(&hdr[offset + name_length + 2 + value_length + 2],
&hdr[next_offset],
header_length - offset + 1);
header_length += (name_length + 2 + value_length + 2) - (next_offset - offset);
} else {
/* Add a new field onto the end */
memmove(&hdr[offset + name_length + 2 + value_length + 2],
&hdr[offset],
header_length - offset + 1);
header_length += (name_length + 2 + value_length + 2);
}
hdr[header_length] = '\0';
/* Copy the new header */
memcpy(&hdr[offset], name, name_length);
memcpy(&hdr[offset + name_length], ": ", 2);
memcpy(&hdr[offset + name_length + 2], value, value_length);
memcpy(&hdr[offset + name_length + 2 + value_length], "\r\n", 2);
return header_length;
}
/***************************************************************************
***************************************************************************/
static const char
http_hello[] = "GET / HTTP/1.0\r\n"
#ifdef IVRE_BUILD
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.\r\n"
#else
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.\r\n"
#endif
"Accept: */*\r\n"
//"Connection: Keep-Alive\r\n"
//"Content-Length: 0\r\n"
"\r\n";
/*****************************************************************************
*****************************************************************************/
void
field_name(struct BannerOutput *banout, size_t id,
struct Patterns *xhttp_fields);
void
field_name(struct BannerOutput *banout, size_t id,
struct Patterns *xhttp_fields)
{
unsigned i;
if (id == HTTPFIELD_INCOMPLETE)
return;
if (id == HTTPFIELD_UNKNOWN)
return;
if (id == HTTPFIELD_NEWLINE)
return;
for (i=0; xhttp_fields[i].pattern; i++) {
if (xhttp_fields[i].id == id) {
banout_newline(banout, PROTO_HTTP);
banout_append( banout, PROTO_HTTP,
(const unsigned char*)xhttp_fields[i].pattern
+ ((xhttp_fields[i].pattern[0]=='<')?1:0), /* bah. hack. ugly. */
xhttp_fields[i].pattern_length
- ((xhttp_fields[i].pattern[0]=='<')?1:0) /* bah. hack. ugly. */
);
return;
}
}
}
/*****************************************************************************
* Initialize some stuff that's part of the HTTP state-machine-parser.
*****************************************************************************/
static void *
http_init(struct Banner1 *b)
{
unsigned i;
/*
* These match HTTP Header-Field: names
*/
b->http_fields = smack_create("http", SMACK_CASE_INSENSITIVE);
for (i=0; http_fields[i].pattern; i++)
smack_add_pattern(
b->http_fields,
http_fields[i].pattern,
http_fields[i].pattern_length,
http_fields[i].id,
http_fields[i].is_anchored);
smack_compile(b->http_fields);
/*
* These match HTML <tag names
*/
b->html_fields = smack_create("html", SMACK_CASE_INSENSITIVE);
for (i=0; html_fields[i].pattern; i++)
smack_add_pattern(
b->html_fields,
html_fields[i].pattern,
html_fields[i].pattern_length,
html_fields[i].id,
html_fields[i].is_anchored);
smack_compile(b->html_fields);
banner_http.hello = MALLOC(banner_http.hello_length);
memcpy((char*)banner_http.hello, http_hello, banner_http.hello_length);
return b->http_fields;
}
/***************************************************************************
* BIZARRE CODE ALERT!
*
* This uses a "byte-by-byte state-machine" to parse the response HTTP
* header. This is standard practice for high-performance network
* devices, but is probably unfamiliar to the average network engineer.
*
* The way this works is that each byte of input causes a transition to
* the next state. That means we can parse the response from a server
* without having to buffer packets. The server can send the response
* one byte at a time (one packet for each byte) or in one entire packet.
* Either way, we don't. We don't need to buffer the entire response
* header waiting for the final packet to arrive, but handle each packet
* individually.
*
* This is especially useful with our custom TCP stack, which simply
* rejects out-of-order packets.
***************************************************************************/
static void
http_parse(
const struct Banner1 *banner1,
void *banner1_private,
struct StreamState *pstate,
const unsigned char *px, size_t length,
struct BannerOutput *banout,
struct stack_handle_t *socket)
{
unsigned state = pstate->state;
unsigned i;
unsigned state2;
unsigned log_begin = 0;
unsigned log_end = 0;
size_t id;
enum {
FIELD_START = 9,
FIELD_NAME,
FIELD_COLON,
FIELD_VALUE,
CONTENT,
CONTENT_TAG,
CONTENT_FIELD,
DONE_PARSING
};
UNUSEDPARM(banner1_private);
UNUSEDPARM(socket);
state2 = (state>>16) & 0xFFFF;
id = (state>>8) & 0xFF;
state = (state>>0) & 0xFF;
for (i=0; i<length; i++)
switch (state) {
case 0: case 1: case 2: case 3: case 4:
if (toupper(px[i]) != "HTTP/"[state]) {
state = DONE_PARSING;
tcpapi_close(socket);
} else
state++;
break;
case 5:
if (px[i] == '.')
state++;
else if (!isdigit(px[i])) {
state = DONE_PARSING;
tcpapi_close(socket);
}
break;
case 6:
if (isspace(px[i]))
state++;
else if (!isdigit(px[i])) {
state = DONE_PARSING;
tcpapi_close(socket);
}
break;
case 7:
/* TODO: look for 1xx response code */
if (px[i] == '\n')
state = FIELD_START;
break;
case FIELD_START:
if (px[i] == '\r')
break;
else if (px[i] == '\n') {
state2 = 0;
state = CONTENT;
log_end = i;
banout_append(banout, PROTO_HTTP, px+log_begin, log_end-log_begin);
log_begin = log_end;
break;
} else {
state2 = 0;
state = FIELD_NAME;
/* drop down */
}
case FIELD_NAME:
if (px[i] == '\r')
break;
id = smack_search_next(
banner1->http_fields,
&state2,
px, &i, (unsigned)length);
i--;
if (id == HTTPFIELD_NEWLINE) {
state2 = 0;
state = FIELD_START;
} else if (id == SMACK_NOT_FOUND)
; /* continue here */
else if (id == HTTPFIELD_UNKNOWN) {
/* Oops, at this point, both ":" and "Server:" will match.
* Therefore, we need to make sure ":" was found, and not
* a known field like "Server:" */
size_t id2;
id2 = smack_next_match(banner1->http_fields, &state2);
if (id2 != SMACK_NOT_FOUND)
id = id2;
state = FIELD_COLON;
} else
state = FIELD_COLON;
break;
case FIELD_COLON:
if (px[i] == '\n') {
state = FIELD_START;
break;
} else if (isspace(px[i])) {
break;
} else {
//field_name(banout, id, http_fields);
state = FIELD_VALUE;
/* drop down */
}
case FIELD_VALUE:
if (px[i] == '\r')
break;
else if (px[i] == '\n') {
state = FIELD_START;
break;
}
switch (id) {
case HTTPFIELD_SERVER:
banout_append(banout, PROTO_HTTP_SERVER, &px[i], 1);
break;
case HTTPFIELD_LOCATION:
case HTTPFIELD_VIA:
//banner_append(&px[i], 1, banout);
break;
case HTTPFIELD_CONTENT_LENGTH:
if (isdigit(px[i]&0xFF)) {
; /* TODO: add content length parsing */
} else {
id = 0;
}
break;
}
break;
case CONTENT:
{
unsigned next = i;
id = smack_search_next(
banner1->html_fields,
&state2,
px, &next, (unsigned)length);
if (banner1->is_capture_html) {
banout_append(banout, PROTO_HTML_FULL, &px[i], next-i);
}
if (id != SMACK_NOT_FOUND) {
state = CONTENT_TAG;
}
i = next - 1;
}
break;
case CONTENT_TAG:
for (; i<length; i++) {
if (banner1->is_capture_html) {
banout_append_char(banout, PROTO_HTML_FULL, px[i]);
}
if (px[i] == '>') {
state = CONTENT_FIELD;
break;
}
}
break;
case CONTENT_FIELD:
if (banner1->is_capture_html) {
banout_append_char(banout, PROTO_HTML_FULL, px[i]);
}
if (px[i] == '<')
state = CONTENT;
else {
banout_append_char(banout, PROTO_HTML_TITLE, px[i]);
}
break;
case DONE_PARSING:
default:
i = (unsigned)length;
break;
}
if (log_end == 0 && state < CONTENT)
log_end = i;
if (log_begin < log_end)
banout_append(banout, PROTO_HTTP, px + log_begin, log_end-log_begin);
if (state == DONE_PARSING)
pstate->state = state;
else
pstate->state = (state2 & 0xFFFF) << 16
| ((unsigned)id & 0xFF) << 8
| (state & 0xFF);
}
static const char *test_response =
"HTTP/1.0 200 OK\r\n"
"Date: Wed, 13 Jan 2021 18:18:25 GMT\r\n"
"Expires: -1\r\n"
"Cache-Control: private, max-age=0\r\n"
"Content-Type: text/html; charset=ISO-8859-1\r\n"
"P3P: CP=\x22This is not a P3P policy! See g.co/p3phelp for more info.\x22\r\n"
"Server: gws\r\n"
"X-XSS-Protection: 0\r\n"
"X-Frame-Options: SAMEORIGIN\r\n"
"Set-Cookie: 1P_JAR=2021-01-13-18; expires=Fri, 12-Feb-2021 18:18:25 GMT; path=/; domain=.google.com; Secure\r\n"
"Set-Cookie: NID=207=QioO2ZqRsR6k1wtvXjuuhLrXYtl6ki8SQhf56doo_wcADvldNoHfnKvFk1YXdxSVTWnmqHQVPC6ZudGneMs7vDftJ6vB36B0OCDy_KetZ3sOT_ZAHcmi1pAGeO0VekZ0SYt_UXMjcDhuvNVW7hbuHEeXQFSgBywyzB6mF2EVN00; expires=Thu, 15-Jul-2021 18:18:25 GMT; path=/; domain=.google.com; HttpOnly\r\n"
"Accept-Ranges: none\r\n"
"Vary: Accept-Encoding\r\n"
"\r\n";
/***************************************************************************
***************************************************************************/
static int
http_selftest_parser(void)
{
struct Banner1 *banner1 = NULL;
struct StreamState pstate[1];
struct BannerOutput banout[1];
memset(pstate, 0, sizeof(pstate[0]));
memset(banout, 0, sizeof(banout[0]));
/*
* Test start
*/
banner1 = banner1_create();
banner1->is_capture_servername = 1;
memset(pstate, 0, sizeof(pstate[0]));
banout_init(banout);
/*
* Run Test
*/
http_parse(banner1, 0, pstate, (const unsigned char *)test_response, strlen(test_response), banout, 0);
/*
* Verify results
*/
{
const unsigned char *string;
size_t length;
string = banout_string(banout, PROTO_HTTP_SERVER);
length = banout_string_length(banout, PROTO_HTTP_SERVER);
if (length != 3 || memcmp(string, "gws", 3) != 0) {
fprintf(stderr, "[-] HTTP parser failed: %s %u\n", __FILE__, __LINE__);
return 1; /* failure */
}
}
/*
* Test end
*/
banner1_destroy(banner1);
banout_release(banout);
return 0; /* success */
}
/***************************************************************************
***************************************************************************/
static int
http_selftest_config(void)
{
size_t i;
static const struct {const char *from; const char *to;} urlsamples[] = {
{"", "GET /foo.html"},
{"GET / HTTP/1.0\r\n\r\n", "GET /foo.html HTTP/1.0\r\n\r\n"},
{"GET /longerthan HTTP/1.0\r\n\r\n", "GET /foo.html HTTP/1.0\r\n\r\n"},
{0,0}
};
static const struct {const char *from; const char *to;} methodsamples[] = {
{"", "POST"},
{"GET / HTTP/1.0\r\n\r\n", "POST / HTTP/1.0\r\n\r\n"},
{"O / HTTP/1.0\r\n\r\n", "POST / HTTP/1.0\r\n\r\n"},
{0,0}
};
static const struct {const char *from; const char *to;} versionsamples[] = {
{"", "GET / HTTP/1.1"},
{"GET / FOO\r\n\r\n", "GET / HTTP/1.1\r\n\r\n"},
{"GET / XXXXXXXXXXXX\r\n\r\n", "GET / HTTP/1.1\r\n\r\n"},
{0,0}
};
static const struct {const char *from; const char *to;} fieldsamples[] = {
{"GET / HTTP/1.0\r\nfoobar: a\r\nHost: xyz\r\n\r\n", "GET / HTTP/1.0\r\nfoobar: a\r\nHost: xyz\r\nfoo: bar\r\n\r\n"},
{"GET / HTTP/1.0\r\nfoo:abc\r\nHost: xyz\r\n\r\n", "GET / HTTP/1.0\r\nfoo: bar\r\nHost: xyz\r\n\r\n"},
{"GET / HTTP/1.0\r\nfoo: abcdef\r\nHost: xyz\r\n\r\n", "GET / HTTP/1.0\r\nfoo: bar\r\nHost: xyz\r\n\r\n"},
{"GET / HTTP/1.0\r\nfoo: a\r\nHost: xyz\r\n\r\n", "GET / HTTP/1.0\r\nfoo: bar\r\nHost: xyz\r\n\r\n"},
{"GET / HTTP/1.0\r\nHost: xyz\r\n\r\n", "GET / HTTP/1.0\r\nHost: xyz\r\nfoo: bar\r\n\r\n"},
{0,0}
};
static const struct {const char *from; const char *to;} removesamples[] = {
{"GET / HTTP/1.0\r\nfoo: a\r\nHost: xyz\r\n\r\n", "GET / HTTP/1.0\r\nHost: xyz\r\n\r\n"},
{"GET / HTTP/1.0\r\nfooa: a\r\nHost: xyz\r\n\r\n", "GET / HTTP/1.0\r\nfooa: a\r\nHost: xyz\r\n\r\n"},
{0,0}
};
static const struct {const char *from; const char *to;} payloadsamples[] = {
{"", "GET / HTTP/1.0\r\n\r\nfoo"},
{"GET / HTTP/1.0\r\nHost: xyz\r\n\r\nbar", "GET / HTTP/1.0\r\nHost: xyz\r\n\r\nfoo"},
{0,0}
};
/* Test replacing URL */
for (i=0; urlsamples[i].from; i++) {
unsigned char *x = (unsigned char*)STRDUP(urlsamples[i].from);
size_t len1 = strlen((const char *)x);
size_t len2;
size_t len3 = strlen(urlsamples[i].to);
/* Replace whatever URL is in the header with this new one */
len2 = http_change_requestline(&x, len1, "/foo.html", ~(size_t)0, 1);
if (len2 != len3 && memcmp(urlsamples[i].to, x, len3) != 0) {
fprintf(stderr, "[-] HTTP.selftest: config URL sample #%u\n", (unsigned)i);
return 1;
}
}
/* Test replacing method */
for (i=0; methodsamples[i].from; i++) {
unsigned char *x = (unsigned char*)STRDUP(methodsamples[i].from);
size_t len1 = strlen((const char *)x);
size_t len2;
size_t len3 = strlen(methodsamples[i].to);
len2 = http_change_requestline(&x, len1, "POST", ~(size_t)0, 0);
if (len2 != len3 && memcmp(methodsamples[i].to, x, len3) != 0) {
fprintf(stderr, "[-] HTTP.selftest: config method sample #%u\n", (unsigned)i);
return 1;
}
}
/* Test replacing version */
for (i=0; versionsamples[i].from; i++) {
unsigned char *x = (unsigned char*)STRDUP(versionsamples[i].from);
size_t len1 = strlen((const char *)x);
size_t len2;
size_t len3 = strlen(versionsamples[i].to);
len2 = http_change_requestline(&x, len1, "HTTP/1.1", ~(size_t)0, 2);
if (len2 != len3 && memcmp(versionsamples[i].to, x, len3) != 0) {
fprintf(stderr, "[-] HTTP.selftest: config version sample #%u\n", (unsigned)i);
return 1;
}
}
/* Test payload */
for (i=0; payloadsamples[i].from; i++) {
unsigned char *x = (unsigned char*)STRDUP(payloadsamples[i].from);
size_t len1 = strlen((const char *)x);
size_t len2;
size_t len3 = strlen(payloadsamples[i].to);
len2 = http_change_requestline(&x, len1, "foo", ~(size_t)0, 3);
if (len2 != len3 && memcmp(payloadsamples[i].to, x, len3) != 0) {
fprintf(stderr, "[-] HTTP.selftest: config payload sample #%u\n", (unsigned)i);
return 1;
}
}
/* Test adding fields */
for (i=0; fieldsamples[i].from; i++) {
unsigned char *x;
size_t len1 = strlen((const char *)fieldsamples[i].from);
size_t len2;
size_t len3 = strlen(fieldsamples[i].to);
/* Replace whatever URL is in the header with this new one */
x = (unsigned char*)STRDUP(fieldsamples[i].from);
len2 = http_change_field(&x, len1, "foo", (const unsigned char *)"bar", ~(size_t)0, http_field_replace);
if (len2 != len3 || memcmp(fieldsamples[i].to, x, len3) != 0) {
fprintf(stderr, "[-] HTTP.selftest: config header field sample #%u\n", (unsigned)i);
return 1;
}
free(x);
/* Same test as above, but when name specified with a colon */
x = (unsigned char*)STRDUP(fieldsamples[i].from);
len2 = http_change_field(&x, len1, "foo:", (const unsigned char *)"bar", ~(size_t)0, http_field_replace);
if (len2 != len3 || memcmp(fieldsamples[i].to, x, len3) != 0) {
fprintf(stderr, "[-] HTTP.selftest: config header field sample #%u\n", (unsigned)i);
return 1;
}
free(x);
/* Same test as above, but with name having additional space */
x = (unsigned char*)STRDUP(fieldsamples[i].from);
len2 = http_change_field(&x, len1, "foo : : ", (const unsigned char *)"bar", ~(size_t)0, http_field_replace);
if (len2 != len3 || memcmp(fieldsamples[i].to, x, len3) != 0) {
fprintf(stderr, "[-] HTTP.selftest: config header field sample #%u\n", (unsigned)i);
return 1;
}
free(x);
}
/* Removing fields */
for (i=0; removesamples[i].from; i++) {
unsigned char *x = (unsigned char*)STRDUP(removesamples[i].from);
size_t len1 = strlen((const char *)x);
size_t len2;
size_t len3 = strlen(removesamples[i].to);
/* Replace whatever URL is in the header with this new one */
len2 = http_change_field(&x, len1, "foo", (const unsigned char *)"bar", ~(size_t)0, http_field_remove);
if (len2 != len3 || memcmp(removesamples[i].to, x, len3) != 0) {
fprintf(stderr, "[-] HTTP.selftest: config remove field sample #%u\n", (unsigned)i);
return 1;
}
free(x);
}
return 0;
}
/***************************************************************************
* Called when `--selftest` command-line parameter in order to do some
* basic unit testing of this module.
***************************************************************************/
static int
http_selftest(void)
{
int err;
/* Test parsing HTTP responses */
err = http_selftest_parser();
if (err)
return 1; /* failure */
/* Test configuring HTTP requests */
err = http_selftest_config();
if (err)
return 1; /* failure */
return 0; /* success */
}
/***************************************************************************
***************************************************************************/
struct ProtocolParserStream banner_http = {
"http", 80, http_hello, sizeof(http_hello)-1, 0,
http_selftest,
http_init,
http_parse,
};