Properly detect section titles with two-digit minor numbers
[privoxy.git] / jcc.c
diff --git a/jcc.c b/jcc.c
index 8f912ca..53de19a 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -1,4 +1,4 @@
-const char jcc_rcs[] = "$Id: jcc.c,v 1.404 2012/10/21 13:04:08 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 */
@@ -1752,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);
@@ -1810,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) ?
@@ -1821,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 (;;)
    {
@@ -2594,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);
@@ -2630,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))
@@ -2647,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),
@@ -2665,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;
@@ -2829,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) */
@@ -3019,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;
@@ -3149,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)
       {
          /*
@@ -3252,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();