Reformat some comments in chat().
[privoxy.git] / jcc.c
diff --git a/jcc.c b/jcc.c
index f637d4a..eb407a9 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -1,4 +1,4 @@
-const char jcc_rcs[] = "$Id: jcc.c,v 1.182 2008/06/27 11:13:56 fabiankeil Exp $";
+const char jcc_rcs[] = "$Id: jcc.c,v 1.190 2008/10/11 14:58:00 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jcc.c,v $
@@ -33,6 +33,38 @@ const char jcc_rcs[] = "$Id: jcc.c,v 1.182 2008/06/27 11:13:56 fabiankeil Exp $"
  *
  * Revisions   :
  *    $Log: jcc.c,v $
+ *    Revision 1.190  2008/10/11 14:58:00  fabiankeil
+ *    In case of chunk-encoded content, stop reading if
+ *    the buffer looks like it ends with the last chunk.
+ *
+ *    Revision 1.189  2008/10/11 09:53:00  fabiankeil
+ *    Let server_response_is_complete() deal properly with
+ *    content that is neither buffered nor read all at once.
+ *
+ *    Revision 1.188  2008/10/09 18:21:41  fabiankeil
+ *    Flush work-in-progress changes to keep outgoing connections
+ *    alive where possible. Incomplete and mostly #ifdef'd out.
+ *
+ *    Revision 1.187  2008/09/07 12:35:05  fabiankeil
+ *    Add mutex lock support for _WIN32.
+ *
+ *    Revision 1.186  2008/09/04 08:13:58  fabiankeil
+ *    Prepare for critical sections on Windows by adding a
+ *    layer of indirection before the pthread mutex functions.
+ *
+ *    Revision 1.185  2008/08/30 12:03:07  fabiankeil
+ *    Remove FEATURE_COOKIE_JAR.
+ *
+ *    Revision 1.184  2008/08/22 15:34:45  fabiankeil
+ *    - Silence LLVM/Clang complaint.
+ *    - Make received_hup_signal static.
+ *    - Hide definitions for basedir, pidfile and received_hup_signal
+ *      from __EMX__ as they only seem to be used in case of #ifdef unix.
+ *
+ *    Revision 1.183  2008/08/21 07:09:35  fabiankeil
+ *    Accept Shoutcast responses again. Problem reported
+ *    and fix suggested by Stefan in #2062860.
+ *
  *    Revision 1.182  2008/06/27 11:13:56  fabiankeil
  *    Fix possible NULL-pointer dereference reported
  *    by din_a4 in #2003937. Pointy hat to me.
@@ -1157,32 +1189,36 @@ static int32 server_thread(void *data);
 #define sleep(N)  DosSleep(((N) * 100))
 #endif
 
-#ifdef FEATURE_PTHREAD
-pthread_mutex_t log_mutex;
-pthread_mutex_t log_init_mutex;
+#ifdef MUTEX_LOCKS_AVAILABLE
+/*
+ * XXX: Does the locking stuff really belong in this file?
+ */
+privoxy_mutex_t log_mutex;
+privoxy_mutex_t log_init_mutex;
+privoxy_mutex_t connection_reuse_mutex;
 
 #if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R)
-pthread_mutex_t resolver_mutex;
+privoxy_mutex_t resolver_mutex;
 #endif /* !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R) */
 
 #ifndef HAVE_GMTIME_R
-pthread_mutex_t gmtime_mutex;
+privoxy_mutex_t gmtime_mutex;
 #endif /* ndef HAVE_GMTIME_R */
 
 #ifndef HAVE_LOCALTIME_R
-pthread_mutex_t localtime_mutex;
+privoxy_mutex_t localtime_mutex;
 #endif /* ndef HAVE_GMTIME_R */
 
 #ifndef HAVE_RANDOM
-pthread_mutex_t rand_mutex;
+privoxy_mutex_t rand_mutex;
 #endif /* ndef HAVE_RANDOM */
 
-#endif /* FEATURE_PTHREAD */
+#endif /* def MUTEX_LOCKS_AVAILABLE */
 
-#if defined(unix) || defined(__EMX__)
+#if defined(unix)
 const char *basedir = NULL;
 const char *pidfile = NULL;
-int received_hup_signal = 0;
+static int received_hup_signal = 0;
 #endif /* defined unix */
 
 /* HTTP snipplets. */
@@ -1291,6 +1327,13 @@ static const struct cruncher crunchers_light[] = {
 };
 
 
+/*
+ * XXX: Don't we really mean
+ *
+ * #if defined(unix)
+ *
+ * here?
+ */
 #if !defined(_WIN32) && !defined(__OS2__) && !defined(AMIGA)
 /*********************************************************************
  *
@@ -1324,7 +1367,9 @@ static void sig_handler(int the_signal)
          break;
 
       case SIGHUP:
+#if defined(unix)
          received_hup_signal = 1;
+#endif
          break;         
 
       default:
@@ -1941,6 +1986,53 @@ static jb_err change_request_destination(struct client_state *csp)
 }
 
 
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+/*********************************************************************
+ *
+ * Function    :  server_response_is_complete
+ *
+ * Description :  Determines whether we should stop reading
+ *                from the server socket.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  content_length = Length of content received so far.
+ *
+ * Returns     :  TRUE if the response is complete,
+ *                FALSE otherwise.
+ *
+ *********************************************************************/
+static int server_response_is_complete(struct client_state *csp, size_t content_length)
+{
+   int content_length_known = (csp->flags & CSP_FLAG_CONTENT_LENGTH_SET);
+
+   if (!strcmpic(csp->http->gpc, "HEAD"))
+   {
+      /*
+       * "HEAD" implies no body, we are thus expecting
+       * no content. XXX: incomplete "list" of methods?
+       */
+      log_error(LOG_LEVEL_INFO, "Method %s implies no body.", csp->http->gpc);
+      csp->expected_content_length = 0;
+      content_length_known = TRUE;
+   }
+
+   if (csp->http->status == 304)
+   {
+      /*
+       * Expect no body. XXX: incomplete "list" of status codes?
+       */
+      log_error(LOG_LEVEL_INFO, "Status code %d implies no body.", csp->http->status);
+      csp->expected_content_length = 0;
+      content_length_known = TRUE;
+   }
+
+   return (content_length_known && ((0 == csp->expected_content_length)
+            || (csp->expected_content_length <= content_length)));
+}
+#endif /* FEATURE_CONNECTION_KEEP_ALIVE */
+
+
 /*********************************************************************
  *
  * Function    :  chat
@@ -2355,7 +2447,6 @@ static void chat(struct client_state *csp)
                 http->hostport);
       }
 
-
       /* Write the answer to the client */
       if (rsp != NULL)
       {
@@ -2368,10 +2459,10 @@ static void chat(struct client_state *csp)
 
    if (fwd->forward_host || (http->ssl == 0))
    {
-      /* write the client's (modified) header to the server
+      /*
+       * Write the client's (modified) header to the server
        * (along with anything else that may be in the buffer)
        */
-
       if (write_socket(csp->sfd, hdr, strlen(hdr))
        || (flush_socket(csp->sfd, csp->iob) <  0))
       {
@@ -2431,6 +2522,22 @@ static void chat(struct client_state *csp)
       FD_SET(csp->cfd, &rfds);
       FD_SET(csp->sfd, &rfds);
 
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+      if (server_body && server_response_is_complete(csp, byte_count))
+      {
+         log_error(LOG_LEVEL_CONNECT,
+            "Done reading from server. Expected content length: %d. "
+            "Actual content length: %d. Most recently received: %d.",
+            csp->expected_content_length, byte_count, len);
+         len = 0;
+         /*
+          * XXX: should not jump around,
+          * chat() is complicated enough already.
+          */
+         goto reading_done;
+      }
+#endif  /* FEATURE_CONNECTION_KEEP_ALIVE */
+
       n = select((int)maxfd+1, &rfds, NULL, NULL, NULL);
 
       if (n < 0)
@@ -2439,10 +2546,10 @@ static void chat(struct client_state *csp)
          return;
       }
 
-      /* this is the body of the browser's request
-       * just read it and write it.
+      /*
+       * This is the body of the browser's request,
+       * just read and write it.
        */
-
       if (FD_ISSET(csp->cfd, &rfds))
       {
          len = read_socket(csp->cfd, buf, sizeof(buf) - 1);
@@ -2461,12 +2568,10 @@ static void chat(struct client_state *csp)
       }
 
       /*
-       * The server wants to talk.  It could be the header or the body.
+       * The server wants to talk. It could be the header or the body.
        * If `hdr' is null, then it's the header otherwise it's the body.
        * FIXME: Does `hdr' really mean `host'? No.
        */
-
-
       if (FD_ISSET(csp->sfd, &rfds))
       {
          fflush( 0 );
@@ -2511,12 +2616,31 @@ static void chat(struct client_state *csp)
             return;
          }
 
-         /* Add a trailing zero.  This lets filter_popups
-          * use string operations.
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+         if (csp->flags & CSP_FLAG_CHUNKED)
+         {
+            if ((len > 5) && !memcmp(buf+len-5, "0\r\n\r\n", 5))
+            {
+               /* XXX: this is a temporary hack */
+               log_error(LOG_LEVEL_CONNECT,
+                  "Looks like we reached the end of the last chunk: "
+                  "%d %d %d %d %d. We better stop reading.",
+                  buf[len-5], buf[len-4], buf[len-3], buf[len-2], buf[len-1]);
+               csp->expected_content_length = byte_count + len;
+               csp->flags |= CSP_FLAG_CONTENT_LENGTH_SET;
+            }
+         }
+         reading_done:
+#endif  /* FEATURE_CONNECTION_KEEP_ALIVE */
+
+         /*
+          * Add a trailing zero to let be able to use string operations.
+          * XXX: do we still need this with filter_popups gone?
           */
          buf[len] = '\0';
 
-         /* Normally, this would indicate that we've read
+         /*
+          * Normally, this would indicate that we've read
           * as much as the server has sent us and we can
           * close the client connection.  However, Microsoft
           * in its wisdom has released IIS/5 with a bug that
@@ -2606,7 +2730,6 @@ static void chat(struct client_state *csp)
           * of the server document, just write it to the client,
           * unless we need to buffer the body for later content-filtering
           */
-
          if (server_body || http->ssl)
          {
             if (content_filter)
@@ -2672,15 +2795,10 @@ static void chat(struct client_state *csp)
          }
          else
          {
-            /* we're still looking for the end of the
-             * server's header ... (does that make header
-             * parsing an "out of body experience" ?
-             */
-
-            /* 
-             * buffer up the data we just read.  If that fails, 
-             * there's little we can do but send our static
-             * out-of-memory page.
+            /*
+             * We're still looking for the end of the server's header.
+             * Buffer up the data we just read.  If that fails, there's
+             * little we can do but send our static out-of-memory page.
              */
             if (add_to_iob(csp, buf, len))
             {
@@ -2696,10 +2814,9 @@ static void chat(struct client_state *csp)
             {
                if (ms_iis5_hack)
                {
-                  /* Well, we tried our MS IIS/5
-                   * hack and it didn't work.
-                   * The header is incomplete
-                   * and there isn't anything
+                  /*
+                   * Well, we tried our MS IIS/5 hack and it didn't work.
+                   * The header is incomplete and there isn't anything
                    * we can do about it.
                    */
                   log_error(LOG_LEVEL_INFO,
@@ -2708,10 +2825,9 @@ static void chat(struct client_state *csp)
                }
                else
                {
-                  /* Since we have to wait for
-                   * more from the server before
-                   * we can parse the headers
-                   * we just continue here.
+                  /*
+                   * Since we have to wait for more from the server before
+                   * we can parse the headers we just continue here.
                    */
                   continue;
                }
@@ -2788,7 +2904,8 @@ static void chat(struct client_state *csp)
              */
             if (!content_filter)
             {
-               /* write the server's (modified) header to
+               /*
+                * Write the server's (modified) header to
                 * the client (along with anything else that
                 * may be in the buffer)
                 */
@@ -2798,9 +2915,9 @@ static void chat(struct client_state *csp)
                {
                   log_error(LOG_LEVEL_CONNECT, "write header to client failed: %E");
 
-                  /* the write failed, so don't bother
-                   * mentioning it to the client...
-                   * it probably can't hear us anyway.
+                  /*
+                   * The write failed, so don't bother mentioning it
+                   * to the client... it probably can't hear us anyway.
                    */
                   freez(hdr);
                   return;
@@ -2814,10 +2931,10 @@ static void chat(struct client_state *csp)
             freez(hdr);
             server_body = 1;
 
-            /* If this was a MS IIS/5 hack then it means
-             * the server has already closed the
-             * connection.  Nothing more to read.  Time
-             * to bail.
+            /*
+             * If this was a MS IIS/5 hack then it means the server
+             * has already closed the connection. Nothing more to read.
+             * Time to bail.
              */
             if (ms_iis5_hack)
             {
@@ -2835,9 +2952,8 @@ static void chat(struct client_state *csp)
    if (csp->content_length == 0)
    {
       /*
-       * If Privoxy didn't recalculate the
-       * Content-Lenght, byte_count is still
-       * correct.
+       * If Privoxy didn't recalculate the Content-Lenght,
+       * byte_count is still correct.
        */
       csp->content_length = byte_count;
    }
@@ -2871,7 +2987,19 @@ static void serve(struct client_state *csp)
 
    if (csp->sfd != JB_INVALID_SOCKET)
    {
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+      if ((csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE))
+      {
+         remember_connection(csp->sfd, csp->http, forward_url(csp, csp->http));
+      }
+      else
+      {
+         forget_connection(csp->sfd);
+         close_socket(csp->sfd);
+      }
+#else
       close_socket(csp->sfd);
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
    }
 
    csp->flags &= ~CSP_FLAG_ACTIVE;
@@ -2933,6 +3061,97 @@ static void usage(const char *myname)
 #endif /* #if !defined(_WIN32) || defined(_WIN_CONSOLE) */
 
 
+#ifdef MUTEX_LOCKS_AVAILABLE
+/*********************************************************************
+ *
+ * Function    :  privoxy_mutex_lock
+ *
+ * Description :  Locks a mutex.
+ *
+ * Parameters  :
+ *          1  :  mutex = The mutex to lock.
+ *
+ * Returns     :  Void. May exit in case of errors.
+ *
+ *********************************************************************/
+void privoxy_mutex_lock(privoxy_mutex_t *mutex)
+{
+#ifdef FEATURE_PTHREAD
+   int err = pthread_mutex_lock(mutex);
+   if (err)
+   {
+      if (mutex != &log_mutex)
+      {
+         log_error(LOG_LEVEL_FATAL,
+            "Mutex locking failed: %s.\n", strerror(err));
+      }
+      exit(1);
+   }
+#else
+   EnterCriticalSection(mutex);
+#endif /* def FEATURE_PTHREAD */
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  privoxy_mutex_unlock
+ *
+ * Description :  Unlocks a mutex.
+ *
+ * Parameters  :
+ *          1  :  mutex = The mutex to unlock.
+ *
+ * Returns     :  Void. May exit in case of errors.
+ *
+ *********************************************************************/
+void privoxy_mutex_unlock(privoxy_mutex_t *mutex)
+{
+#ifdef FEATURE_PTHREAD
+   int err = pthread_mutex_unlock(mutex);
+   if (err)
+   {
+      if (mutex != &log_mutex)
+      {
+         log_error(LOG_LEVEL_FATAL,
+            "Mutex unlocking failed: %s.\n", strerror(err));
+      }
+      exit(1);
+   }
+#else
+   LeaveCriticalSection(mutex);
+#endif /* def FEATURE_PTHREAD */
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  privoxy_mutex_init
+ *
+ * Description :  Prepares a mutex.
+ *
+ * Parameters  :
+ *          1  :  mutex = The mutex to initialize.
+ *
+ * Returns     :  Void. May exit in case of errors.
+ *
+ *********************************************************************/
+static void privoxy_mutex_init(privoxy_mutex_t *mutex)
+{
+#ifdef FEATURE_PTHREAD
+   int err = pthread_mutex_init(mutex, 0);
+   if (err)
+   {
+      printf("Fatal error. Mutex initialization failed: %s.\n",
+         strerror(err));
+      exit(1);
+   }
+#else
+   InitializeCriticalSection(mutex);
+#endif /* def FEATURE_PTHREAD */
+}
+#endif /* def MUTEX_LOCKS_AVAILABLE */
+
 /*********************************************************************
  *
  * Function    :  initialize_mutexes
@@ -2946,15 +3165,13 @@ static void usage(const char *myname)
  *********************************************************************/
 static void initialize_mutexes(void)
 {
-   int err = 0;
-
-#ifdef FEATURE_PTHREAD
+#ifdef MUTEX_LOCKS_AVAILABLE
    /*
     * Prepare global mutex semaphores
     */
-   err = pthread_mutex_init(&log_mutex, 0);
-
-   if (!err) err = pthread_mutex_init(&log_init_mutex, 0);
+   privoxy_mutex_init(&log_mutex);
+   privoxy_mutex_init(&log_init_mutex);
+   privoxy_mutex_init(&connection_reuse_mutex);
 
    /*
     * XXX: The assumptions below are a bit naive
@@ -2965,37 +3182,24 @@ static void initialize_mutexes(void)
     * thread safe.
     */
 #if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R)
-   if (!err) err = pthread_mutex_init(&resolver_mutex, 0);
+   privoxy_mutex_init(&resolver_mutex);
 #endif /* !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R) */
    /*
     * XXX: should we use a single mutex for
     * localtime() and gmtime() as well?
     */
 #ifndef HAVE_GMTIME_R
-   if (!err) err = pthread_mutex_init(&gmtime_mutex, 0);
+   privoxy_mutex_init(&gmtime_mutex);
 #endif /* ndef HAVE_GMTIME_R */
 
 #ifndef HAVE_LOCALTIME_R
-   if (!err) err = pthread_mutex_init(&localtime_mutex, 0);
+   privoxy_mutex_init(&localtime_mutex);
 #endif /* ndef HAVE_GMTIME_R */
 
 #ifndef HAVE_RANDOM
-   if (!err) err = pthread_mutex_init(&rand_mutex, 0);
+   privoxy_mutex_init(&rand_mutex);
 #endif /* ndef HAVE_RANDOM */
-#endif /* FEATURE_PTHREAD */
-
-   /*
-    * TODO: mutex support for mingw32 would be swell.
-    */
-
-   if (err)
-   {
-      printf("Fatal error. Mutex initialization failed: %s.\n",
-         strerror(err));
-      exit(1);
-   }
-
-   return;
+#endif /* def MUTEX_LOCKS_AVAILABLE */
 }
 
 
@@ -3211,15 +3415,6 @@ int main(int argc, const char *argv[])
    random_seed = (unsigned int)time(NULL);
 #ifdef HAVE_RANDOM
    srandom(random_seed);
-#elif defined (_WIN32)
-   /*
-    * See pick_from_range() in miscutil.c for details.
-    */
-   log_error(LOG_LEVEL_INFO,
-      "No thread-safe PRNG implemented for your platform. "
-      "Using weak \'randomization\' factor which will "
-      "limit the already questionable usefulness of "
-      "header-time-randomizing actions (disabled by default).");
 #else
    srand(random_seed);
 #endif /* ifdef HAVE_RANDOM */
@@ -3533,6 +3728,14 @@ static void listen_loop(void)
 
    config = load_config();
 
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+   /*
+    * XXX: Should be relocated once it no
+    * longer needs to emit log messages.
+    */
+   initialize_reusable_connections();
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+
    bfd = bind_port_helper(config);
 
 #ifdef FEATURE_GRACEFUL_TERMINATION
@@ -3564,23 +3767,6 @@ static void listen_loop(void)
       }
 #endif
 
-#ifdef __OS2__
-#ifdef FEATURE_COOKIE_JAR
-      /*
-       * Need a workaround here: we have to fclose() the jarfile, or we die because it's
-       * already open.  I think unload_configfile() is not being run, which should do
-       * this work.  Until that can get resolved, we'll use this workaround.
-       */
-       if (csp)
-         if(csp->config)
-           if (csp->config->jar)
-           {
-             fclose(csp->config->jar);
-             csp->config->jar = NULL;
-           }
-#endif /* FEATURE_COOKIE_JAR */
-#endif /* __OS2__ */
-
       if ( NULL == (csp = (struct client_state *) zalloc(sizeof(*csp))) )
       {
          log_error(LOG_LEVEL_FATAL, "malloc(%d) for csp failed: %E", sizeof(*csp));
@@ -3878,13 +4064,6 @@ static void listen_loop(void)
 #endif
    freez(configfile);
 
-#ifdef FEATURE_COOKIE_JAR
-   if (NULL != config->jar)
-   {
-      fclose(config->jar);
-   }
-#endif
-
 #if defined(_WIN32) && !defined(_WIN_CONSOLE)
    /* Cleanup - remove taskbar icon etc. */
    TermLogWindow();