Fixed gcc43 conversion warnings.
[privoxy.git] / cgiedit.c
index fac1af9..5058dd7 100644 (file)
--- a/cgiedit.c
+++ b/cgiedit.c
@@ -1,4 +1,4 @@
-const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.33 2002/04/24 02:17:47 oes Exp $";
+const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.46 2006/12/27 18:44:52 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/cgiedit.c,v $
@@ -42,6 +42,99 @@ const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.33 2002/04/24 02:17:47 oes Exp $"
  *
  * Revisions   :
  *    $Log: cgiedit.c,v $
+ *    Revision 1.46  2006/12/27 18:44:52  fabiankeil
+ *    Stop shadowing string.h's index().
+ *
+ *    Revision 1.45  2006/12/21 12:57:48  fabiankeil
+ *    Add config option "split-large-forms"
+ *    to work around the browser bug reported
+ *    in BR #1570678.
+ *
+ *    Revision 1.44  2006/12/09 13:49:16  fabiankeil
+ *    Fix configure option --disable-toggle.
+ *    Thanks to Peter Thoenen for reporting this.
+ *
+ *    Revision 1.43  2006/07/18 14:48:45  david__schmidt
+ *    Reorganizing the repository: swapping out what was HEAD (the old 3.1 branch)
+ *    with what was really the latest development (the v_3_0_branch branch)
+ *
+ *    Revision 1.41.2.12  2006/01/30 15:16:25  david__schmidt
+ *    Remove a little residual debugging info
+ *
+ *    Revision 1.41.2.11  2006/01/29 23:10:56  david__schmidt
+ *    Multiple filter file support
+ *
+ *    Revision 1.41.2.10  2005/07/04 03:13:43  david__schmidt
+ *    Undo some damaging memory leak patches
+ *
+ *    Revision 1.41.2.9  2005/07/04 00:31:04  david__schmidt
+ *    Removing a double free
+ *
+ *    Revision 1.41.2.8  2005/05/07 21:50:54  david__schmidt
+ *    A few memory leaks plugged (mostly on error paths)
+ *
+ *    Revision 1.41.2.7  2004/02/17 13:30:23  oes
+ *    Moved cgi_error_disabled() from cgiedit.c to
+ *    cgi.c to re-enable build with --disable-editor.
+ *    Fixes Bug #892744. Thanks to Matthew Fischer
+ *    for spotting.
+ *
+ *    Revision 1.41.2.6  2003/12/18 08:13:48  oes
+ *    One line lost in last commit
+ *
+ *    Revision 1.41.2.5  2003/12/17 16:33:47  oes
+ *     - All edit functions that redirect back to the list page
+ *       now use cgi_redirect
+ *     - All redirects now contain useless parameter "foo", whose
+ *       value are raw seconds since epoch, in order to force
+ *       Opera and Konqueror to properly reload the list. Closes
+ *       bug #859993
+ *
+ *    Revision 1.41.2.4  2003/03/11 11:53:59  oes
+ *    Cosmetic: Renamed cryptic variable
+ *
+ *    Revision 1.41.2.3  2002/11/12 15:01:41  oes
+ *    Fix: Don't free uninitialized struct editable_file
+ *
+ *    Revision 1.41.2.2  2002/08/05 20:02:59  oes
+ *    Bugfix: "Insert new section at top" did not work properly if first non-comment line in file was of type FILE_LINE_ACTION
+ *
+ *    Revision 1.41.2.1  2002/08/02 12:43:14  oes
+ *    Fixed bug #588514: first_time now set on a per-string basis in actions_from_radio; javascriptify now called on copies
+ *
+ *    Revision 1.41  2002/05/21 19:09:45  oes
+ *     - Made Add/Edit/Remove URL Submit and Cancel
+ *       buttons jump back to relevant section in eal
+ *     - Bugfix: remove-url-form needs p export
+ *
+ *    Revision 1.40  2002/05/19 11:34:35  jongfoster
+ *    Handling read-only actions files better - report the actual
+ *    error, not "Out of memory"!
+ *
+ *    Bug report:
+ *    http://sourceforge.net/tracker/index.php?func=detail
+ *    &aid=557905&group_id=11118&atid=111118
+ *
+ *    Revision 1.39  2002/05/12 21:39:15  jongfoster
+ *    - Adding Doxygen-style comments to structures and #defines.
+ *    - Correcting function comments
+ *
+ *    Revision 1.38  2002/05/03 23:00:38  jongfoster
+ *    Support for templates for "standard actions" buttons.
+ *    See bug #549871
+ *
+ *    Revision 1.37  2002/04/30 11:14:52  oes
+ *    Made csp the first parameter in *action_to_html
+ *
+ *    Revision 1.36  2002/04/26 21:53:30  jongfoster
+ *    Fixing a memory leak.  (Near, but not caused by, my earlier commit).
+ *
+ *    Revision 1.35  2002/04/26 21:50:02  jongfoster
+ *    Honouring default exports in edit-actions-for-url-filter template.
+ *
+ *    Revision 1.34  2002/04/26 12:54:17  oes
+ *    Adaptions to changes in actions.c
+ *
  *    Revision 1.33  2002/04/24 02:17:47  oes
  *     - Moved get_char_param, get_string_param and get_number_param to cgi.c
  *     - Comments
@@ -252,8 +345,10 @@ const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.33 2002/04/24 02:17:47 oes Exp $"
 #include "miscutil.h"
 #include "errlog.h"
 #include "loaders.h"
+#ifdef FEATURE_TOGGLE
+/* loadcfg.h is for global_toggle_state only */
 #include "loadcfg.h"
-/* loadcfg.h is for g_bToggleIJB only */
+#endif /* def FEATURE_TOGGLE */
 #include "urlmatch.h"
 
 const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION;
@@ -261,23 +356,49 @@ const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION;
 
 #ifdef FEATURE_CGI_EDIT_ACTIONS
 
+/**
+ * A line in an editable_file.
+ */
 struct file_line
 {
+   /** Next entry in the linked list */
    struct file_line * next;
+   
+   /** The raw data, to write out if this line is unmodified. */
    char * raw;
+   
+   /** Comments and/or whitespace to put before this line if it's modified
+       and then written out. */
    char * prefix;
+
+   /** The actual data, as a string.  Line continuation and comment removal
+       are performed on the data read from file before it's stored here, so
+       it will be a single line of data.  */
    char * unprocessed;
+   
+   /** The type of data on this line.  One of the FILE_LINE_xxx constants. */
    int type;
 
+   /** The actual data, processed into some sensible data type. */
    union
    {
+
+      /** An action specification. */
       struct action_spec action[1];
 
+      /** A name=value pair. */
       struct
       {
+
+         /** The name in the name=value pair. */
          char * name;
+
+         /** The value in the name=value pair, as a string. */
          char * svalue;
+
+         /** The value in the name=value pair, as an integer. */
          int ivalue;
+
       } setting;
 
       /* Add more data types here... e.g.
@@ -294,42 +415,65 @@ struct file_line
       */
 
    } data;
+
 };
 
+/** This file_line has not been processed yet. */
 #define FILE_LINE_UNPROCESSED           1
+
+/** This file_line is blank. Can only appear at the end of a file, due to
+    the way the parser works. */
 #define FILE_LINE_BLANK                 2
+
+/** This file_line says {{alias}}. */
 #define FILE_LINE_ALIAS_HEADER          3
+
+/** This file_line defines an alias. */
 #define FILE_LINE_ALIAS_ENTRY           4
+
+/** This file_line defines an {action}. */
 #define FILE_LINE_ACTION                5
+
+/** This file_line specifies a URL pattern. */
 #define FILE_LINE_URL                   6
+
+/** This file_line says {{settings}}. */
 #define FILE_LINE_SETTINGS_HEADER       7
+
+/** This file_line is in a {{settings}} block. */
 #define FILE_LINE_SETTINGS_ENTRY        8
+
+/** This file_line says {{description}}. */
 #define FILE_LINE_DESCRIPTION_HEADER    9
+
+/** This file_line is in a {{description}} block. */
 #define FILE_LINE_DESCRIPTION_ENTRY    10
 
 
+/**
+ * A configuration file, in a format that can be edited and written back to
+ * disk.
+ */
 struct editable_file
 {
-   struct file_line * lines;
-   const char * filename;     /* Full pathname - e.g. "/etc/privoxy/wibble.action" */
-   const char * identifier;   /* Filename stub - e.g. "wibble".  Use for CGI param. */
-                              /* Pre-encoded with url_encode() for ease of use. */
-   const char * version_str;  /* Last modification time, as a string.  For CGI param */
-                              /* Can be used in URL without using url_param(). */
-   unsigned version;          /* Last modification time - prevents chaos with
-                               * the browser's "back" button.  Note that this is a
-                               * time_t cast to an unsigned.  When comparing, always
-                               * cast the time_t to an unsigned, and *NOT* vice-versa.
-                               * This may lose the top few bits, but they're not
-                               * significant anyway.
-                               */
-   int newline;               /* Newline convention - one of the NEWLINE_xxx constants.
-                               * Note that changing this after the file has been
-                               * read in will cause a mess.
-                               */
-   struct file_line * parse_error; /* On parse error, this is the offending line. */
-   const char * parse_error_text;  /* On parse error, this is the problem.
-                                    * (Statically allocated) */
+   struct file_line * lines;  /**< The contents of the file.  A linked list of lines. */
+   const char * filename;     /**< Full pathname - e.g. "/etc/privoxy/wibble.action". */
+   const char * identifier;   /**< Filename stub - e.g. "wibble".  Use for CGI param. */
+                              /**< Pre-encoded with url_encode() for ease of use. */
+   const char * version_str;  /**< Last modification time, as a string.  For CGI param. */
+                              /**< Can be used in URL without using url_param(). */
+   unsigned version;          /**< Last modification time - prevents chaos with
+                                   the browser's "back" button.  Note that this is a
+                                   time_t cast to an unsigned.  When comparing, always
+                                   cast the time_t to an unsigned, and *NOT* vice-versa.
+                                   This may lose the top few bits, but they're not
+                                   significant anyway. */
+   int newline;               /**< Newline convention - one of the NEWLINE_xxx constants.
+                                   Note that changing this after the file has been
+                                   read in will cause a mess. */
+   struct file_line * parse_error; /**< On parse error, this is the offending line. */
+   const char * parse_error_text;  /**< On parse error, this is the problem.
+                                        (Statically allocated) */
 };
 
 /* FIXME: Following non-static functions should be prototyped in .h or made static */
@@ -361,8 +505,9 @@ jb_err cgi_error_parse(struct client_state *csp,
 jb_err cgi_error_file(struct client_state *csp,
                       struct http_response *rsp,
                       const char *filename);
-jb_err cgi_error_disabled(struct client_state *csp,
-                          struct http_response *rsp);
+jb_err cgi_error_file_read_only(struct client_state *csp,
+                                struct http_response *rsp,
+                                const char *filename);
 
 /* Internal arbitrary config file support functions */
 static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline);
@@ -406,6 +551,34 @@ static jb_err map_copy_parameter_url(struct map *out,
                                      const char *name);
 #endif /* unused function */
 
+/* Internal convenience functions */
+static char *section_target(const unsigned sectionid);
+
+/*********************************************************************
+ *
+ * Function    :  section_target
+ *
+ * Description :  Given an unsigned (section id) n, produce a dynamically
+ *                allocated string of the form #l<n>, for use in link
+ *                targets.
+ *
+ * Parameters  :
+ *          1  :  sectionid = start line number of section
+ *
+ * Returns     :  String with link target, or NULL if out of
+ *                memory
+ *
+ *********************************************************************/
+static char *section_target(const unsigned sectionid)
+{
+   char buf[30];
+
+   snprintf(buf, 30, "#l%d", sectionid);
+   return(strdup(buf));
+
+}
+
+
 /*********************************************************************
  *
  * Function    :  map_copy_parameter_html
@@ -535,6 +708,7 @@ jb_err cgi_edit_actions_url_form(struct client_state *csp,
    struct editable_file * file;
    struct file_line * cur_line;
    unsigned line_number;
+   unsigned section_start_line_number = 0;
    jb_err err;
 
    assert(csp);
@@ -563,6 +737,10 @@ jb_err cgi_edit_actions_url_form(struct client_state *csp,
 
    for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
    {
+      if (cur_line->type == FILE_LINE_ACTION)
+      {
+         section_start_line_number = line_number;
+      }
       cur_line = cur_line->next;
    }
 
@@ -586,6 +764,7 @@ jb_err cgi_edit_actions_url_form(struct client_state *csp,
    if (!err) err = map(exports, "v", 1, file->version_str, 1);
    if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
    if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
+   if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0);
 
    edit_free_file(file);
 
@@ -689,6 +868,7 @@ jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
    struct editable_file * file;
    struct file_line * cur_line;
    unsigned line_number;
+   unsigned section_start_line_number = 0;
    jb_err err;
 
    assert(csp);
@@ -717,6 +897,10 @@ jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
 
    for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
    {
+      if (cur_line->type == FILE_LINE_ACTION)
+      {
+         section_start_line_number = line_number;
+      }      
       cur_line = cur_line->next;
    }
 
@@ -738,8 +922,9 @@ jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
 
    err = map(exports, "f", 1, file->identifier, 1);
    if (!err) err = map(exports, "v", 1, file->version_str, 1);
-   if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0);
+   if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
    if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
+   if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0);
 
    edit_free_file(file);
 
@@ -760,8 +945,7 @@ jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
  * Description :  Write a complete file to disk.
  *
  * Parameters  :
- *          1  :  filename = File to write to.
- *          2  :  file = Data structure to write.
+ *          1  :  file = File to write.
  *
  * Returns     :  JB_ERR_OK     on success
  *                JB_ERR_FILE   on error writing to file.
@@ -814,7 +998,7 @@ jb_err edit_write_file(struct editable_file * file)
             {
                /* Must quote '#' characters */
                int numhash = 0;
-               int len;
+               size_t len;
                char * src;
                char * dest;
                char * str;
@@ -830,7 +1014,7 @@ jb_err edit_write_file(struct editable_file * file)
 
                /* Allocate new memory for string */
                len = strlen(cur_line->unprocessed);
-               if (NULL == (str = malloc((size_t) len + 1 + numhash)))
+               if (NULL == (str = malloc(len + 1 + (size_t)numhash)))
                {
                   /* Uh oh, just trashed file! */
                   fclose(fp);
@@ -1069,8 +1253,8 @@ static int match_actions_file_header_line(const char * line, const char * name)
  * Parameters  :
  *          1  :  line = String from file.  Must not start with
  *                       whitespace (else infinite loop!)
- *          2  :  name = Destination for name
- *          2  :  name = Destination for value
+ *          2  :  pname = Destination for name
+ *          2  :  pvalue = Destination for value
  *
  * Returns     :  JB_ERR_OK     on success
  *                JB_ERR_MEMORY on out-of-memory
@@ -1112,7 +1296,7 @@ static jb_err split_line_on_equals(const char * line, char ** pname, char ** pva
       name_end--;
    }
 
-   name_len = name_end - line + 1; /* Length excluding \0 */
+   name_len = (size_t)(name_end - line) + 1; /* Length excluding \0 */
    if (NULL == (*pname = (char *) malloc(name_len + 1)))
    {
       return JB_ERR_MEMORY;
@@ -1448,6 +1632,7 @@ jb_err edit_parse_actions_file(struct editable_file * file)
  *                     at EOF but it will not have been closed.
  *          2  :  pfile = Destination for a linked list of file_lines.
  *                        Will be set to NULL on error.
+ *          3  :  newline = How to handle newlines.
  *
  * Returns     :  JB_ERR_OK     on success
  *                JB_ERR_MEMORY on out-of-memory
@@ -1807,7 +1992,7 @@ static jb_err get_file_name_param(struct client_state *csp,
    char *name;
    char *fullpath;
    char ch;
-   int len;
+   size_t len;
 
    assert(csp);
    assert(parameters);
@@ -2064,7 +2249,7 @@ static jb_err map_radio(struct map * exports,
       }
    }
 
-   *p = value;
+   *p = (char)value;
    return map(exports, buf, 0, "checked", 1);
 }
 
@@ -2215,14 +2400,15 @@ jb_err cgi_error_file(struct client_state *csp,
 
 /*********************************************************************
  *
- * Function    :  cgi_error_disabled
+ * Function    :  cgi_error_file
  *
- * Description :  CGI function that is called if the actions editor
- *                is called although it's disabled in config
+ * Description :  CGI function that is called when a file cannot be
+ *                opened for writing by the CGI editor.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
  *          2  :  rsp = http_response data structure for output
+ *          3  :  filename = The file that we can't write to
  *
  * CGI Parameters : none
  *
@@ -2230,20 +2416,30 @@ jb_err cgi_error_file(struct client_state *csp,
  *                JB_ERR_MEMORY on out-of-memory error.
  *
  *********************************************************************/
-jb_err cgi_error_disabled(struct client_state *csp,
-                          struct http_response *rsp)
+jb_err cgi_error_file_read_only(struct client_state *csp,
+                                struct http_response *rsp,
+                                const char *filename)
 {
    struct map *exports;
+   jb_err err;
 
    assert(csp);
    assert(rsp);
+   assert(filename);
 
    if (NULL == (exports = default_exports(csp, NULL)))
    {
       return JB_ERR_MEMORY;
    }
 
-   return template_fill_for_cgi(csp, "cgi-error-disabled", exports, rsp);
+   err = map(exports, "f", 1, html_encode(filename), 0);
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   return template_fill_for_cgi(csp, "cgi-error-file-read-only", exports, rsp);
 }
 
 
@@ -2276,20 +2472,9 @@ jb_err cgi_edit_actions(struct client_state *csp,
    }
 
    /* FIXME: Incomplete */
-   rsp->status = strdup("302 Local Redirect from Privoxy");
-   if (rsp->status == NULL)
-   {
-      return JB_ERR_MEMORY;
-   }
-   if (enlist_unique_header(rsp->headers, "Location",
-      CGI_PREFIX "edit-actions-list?f=default"))
-   {
-      free(rsp->status);
-      rsp->status = NULL;
-      return JB_ERR_MEMORY;
-   }
 
-   return JB_ERR_OK;
+   return cgi_redirect(rsp, CGI_PREFIX "edit-actions-list?f=default");
+
 }
 
 
@@ -2345,7 +2530,6 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
 
    if (NULL == (exports = default_exports(csp, NULL)))
    {
-      edit_free_file(file);
       return JB_ERR_MEMORY;
    }
 
@@ -2384,13 +2568,32 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
    {
       /*
        * Generate string with buttons to set actions for "/" to
-       * any predefined set of actions (named standard.*, propably
+       * any predefined set of actions (named standard.*, probably
        * residing in standard.action).
-       * FIXME: Shouldn't include hardwired HTML here, use line template instead!
        */
 
-      buttons = strdup(" ");
-      for (i = 0; i < MAX_ACTION_FILES; i++)
+      err = template_load(csp, &section_template, "edit-actions-list-button", 0);
+      if (err)
+      {
+         edit_free_file(file);
+         free_map(exports);
+         if (err == JB_ERR_FILE)
+         {
+            return cgi_error_no_template(csp, rsp, "edit-actions-list-button");
+         }
+         return err;
+      }
+
+      err = template_fill(&section_template, exports);
+      if (err)
+      {
+         edit_free_file(file);
+         free_map(exports);
+         return err;
+      }
+
+      buttons = strdup("");
+      for (i = 0; i < MAX_AF_FILES; i++)
       {
          if (((fl = csp->actions_list[i]) != NULL) && ((b = fl->f) != NULL))
          {
@@ -2398,27 +2601,49 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
             {
                if (!strncmp(b->url->spec, "standard.", 9) && *(b->url->spec + 9) != '\0')
                {
-                  snprintf(buf, 150, "<p><a name=\"l@s@\" href=\"eas?f=@f@&amp;v=@v@&amp;s=@all-urls-s@&amp;p=%s\">Set"
-                                     " to %s</a></p>", b->url->spec + 9, b->url->spec + 9);
-                  if (!err) err = string_append(&buttons, buf);
+                  if (err || (NULL == (section_exports = new_map())))
+                  {
+                     freez(buttons);
+                     free(section_template);
+                     edit_free_file(file);
+                     free_map(exports);
+                     return JB_ERR_MEMORY;
+                  }
+
+                  err = map(section_exports, "button-name", 1, b->url->spec + 9, 1);
+
+                  if (err || (NULL == (s = strdup(section_template))))
+                  {
+                     free_map(section_exports);
+                     freez(buttons);
+                     free(section_template);
+                     edit_free_file(file);
+                     free_map(exports);
+                     return JB_ERR_MEMORY;
+                  }
+
+                  if (!err) err = template_fill(&s, section_exports);
+                  free_map(section_exports);
+                  if (!err) err = string_join(&buttons, s);
                }
             }
          }
       }
+      freez(section_template);
       if (!err) err = map(exports, "all-urls-buttons", 1, buttons, 0);
 
       /*
        * Conventional actions file, supply extra editing help.
        * (e.g. don't allow them to make it an unconventional one).
        */
-      err = map_conditional(exports, "all-urls-present", 1);
+      if (!err) err = map_conditional(exports, "all-urls-present", 1);
 
       snprintf(buf, 150, "%d", line_number);
       if (!err) err = map(exports, "all-urls-s", 1, buf, 1);
       snprintf(buf, 150, "%d", line_number + 2);
       if (!err) err = map(exports, "all-urls-s-next", 1, buf, 1);
       if (!err) err = map(exports, "all-urls-actions", 1,
-                          actions_to_html(cur_line->data.action, csp), 0);
+                          actions_to_html(csp, cur_line->data.action), 0);
 
        /* Skip the 2 lines */
       cur_line = cur_line->next->next;
@@ -2438,12 +2663,12 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
        * Non-standard actions file - does not begin with
        * the "All URLs" section.
        */
-      err = map_conditional(exports, "all-urls-present", 0);
+      if (!err) err = map_conditional(exports, "all-urls-present", 0);
    }
 
    /* Set up global exports */
 
-   err = map(exports, "f", 1, file->identifier, 1);
+   if (!err) err = map(exports, "f", 1, file->identifier, 1);
    if (!err) err = map(exports, "v", 1, file->version_str, 1);
 
    /* Discourage private additions to default.action */
@@ -2529,7 +2754,7 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
       snprintf(buf, 150, "%d", line_number);
       err = map(section_exports, "s", 1, buf, 1);
       if (!err) err = map(section_exports, "actions", 1,
-                          actions_to_html(cur_line->data.action, csp), 0);
+                          actions_to_html(csp, cur_line->data.action), 0);
 
       if ( (!err)
         && (cur_line->next != NULL)
@@ -2666,12 +2891,13 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
 
       /* Could also do section-specific exports here, but it wouldn't be as fast */
 
+      snprintf(buf, 150, "%d", line_number);
+      if (!err) err = map(section_exports, "s-next", 1, buf, 1);
+
       if ( (cur_line != NULL)
         && (cur_line->type == FILE_LINE_ACTION))
       {
          /* Not last section */
-         snprintf(buf, 150, "%d", line_number);
-         if (!err) err = map(section_exports, "s-next", 1, buf, 1);
          if (!err) err = map_block_keep(section_exports, "s-next-exists");
       }
       else
@@ -2765,8 +2991,8 @@ jb_err cgi_edit_actions_for_url(struct client_state *csp,
    struct file_line * cur_line;
    unsigned line_number;
    jb_err err;
-   struct file_list *filter_file;
    struct re_filterfile_spec *filter_group;
+   int i, have_filters = 0;
 
    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
    {
@@ -2815,10 +3041,44 @@ jb_err cgi_edit_actions_for_url(struct client_state *csp,
 
    if (!err) err = actions_to_radio(exports, cur_line->data.action);
 
-   filter_file = csp->rlist;
-   filter_group = ((filter_file != NULL) ? filter_file->f : NULL);
+   /*
+    * XXX: Some browsers (at least IE6 and IE7) have an artifical URL
+    * length limitation and ignore clicks on the Submit buttons if
+    * the resulting GET URL would be longer than their limit.
+    *
+    * In Privoxy 3.0.5 beta the standard edit-actions-for-url template
+    * reached this limit and action editing stopped working in these
+    * browsers (BR #1570678).
+    *
+    * The config option split-large-forms works around this browser
+    * bug (HTTP has no URL lenght limitation) by deviding the action
+    * list form into multiple smaller ones. It means the URLs are shorter
+    * and work in broken browsers as well, but the user can no longer change
+    * all actions with one submit.
+    *
+    * A better solution would be to switch to POST requests,
+    * but this will do for now.
+    */
+   if(!err && (csp->config->feature_flags & RUNTIME_FEATURE_SPLIT_LARGE_FORMS))
+   {
+      /* Generate multiple smaller form by killing the big one. */
+      err = map_block_killer(exports, "one-form-only");
+   }
+   else
+   {
+      /* Default: Generate one large form by killing the smaller ones. */
+      err = map_block_killer(exports, "multiple-forms");
+   }
 
-   if (!err) err = map_conditional(exports, "any-filters-defined", (filter_group != NULL));
+   for (i = 0; i < MAX_AF_FILES; i++)
+   {
+      if ((csp->rlist[i] != NULL) && (csp->rlist[i]->f != NULL))
+      {
+         if (!err) err = map_conditional(exports, "any-filters-defined", 1);
+         have_filters = 1;
+         break;
+      }
+   }
 
    if (err)
    {
@@ -2827,15 +3087,13 @@ jb_err cgi_edit_actions_for_url(struct client_state *csp,
       return err;
    }
 
-   if (filter_group == NULL)
-   {
+   if (0 == have_filters)
       err = map(exports, "filter-params", 1, "", 1);
-   }
    else
    {
       /* We have some entries in the filter list */
       char * result;
-      int index = 0;
+      int filter_identifier = 0;
       char * filter_template;
 
       err = template_load(csp, &filter_template, "edit-actions-for-url-filter", 0);
@@ -2850,70 +3108,81 @@ jb_err cgi_edit_actions_for_url(struct client_state *csp,
          return err;
       }
 
+      err = template_fill(&filter_template, exports);
+
       result = strdup("");
 
-      for (;(!err) && (filter_group != NULL); filter_group = filter_group->next)
+      for (i = 0; i < MAX_AF_FILES; i++)
       {
-         char current_mode = 'x';
-         struct list_entry *filter_name;
-         char * this_line;
-         struct map *line_exports;
-         char number[20];
-
-         filter_name = cur_line->data.action->multi_add[ACTION_MULTI_FILTER]->first;
-         while ((filter_name != NULL)
-             && (0 != strcmp(filter_group->name, filter_name->str)))
-         {
-              filter_name = filter_name->next;
-         }
-
-         if (filter_name != NULL)
-         {
-            current_mode = 'y';
-         }
-         else
+         if ((csp->rlist[i] != NULL) && (csp->rlist[i]->f != NULL))
          {
-            filter_name = cur_line->data.action->multi_remove[ACTION_MULTI_FILTER]->first;
-            while ((filter_name != NULL)
-                && (0 != strcmp(filter_group->name, filter_name->str)))
+            filter_group = csp->rlist[i]->f;
+            for (;(!err) && (filter_group != NULL); filter_group = filter_group->next)
             {
-                 filter_name = filter_name->next;
-            }
-            if (filter_name != NULL)
-            {
-               current_mode = 'n';
-            }
-         }
+               char current_mode = 'x';
+               struct list_entry *filter_name;
+               char * this_line;
+               struct map *line_exports;
+               char number[20];
+
+               filter_name = cur_line->data.action->multi_add[ACTION_MULTI_FILTER]->first;
+               while ((filter_name != NULL)
+                   && (0 != strcmp(filter_group->name, filter_name->str)))
+               {
+                    filter_name = filter_name->next;
+               }
 
-         /* Generate a unique serial number */
-         snprintf(number, sizeof(number), "%x", index++);
-         number[sizeof(number) - 1] = '\0';
+               if (filter_name != NULL)
+               {
+                  current_mode = 'y';
+               }
+               else
+               {
+                  filter_name = cur_line->data.action->multi_remove[ACTION_MULTI_FILTER]->first;
+                  while ((filter_name != NULL)
+                      && (0 != strcmp(filter_group->name, filter_name->str)))
+                  {
+                       filter_name = filter_name->next;
+                  }
+                  if (filter_name != NULL)
+                  {
+                     current_mode = 'n';
+                  }
+               }
 
-         line_exports = new_map();
-         if (line_exports == NULL)
-         {
-            err = JB_ERR_MEMORY;
-            freez(result);
-         }
-         else
-         {
-            if (!err) err = map(line_exports, "index", 1, number, 1);
-            if (!err) err = map(line_exports, "name",  1, filter_group->name, 1);
-            if (!err) err = map(line_exports, "description",  1, filter_group->description, 1);
-            if (!err) err = map_radio(line_exports, "this-filter", "ynx", current_mode);
+               /* Generate a unique serial number */
+               snprintf(number, sizeof(number), "%x", filter_identifier++);
+               number[sizeof(number) - 1] = '\0';
 
-            this_line = NULL;
-            if (!err)
-            {
-               this_line = strdup(filter_template);
-               if (this_line == NULL) err = JB_ERR_MEMORY;
-            }
-            if (!err) err = template_fill(&this_line, line_exports);
-            string_join(&result, this_line);
+               line_exports = new_map();
+               if (line_exports == NULL)
+               {
+                  err = JB_ERR_MEMORY;
+                  freez(result);
+               }
+               else
+               {
+                  if (!err) err = map(line_exports, "index", 1, number, 1);
+                  if (!err) err = map(line_exports, "name",  1, filter_group->name, 1);
+                  if (!err) err = map(line_exports, "description",  1, filter_group->description, 1);
+                  if (!err) err = map_radio(line_exports, "this-filter", "ynx", current_mode);
 
-            free_map(line_exports);
+                  this_line = NULL;
+                  if (!err)
+                  {
+                     this_line = strdup(filter_template);
+                     if (this_line == NULL) err = JB_ERR_MEMORY;
+                  }
+                  if (!err) err = template_fill(&this_line, line_exports);
+                  string_join(&result, this_line);
+
+                  free_map(line_exports);
+               }
+            }
          }
       }
+      freez(filter_template);
+
       if (!err)
       {
          err = map(exports, "filter-params", 1, result, 0);
@@ -2969,9 +3238,9 @@ jb_err cgi_edit_actions_submit(struct client_state *csp,
    struct editable_file * file;
    struct file_line * cur_line;
    unsigned line_number;
-   char * target;
+   char target[1024];
    jb_err err;
-   int index;
+   int filter_identifier;
    const char * action_set_name;
    char ch;
    struct file_list * fl;
@@ -3015,9 +3284,9 @@ jb_err cgi_edit_actions_submit(struct client_state *csp,
    get_string_param(parameters, "p", &action_set_name);
    if (action_set_name != NULL)
    {
-      for (index = 0; index < MAX_ACTION_FILES; index++)
+      for (filter_identifier = 0; filter_identifier < MAX_AF_FILES; filter_identifier++)
       {
-         if (((fl = csp->actions_list[index]) != NULL) && ((b = fl->f) != NULL))
+         if (((fl = csp->actions_list[filter_identifier]) != NULL) && ((b = fl->f) != NULL))
          {
             for (b = b->next; NULL != b; b = b->next)
             {
@@ -3058,7 +3327,7 @@ jb_err cgi_edit_actions_submit(struct client_state *csp,
       cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] = 0;
    }
 
-   for (index = 0; !err; index++)
+   for (filter_identifier = 0; !err; filter_identifier++)
    {
       char key_value[30];
       char key_name[30];
@@ -3066,9 +3335,9 @@ jb_err cgi_edit_actions_submit(struct client_state *csp,
       char value;
 
       /* Generate the keys */
-      snprintf(key_value, sizeof(key_value), "filter_r%x", index);
+      snprintf(key_value, sizeof(key_value), "filter_r%x", filter_identifier);
       key_value[sizeof(key_value) - 1] = '\0';
-      snprintf(key_name, sizeof(key_name), "filter_n%x", index);
+      snprintf(key_name, sizeof(key_name), "filter_n%x", filter_identifier);
       key_name[sizeof(key_name) - 1] = '\0';
 
       err = get_string_param(parameters, key_name, &name);
@@ -3148,31 +3417,21 @@ jb_err cgi_edit_actions_submit(struct client_state *csp,
    if (err)
    {
       /* Error writing file */
+      if (err == JB_ERR_FILE)
+      {
+         /* Read-only file. */
+         err = cgi_error_file_read_only(csp, rsp, file->identifier);
+      }
       edit_free_file(file);
       return err;
    }
 
-   target = strdup(CGI_PREFIX "edit-actions-list?f=");
-   string_append(&target, file->identifier);
+   snprintf(target, 1024, CGI_PREFIX "edit-actions-list?foo=%lu&f=%s#l%d",
+            (long) time(NULL), file->identifier, sectionid);
 
    edit_free_file(file);
 
-   if (target == NULL)
-   {
-      /* Out of memory */
-      return JB_ERR_MEMORY;
-   }
-
-   rsp->status = strdup("302 Local Redirect from Privoxy");
-   if (rsp->status == NULL)
-   {
-      free(target);
-      return JB_ERR_MEMORY;
-   }
-   err = enlist_unique_header(rsp->headers, "Location", target);
-   free(target);
-
-   return err;
+   return cgi_redirect(rsp, target);
 }
 
 
@@ -3210,9 +3469,14 @@ jb_err cgi_edit_actions_url(struct client_state *csp,
    struct editable_file * file;
    struct file_line * cur_line;
    unsigned line_number;
-   char * target;
+   unsigned section_start_line_number = 0;
+   char target[1024];
    jb_err err;
 
+   assert(csp);
+   assert(rsp);
+   assert(parameters);
+
    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
    {
       return cgi_error_disabled(csp, rsp);
@@ -3247,6 +3511,10 @@ jb_err cgi_edit_actions_url(struct client_state *csp,
 
    while ((cur_line != NULL) && (line_number < patternid))
    {
+      if (cur_line->type == FILE_LINE_ACTION)
+      {
+         section_start_line_number = line_number;
+      }      
       cur_line = cur_line->next;
       line_number++;
    }
@@ -3270,31 +3538,21 @@ jb_err cgi_edit_actions_url(struct client_state *csp,
    if (err)
    {
       /* Error writing file */
+      if (err == JB_ERR_FILE)
+      {
+         /* Read-only file. */
+         err = cgi_error_file_read_only(csp, rsp, file->identifier);
+      }
       edit_free_file(file);
       return err;
    }
 
-   target = strdup(CGI_PREFIX "edit-actions-list?f=");
-   string_append(&target, file->identifier);
+   snprintf(target, 1024, CGI_PREFIX "edit-actions-list?foo=%lu&f=%s#l%d",
+            (long) time(NULL), file->identifier, section_start_line_number);
 
    edit_free_file(file);
 
-   if (target == NULL)
-   {
-      /* Out of memory */
-      return JB_ERR_MEMORY;
-   }
-
-   rsp->status = strdup("302 Local Redirect from Privoxy");
-   if (rsp->status == NULL)
-   {
-      free(target);
-      return JB_ERR_MEMORY;
-   }
-   err = enlist_unique_header(rsp->headers, "Location", target);
-   free(target);
-
-   return err;
+   return cgi_redirect(rsp, target);
 }
 
 
@@ -3332,7 +3590,7 @@ jb_err cgi_edit_actions_add_url(struct client_state *csp,
    struct editable_file * file;
    struct file_line * cur_line;
    unsigned line_number;
-   char * target;
+   char target[1024];
    jb_err err;
 
    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
@@ -3409,31 +3667,21 @@ jb_err cgi_edit_actions_add_url(struct client_state *csp,
    if (err)
    {
       /* Error writing file */
+      if (err == JB_ERR_FILE)
+      {
+         /* Read-only file. */
+         err = cgi_error_file_read_only(csp, rsp, file->identifier);
+      }
       edit_free_file(file);
       return err;
    }
 
-   target = strdup(CGI_PREFIX "edit-actions-list?f=");
-   string_append(&target, file->identifier);
+   snprintf(target, 1024, CGI_PREFIX "edit-actions-list?foo=%lu&f=%s#l%d",
+            (long) time(NULL), file->identifier, sectionid);
 
    edit_free_file(file);
 
-   if (target == NULL)
-   {
-      /* Out of memory */
-      return JB_ERR_MEMORY;
-   }
-
-   rsp->status = strdup("302 Local Redirect from Privoxy");
-   if (rsp->status == NULL)
-   {
-      free(target);
-      return JB_ERR_MEMORY;
-   }
-   err = enlist_unique_header(rsp->headers, "Location", target);
-   free(target);
-
-   return err;
+   return cgi_redirect(rsp, target);
 }
 
 
@@ -3469,7 +3717,8 @@ jb_err cgi_edit_actions_remove_url(struct client_state *csp,
    struct file_line * cur_line;
    struct file_line * prev_line;
    unsigned line_number;
-   char * target;
+   unsigned section_start_line_number = 0;
+   char target[1024];
    jb_err err;
 
    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
@@ -3496,6 +3745,10 @@ jb_err cgi_edit_actions_remove_url(struct client_state *csp,
 
    while ((cur_line != NULL) && (line_number < patternid))
    {
+      if (cur_line->type == FILE_LINE_ACTION)
+      {
+         section_start_line_number = line_number;
+      }
       prev_line = cur_line;
       cur_line = cur_line->next;
       line_number++;
@@ -3525,31 +3778,21 @@ jb_err cgi_edit_actions_remove_url(struct client_state *csp,
    if (err)
    {
       /* Error writing file */
+      if (err == JB_ERR_FILE)
+      {
+         /* Read-only file. */
+         err = cgi_error_file_read_only(csp, rsp, file->identifier);
+      }
       edit_free_file(file);
       return err;
    }
 
-   target = strdup(CGI_PREFIX "edit-actions-list?f=");
-   string_append(&target, file->identifier);
+   snprintf(target, 1024, CGI_PREFIX "edit-actions-list?foo=%lu&f=%s#l%d",
+            (long) time(NULL), file->identifier, section_start_line_number);
 
    edit_free_file(file);
 
-   if (target == NULL)
-   {
-      /* Out of memory */
-      return JB_ERR_MEMORY;
-   }
-
-   rsp->status = strdup("302 Local Redirect from Privoxy");
-   if (rsp->status == NULL)
-   {
-      free(target);
-      return JB_ERR_MEMORY;
-   }
-   err = enlist_unique_header(rsp->headers, "Location", target);
-   free(target);
-
-   return err;
+   return cgi_redirect(rsp, target);
 }
 
 
@@ -3586,7 +3829,7 @@ jb_err cgi_edit_actions_section_remove(struct client_state *csp,
    struct file_line * cur_line;
    struct file_line * prev_line;
    unsigned line_number;
-   char * target;
+   char target[1024];
    jb_err err;
 
    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
@@ -3657,31 +3900,21 @@ jb_err cgi_edit_actions_section_remove(struct client_state *csp,
    if (err)
    {
       /* Error writing file */
+      if (err == JB_ERR_FILE)
+      {
+         /* Read-only file. */
+         err = cgi_error_file_read_only(csp, rsp, file->identifier);
+      }
       edit_free_file(file);
       return err;
    }
 
-   target = strdup(CGI_PREFIX "edit-actions-list?f=");
-   string_append(&target, file->identifier);
+   snprintf(target, 1024, CGI_PREFIX "edit-actions-list?foo=%lu&f=%s",
+            (long) time(NULL), file->identifier);
 
    edit_free_file(file);
 
-   if (target == NULL)
-   {
-      /* Out of memory */
-      return JB_ERR_MEMORY;
-   }
-
-   rsp->status = strdup("302 Local Redirect from Privoxy");
-   if (rsp->status == NULL)
-   {
-      free(target);
-      return JB_ERR_MEMORY;
-   }
-   err = enlist_unique_header(rsp->headers, "Location", target);
-   free(target);
-
-   return err;
+   return cgi_redirect(rsp, target);
 }
 
 
@@ -3719,7 +3952,7 @@ jb_err cgi_edit_actions_section_add(struct client_state *csp,
    struct editable_file * file;
    struct file_line * cur_line;
    unsigned line_number;
-   char * target;
+   char target[1024];
    jb_err err;
 
    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
@@ -3743,10 +3976,10 @@ jb_err cgi_edit_actions_section_add(struct client_state *csp,
    line_number = 1;
    cur_line = file->lines;
 
-   if (sectionid < 1U)
+   if (sectionid <= 1U)
    {
       /* Add to start of file */
-      if (cur_line != NULL)
+      if (cur_line != NULL && cur_line->type != FILE_LINE_ACTION)
       {
          /* There's something in the file, find the line before the first
           * action.
@@ -3758,6 +3991,11 @@ jb_err cgi_edit_actions_section_add(struct client_state *csp,
             line_number++;
          }
       }
+      else
+      {
+         /* File starts with action line, so insert at top */
+         cur_line = NULL;
+      }
    }
    else
    {
@@ -3831,31 +4069,21 @@ jb_err cgi_edit_actions_section_add(struct client_state *csp,
    if (err)
    {
       /* Error writing file */
+      if (err == JB_ERR_FILE)
+      {
+         /* Read-only file. */
+         err = cgi_error_file_read_only(csp, rsp, file->identifier);
+      }
       edit_free_file(file);
       return err;
    }
 
-   target = strdup(CGI_PREFIX "edit-actions-list?f=");
-   string_append(&target, file->identifier);
+   snprintf(target, 1024, CGI_PREFIX "edit-actions-list?foo=%lu&f=%s",
+            (long) time(NULL), file->identifier);
 
    edit_free_file(file);
 
-   if (target == NULL)
-   {
-      /* Out of memory */
-      return JB_ERR_MEMORY;
-   }
-
-   rsp->status = strdup("302 Local Redirect from Privoxy");
-   if (rsp->status == NULL)
-   {
-      free(target);
-      return JB_ERR_MEMORY;
-   }
-   err = enlist_unique_header(rsp->headers, "Location", target);
-   free(target);
-
-   return err;
+   return cgi_redirect(rsp, target);
 }
 
 
@@ -3904,7 +4132,7 @@ jb_err cgi_edit_actions_section_swap(struct client_state *csp,
    struct file_line * line_end_section2;
    struct file_line * line_after_section2;
    unsigned line_number;
-   char * target;
+   char target[1024];
    jb_err err;
 
    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
@@ -4029,35 +4257,25 @@ jb_err cgi_edit_actions_section_swap(struct client_state *csp,
       if (err)
       {
          /* Error writing file */
+         if (err == JB_ERR_FILE)
+         {
+            /* Read-only file. */
+            err = cgi_error_file_read_only(csp, rsp, file->identifier);
+         }
          edit_free_file(file);
          return err;
       }
    } /* END if (section1 != section2) */
 
-   target = strdup(CGI_PREFIX "edit-actions-list?f=");
-   string_append(&target, file->identifier);
+   snprintf(target, 1024, CGI_PREFIX "edit-actions-list?foo=%lu&f=%s",
+            (long) time(NULL), file->identifier);
 
    edit_free_file(file);
 
-   if (target == NULL)
-   {
-      /* Out of memory */
-      return JB_ERR_MEMORY;
-   }
-
-   rsp->status = strdup("302 Local Redirect from Privoxy");
-   if (rsp->status == NULL)
-   {
-      free(target);
-      return JB_ERR_MEMORY;
-   }
-   err = enlist_unique_header(rsp->headers, "Location", target);
-   free(target);
-
-   return err;
+   return cgi_redirect(rsp, target);
 }
 
-
+#ifdef FEATURE_TOGGLE
 /*********************************************************************
  *
  * Function    :  cgi_toggle
@@ -4101,17 +4319,17 @@ jb_err cgi_toggle(struct client_state *csp,
    if (mode == 'E')
    {
       /* Enable */
-      g_bToggleIJB = 1;
+      global_toggle_state = 1;
    }
    else if (mode == 'D')
    {
       /* Disable */
-      g_bToggleIJB = 0;
+      global_toggle_state = 0;
    }
    else if (mode == 'T')
    {
       /* Toggle */
-      g_bToggleIJB = !g_bToggleIJB;
+      global_toggle_state = !global_toggle_state;
    }
 
    if (NULL == (exports = default_exports(csp, "toggle")))
@@ -4125,7 +4343,7 @@ jb_err cgi_toggle(struct client_state *csp,
 
    return template_fill_for_cgi(csp, template_name, exports, rsp);
 }
-
+#endif /* def FEATURE_TOGGLE */
 
 /*********************************************************************
  *
@@ -4314,7 +4532,6 @@ static jb_err actions_to_radio(struct map * exports,
 static jb_err actions_from_radio(const struct map * parameters,
                                  struct action_spec *action)
 {
-   static int first_time = 1;
    const char * param;
    char * param_dup;
    char ch;
@@ -4328,16 +4545,22 @@ static jb_err actions_from_radio(const struct map * parameters,
     * but in this case we're safe and don't need semaphores.
     * Be careful if you modify this function.
     * - Jon
+    * The js_name_arr's are never free()d, but this is no
+    * problem, since they will only be created once and
+    * used by all threads thereafter. -oes
     */
 
 #define JAVASCRIPTIFY(dest_var, string)               \
    {                                                  \
-      static char js_name_arr[] = string;             \
+     static int first_time = 1;                       \
+     static char *js_name_arr;                        \
       if (first_time)                                 \
       {                                               \
+         js_name_arr = strdup(string);                \
          javascriptify(js_name_arr);                  \
       }                                               \
       dest_var = js_name_arr;                         \
+      first_time = 0;                                 \
    }                                                  \
 
 #define DEFINE_ACTION_BOOL(name, bit)                 \
@@ -4433,8 +4656,6 @@ static jb_err actions_from_radio(const struct map * parameters,
 #undef DEFINE_ACTION_ALIAS
 #undef JAVASCRIPTIFY
 
-   first_time = 0;
-
    return err;
 }