Add optional client-side pipelining support
authorFabian Keil <fk@fabiankeil.de>
Sun, 21 Oct 2012 12:53:33 +0000 (12:53 +0000)
committerFabian Keil <fk@fabiankeil.de>
Sun, 21 Oct 2012 12:53:33 +0000 (12:53 +0000)
If the new config directive tolerate-pipelining is enabled,
Privoxy will keep pipelined client requests around to deal
with them once the current request has been served.

At the moment this is only done if the current request
is known to have no body.

jcc.c
loadcfg.c
loaders.c
project.h

diff --git a/jcc.c b/jcc.c
index 059e9e6..ac6f282 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -1,4 +1,4 @@
-const char jcc_rcs[] = "$Id: jcc.c,v 1.397 2012/10/21 12:51:07 fabiankeil Exp $";
+const char jcc_rcs[] = "$Id: jcc.c,v 1.398 2012/10/21 12:51:57 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jcc.c,v $
@@ -1111,7 +1111,6 @@ 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->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED;
       if (strcmpic(csp->http->gpc, "GET")
          && strcmpic(csp->http->gpc, "HEAD")
          && strcmpic(csp->http->gpc, "TRACE")
@@ -1120,19 +1119,36 @@ 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.");
       }
       else
       {
-         /* XXX: and so is this */
          csp->flags |= CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ;
-         log_error(LOG_LEVEL_CONNECT,
-            "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;
+
+         if ((csp->config->feature_flags & RUNTIME_FEATURE_TOLERATE_PIPELINING) == 0)
+         {
+            csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED;
+            log_error(LOG_LEVEL_CONNECT,
+               "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;
+         }
+         else
+         {
+            /*
+             * Keep the pipelined data around for now, we'll deal with
+             * 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);
+            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));
+         }
       }
    }
    else
@@ -1196,6 +1212,27 @@ static char *get_request_line(struct client_state *csp)
 
    memset(buf, 0, sizeof(buf));
 
+   if ((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) != 0)
+   {
+      /*
+       * If there are multiple pipelined requests waiting,
+       * the flag will be set again once the next request
+       * has been parsed.
+       */
+      csp->flags &= ~CSP_FLAG_PIPELINED_REQUEST_WAITING;
+
+      request_line = get_header(csp->client_iob);
+      if ((NULL != request_line) && ('\0' != *request_line))
+      {
+         return request_line;
+      }
+      else
+      {
+         log_error(LOG_LEVEL_CONNECT, "No complete request line "
+            "received yet. Continuing reading from %d.", csp->cfd);
+      }
+   }
+
    do
    {
       if (!data_is_available(csp->cfd, csp->config->socket_timeout))
@@ -1539,7 +1576,7 @@ static void chat(struct client_state *csp)
    struct http_response *rsp;
    struct timeval timeout;
 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
-   int watch_client_socket = 1;
+   int watch_client_socket;
 #endif
 
    memset(buf, 0, sizeof(buf));
@@ -1723,7 +1760,8 @@ static void chat(struct client_state *csp)
          log_error(LOG_LEVEL_CONNECT,
             "Failed sending request headers to: %s: %E", http->hostport);
       }
-      else if (flush_socket(csp->server_connection.sfd, csp->client_iob) <  0))
+      else if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0)
+         && (flush_socket(csp->server_connection.sfd, csp->client_iob) < 0))
       {
          write_failure = 1;
          log_error(LOG_LEVEL_CONNECT,
@@ -1768,6 +1806,8 @@ static void chat(struct client_state *csp)
 
    server_body = 0;
 
+   watch_client_socket = 0 == (csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING);
+
    for (;;)
    {
 #ifdef __OS2__
@@ -2484,6 +2524,37 @@ static void prepare_csp_for_next_request(struct client_state *csp)
    {
       csp->flags |= CSP_FLAG_TOGGLED_ON;
    }
+
+   if (csp->client_iob->eod > csp->client_iob->cur)
+   {
+      long bytes_to_shift = csp->client_iob->cur - csp->client_iob->buf;
+      size_t data_length  = (size_t)(csp->client_iob->eod - csp->client_iob->cur);
+
+      assert(bytes_to_shift > 0);
+      assert(data_length > 0);
+
+      log_error(LOG_LEVEL_CONNECT, "Shifting %d pipelined bytes by %d bytes",
+         data_length, bytes_to_shift);
+      memmove(csp->client_iob->buf, csp->client_iob->cur, data_length);
+      csp->client_iob->cur = csp->client_iob->buf;
+      assert(csp->client_iob->eod == csp->client_iob->buf + bytes_to_shift + data_length);
+      csp->client_iob->eod = csp->client_iob->buf + data_length;
+      memset(csp->client_iob->eod, '\0', (size_t)bytes_to_shift);
+
+      csp->flags |= CSP_FLAG_PIPELINED_REQUEST_WAITING;
+   }
+   else
+   {
+      /*
+       * We mainly care about resetting client_iob->cur so we don't
+       * waste buffer space at the beginning and don't mess up the
+       * request restoration done by cgi_show_request().
+       *
+       * Freeing the buffer itself isn't technically necessary,
+       * but makes debugging more convenient.
+       */
+      IOB_RESET(csp->client_iob);
+   }
 }
 #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
 
@@ -2531,6 +2602,15 @@ static void serve(struct client_state *csp)
       latency = (unsigned)(csp->server_connection.response_received -
          csp->server_connection.request_sent) / 2;
 
+      if ((csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE)
+         && (csp->flags & CSP_FLAG_CRUNCHED)
+         && (csp->expected_client_content_length != 0))
+      {
+         csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED;
+         log_error(LOG_LEVEL_CONNECT,
+            "Tainting client socket %d due to unread data.", csp->cfd);
+      }
+
       continue_chatting = (csp->config->feature_flags
          & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE)
          && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED)
@@ -2579,6 +2659,16 @@ static void serve(struct client_state *csp)
 
       if (continue_chatting)
       {
+         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 "
+               "pipelined on socket %d and the socket is still alive.",
+               csp->cfd);
+            prepare_csp_for_next_request(csp);
+            continue;
+         }
+
          if (0 != (csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE))
          {
             if (csp->server_connection.sfd != JB_INVALID_SOCKET)
@@ -2595,6 +2685,7 @@ static void serve(struct client_state *csp)
                   "No server socket to keep open.", csp->cfd);
             }
          }
+
          if ((csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE)
             && data_is_available(csp->cfd, (int)csp->config->keep_alive_timeout)
             && socket_is_still_alive(csp->cfd))
index a742fe8..f3bec21 100644 (file)
--- a/loadcfg.c
+++ b/loadcfg.c
@@ -1,4 +1,4 @@
-const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.132 2012/10/17 18:02:10 fabiankeil Exp $";
+const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.133 2012/10/21 12:32:21 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/loadcfg.c,v $
@@ -155,6 +155,7 @@ static struct file_list *current_configfile = NULL;
 #define hash_split_large_cgi_forms        671658948U /* "split-large-cgi-forms" */
 #define hash_suppress_blocklists         1948693308U /* "suppress-blocklists" */
 #define hash_templdir                      11067889U /* "templdir" */
+#define hash_tolerate_pipelining         1360286620U /* "tolerate-pipelining" */
 #define hash_toggle                          447966U /* "toggle" */
 #define hash_trust_info_url               430331967U /* "trust-info-url" */
 #define hash_trustfile                     56494766U /* "trustfile" */
@@ -484,6 +485,7 @@ struct configuration_spec * load_config(void)
     */
    config->compression_level         = 1;
 #endif
+   config->feature_flags            &= ~RUNTIME_FEATURE_TOLERATE_PIPELINING;
 
    configfp = fopen(configfile, "r");
    if (NULL == configfp)
@@ -1335,6 +1337,20 @@ struct configuration_spec * load_config(void)
             config->templdir = make_path(NULL, arg);
             break;
 
+/* *************************************************************************
+ * tolerate-pipelining (0|1)
+ * *************************************************************************/
+         case hash_tolerate_pipelining :
+            if (parse_toggle_state(cmd, arg) == 1)
+            {
+               config->feature_flags |= RUNTIME_FEATURE_TOLERATE_PIPELINING;
+            }
+            else
+            {
+               config->feature_flags &= ~RUNTIME_FEATURE_TOLERATE_PIPELINING;
+            }
+            break;
+
 /* *************************************************************************
  * toggle (0|1)
  * *************************************************************************/
index 69febbc..0e67da2 100644 (file)
--- a/loaders.c
+++ b/loaders.c
@@ -1,4 +1,4 @@
-const char loaders_rcs[] = "$Id: loaders.c,v 1.91 2012/06/19 12:50:22 fabiankeil Exp $";
+const char loaders_rcs[] = "$Id: loaders.c,v 1.92 2012/07/23 12:43:56 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/loaders.c,v $
@@ -182,6 +182,7 @@ unsigned int sweep(void)
          last_active->next = client_list->next;
 
          freez(csp->ip_addr_str);
+         freez(csp->client_iob->buf);
          freez(csp->iob->buf);
          freez(csp->error_message);
 
index 36913f6..2b1ca3d 100644 (file)
--- 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.185 2012/10/21 12:39:27 fabiankeil Exp $"
+#define PROJECT_H_VERSION "$Id: project.h,v 1.186 2012/10/21 12:52:35 fabiankeil Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/project.h,v $
@@ -829,6 +829,12 @@ struct reusable_connection
  */
 #define CSP_FLAG_BUFFERED_CONTENT_DEFLATED          0x00400000U
 
+/**
+ * Flag for csp->flags: Set if we already read (parts of)
+ * a pipelined request in which case the client obviously
+ * isn't done talking.
+ */
+#define CSP_FLAG_PIPELINED_REQUEST_WAITING          0x00800000U
 
 /*
  * Flags for use in return codes of child processes
@@ -1202,6 +1208,9 @@ struct access_control_list
 /** configuration_spec::feature_flags: Buffered content is sent compressed if the client supports it. */
 #define RUNTIME_FEATURE_COMPRESSION               1024U
 
+/** configuration_spec::feature_flags: Pipelined requests are served instead of being discarded. */
+#define RUNTIME_FEATURE_TOLERATE_PIPELINING       2048U
+
 /**
  * Data loaded from the configuration file.
  *