X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=blobdiff_plain;f=jcc.c;h=53de19a607b4ca16450e2307b83f9877edbbef18;hp=fb675c1feb07865e3ee4084f2ecc40300defbd03;hb=78a0d3d36266461035d274249c825e6f8ee7fb99;hpb=dadd1f7c43c43b4a1d9a3f4d50acc542b122556e diff --git a/jcc.c b/jcc.c index fb675c1f..53de19a6 100644 --- a/jcc.c +++ b/jcc.c @@ -1,4 +1,4 @@ -const char jcc_rcs[] = "$Id: jcc.c,v 1.403 2012/10/21 13:00:06 fabiankeil Exp $"; +const char jcc_rcs[] = "$Id: jcc.c,v 1.421 2012/12/07 12:50:37 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 *); @@ -904,7 +911,8 @@ static jb_err change_request_destination(struct client_state *csp) struct http_request *http = csp->http; jb_err err; - log_error(LOG_LEVEL_INFO, "Rewrite detected: %s", csp->headers->first->str); + log_error(LOG_LEVEL_REDIRECTS, "Rewrite detected: %s", + csp->headers->first->str); free_http_request(http); err = parse_http_request(csp->headers->first->str, http); if (JB_ERR_OK != err) @@ -951,6 +959,7 @@ static int server_response_is_complete(struct client_state *csp, */ csp->expected_content_length = 0; content_length_known = TRUE; + csp->flags |= CSP_FLAG_SERVER_CONTENT_LENGTH_SET; } if (csp->http->status == 204 || csp->http->status == 304) @@ -960,6 +969,7 @@ static int server_response_is_complete(struct client_state *csp, */ csp->expected_content_length = 0; content_length_known = TRUE; + csp->flags |= CSP_FLAG_SERVER_CONTENT_LENGTH_SET; } return (content_length_known && ((0 == csp->expected_content_length) @@ -1064,11 +1074,9 @@ void save_connection_destination(jb_socket sfd, * we do. * * Data that doesn't belong to the current request is - * thrown away to let the client retry on a clean socket. - * - * XXX: This is a hack until we can deal with multiple - * pipelined requests at the same time. - * + * either thrown away to let the client retry on a clean + * socket, or stashed to be dealt with after the current + * request is served. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) @@ -1109,7 +1117,8 @@ static void verify_request_length(struct client_state *csp) } if (!(csp->flags & CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ) - && ((csp->client_iob->cur[0] != '\0') || (csp->expected_client_content_length != 0))) + && ((csp->client_iob->cur < csp->client_iob->eod) + || (csp->expected_client_content_length != 0))) { if (strcmpic(csp->http->gpc, "GET") && strcmpic(csp->http->gpc, "HEAD") @@ -1119,9 +1128,7 @@ static void verify_request_length(struct client_state *csp) { /* XXX: this is an incomplete hack */ csp->flags &= ~CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ; - csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED; - log_error(LOG_LEVEL_CONNECT, - "There might be a request body. The connection will not be kept alive."); + log_error(LOG_LEVEL_CONNECT, "There better be a request body."); } else { @@ -1143,7 +1150,7 @@ static void verify_request_length(struct client_state *csp) * it once we're done serving the current request. */ csp->flags |= CSP_FLAG_PIPELINED_REQUEST_WAITING; - assert(csp->client_iob->eod > csp->client_iob->cur); + assert(csp->client_iob->eod >= csp->client_iob->cur); log_error(LOG_LEVEL_CONNECT, "Complete client request followed by " "%d bytes of pipelined data received.", (int)(csp->client_iob->eod - csp->client_iob->cur)); @@ -1182,7 +1189,7 @@ static void mark_server_socket_tainted(struct client_state *csp) * actually been reused. */ if ((csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE) - && !(csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED)) + && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED)) { log_error(LOG_LEVEL_CONNECT, "Marking the server socket %d tainted.", @@ -1274,6 +1281,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; + +} /********************************************************************* * @@ -1403,6 +1550,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. @@ -1496,7 +1651,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 */ @@ -1698,10 +1867,20 @@ static void chat(struct client_state *csp) { if (csp->server_connection.sfd != JB_INVALID_SOCKET) { - log_error(LOG_LEVEL_CONNECT, - "Closing server socket %u. Opened for %s.", - csp->server_connection.sfd, csp->server_connection.host); - close_socket(csp->server_connection.sfd); +#ifdef FEATURE_CONNECTION_SHARING + if (csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING) + { + remember_connection(&csp->server_connection); + } + else +#endif /* def FEATURE_CONNECTION_SHARING */ + { + log_error(LOG_LEVEL_CONNECT, + "Closing server socket %d connected to %s. Total requests: %u.", + csp->server_connection.sfd, csp->server_connection.host, + csp->server_connection.requests_sent_total); + close_socket(csp->server_connection.sfd); + } mark_connection_closed(&csp->server_connection); } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ @@ -1742,7 +1921,12 @@ static void chat(struct client_state *csp) csp->server_connection.requests_sent_total++; - if (fwd->forward_host || (http->ssl == 0)) + if ((fwd->type == SOCKS_5T) && (NULL == csp->headers->first)) + { + /* Client headers have been sent optimistically */ + assert(csp->headers->last == NULL); + } + else if (fwd->forward_host || (http->ssl == 0)) { int write_failure; hdr = list_to_text(csp->headers); @@ -1800,6 +1984,7 @@ static void chat(struct client_state *csp) log_error(LOG_LEVEL_CONNECT, "to %s successful", http->hostport); + /* XXX: should the time start earlier for optimistically sent data? */ csp->server_connection.request_sent = time(NULL); maxfd = (csp->cfd > csp->server_connection.sfd) ? @@ -1811,7 +1996,9 @@ static void chat(struct client_state *csp) server_body = 0; +#ifdef FEATURE_CONNECTION_KEEP_ALIVE watch_client_socket = 0 == (csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING); +#endif for (;;) { @@ -2584,12 +2771,12 @@ void serve(struct client_state *csp) static void serve(struct client_state *csp) #endif /* def AMIGA */ { + int config_file_change_detected = 0; /* Only used for debugging */ #ifdef FEATURE_CONNECTION_KEEP_ALIVE #ifdef FEATURE_CONNECTION_SHARING static int monitor_thread_running = 0; #endif /* def FEATURE_CONNECTION_SHARING */ int continue_chatting = 0; - int config_file_change_detected = 0; /* Only used for debugging */ log_error(LOG_LEVEL_CONNECT, "Accepted connection from %s on socket %d", csp->ip_addr_str, csp->cfd); @@ -2620,9 +2807,9 @@ static void serve(struct client_state *csp) & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE) && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED) && (csp->cfd != JB_INVALID_SOCKET) - && ((csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE) - || (csp->config->feature_flags & - RUNTIME_FEATURE_CONNECTION_SHARING)); + && (csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE) + && ((csp->flags & CSP_FLAG_SERVER_CONTENT_LENGTH_SET) + || (csp->flags & CSP_FLAG_CHUNKED)); if (!(csp->flags & CSP_FLAG_CRUNCHED) && (csp->server_connection.sfd != JB_INVALID_SOCKET)) @@ -2637,7 +2824,7 @@ static void serve(struct client_state *csp) || !(latency < csp->server_connection.keep_alive_timeout)) { log_error(LOG_LEVEL_CONNECT, - "Closing server socket %d connected to %s: " + "Closing server socket %d connected to %s. " "Keep-alive %u. Tainted: %u. Socket alive %u. Timeout: %u.", csp->server_connection.sfd, csp->server_connection.host, 0 != (csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE), @@ -2655,7 +2842,7 @@ static void serve(struct client_state *csp) } } - if (continue_chatting && any_loaded_file_changed(csp->config->config_file_list)) + if (continue_chatting && any_loaded_file_changed(csp)) { continue_chatting = 0; config_file_change_detected = 1; @@ -2819,7 +3006,7 @@ static int32 server_thread(void *data) static void usage(const char *myname) { printf("Privoxy version " VERSION " (" HOME_PAGE_URL ")\n" - "Usage: %s " + "Usage: %s [--config-test] " #if defined(unix) "[--chroot] " #endif /* defined(unix) */ @@ -3009,6 +3196,7 @@ int main(int argc, char **argv) #endif { int argc_pos = 0; + int do_config_test = 0; unsigned int random_seed; #ifdef unix struct passwd *pw = NULL; @@ -3139,6 +3327,11 @@ int main(int argc, char **argv) } #endif /* defined(unix) */ + else if (strcmp(argv[argc_pos], "--config-test") == 0) + { + do_config_test = 1; + } + else if (argc_pos + 1 != argc) { /* @@ -3242,6 +3435,10 @@ int main(int argc, char **argv) # endif /* def _WIN_CONSOLE */ #endif /* def _WIN32 */ + if (do_config_test) + { + exit(NULL == load_config()); + } /* Initialize the CGI subsystem */ cgi_init_error_messages();