Made csp the first parameter in *action_to_html
[privoxy.git] / cgiedit.c
index 4e4927d..1547518 100644 (file)
--- a/cgiedit.c
+++ b/cgiedit.c
@@ -1,4 +1,4 @@
-const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.14 2002/03/05 00:24:51 jongfoster Exp $";
+const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.36 2002/04/26 21:53:30 jongfoster Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/cgiedit.c,v $
@@ -16,7 +16,7 @@ const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.14 2002/03/05 00:24:51 jongfoster
  *                Stick to the short names in this file for consistency.
  *
  * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
- *                IJBSWA team.  http://ijbswa.sourceforge.net
+ *                Privoxy team. http://www.privoxy.org/
  *
  *                Based on the Internet Junkbuster originally written
  *                by and Copyright (C) 1997 Anonymous Coders and
@@ -42,6 +42,87 @@ const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.14 2002/03/05 00:24:51 jongfoster
  *
  * Revisions   :
  *    $Log: cgiedit.c,v $
+ *    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
+ *     - Activated Jon's code for editing multiple AFs
+ *     - cgi_edit_list_actions now provides context-sensitive
+ *       help, looks up all action sets from standard.action and
+ *       makes buttons for them in the catchall section
+ *     - cgi_edit_action_submit now honors a p parameter, looks up
+ *       the corresponding action set, and sets the catchall pattern's
+ *       actions accordingly.
+ *
+ *    Revision 1.32  2002/04/19 16:55:31  jongfoster
+ *    Fixing newline problems.  If we do our own text file newline
+ *    mangling, we don't want the library to do any, so we need to
+ *    open the files in *binary* mode.
+ *
+ *    Revision 1.31  2002/04/18 19:21:08  jongfoster
+ *    Added code to detect "conventional" action files, that start
+ *    with a set of actions for all URLs (the pattern "/").
+ *    These are special-cased in the "edit-actions-list" CGI, so
+ *    that a special UI can be written for them.
+ *
+ *    Revision 1.30  2002/04/10 13:38:35  oes
+ *    load_template signature changed
+ *
+ *    Revision 1.29  2002/04/08 16:59:08  oes
+ *    Fixed comment
+ *
+ *    Revision 1.28  2002/03/27 12:30:29  oes
+ *    Deleted unsused variable
+ *
+ *    Revision 1.27  2002/03/26 23:06:04  jongfoster
+ *    Removing duplicate @ifs on the toggle page
+ *
+ *    Revision 1.26  2002/03/26 22:59:17  jongfoster
+ *    Fixing /toggle to display status consistently.
+ *
+ *    Revision 1.25  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.24  2002/03/24 15:23:33  jongfoster
+ *    Name changes
+ *
+ *    Revision 1.23  2002/03/24 13:32:41  swa
+ *    name change related issues
+ *
+ *    Revision 1.22  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.21  2002/03/22 18:02:48  jongfoster
+ *    Fixing remote toggle
+ *
+ *    Revision 1.20  2002/03/16 20:28:34  oes
+ *    Added descriptions to the filters so users will know what they select in the cgi editor
+ *
+ *    Revision 1.19  2002/03/16 18:38:14  jongfoster
+ *    Stopping stupid or malicious users from breaking the actions
+ *    file using the web-based editor.
+ *
+ *    Revision 1.18  2002/03/16 14:57:44  jongfoster
+ *    Full support for enabling/disabling modular filters.
+ *
+ *    Revision 1.17  2002/03/16 14:26:42  jongfoster
+ *    First version of modular filters support - READ ONLY!
+ *    Fixing a double-free bug in the out-of-memory handling in map_radio().
+ *
+ *    Revision 1.16  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.15  2002/03/06 22:54:35  jongfoster
+ *    Automated function-comment nitpicking.
+ *
  *    Revision 1.14  2002/03/05 00:24:51  jongfoster
  *    Patch to always edit the current actions file.
  *
@@ -164,7 +245,6 @@ const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.14 2002/03/05 00:24:51 jongfoster
 #include <ctype.h>
 #include <string.h>
 #include <assert.h>
-#include <limits.h>
 #include <sys/stat.h>
 
 #ifdef _WIN32
@@ -240,7 +320,7 @@ struct file_line
 struct editable_file
 {
    struct file_line * lines;
-   const char * filename;     /* Full pathname - e.g. "/etc/junkbuster/wibble.action" */
+   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 */
@@ -308,20 +388,18 @@ static jb_err get_file_name_param(struct client_state *csp,
                                   const char *suffix,
                                   char **pfilename,
                                   const char **pparam);
-static jb_err get_number_param(struct client_state *csp,
-                               const struct map *parameters,
-                               char *name,
-                               unsigned *pvalue);
+
 static jb_err get_url_spec_param(struct client_state *csp,
                                  const struct map *parameters,
                                  const char *name,
                                  char **pvalue);
 
+
 /* Internal actionsfile <==> HTML conversion functions */
 static jb_err map_radio(struct map * exports,
                         const char * optionname,
                         const char * values,
-                        char value);
+                        int value);
 static jb_err actions_to_radio(struct map * exports,
                                const struct action_spec *action);
 static jb_err actions_from_radio(const struct map * parameters,
@@ -388,7 +466,7 @@ static jb_err map_copy_parameter_html(struct map *out,
 #if 0 /* unused function */
 /*********************************************************************
  *
- * Function    :  map_copy_parameter_html
+ * Function    :  map_copy_parameter_url
  *
  * Description :  Copy a CGI parameter from one map to another, URL
  *                encoding it.
@@ -712,11 +790,7 @@ jb_err edit_write_file(struct editable_file * file)
    assert(file);
    assert(file->filename);
 
-#if defined(AMIGA) || defined(__OS2__)
-   if (NULL == (fp = fopen(file->filename, "w")))
-#else
-   if (NULL == (fp = fopen(file->filename, "wt")))
-#endif /* def AMIGA */
+   if (NULL == (fp = fopen(file->filename, "wb")))
    {
       return JB_ERR_FILE;
    }
@@ -744,9 +818,6 @@ jb_err edit_write_file(struct editable_file * file)
          }
          if (cur_line->unprocessed)
          {
-            /* This should be a single line - sanity check. */
-            assert(NULL == strchr(cur_line->unprocessed, '\r'));
-            assert(NULL == strchr(cur_line->unprocessed, '\n'));
 
             if (NULL != strchr(cur_line->unprocessed, '#'))
             {
@@ -768,7 +839,7 @@ jb_err edit_write_file(struct editable_file * file)
 
                /* Allocate new memory for string */
                len = strlen(cur_line->unprocessed);
-               if (NULL == (str = malloc(len + 1 + numhash)))
+               if (NULL == (str = malloc((size_t) len + 1 + numhash)))
                {
                   /* Uh oh, just trashed file! */
                   fclose(fp);
@@ -885,7 +956,7 @@ void edit_free_file(struct editable_file * file)
 
 /*********************************************************************
  *
- * Function    :  edit_free_file
+ * Function    :  edit_free_file_lines
  *
  * Description :  Free an entire linked list of file lines.
  *
@@ -955,7 +1026,7 @@ static void edit_free_file_lines(struct file_line * first_line)
  *********************************************************************/
 static int match_actions_file_header_line(const char * line, const char * name)
 {
-   int len;
+   size_t len;
 
    assert(line);
    assert(name);
@@ -1021,7 +1092,7 @@ static jb_err split_line_on_equals(const char * line, char ** pname, char ** pva
 {
    const char * name_end;
    const char * value_start;
-   int name_len;
+   size_t name_len;
 
    assert(line);
    assert(pname);
@@ -1101,7 +1172,7 @@ static jb_err split_line_on_equals(const char * line, char ** pname, char ** pva
 jb_err edit_parse_actions_file(struct editable_file * file)
 {
    struct file_line * cur_line;
-   int len;
+   size_t len;
    const char * text; /* Text from a line */
    char * name;  /* For lines of the form name=value */
    char * value; /* For lines of the form name=value */
@@ -1543,11 +1614,7 @@ jb_err edit_read_file(struct client_state *csp,
       }
    }
 
-#if defined(AMIGA) || defined(__OS2__)
-   if (NULL == (fp = fopen(filename,"r")))
-#else
-   if (NULL == (fp = fopen(filename,"rt")))
-#endif /* def AMIGA */
+   if (NULL == (fp = fopen(filename,"rb")))
    {
       free(filename);
       return JB_ERR_FILE;
@@ -1746,9 +1813,7 @@ static jb_err get_file_name_param(struct client_state *csp,
 {
    const char *param;
    const char *s;
-#if 0 /* Patch to make 3.0.0 work properly. */
    char *name;
-#endif /* 0 - Patch to make 3.0.0 work properly. */
    char *fullpath;
    char ch;
    int len;
@@ -1792,13 +1857,6 @@ static jb_err get_file_name_param(struct client_state *csp,
       }
    }
 
-   /*
-    * FIXME Following is a hack to make 3.0.0 work properly.
-    * Change "#if 0" --> "#if 1" below when we have modular action
-    * files.
-    *    -- Jon
-    */
-#if 0 /* Patch to make 3.0.0 work properly. */
    /* Append extension */
    name = malloc(len + strlen(suffix) + 1);
    if (name == NULL)
@@ -1811,16 +1869,7 @@ static jb_err get_file_name_param(struct client_state *csp,
    /* Prepend path */
    fullpath = make_path(csp->config->confdir, name);
    free(name);
-#else /* 1 - Patch to make 3.0.0 work properly. */
-   if ((csp->actions_list == NULL)
-    || (csp->actions_list->filename == NULL))
-   {
-      return JB_ERR_CGI_PARAMS;
-   }
 
-   fullpath = ( (csp->actions_list && csp->actions_list->filename)
-             ? strdup(csp->actions_list->filename) : NULL);
-#endif /* 1 - Patch to make 3.0.0 work properly. */
    if (fullpath == NULL)
    {
       return JB_ERR_MEMORY;
@@ -1833,82 +1882,6 @@ static jb_err get_file_name_param(struct client_state *csp,
 }
 
 
-/*********************************************************************
- *
- * Function    :  get_number_param
- *
- * Description :  Get a non-negative integer from the parameters
- *                passed to a CGI function.
- *
- * Parameters  :
- *          1  :  csp = Current client state (buffers, headers, etc...)
- *          2  :  parameters = map of cgi parameters
- *          3  :  name = Name of CGI parameter to read
- *          4  :  pvalue = destination for value.
- *                         Set to -1 on error.
- *
- * Returns     :  JB_ERR_OK         on success
- *                JB_ERR_MEMORY     on out-of-memory
- *                JB_ERR_CGI_PARAMS if the parameter was not specified
- *                                  or is not valid.
- *
- *********************************************************************/
-static jb_err get_number_param(struct client_state *csp,
-                               const struct map *parameters,
-                               char *name,
-                               unsigned *pvalue)
-{
-   const char *param;
-   char ch;
-   unsigned value;
-
-   assert(csp);
-   assert(parameters);
-   assert(name);
-   assert(pvalue);
-
-   *pvalue = -1;
-
-   param = lookup(parameters, name);
-   if (!*param)
-   {
-      return JB_ERR_CGI_PARAMS;
-   }
-
-   /* We don't use atoi because I want to check this carefully... */
-
-   value = 0;
-   while ((ch = *param++) != '\0')
-   {
-      if ((ch < '0') || (ch > '9'))
-      {
-         return JB_ERR_CGI_PARAMS;
-      }
-
-      ch -= '0';
-
-      /* Note:
-       *
-       * <limits.h> defines UINT_MAX
-       *
-       * (UINT_MAX - ch) / 10 is the largest number that
-       *     can be safely multiplied by 10 then have ch added.
-       */
-      if (value > ((UINT_MAX - (unsigned)ch) / 10U))
-      {
-         return JB_ERR_CGI_PARAMS;
-      }
-
-      value = value * 10 + ch;
-   }
-
-   /* Success */
-   *pvalue = value;
-
-   return JB_ERR_OK;
-}
-
-
 /*********************************************************************
  *
  * Function    :  get_url_spec_param
@@ -2065,9 +2038,9 @@ static jb_err get_url_spec_param(struct client_state *csp,
 static jb_err map_radio(struct map * exports,
                         const char * optionname,
                         const char * values,
-                        char value)
+                        int value)
 {
-   int len;
+   size_t len;
    char * buf;
    char * p;
    char c;
@@ -2095,20 +2068,13 @@ static jb_err map_radio(struct map * exports,
          *p = c;
          if (map(exports, buf, 1, "", 1))
          {
-            free(buf);
             return JB_ERR_MEMORY;
          }
       }
    }
 
    *p = value;
-   if (map(exports, buf, 0, "checked", 1))
-   {
-      free(buf);
-      return JB_ERR_MEMORY;
-   }
-
-   return JB_ERR_OK;
+   return map(exports, buf, 0, "checked", 1);
 }
 
 
@@ -2258,10 +2224,10 @@ jb_err cgi_error_file(struct client_state *csp,
 
 /*********************************************************************
  *
- * Function    :  cgi_error_bad_param
+ * Function    :  cgi_error_disabled
  *
- * Description :  CGI function that is called if the parameters
- *                (query string) for a CGI were wrong.
+ * Description :  CGI function that is called if the actions editor
+ *                is called although it's disabled in config
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
@@ -2319,13 +2285,13 @@ jb_err cgi_edit_actions(struct client_state *csp,
    }
 
    /* FIXME: Incomplete */
-   rsp->status = strdup("302 Local Redirect from Junkbuster");
+   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=ijb"))
+      CGI_PREFIX "edit-actions-list?f=default"))
    {
       free(rsp->status);
       rsp->status = NULL;
@@ -2366,7 +2332,7 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
    char * url_template;
    char * sections;
    char * urls;
-   char buf[50];
+   char buf[150];
    char * s;
    struct map * exports;
    struct map * section_exports;
@@ -2375,7 +2341,10 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
    struct file_line * cur_line;
    unsigned line_number = 0;
    unsigned prev_section_line_number = ((unsigned) (-1));
-   int url_1_2;
+   int i, url_1_2;
+   struct file_list * fl;
+   struct url_actions * b;
+   char * buttons = NULL;
    jb_err err;
 
    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
@@ -2383,6 +2352,13 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
       return cgi_error_disabled(csp, rsp);
    }
 
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      edit_free_file(file);
+      return JB_ERR_MEMORY;
+   }
+
+   /* Load actions file */
    err = edit_read_actions_file(csp, rsp, parameters, 0, &file);
    if (err)
    {
@@ -2390,15 +2366,99 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
    }
 
-   if (NULL == (exports = default_exports(csp, NULL)))
+   /* Find start of actions in file */
+   cur_line = file->lines;
+   line_number = 1;
+   while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
    {
-      edit_free_file(file);
-      return JB_ERR_MEMORY;
+      cur_line = cur_line->next;
+      line_number++;
+   }
+
+   /*
+    * Conventional actions files should have a match all block
+    * at the start:
+    * cur_line             = {...global actions...}
+    * cur_line->next       = /
+    * cur_line->next->next = {...actions...} or EOF
+    */
+   if ( (cur_line != NULL)
+     && (cur_line->type == FILE_LINE_ACTION)
+     && (cur_line->next != NULL)
+     && (cur_line->next->type == FILE_LINE_URL)
+     && (0 == strcmp(cur_line->next->unprocessed, "/"))
+     && ( (cur_line->next->next == NULL)
+       || (cur_line->next->next->type != FILE_LINE_URL)
+      ) )
+   {
+      /*
+       * Generate string with buttons to set actions for "/" to
+       * any predefined set of actions (named standard.*, propably
+       * 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++)
+      {
+         if (((fl = csp->actions_list[i]) != NULL) && ((b = fl->f) != NULL))
+         {
+            for (b = b->next; NULL != b; b = b->next)
+            {
+               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) 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);
+
+      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(csp, cur_line->data.action), 0);
+
+       /* Skip the 2 lines */
+      cur_line = cur_line->next->next;
+      line_number += 2;
+
+      /*
+       * Note that prev_section_line_number is NOT set here.
+       * This is deliberate and not a bug.  It stops a "Move up"
+       * option appearing on the next section.  Clicking "Move
+       * up" would make the actions file unconventional, which
+       * we don't want, so we hide this option.
+       */
+   }
+   else
+   {
+      /*
+       * Non-standard actions file - does not begin with
+       * the "All URLs" section.
+       */
+      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, "v", 1, file->version_str, 1);
 
+   /* Discourage private additions to default.action */
+
+   if (!err) err = map_conditional(exports, "default-action",
+                                   (strcmp("default", lookup(parameters, "f")) == 0));
    if (err)
    {
       edit_free_file(file);
@@ -2408,7 +2468,9 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
 
    /* Should do all global exports above this point */
 
-   err = template_load(csp, &section_template, "edit-actions-list-section");
+   /* Load templates */
+
+   err = template_load(csp, &section_template, "edit-actions-list-section", 0);
    if (err)
    {
       edit_free_file(file);
@@ -2420,7 +2482,7 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
       return err;
    }
 
-   err = template_load(csp, &url_template, "edit-actions-list-url");
+   err = template_load(csp, &url_template, "edit-actions-list-url", 0);
    if (err)
    {
       free(section_template);
@@ -2452,15 +2514,6 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
       return err;
    }
 
-   /* Find start of actions in file */
-   cur_line = file->lines;
-   line_number = 1;
-   while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
-   {
-      cur_line = cur_line->next;
-      line_number++;
-   }
-
    if (NULL == (sections = strdup("")))
    {
       free(section_template);
@@ -2482,10 +2535,10 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
          return JB_ERR_MEMORY;
       }
 
-      snprintf(buf, 50, "%d", line_number);
+      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), 0);
+                          actions_to_html(csp, cur_line->data.action), 0);
 
       if ( (!err)
         && (cur_line->next != NULL)
@@ -2502,7 +2555,7 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
       if (prev_section_line_number != ((unsigned)(-1)))
       {
          /* Not last section */
-         snprintf(buf, 50, "%d", prev_section_line_number);
+         snprintf(buf, 150, "%d", prev_section_line_number);
          if (!err) err = map(section_exports, "s-prev", 1, buf, 1);
          if (!err) err = map_block_keep(section_exports, "s-prev-exists");
       }
@@ -2556,10 +2609,10 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
             return JB_ERR_MEMORY;
          }
 
-         snprintf(buf, 50, "%d", line_number);
+         snprintf(buf, 150, "%d", line_number);
          err = map(url_exports, "p", 1, buf, 1);
 
-         snprintf(buf, 50, "%d", url_1_2);
+         snprintf(buf, 150, "%d", url_1_2);
          if (!err) err = map(url_exports, "url-1-2", 1, buf, 1);
 
          if (!err) err = map(url_exports, "url-html", 1,
@@ -2626,7 +2679,7 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
         && (cur_line->type == FILE_LINE_ACTION))
       {
          /* Not last section */
-         snprintf(buf, 50, "%d", line_number);
+         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");
       }
@@ -2694,7 +2747,7 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
 
 /*********************************************************************
  *
- * Function    :  cgi_edit_actions
+ * Function    :  cgi_edit_actions_for_url
  *
  * Description :  CGI function that edits the Actions list.
  *
@@ -2721,6 +2774,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;
 
    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
    {
@@ -2769,6 +2824,123 @@ 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);
+
+   if (!err) err = map_conditional(exports, "any-filters-defined", (filter_group != NULL));
+
+   if (err)
+   {
+      edit_free_file(file);
+      free_map(exports);
+      return err;
+   }
+
+   if (filter_group == NULL)
+   {
+      err = map(exports, "filter-params", 1, "", 1);
+   }
+   else
+   {
+      /* We have some entries in the filter list */
+      char * result;
+      int index = 0;
+      char * filter_template;
+
+      err = template_load(csp, &filter_template, "edit-actions-for-url-filter", 0);
+      if (err)
+      {
+         edit_free_file(file);
+         free_map(exports);
+         if (err == JB_ERR_FILE)
+         {
+            return cgi_error_no_template(csp, rsp, "edit-actions-for-url-filter");
+         }
+         return err;
+      }
+
+      err = template_fill(&filter_template, exports);
+
+      result = strdup("");
+
+      for (;(!err) && (filter_group != NULL); filter_group = filter_group->next)
+      {
+         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
+         {
+            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';
+            }
+         }
+
+         /* Generate a unique serial number */
+         snprintf(number, sizeof(number), "%x", index++);
+         number[sizeof(number) - 1] = '\0';
+
+         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);
+
+            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);
+      }
+      else
+      {
+         freez(result);
+      }
+   }
+
+   if (!err) err = map_radio(exports, "filter-all", "nx",
+      (cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] ? 'n' : 'x'));
+
    edit_free_file(file);
 
    if (err)
@@ -2807,12 +2979,17 @@ jb_err cgi_edit_actions_submit(struct client_state *csp,
    unsigned sectionid;
    char * actiontext;
    char * newtext;
-   int len;
+   size_t len;
    struct editable_file * file;
    struct file_line * cur_line;
    unsigned line_number;
    char * target;
    jb_err err;
+   int index;
+   const char * action_set_name;
+   char ch;
+   struct file_list * fl;
+   struct url_actions * b;
 
    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
    {
@@ -2849,7 +3026,97 @@ jb_err cgi_edit_actions_submit(struct client_state *csp,
       return JB_ERR_CGI_PARAMS;
    }
 
-   err = actions_from_radio(parameters, cur_line->data.action);
+   get_string_param(parameters, "p", &action_set_name);
+   if (action_set_name != NULL)
+   {
+      for (index = 0; index < MAX_ACTION_FILES; index++)
+      {
+         if (((fl = csp->actions_list[index]) != NULL) && ((b = fl->f) != NULL))
+         {
+            for (b = b->next; NULL != b; b = b->next)
+            {
+               if (!strncmp(b->url->spec, "standard.", 9) && !strcmp(b->url->spec + 9, action_set_name))
+               {
+                  copy_action(cur_line->data.action, b->action); 
+                  goto found;
+               }
+            }
+         }
+      }
+      edit_free_file(file);
+      return JB_ERR_CGI_PARAMS;
+
+      found: ;
+   }
+   else
+   {
+      err = actions_from_radio(parameters, cur_line->data.action);
+   }
+
+   if(err)
+   {
+      /* Out of memory */
+      edit_free_file(file);
+      return err;
+   }
+
+   ch = get_char_param(parameters, "filter_all");
+   if (ch == 'N')
+   {
+      list_remove_all(cur_line->data.action->multi_add[ACTION_MULTI_FILTER]);
+      list_remove_all(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER]);
+      cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] = 1;
+   }
+   else if (ch == 'X')
+   {
+      cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] = 0;
+   }
+
+   for (index = 0; !err; index++)
+   {
+      char key_value[30];
+      char key_name[30];
+      const char *name;
+      char value;
+
+      /* Generate the keys */
+      snprintf(key_value, sizeof(key_value), "filter_r%x", index);
+      key_value[sizeof(key_value) - 1] = '\0';
+      snprintf(key_name, sizeof(key_name), "filter_n%x", index);
+      key_name[sizeof(key_name) - 1] = '\0';
+
+      err = get_string_param(parameters, key_name, &name);
+      if (err) break;
+
+      if (name == NULL)
+      {
+         /* End of list */
+         break;
+      }
+
+      value = get_char_param(parameters, key_value);
+      if (value == 'Y')
+      {
+         list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
+         if (!err) err = enlist(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
+         list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
+      }
+      else if (value == 'N')
+      {
+         list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
+         if (!cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER])
+         {
+            list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
+            if (!err) err = enlist(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
+         }
+      }
+      else if (value == 'X')
+      {
+         list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
+         list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
+      }
+   }
+
    if(err)
    {
       /* Out of memory */
@@ -2910,7 +3177,7 @@ jb_err cgi_edit_actions_submit(struct client_state *csp,
       return JB_ERR_MEMORY;
    }
 
-   rsp->status = strdup("302 Local Redirect from Junkbuster");
+   rsp->status = strdup("302 Local Redirect from Privoxy");
    if (rsp->status == NULL)
    {
       free(target);
@@ -3032,7 +3299,7 @@ jb_err cgi_edit_actions_url(struct client_state *csp,
       return JB_ERR_MEMORY;
    }
 
-   rsp->status = strdup("302 Local Redirect from Junkbuster");
+   rsp->status = strdup("302 Local Redirect from Privoxy");
    if (rsp->status == NULL)
    {
       free(target);
@@ -3171,7 +3438,7 @@ jb_err cgi_edit_actions_add_url(struct client_state *csp,
       return JB_ERR_MEMORY;
    }
 
-   rsp->status = strdup("302 Local Redirect from Junkbuster");
+   rsp->status = strdup("302 Local Redirect from Privoxy");
    if (rsp->status == NULL)
    {
       free(target);
@@ -3287,7 +3554,7 @@ jb_err cgi_edit_actions_remove_url(struct client_state *csp,
       return JB_ERR_MEMORY;
    }
 
-   rsp->status = strdup("302 Local Redirect from Junkbuster");
+   rsp->status = strdup("302 Local Redirect from Privoxy");
    if (rsp->status == NULL)
    {
       free(target);
@@ -3419,7 +3686,7 @@ jb_err cgi_edit_actions_section_remove(struct client_state *csp,
       return JB_ERR_MEMORY;
    }
 
-   rsp->status = strdup("302 Local Redirect from Junkbuster");
+   rsp->status = strdup("302 Local Redirect from Privoxy");
    if (rsp->status == NULL)
    {
       free(target);
@@ -3593,7 +3860,7 @@ jb_err cgi_edit_actions_section_add(struct client_state *csp,
       return JB_ERR_MEMORY;
    }
 
-   rsp->status = strdup("302 Local Redirect from Junkbuster");
+   rsp->status = strdup("302 Local Redirect from Privoxy");
    if (rsp->status == NULL)
    {
       free(target);
@@ -3792,7 +4059,7 @@ jb_err cgi_edit_actions_section_swap(struct client_state *csp,
       return JB_ERR_MEMORY;
    }
 
-   rsp->status = strdup("302 Local Redirect from Junkbuster");
+   rsp->status = strdup("302 Local Redirect from Privoxy");
    if (rsp->status == NULL)
    {
       free(target);
@@ -3833,7 +4100,6 @@ jb_err cgi_toggle(struct client_state *csp,
    struct map *exports;
    char mode;
    const char *template_name;
-   jb_err err;
 
    assert(csp);
    assert(rsp);
@@ -3844,37 +4110,30 @@ jb_err cgi_toggle(struct client_state *csp,
       return cgi_error_disabled(csp, rsp);
    }
 
-   if (NULL == (exports = default_exports(csp, "toggle")))
-   {
-      return JB_ERR_MEMORY;
-   }
+   mode = get_char_param(parameters, "set");
 
-   mode = *(lookup(parameters, "set"));
-
-   if (mode == 'e')
+   if (mode == 'E')
    {
       /* Enable */
       g_bToggleIJB = 1;
    }
-   else if (mode == 'd')
+   else if (mode == 'D')
    {
       /* Disable */
       g_bToggleIJB = 0;
    }
-   else if (mode == 't')
+   else if (mode == 'T')
    {
       /* Toggle */
       g_bToggleIJB = !g_bToggleIJB;
    }
 
-   err = map_conditional(exports, "enabled", g_bToggleIJB);
-   if (err)
+   if (NULL == (exports = default_exports(csp, "toggle")))
    {
-      free_map(exports);
-      return err;
+      return JB_ERR_MEMORY;
    }
 
-   template_name = (*(lookup(parameters, "mini"))
+   template_name = (get_char_param(parameters, "mini")
                  ? "toggle-mini"
                  : "toggle");
 
@@ -3882,6 +4141,44 @@ jb_err cgi_toggle(struct client_state *csp,
 }
 
 
+/*********************************************************************
+ *
+ * Function    :  javascriptify
+ *
+ * Description :  Converts a string into a form JavaScript will like.
+ *
+ *                Netscape 4's JavaScript sucks - it doesn't use 
+ *                "id" parameters, so you have to set the "name"
+ *                used to submit a form element to something JavaScript
+ *                will like.  (Or access the elements by index in an
+ *                array.  That array contains >60 elements and will
+ *                be changed whenever we add a new action to the
+ *                editor, so I'm NOT going to use indexes that have
+ *                to be figured out by hand.)
+ *
+ *                Currently the only thing we have to worry about
+ *                is "-" ==> "_" conversion.
+ *
+ *                This is a length-preserving operation so it is
+ *                carried out in-place, no memory is allocated
+ *                or freed.
+ *
+ * Parameters  :
+ *          1  :  identifier = String to make JavaScript-friendly.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+static void javascriptify(char * identifier)
+{
+   char * p = identifier;
+   while (NULL != (p = strchr(p, '-')))
+   {
+      *p++ = '_';
+   }
+}
+
+
 /*********************************************************************
  *
  * Function    :  actions_to_radio
@@ -4011,44 +4308,6 @@ static jb_err actions_to_radio(struct map * exports,
 }
 
 
-/*********************************************************************
- *
- * Function    :  javascriptify
- *
- * Description :  Converts a string into a form JavaScript will like.
- *
- *                Netscape 4's JavaScript sucks - it doesn't use 
- *                "id" parameters, so you have to set the "name"
- *                used to submit a form element to something JavaScript
- *                will like.  (Or access the elements by index in an
- *                array.  That array contains >60 elements and will
- *                be changed whenever we add a new action to the
- *                editor, so I'm NOT going to use indexes that have
- *                to be figured out by hand.)
- *
- *                Currently the only thing we have to worry about
- *                is "-" ==> "_" conversion.
- *
- *                This is a length-preserving operation so it is
- *                carried out in-place, no memory is allocated
- *                or freed.
- *
- * Parameters  :
- *          1  :  identifier = String to make JavaScript-friendly.
- *
- * Returns     :  N/A
- *
- *********************************************************************/
-static void javascriptify(char * identifier)
-{
-   char * p = identifier;
-   while (NULL != (p = strchr(p, '-')))
-   {
-      *p++ = '_';
-   }
-}
-
-
 /*********************************************************************
  *
  * Function    :  actions_from_radio
@@ -4067,13 +4326,14 @@ static void javascriptify(char * identifier)
  *
  *********************************************************************/
 static jb_err actions_from_radio(const struct map * parameters,
-                              struct action_spec *action)
+                                 struct action_spec *action)
 {
    static int first_time = 1;
    const char * param;
    char * param_dup;
    char ch;
    const char * js_name;
+   jb_err err = JB_ERR_OK;
 
    assert(parameters);
    assert(action);
@@ -4096,8 +4356,7 @@ static jb_err actions_from_radio(const struct map * parameters,
 
 #define DEFINE_ACTION_BOOL(name, bit)                 \
    JAVASCRIPTIFY(js_name, name);                      \
-   param = lookup(parameters, js_name);               \
-   ch = ijb_toupper(param[0]);                        \
+   ch = get_char_param(parameters, js_name);          \
    if (ch == 'Y')                                     \
    {                                                  \
       action->add  |= bit;                            \
@@ -4116,18 +4375,18 @@ static jb_err actions_from_radio(const struct map * parameters,
 
 #define DEFINE_ACTION_STRING(name, bit, index)                 \
    JAVASCRIPTIFY(js_name, name);                               \
-   param = lookup(parameters, js_name);                        \
-   ch = ijb_toupper(param[0]);                                 \
+   ch = get_char_param(parameters, js_name);                   \
    if (ch == 'Y')                                              \
    {                                                           \
+      param = NULL;                                            \
       JAVASCRIPTIFY(js_name, name "-mode");                    \
-      param = lookup(parameters, js_name);                     \
-      if ((*param == '\0') || (0 == strcmp(param, "CUSTOM")))  \
-      {                                                        \
-         JAVASCRIPTIFY(js_name, name "-param");                \
-         param = lookup(parameters, js_name);                  \
+      if (!err) err = get_string_param(parameters, js_name, &param);    \
+      if ((param == NULL) || (0 == strcmp(param, "CUSTOM")))            \
+      {                                                                 \
+         JAVASCRIPTIFY(js_name, name "-param");                         \
+         if (!err) err = get_string_param(parameters, js_name, &param); \
       }                                                        \
-      if (*param != '\0')                                      \
+      if (param != NULL)                                       \
       {                                                        \
          if (NULL == (param_dup = strdup(param)))              \
          {                                                     \
@@ -4160,8 +4419,7 @@ static jb_err actions_from_radio(const struct map * parameters,
 
 #define DEFINE_ACTION_MULTI(name, index)                       \
    JAVASCRIPTIFY(js_name, name);                               \
-   param = lookup(parameters, js_name);                        \
-   ch = ijb_toupper((int)param[0]);                            \
+   ch = get_char_param(parameters, js_name);                   \
    if (ch == 'Y')                                              \
    {                                                           \
       /* FIXME */                                              \
@@ -4191,7 +4449,7 @@ static jb_err actions_from_radio(const struct map * parameters,
 
    first_time = 0;
 
-   return JB_ERR_OK;
+   return err;
 }