Created
August 5, 2012 07:56
-
-
Save mnunberg/3262742 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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