Properly detect section titles with two-digit minor numbers
[privoxy.git] / jcc.c
diff --git a/jcc.c b/jcc.c
index 7d3b723..53de19a 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -1,4 +1,4 @@
-const char jcc_rcs[] = "$Id: jcc.c,v 1.416 2012/11/24 14:01:25 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)
@@ -1273,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;
+
+}
 
 /*********************************************************************
  *
@@ -1402,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.
@@ -1495,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 */
@@ -1751,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);
@@ -1809,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) ?
@@ -2666,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;