X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=blobdiff_plain;f=jcc.c;h=53de19a607b4ca16450e2307b83f9877edbbef18;hp=ac6f282d73c7dbd9ff13de04a3064a5daeaa21aa;hb=59587f70b2176457abad3d978404a32205d432a3;hpb=e4e794d8b8965c902489f8784a6a02859aa3658d diff --git a/jcc.c b/jcc.c index ac6f282d..53de19a6 100644 --- a/jcc.c +++ b/jcc.c @@ -1,4 +1,4 @@ -const char jcc_rcs[] = "$Id: jcc.c,v 1.398 2012/10/21 12:51:57 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 { @@ -1134,8 +1141,7 @@ static void verify_request_length(struct client_state *csp) "Possible pipeline attempt detected. The connection will not " "be kept alive and we will only serve the first request."); /* Nuke the pipelined requests from orbit, just to be sure. */ - csp->client_iob->buf[0] = '\0'; - csp->client_iob->eod = csp->client_iob->cur = csp->client_iob->buf; + clear_iob(csp->client_iob); } else { @@ -1144,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)); @@ -1183,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.", @@ -1275,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; + +} /********************************************************************* * @@ -1302,6 +1448,9 @@ static jb_err receive_client_request(struct client_state *csp) struct list header_list; struct list *headers = &header_list; + /* We don't care if the arriving data is a valid HTTP request or not. */ + csp->requests_received_total++; + http = csp->http; memset(buf, 0, sizeof(buf)); @@ -1401,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. @@ -1494,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 */ @@ -1688,17 +1859,28 @@ static void chat(struct client_state *csp) && connection_destination_matches(&csp->server_connection, http, fwd)) { log_error(LOG_LEVEL_CONNECT, - "Reusing server socket %u. Opened for %s.", - csp->server_connection.sfd, csp->server_connection.host); + "Reusing server socket %d connected to %s. Total requests: %u.", + csp->server_connection.sfd, csp->server_connection.host, + csp->server_connection.requests_sent_total); } else { 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 */ @@ -1737,7 +1919,14 @@ static void chat(struct client_state *csp) } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ - if (fwd->forward_host || (http->ssl == 0)) + csp->server_connection.requests_sent_total++; + + 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); @@ -1790,11 +1979,12 @@ static void chat(struct client_state *csp) { return; } - IOB_RESET(csp->client_iob); + clear_iob(csp->client_iob); } 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) ? @@ -1806,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 (;;) { @@ -2507,7 +2699,7 @@ static void prepare_csp_for_next_request(struct client_state *csp) csp->expected_content_length = 0; csp->expected_client_content_length = 0; list_remove_all(csp->headers); - IOB_RESET(csp->iob); + clear_iob(csp->iob); freez(csp->error_message); free_http_request(csp->http); destroy_list(csp->headers); @@ -2553,7 +2745,7 @@ static void prepare_csp_for_next_request(struct client_state *csp) * Freeing the buffer itself isn't technically necessary, * but makes debugging more convenient. */ - IOB_RESET(csp->client_iob); + clear_iob(csp->client_iob); } } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ @@ -2579,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); @@ -2615,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)) @@ -2632,14 +2824,13 @@ static void serve(struct client_state *csp) || !(latency < csp->server_connection.keep_alive_timeout)) { log_error(LOG_LEVEL_CONNECT, - "Closing connection on server socket %d to %s: " - "keep-alive %u, tainted: %u, socket alive %u. %s timeout: %u.", + "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), 0 != (csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED), socket_is_still_alive(csp->server_connection.sfd), - ((csp->flags & CSP_FLAG_SERVER_KEEP_ALIVE_TIMEOUT_SET) ? - "Specified" : "Assumed"), csp->server_connection.keep_alive_timeout); + csp->server_connection.keep_alive_timeout); #ifdef FEATURE_CONNECTION_SHARING if (csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING) { @@ -2651,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; @@ -2662,9 +2853,9 @@ static void serve(struct client_state *csp) if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) != 0) && socket_is_still_alive(csp->cfd)) { - log_error(LOG_LEVEL_CONNECT, "A client request has been " + log_error(LOG_LEVEL_CONNECT, "Client request %d has been " "pipelined on socket %d and the socket is still alive.", - csp->cfd); + csp->requests_received_total+1, csp->cfd); prepare_csp_for_next_request(csp); continue; } @@ -2691,7 +2882,8 @@ static void serve(struct client_state *csp) && socket_is_still_alive(csp->cfd)) { log_error(LOG_LEVEL_CONNECT, - "Client request arrived in time on socket %d.", csp->cfd); + "Client request %u arrived in time on socket %d.", + csp->requests_received_total+1, csp->cfd); prepare_csp_for_next_request(csp); } else @@ -2730,8 +2922,8 @@ static void serve(struct client_state *csp) else if (csp->server_connection.sfd != JB_INVALID_SOCKET) { log_error(LOG_LEVEL_CONNECT, - "Closing server socket %d connected to %s. Keep-alive %u. " - "Tainted: %u. Socket alive %u. Timeout: %u. " + "Closing server socket %d connected to %s. Keep-alive: %u. " + "Tainted: %u. Socket alive: %u. Timeout: %u. " "Configuration file change detected: %u", csp->server_connection.sfd, csp->server_connection.host, 0 != (csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE), @@ -2764,11 +2956,11 @@ static void serve(struct client_state *csp) if (csp->cfd != JB_INVALID_SOCKET) { log_error(LOG_LEVEL_CONNECT, "Closing client socket %d. " - "Keep-alive: %u, Socket alive: %u. Data available: %u. " - "Configuration file change detected: %u.", + "Keep-alive: %u. Socket alive: %u. Data available: %u. " + "Configuration file change detected: %u. Requests received: %u.", csp->cfd, 0 != (csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE), socket_is_still_alive(csp->cfd), data_is_available(csp->cfd, 0), - config_file_change_detected); + config_file_change_detected, csp->requests_received_total); drain_and_close_socket(csp->cfd); } @@ -2814,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) */ @@ -3004,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; @@ -3134,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) { /* @@ -3237,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();