- Be a bit more descriptive about why CGI access
[privoxy.git] / cgi.c
diff --git a/cgi.c b/cgi.c
index 3eec387..b9863c7 100644 (file)
--- a/cgi.c
+++ b/cgi.c
@@ -1,4 +1,4 @@
-const char cgi_rcs[] = "$Id: cgi.c,v 1.73 2006/08/03 02:46:41 david__schmidt Exp $";
+const char cgi_rcs[] = "$Id: cgi.c,v 1.85 2007/01/05 14:19:02 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/cgi.c,v $
@@ -11,8 +11,8 @@ const char cgi_rcs[] = "$Id: cgi.c,v 1.73 2006/08/03 02:46:41 david__schmidt Exp
  *                Functions declared include:
  * 
  *
- * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
- *                Privoxy team. http://www.privoxy.org/
+ * Copyright   :  Written by and Copyright (C) 2001-2004, 2006
+ *                the SourceForge Privoxy team. http://www.privoxy.org/
  *
  *                Based on the Internet Junkbuster originally written
  *                by and Copyright (C) 1997 Anonymous Coders and 
@@ -38,6 +38,67 @@ const char cgi_rcs[] = "$Id: cgi.c,v 1.73 2006/08/03 02:46:41 david__schmidt Exp
  *
  * Revisions   :
  *    $Log: cgi.c,v $
+ *    Revision 1.85  2007/01/05 14:19:02  fabiankeil
+ *    Handle pcrs_execute() errors in template_fill() properly.
+ *
+ *    Revision 1.84  2006/12/28 17:54:22  fabiankeil
+ *    Fixed gcc43 conversion warnings and replaced sprintf
+ *    calls with snprintf to give OpenBSD's gcc one less reason
+ *    to complain.
+ *
+ *    Revision 1.83  2006/12/17 19:35:19  fabiankeil
+ *    Escape ampersand in Privoxy menu.
+ *
+ *    Revision 1.82  2006/12/17 17:53:39  fabiankeil
+ *    Suppress the toggle link if remote toggling is disabled.
+ *
+ *    Revision 1.81  2006/12/09 13:49:16  fabiankeil
+ *    Fix configure option --disable-toggle.
+ *    Thanks to Peter Thoenen for reporting this.
+ *
+ *    Revision 1.80  2006/12/08 14:45:32  fabiankeil
+ *    Don't lose the FORCE_PREFIX in case of
+ *    connection problems. Fixes #612235.
+ *
+ *    Revision 1.79  2006/11/13 19:05:50  fabiankeil
+ *    Make pthread mutex locking more generic. Instead of
+ *    checking for OSX and OpenBSD, check for FEATURE_PTHREAD
+ *    and use mutex locking unless there is an _r function
+ *    available. Better safe than sorry.
+ *
+ *    Fixes "./configure --disable-pthread" and should result
+ *    in less threading-related problems on pthread-using platforms,
+ *    but it still doesn't fix BR#1122404.
+ *
+ *    Revision 1.78  2006/09/21 19:22:07  fabiankeil
+ *    Use CGI_PREFIX to check the referrer.
+ *    The check for "http://config.privoxy.org/" fails
+ *    if the user modified CGI_SITE_2_HOST.
+ *
+ *    Revision 1.77  2006/09/21 15:17:23  fabiankeil
+ *    Adjusted headers for Privoxy's cgi responses:
+ *    Don't set Last-Modified, Expires and Cache-Control
+ *    headers for redirects; always set "Connection: close".
+ *
+ *    Revision 1.76  2006/09/07 14:06:38  fabiankeil
+ *    Only predate the Last-Modified header for cgi responses
+ *    that are delivered with status code 404 or 503.
+ *
+ *    Revision 1.75  2006/09/07 11:56:39  fabiankeil
+ *    Mark cgi_send_user_manual as harmless,
+ *    to fix the access denied problem Hal spotted.
+ *    The manual has no secret content, therefore we
+ *    don't have to care about "secure" referrers.
+ *
+ *    Revision 1.74  2006/09/06 18:45:03  fabiankeil
+ *    Incorporate modified version of Roland Rosenfeld's patch to
+ *    optionally access the user-manual via Privoxy. Closes patch 679075.
+ *
+ *    Formatting changed to Privoxy style, added call to
+ *    cgi_error_no_template if the requested file doesn't
+ *    exist and modified check whether or not Privoxy itself
+ *    should serve the manual. Should work cross-platform now.
+ *
  *    Revision 1.73  2006/08/03 02:46:41  david__schmidt
  *    Incorporate Fabian Keil's patch work:\rhttp://www.fabiankeil.de/sourcecode/privoxy/
  *
@@ -489,7 +550,6 @@ const char cgi_rcs[] = "$Id: cgi.c,v 1.73 2006/08/03 02:46:41 david__schmidt Exp
 #include "loadcfg.h"
 /* loadcfg.h is for global_toggle_state only */
 #ifdef FEATURE_PTHREAD
-#include <pthread.h>
 #include "jcc.h"
 /* jcc.h is for mutex semaphore globals only */
 #endif /* def FEATURE_PTHREAD */
@@ -515,7 +575,7 @@ static const struct cgi_dispatcher cgi_dispatchers[] = {
    { "show-status", 
          cgi_show_status,  
 #ifdef FEATURE_CGI_EDIT_ACTIONS
-        "View & change the current configuration",
+        "View &amp; change the current configuration",
 #else
         "View the current configuration",
 #endif
@@ -533,10 +593,12 @@ static const struct cgi_dispatcher cgi_dispatchers[] = {
          "Look up which actions apply to a URL and why",
          TRUE },
 #ifdef FEATURE_CGI_EDIT_ACTIONS
+#ifdef FEATURE_TOGGLE
    { "toggle",
          cgi_toggle, 
          "Toggle Privoxy on or off",
          FALSE },
+#endif /* def FEATURE_TOGGLE */
    { "edit-actions", /* Edit the actions list */
          cgi_edit_actions, 
          NULL, FALSE },
@@ -618,7 +680,7 @@ static const struct cgi_dispatcher cgi_dispatchers[] = {
          NULL, TRUE /* Send a transparent image (short name) */ },
    { "user-manual",
           cgi_send_user_manual,
-          NULL /* Send user-manual */ },
+          NULL, TRUE /* Send user-manual */ },
    { NULL, /* NULL Indicates end of list and default page */
          cgi_error_404,
          NULL, TRUE /* Unknown CGI page */ }
@@ -889,7 +951,7 @@ static struct http_response *dispatch_known_cgi(struct client_state * csp,
           */
          if (d->harmless
              || ((NULL != (referrer = grep_cgi_referrer(csp)))
-                 && (0 == strncmp(referrer, "http://config.privoxy.org/", 26)))
+                 && (0 == strncmp(referrer, CGI_PREFIX, sizeof(CGI_PREFIX)-1)))
              )
          {
             err = (d->handler)(csp, rsp, param_list);
@@ -1021,7 +1083,7 @@ char get_char_param(const struct map *parameters,
    ch = *(lookup(parameters, param_name));
    if ((ch >= 'a') && (ch <= 'z'))
    {
-      ch = ch - 'a' + 'A';
+      ch = (char)(ch - 'a' + 'A');
    }
 
    return ch;
@@ -1153,7 +1215,7 @@ jb_err get_number_param(struct client_state *csp,
          return JB_ERR_CGI_PARAMS;
       }
 
-      ch -= '0';
+      ch = (char)(ch - '0');
 
       /* Note:
        *
@@ -1167,7 +1229,7 @@ jb_err get_number_param(struct client_state *csp,
          return JB_ERR_CGI_PARAMS;
       }
 
-      value = value * 10 + ch;
+      value = value * 10 + (unsigned)ch;
    }
 
    /* Success */
@@ -1200,7 +1262,9 @@ struct http_response *error_response(struct client_state *csp,
 {
    jb_err err;
    struct http_response *rsp;
-   struct map * exports = default_exports(csp, NULL);
+   struct map *exports = default_exports(csp, NULL);
+   char *path = NULL;
+
    if (exports == NULL)
    {
       return cgi_error_memory();
@@ -1212,9 +1276,19 @@ struct http_response *error_response(struct client_state *csp,
       return cgi_error_memory();
    }
 
-   err = map(exports, "host", 1, html_encode(csp->http->host), 0);
+   if (csp->flags & CSP_FLAG_FORCED)
+   {
+      path = strdup(FORCE_PREFIX);
+   }
+   else
+   {
+      path = strdup("");
+   }
+   err = string_append(&path, csp->http->path);
+
+   if (!err) err = map(exports, "host", 1, html_encode(csp->http->host), 0);
    if (!err) err = map(exports, "hostport", 1, html_encode(csp->http->hostport), 0);
-   if (!err) err = map(exports, "path", 1, html_encode(csp->http->path), 0);
+   if (!err) err = map(exports, "path", 1, html_encode_and_free_original(path), 0);
    if (!err) err = map(exports, "error", 1, html_encode_and_free_original(safe_strerror(sys_err)), 0);
    if (!err) err = map(exports, "protocol", 1, csp->http->ssl ? "https://" : "http://", 1); 
    if (!err)
@@ -1430,7 +1504,7 @@ jb_err cgi_error_no_template(struct client_state *csp,
    strcat(rsp->body, body_suffix);
 
    rsp->status = strdup(status);
-   if (rsp->body == NULL)
+   if (rsp->status == NULL)
    {
       return JB_ERR_MEMORY;
    }
@@ -1495,7 +1569,7 @@ jb_err cgi_error_unknown(struct client_state *csp,
    rsp->head_length = 0;
    rsp->is_static = 0;
 
-   sprintf(errnumbuf, "%d", error_to_report);
+   snprintf(errnumbuf, sizeof(errnumbuf), "%d", error_to_report);
 
    rsp->body = malloc(strlen(body_prefix) + strlen(errnumbuf) + strlen(body_suffix) + 1);
    if (rsp->body == NULL)
@@ -1507,7 +1581,7 @@ jb_err cgi_error_unknown(struct client_state *csp,
    strcat(rsp->body, body_suffix);
 
    rsp->status = strdup(status);
-   if (rsp->body == NULL)
+   if (rsp->status == NULL)
    {
       return JB_ERR_MEMORY;
    }
@@ -1664,7 +1738,7 @@ void get_http_time(int time_offset, char *buf)
 
    struct tm *t;
    time_t current_time;
-#if defined(HAVE_GMTIME_R) && !defined(OSX_DARWIN)
+#if defined(HAVE_GMTIME_R)
    /*
     * Declare dummy up here (instead of inside get/set gmt block) so it
     * doesn't go out of scope before it's potentially used in snprintf later.
@@ -1682,12 +1756,12 @@ void get_http_time(int time_offset, char *buf)
 
    /* get and save the gmt */
    {
-#ifdef OSX_DARWIN
+#if HAVE_GMTIME_R
+      t = gmtime_r(&current_time, &dummy);
+#elif FEATURE_PTHREAD
       pthread_mutex_lock(&gmtime_mutex);
       t = gmtime(&current_time);
       pthread_mutex_unlock(&gmtime_mutex);
-#elif HAVE_GMTIME_R
-      t = gmtime_r(&current_time, &dummy);
 #else
       t = gmtime(&current_time);
 #endif
@@ -1738,7 +1812,7 @@ struct http_response *finish_http_response(struct http_response *rsp)
    /* 
     * Fill in the HTTP Status
     */
-   sprintf(buf, "HTTP/1.0 %s", rsp->status ? rsp->status : "200 OK");
+   snprintf(buf, sizeof(buf), "HTTP/1.0 %s", rsp->status ? rsp->status : "200 OK");
    err = enlist_first(rsp->headers, buf);
 
    /* 
@@ -1750,14 +1824,23 @@ struct http_response *finish_http_response(struct http_response *rsp)
    }
    if (!err)
    {
-      sprintf(buf, "Content-Length: %d", (int)rsp->content_length);
+      snprintf(buf, sizeof(buf), "Content-Length: %d", (int)rsp->content_length);
       err = enlist(rsp->headers, buf);
    }
 
-   /* 
-    * Fill in the default headers:
+   if (strncmpic(rsp->status, "302", 3))
+   {
+     /*
+      * If it's not a redirect without any content,
+      * set the Content-Type to text/html if it's
+      * not already specified.
+      */
+     if (!err) err = enlist_unique(rsp->headers, "Content-Type: text/html", 13);
+   }
+
+   /*
+    * Fill in the rest of the default headers:
     *
-    * Content-Type: default to text/html if not already specified.
     * Date: set to current date/time.
     * Last-Modified: set to date/time the page was last changed.
     * Expires: set to date/time page next needs reloading.
@@ -1765,8 +1848,6 @@ struct http_response *finish_http_response(struct http_response *rsp)
     * 
     * See http://www.w3.org/Protocols/rfc2068/rfc2068
     */
-   if (!err) err = enlist_unique(rsp->headers, "Content-Type: text/html", 13);
-
    if (rsp->is_static)
    {
       /*
@@ -1789,6 +1870,11 @@ struct http_response *finish_http_response(struct http_response *rsp)
          err = enlist_unique_header(rsp->headers, "Expires", buf);
       }
    }
+   else if (!strncmpic(rsp->status, "302", 3))
+   {
+      get_http_time(0, buf);
+      if (!err) err = enlist_unique_header(rsp->headers, "Date", buf);
+   }
    else
    {
       /*
@@ -1796,16 +1882,16 @@ struct http_response *finish_http_response(struct http_response *rsp)
        * the current time doesn't exactly forbid caching, it just
        * requires the client to revalidate the cached copy.
        *
-       * If a temporary problem occurres and the user tries again after
+       * If a temporary problem occurs and the user tries again after
        * getting Privoxy's error message, a compliant browser may set the
        * If-Modified-Since header with the content of the error page's
        * Last-Modified header. More often than not, the document on the server
        * is older than Privoxy's error message, the server would send status code
        * 304 and the browser would display the outdated error message again and again.
        *
-       * As a last resort we set "Last-Modified" to Tim Berners-Lee's birthday,
-       * which predates the age of any page on the web and can be safely used to
-       * "revalidate" without getting a status code 304.
+       * For documents delivered with status code 404 or 503 we set "Last-Modified"
+       * to Tim Berners-Lee's birthday, which predates the age of any page on the web
+       * and can be safely used to "revalidate" without getting a status code 304.
        *
        * There is no need to let the useless If-Modified-Since header reach the
        * server, it is therefore stripped by client_if_modified_since in parsers.c.
@@ -1814,11 +1900,25 @@ struct http_response *finish_http_response(struct http_response *rsp)
 
       get_http_time(0, buf);
       if (!err) err = enlist_unique_header(rsp->headers, "Date", buf);
-      if (!err) err = enlist_unique_header(rsp->headers, "Last-Modified", "Wed, 08 Jun 1955 12:00:00 GMT");
+      if (!strncmpic(rsp->status, "404", 3) || !strncmpic(rsp->status, "503", 3))
+      {
+         if (!err) err = enlist_unique_header(rsp->headers, "Last-Modified", "Wed, 08 Jun 1955 12:00:00 GMT");
+      }
+      else
+      {
+         if (!err) err = enlist_unique_header(rsp->headers, "Last-Modified", buf);
+      }
       if (!err) err = enlist_unique_header(rsp->headers, "Expires", "Sat, 17 Jun 2000 12:00:00 GMT");
       if (!err) err = enlist_unique_header(rsp->headers, "Pragma", "no-cache");
    }
 
+   /*
+    * Quoting RFC 2616:
+    *
+    * HTTP/1.1 applications that do not support persistent connections MUST
+    * include the "close" connection option in every message.
+    */
+   if (!err) err = enlist_unique_header(rsp->headers, "Connection", "close");
 
    /* 
     * Write the head
@@ -2038,7 +2138,7 @@ jb_err template_load(struct client_state *csp, char **template_ptr,
  *                                    Caller must free().
  *          2  :  exports = map with fill in symbol -> name pairs
  *
- * Returns     :  JB_ERR_OK on success
+ * Returns     :  JB_ERR_OK on success (and for uncritical errors)
  *                JB_ERR_MEMORY on out-of-memory error
  *
  *********************************************************************/
@@ -2109,15 +2209,35 @@ jb_err template_fill(char **template_ptr, const struct map *exports)
       }
       else
       {
-         pcrs_execute(job, file_buffer, size, &tmp_out_buffer, &size);
-         free(file_buffer);
+         error = pcrs_execute(job, file_buffer, size, &tmp_out_buffer, &size);
+
          pcrs_free_job(job);
          if (NULL == tmp_out_buffer)
          {
             *template_ptr = NULL;
             return JB_ERR_MEMORY;
          }
-         file_buffer = tmp_out_buffer;
+
+         if (error < 0)
+         {
+            /* 
+             * Substitution failed, keep the original buffer,
+             * log the problem and ignore it.
+             * 
+             * The user might see some unresolved @CGI_VARIABLES@,
+             * but returning a special CGI error page seems unreasonable
+             * and could mask more important error messages.
+             */
+            free(tmp_out_buffer);
+            log_error(LOG_LEVEL_ERROR, "Failed to execute s/%s/%s/%s. %s",
+               buf, m->value, flags, pcrs_strerror(error));
+         }
+         else
+         {
+            /* Substitution succeeded, use modified buffer. */
+            free(file_buffer);
+            file_buffer = tmp_out_buffer;
+         }
       }
    }
 
@@ -2213,7 +2333,7 @@ struct map *default_exports(const struct client_state *csp, const char *caller)
    if (!err) err = map(exports, "my-hostname",   1, html_encode(csp->my_hostname ? csp->my_hostname : "unknown"), 0);
    if (!err) err = map(exports, "homepage",      1, html_encode(HOME_PAGE_URL), 0);
    if (!err) err = map(exports, "default-cgi",   1, html_encode(CGI_PREFIX), 0);
-   if (!err) err = map(exports, "menu",          1, make_menu(caller), 0);
+   if (!err) err = map(exports, "menu",          1, make_menu(caller, csp->config->feature_flags), 0);
    if (!err) err = map(exports, "code-status",   1, CODE_STATUS, 1);
    if (!strncmpic(csp->config->usermanual, "file://", 7) ||
        !strncmpic(csp->config->usermanual, "http", 4))
@@ -2399,14 +2519,18 @@ jb_err map_conditional(struct map *exports, const char *name, int choose_first)
  *
  * Description :  Returns an HTML-formatted menu of the available 
  *                unhidden CGIs, excluding the one given in <self>
+ *                and the toggle CGI if toggling is disabled.
  *
- * Parameters  :  self = name of CGI to leave out, can be NULL for
+ * Parameters  :
+ *          1  :  self = name of CGI to leave out, can be NULL for
  *                complete listing.
+ *          2  :  feature_flags = feature bitmap from csp->config
+ *                
  *
  * Returns     :  menu string, or NULL on out-of-memory error.
  *
  *********************************************************************/
-char *make_menu(const char *self)
+char *make_menu(const char *self, const unsigned feature_flags)
 {
    const struct cgi_dispatcher *d;
    char *result = strdup("");
@@ -2419,6 +2543,17 @@ char *make_menu(const char *self)
    /* List available unhidden CGI's and export as "other-cgis" */
    for (d = cgi_dispatchers; d->name; d++)
    {
+
+#ifdef FEATURE_TOGGLE
+      if (!(feature_flags & RUNTIME_FEATURE_CGI_TOGGLE) && !strcmp(d->name, "toggle"))
+      {
+         /*
+          * Suppress the toggle link if remote toggling is disabled.
+          */
+         continue;
+      }
+#endif /* def FEATURE_TOGGLE */
+
       if (d->description && strcmp(d->name, self))
       {
          string_append(&result, "<li><a href=\"" CGI_PREFIX);