Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
#include "proto-http.h"
#include "proto-banner1.h"
#include "proto-interactive.h"
#include "smack.h"
#include "unusedparm.h"
#include "string_s.h"
#include "masscan-app.h"
#include "util-malloc.h"
#include "util-bool.h"
#include "proto-tcp.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};
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;
}
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"
"User-Agent: masscan/1.3 (https://github.com/robertdavidgraham/masscan)\r\n"
"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 ProtocolState *pstate,
const unsigned char *px, size_t length,
struct BannerOutput *banout,
struct InteractiveData *more)
{
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(more);
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;
tcp_close(more);
} else
state++;
break;
case 5:
if (px[i] == '.')
state++;
else if (!isdigit(px[i])) {
state = DONE_PARSING;
tcp_close(more);
}
break;
case 6:
if (isspace(px[i]))
state++;
else if (!isdigit(px[i])) {
state = DONE_PARSING;
tcp_close(more);
}
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 ProtocolState pstate[1];
struct BannerOutput banout[1];
struct InteractiveData more[1];
memset(pstate, 0, sizeof(pstate[0]));
memset(banout, 0, sizeof(banout[0]));
memset(more, 0, sizeof(more[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, more);
/*
* 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,
};