Skip to content

Instantly share code, notes, and snippets.

@mnunberg
Created August 5, 2012 07:56
Show Gist options
  • Select an option

  • Save mnunberg/3262742 to your computer and use it in GitHub Desktop.

Select an option

Save mnunberg/3262742 to your computer and use it in GitHub Desktop.
#include "bufio.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "internal.h"
#include <ctype.h>
/**
* Parse a chunk of data, splitting along message boundaries.
*
* We must make sure that 'new_chunk' never contains more than a single
* message when we're done with it, too.
*
*
* In reality there will only ever be one buffer, and that is read into from
* the network from one end, and written back to the network from the other.
*
* The caveats are the transformations needed for the headers, in which case
* we can maintain a 'privbuf' buffer, and wherein to-be-modified data can
* be directed towards, so that the output buffer remains NULL.
*
*/
#define SET_DO_RESET_PARSER(hio) \
((hio)->do_reset_parser = 0xff)
enum {
HTBUF_PRIVATE = 0,
HTBUF_OUTPUT = 1
};
#define DEBUG_REQUEST if (pp_debug_requests)
#define DEBUG_RESPONSE if (pp_debug_responses)
static size_t
get_size_to_allocate(size_t base)
{
int ii;
for (ii = 0; 1<<ii < base; ii++);
return 1<<ii;
}
GString *
pp_htbufio_get_inbuf(struct PP_HtBufIO *hio)
{
if (hio->next_buffer == HTBUF_PRIVATE) {
if (!hio->privbuf) {
hio->privbuf = g_string_sized_new(4096);
}
return hio->privbuf;
}
/* else */
assert (hio->output);
return hio->output;
}
static size_t
handle_headers_done(
struct PP_HtBufIO *hio,
size_t nread,
int pstate,
size_t nparsed,
struct pp_htbufio_res *res)
{
GString *chunk = hio->privbuf;
/**
* header is done.
* We should assume that the beginning of the header is the beginning
* of the chunk we just parsed.
*
* Now we do the following:
* 1) Create a new buffer
* 2) Re-Write the header and place the re-written version at the
* beginning of the new buffer
*
* 3) If there was trailing data after the header, copy that into the
* new buffer as well
*
* 4) Set the output buffer to the new buffer we just created.
*/
GString *newhdr = NULL;
if (hio->type == PP_HTBUFIO_REQUEST) {
char *hdrbuf = NULL;
size_t nhb_a = get_size_to_allocate(hio->msg.len + 200);
ssize_t nhb_l;
/**
* For HTTP CONNECT handling we actually don't rewrite anything but
* swallow the request in its entirety. If there is trailing data
* (which is actually encrypted data possibly containing handshake
* information), then this gets put as the message's output.
*/
if (hio->msg.htmethod != HTTP_GET) {
log_bufio_warn("Got exotic '%s' method",
http_method_str(hio->msg.htmethod));
}
if (hio->msg.htmethod == HTTP_CONNECT) {
res->pevents |= PP_PARSE_STATE_BODY_DONE;
if (hio->msg.len < chunk->len) {
GString *tlsinfo = g_string_new_len(
chunk->str + hio->msg.len,
chunk->len - hio->msg.len);
hio->output = tlsinfo;
}
chunk->len = 0;
SET_DO_RESET_PARSER(hio);
hio->next_buffer = HTBUF_PRIVATE;
return 0;
}
if ((pstate & PP_PARSE_STATE_BODY_DONE) &&
hio->msg.len == chunk->len) {
switch (hio->msg.htmethod) {
case HTTP_GET:
case HTTP_HEAD:
case HTTP_DELETE:
break;
default:
log_bufio_crit("msg->len == chunk->len but method is %s",
http_method_str(hio->msg.htmethod));
abort();
break;
}
hdrbuf = malloc(nhb_a);
} else {
newhdr = g_string_sized_new(nhb_a);
}
do {
char *wrbuf = (newhdr) ? newhdr->str : hdrbuf;
nhb_l = pp_http_write_client_headers(
&hio->msg,
wrbuf,
nhb_a);
if (nhb_l == PP_HTWRITE_ETOOSMALL) {
nhb_a *= 2;
if (hdrbuf) {
hdrbuf = realloc(hdrbuf, nhb_a);
} else {
g_string_set_size(newhdr, nhb_a);
}
} else if (nhb_l == PP_HTWRITE_EBADHDR) {
if (hdrbuf) {
free (hdrbuf);
} else {
g_string_free(newhdr, TRUE);
}
return -1;
}
} while (nhb_l < 1);
res->reqhdr = hdrbuf;
res->nreqhdr = nhb_l;
if (newhdr) {
newhdr->len = nhb_l;
} else {
assert (hdrbuf);
newhdr = g_string_new_len(hdrbuf, nhb_l);
}
DEBUG_REQUEST {
printf("== REQUEST HEADER ==\n");
if (pp_debug_hex) {
hex_dump(newhdr->str, newhdr->len);
} else {
printf("%.*s",
(int)newhdr->len, newhdr->str);
}
}
} else {
/* no need to save the header */
newhdr = g_string_sized_new(hio->msg.len * 2);
size_t nhb_l;
do {
nhb_l = pp_http_write_server_headers(
&hio->msg,
newhdr->str,
newhdr->allocated_len);
if (nhb_l == PP_HTWRITE_ETOOSMALL) {
g_string_set_size(newhdr,
newhdr->allocated_len * 2);
} else if (nhb_l == PP_HTWRITE_EBADHDR) {
g_string_free(newhdr, TRUE);
return -1;
}
} while (nhb_l < 1);
newhdr->len = nhb_l;
DEBUG_RESPONSE {
printf("== RESPONSE HEADER ==\n");
if (pp_debug_hex) {
hex_dump(newhdr->str, newhdr->len);
} else {
printf("%.*s",
(int)newhdr->len, newhdr->str);
}
}
}
/**
* Switch the buffers around.
* On header completion we must assert that the chunk we just parsed
* came from the 'privbuf' buffer, and that the output buffer is NULL
*/
assert (hio->privbuf == chunk);
assert (hio->output == NULL);
hio->output = newhdr;
res->msg = &hio->msg;
/**
* if the body is not done, then the rest of the data is the beginning
* of the body, and no new header will follow.
*
* If so, blindly add the rest of the excess data (minus the length
* of the original header) to the new header
*/
if (nparsed == nread) {
g_string_append_len(newhdr,
chunk->str + hio->msg.len,
chunk->len - hio->msg.len);
chunk->len = 0;
} else {
assert (pstate & PP_PARSE_STATE_BODY_DONE);
size_t diff = nread - nparsed;
log_bufio_trace("Read %d bytes, but only parsed %d", nread, nparsed);
/* offset where the next message begins */
size_t old_len = chunk->len - diff;
/* length of the new message */
size_t new_len = chunk->len - old_len;
g_string_append_len(newhdr,
chunk->str + hio->msg.len,
old_len - hio->msg.len);
/**
* We still need the msg->buffer to point to valid memory when
* this function returns because it may possibly be inspected further.
* But we also need to move the beginning of the new message to
* the beginning of the privbuf buffer..
*/
chunk->len = new_len;
hio->priv_offset = old_len;
log_bufio_debug("Excess data (%d bytes):"
"(should be new response header)", new_len);
if (hio->type == PP_HTBUFIO_RESPONSE) {
assert (tolower(chunk->str[hio->priv_offset]) == 'h');
}
SET_DO_RESET_PARSER(hio);
}
if (pstate & PP_PARSE_STATE_BODY_DONE) {
SET_DO_RESET_PARSER(hio);
hio->next_buffer = HTBUF_PRIVATE;
} else {
hio->next_buffer = HTBUF_OUTPUT;
}
return hio->privbuf->len;
}
ssize_t
pp_htbufio_parse_chunk(
struct PP_HtBufIO *hio,
size_t nread,
struct pp_htbufio_res *res)
{
/* amount we parsed, this will never overlap a single message */
size_t nparsed;
GString *chunk = hio->next_buffer == HTBUF_PRIVATE
? hio->privbuf : hio->output;
/* parse state */
int pstate;
res->msg = NULL;
if (hio->do_reset_parser) {
unsigned char next_htmeth = pp_htbufio_get_next_htmethod(hio);
pp_http_message_reset(&hio->msg);
if (next_htmeth != 0xff) {
hio->msg.htmethod = next_htmeth;
}
/* if this is a response, figure out the htmethod with which to set
* the message's field
*/
if (hio->type == PP_HTBUFIO_REQUEST) {
log_bufio_debug("%p: Resetting for request", hio);
pp_http_parse_client_reset(&hio->mp);
} else {
log_bufio_debug("%p: Resetting for response", hio);
pp_http_parse_server_reset(&hio->mp);
}
/* this is assigned via 'get rbuf' */
assert (hio->privbuf);
if (hio->priv_offset) {
size_t n = hio->privbuf->len;
char *dst = hio->privbuf->str;
char *src = dst + hio->priv_offset;
if (hio->priv_offset > hio->privbuf->len) {
memcpy(dst, src, n);
} else {
memmove(dst, src, n);
}
hio->priv_offset = 0;
}
hio->do_reset_parser = 0;
}
/* synchronize contiguous buffers */
hio->mp.msg = &hio->msg;
hio->msg.buffer = chunk->str;
res->msg = &hio->msg;
pstate = pp_http_parse_execute(
&hio->mp,
chunk->str + (chunk->len - nread),
nread,
&nparsed);
res->pevents = pstate;
if (pstate & PP_PARSE_STATE_ERROR) {
return -1;
}
if (pstate & PP_PARSE_STATE_HDRS_DONE) {
return handle_headers_done(hio,
nread,
pstate,
nparsed,
res);
}
if (pstate & PP_PARSE_STATE_BODY_DONE) {
/**
* Message is complete.
* We relocate the excess to the privbuf buffer, except that in
* this case, we don't have to chop off the header.
*/
/**
* The old length of the buffer is the ->len minus the nread.
* The amount of data not to the current message is
* the difference between nread - nparsed
*/
SET_DO_RESET_PARSER(hio);
hio->next_buffer = HTBUF_PRIVATE;
res->msg = &hio->msg;
size_t diff = nread - nparsed;
if (diff) {
hio->privbuf = g_string_new_len(
chunk->str + (chunk->len - diff),
diff);
hio->output->len -= diff;
return hio->privbuf->len;
} else {
assert (hio->privbuf == NULL || hio->privbuf->len == 0);
}
res->msg = &hio->msg;
return hio->privbuf->len;
}
/* everything parsed, but nothing interesting */
return 0;
}
void
pp_htbufio_init(struct PP_HtBufIO *hio, int mode)
{
memset(hio, 0, sizeof(*hio));
SET_DO_RESET_PARSER(hio);
hio->type = mode;
hio->next_buffer = HTBUF_PRIVATE;
}
void
pp_htbufio_cleanup(struct PP_HtBufIO *hio)
{
pp_http_message_reset(&hio->msg);
if (hio->privbuf) {
g_string_free(hio->privbuf, TRUE);
hio->privbuf = NULL;
}
hio->priv_offset = 0;
SET_DO_RESET_PARSER(hio);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment