From 84bc28fd921cef0f8be77172da1751ada7c57bd5 Mon Sep 17 00:00:00 2001 From: Fabian Keil Date: Fri, 7 Dec 2012 12:43:55 +0000 Subject: [PATCH] Add support for chunk-encoded client request bodies It's simplistic and requires the whole request body to be buffered before the request is forwarded. If the buffer limit is reached, the request is rejected. Previously chunk-encoded request bodies weren't guaranteed to be forwarded correctly, so this can also be considered a bug fix although chunk-encoded request bodies aren't commonly used in the real world. --- jcc.c | 173 +++++++++++++++++++++++++++++++++++++++++++++++++++++- parsers.c | 37 +++++++++++- parsers.h | 3 +- project.h | 8 ++- 4 files changed, 216 insertions(+), 5 deletions(-) diff --git a/jcc.c b/jcc.c index 919e199f..6967ad8d 100644 --- a/jcc.c +++ b/jcc.c @@ -1,4 +1,4 @@ -const char jcc_rcs[] = "$Id: jcc.c,v 1.417 2012/11/24 14:04:29 fabiankeil Exp $"; +const char jcc_rcs[] = "$Id: jcc.c,v 1.418 2012/12/07 12:43:05 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/jcc.c,v $ @@ -268,6 +268,13 @@ static const char CLIENT_CONNECTION_TIMEOUT_RESPONSE[] = "Connection: close\r\n\r\n" "The connection timed out because the client request didn't arrive in time.\r\n"; +static const char CLIENT_BODY_PARSE_ERROR_RESPONSE[] = + "HTTP/1.1 400 Failed reading client body\r\n" + "Proxy-Agent: Privoxy " VERSION "\r\n" + "Content-Type: text/plain\r\n" + "Connection: close\r\n\r\n" + "Failed parsing or buffering the chunk-encoded client body.\r\n"; + /* A function to crunch a response */ typedef struct http_response *(*crunch_func_ptr)(struct client_state *); @@ -1273,6 +1280,146 @@ static char *get_request_line(struct client_state *csp) } +enum chunk_status +{ + CHUNK_STATUS_MISSING_DATA, + CHUNK_STATUS_BODY_COMPLETE, + CHUNK_STATUS_PARSE_ERROR +}; + + +/********************************************************************* + * + * Function : chunked_body_is_complete + * + * Description : Figures out wheter or not a chunked body is complete. + * + * Currently it always starts at the beginning of the + * buffer which is somewhat wasteful and prevents Privoxy + * from starting to forward the correctly parsed chunks + * as soon as theoretically possible. + * + * Should be modified to work with a common buffer, + * and allow the caller to skip already parsed chunks. + * + * This would allow the function to be used for unbuffered + * response bodies as well. + * + * Parameters : + * 1 : iob = Buffer with the body to check. + * 2 : length = Length of complete body + * + * Returns : Enum with the result of the check. + * + *********************************************************************/ +static enum chunk_status chunked_body_is_complete(struct iob *iob, size_t *length) +{ + unsigned int chunksize; + char *p = iob->cur; + + do + { + /* + * We need at least a single digit, followed by "\r\n", + * followed by an unknown amount of data, followed by "\r\n". + */ + if (p + 5 > iob->eod) + { + return CHUNK_STATUS_MISSING_DATA; + } + if (sscanf(p, "%x", &chunksize) != 1) + { + return CHUNK_STATUS_PARSE_ERROR; + } + + /* + * We want at least a single digit, followed by "\r\n", + * followed by the specified amount of data, followed by "\r\n". + */ + if (p + chunksize + 5 > iob->eod) + { + return CHUNK_STATUS_MISSING_DATA; + } + + /* Skip chunk-size. */ + p = strstr(p, "\r\n"); + if (NULL == p) + { + return CHUNK_STATUS_PARSE_ERROR; + } + /* + * Skip "\r\n", the chunk data and another "\r\n". + * Moving p to either the beginning of the next chunk-size + * or one byte beyond the end of the chunked data. + */ + p += 2 + chunksize + 2; + } while (chunksize > 0U); + + *length = (size_t)(p - iob->cur); + assert(*length <= (size_t)(iob->eod - iob->cur)); + assert(p <= iob->eod); + + return CHUNK_STATUS_BODY_COMPLETE; + +} + + +/********************************************************************* + * + * Function : receive_chunked_client_request_body + * + * Description : Read the chunk-encoded client request body. + * Failures are dealt with. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : JB_ERR_OK or JB_ERR_PARSE + * + *********************************************************************/ +static jb_err receive_chunked_client_request_body(struct client_state *csp) +{ + size_t body_length; + enum chunk_status status; + + while (CHUNK_STATUS_MISSING_DATA == + (status = chunked_body_is_complete(csp->client_iob,&body_length))) + { + char buf[BUFFER_SIZE]; + int len; + + if (!data_is_available(csp->cfd, csp->config->socket_timeout)) + { + log_error(LOG_LEVEL_ERROR, + "Timeout while waiting for the client body."); + break; + } + len = read_socket(csp->cfd, buf, sizeof(buf) - 1); + if (len <= 0) + { + log_error(LOG_LEVEL_ERROR, "Read the client body failed: %E"); + break; + } + if (add_to_iob(csp->client_iob, csp->config->buffer_limit, buf, len)) + { + break; + } + } + if (status != CHUNK_STATUS_BODY_COMPLETE) + { + write_socket(csp->cfd, CLIENT_BODY_PARSE_ERROR_RESPONSE, + strlen(CLIENT_BODY_PARSE_ERROR_RESPONSE)); + log_error(LOG_LEVEL_CLF, + "%s - - [%T] \"Failed reading chunked client body\" 400 0", csp->ip_addr_str); + return JB_ERR_PARSE; + } + log_error(LOG_LEVEL_CONNECT, + "Chunked client body completely read. Length: %d", body_length); + csp->expected_client_content_length = body_length; + + return JB_ERR_OK; + +} /********************************************************************* * @@ -1402,6 +1549,14 @@ static jb_err receive_client_request(struct client_state *csp) } else { + if (!strncmpic(p, "Transfer-Encoding:", 18)) + { + /* + * XXX: should be called through sed() + * but currently can't. + */ + client_transfer_encoding(csp, &p); + } /* * We were able to read a complete * header and can finally enlist it. @@ -1495,7 +1650,21 @@ static jb_err parse_client_request(struct client_state *csp) if (csp->http->ssl == 0) { - csp->expected_client_content_length = get_expected_content_length(csp->headers); + /* + * This whole block belongs to chat() but currently + * has to be executed before sed(). + */ + if (csp->flags & CSP_FLAG_CHUNKED_CLIENT_BODY) + { + if (receive_chunked_client_request_body(csp) != JB_ERR_OK) + { + return JB_ERR_PARSE; + } + } + else + { + csp->expected_client_content_length = get_expected_content_length(csp->headers); + } verify_request_length(csp); } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ diff --git a/parsers.c b/parsers.c index 70a57a2e..96fab7f6 100644 --- a/parsers.c +++ b/parsers.c @@ -1,4 +1,4 @@ -const char parsers_rcs[] = "$Id: parsers.c,v 1.268 2012/11/24 14:07:57 fabiankeil Exp $"; +const char parsers_rcs[] = "$Id: parsers.c,v 1.269 2012/11/24 14:09:11 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/parsers.c,v $ @@ -198,6 +198,9 @@ static const struct parsers client_patterns[] = { { "Request-Range:", 14, client_range }, { "If-Range:", 9, client_range }, { "X-Filter:", 9, client_x_filter }, +#if 0 + { "Transfer-Encoding:", 18, client_transfer_encoding }, +#endif { "*", 0, crunch_client_header }, { "*", 0, filter_header }, { NULL, 0, NULL } @@ -1979,6 +1982,38 @@ static jb_err client_proxy_connection(struct client_state *csp, char **header) #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ +/********************************************************************* + * + * Function : client_transfer_encoding + * + * Description : Raise the CSP_FLAG_CHUNKED_CLIENT_BODY flag if + * the request body is "chunked" + * + * XXX: Currently not called through sed() as we + * need the flag earlier on. Should be fixed. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : header = On input, pointer to header to modify. + * On output, pointer to the modified header, or NULL + * to remove the header. This function frees the + * original string if necessary. + * + * Returns : JB_ERR_OK on success, or + * + *********************************************************************/ +jb_err client_transfer_encoding(struct client_state *csp, char **header) +{ + if (strstr(*header, "chunked")) + { + csp->flags |= CSP_FLAG_CHUNKED_CLIENT_BODY; + log_error(LOG_LEVEL_HEADER, "Expecting chunked client body"); + } + + return JB_ERR_OK; +} + + /********************************************************************* * * Function : crumble diff --git a/parsers.h b/parsers.h index ce774c91..7f958a52 100644 --- a/parsers.h +++ b/parsers.h @@ -1,6 +1,6 @@ #ifndef PARSERS_H_INCLUDED #define PARSERS_H_INCLUDED -#define PARSERS_H_VERSION "$Id: parsers.h,v 1.53 2012/10/21 12:39:27 fabiankeil Exp $" +#define PARSERS_H_VERSION "$Id: parsers.h,v 1.54 2012/10/21 12:58:03 fabiankeil Exp $" /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/parsers.h,v $ @@ -65,6 +65,7 @@ extern jb_err update_server_headers(struct client_state *csp); extern void get_http_time(int time_offset, char *buf, size_t buffer_size); extern jb_err get_destination_from_headers(const struct list *headers, struct http_request *http); extern unsigned long long get_expected_content_length(struct list *headers); +extern jb_err client_transfer_encoding(struct client_state *csp, char **header); #ifdef FEATURE_FORCE_LOAD extern int strclean(char *string, const char *substring); diff --git a/project.h b/project.h index e308ce59..eba76a6f 100644 --- a/project.h +++ b/project.h @@ -1,7 +1,7 @@ #ifndef PROJECT_H_INCLUDED #define PROJECT_H_INCLUDED /** Version string. */ -#define PROJECT_H_VERSION "$Id: project.h,v 1.192 2012/11/11 12:38:42 fabiankeil Exp $" +#define PROJECT_H_VERSION "$Id: project.h,v 1.193 2012/11/24 13:55:51 fabiankeil Exp $" /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/project.h,v $ @@ -836,6 +836,12 @@ struct reusable_connection */ #define CSP_FLAG_PIPELINED_REQUEST_WAITING 0x00800000U +/** + * Flag for csp->flags: Set if the client body is chunk-encoded + */ +#define CSP_FLAG_CHUNKED_CLIENT_BODY 0x01000000U + + /* * Flags for use in return codes of child processes */ -- 2.39.2