Moving all our URL and URL pattern parsing code to urlmatch.c.
[privoxy.git] / cgiedit.c
index bcba425..789bc3b 100644 (file)
--- a/cgiedit.c
+++ b/cgiedit.c
@@ -1,21 +1,21 @@
-const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.3 2001/10/14 22:12:49 jongfoster Exp $";
+const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.8 2001/11/30 23:35:51 jongfoster Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/cgiedit.c,v $
  *
  * Purpose     :  CGI-based actionsfile editor.
- *                
+ *
  *                Functions declared include:
- * 
+ *
  *
  * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
  *                IJBSWA team.  http://ijbswa.sourceforge.net
  *
  *                Based on the Internet Junkbuster originally written
- *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                by and Copyright (C) 1997 Anonymous Coders and
  *                Junkbusters Corporation.  http://www.junkbusters.com
  *
- *                This program is free software; you can redistribute it 
+ *                This program is free software; you can redistribute it
  *                and/or modify it under the terms of the GNU General
  *                Public License as published by the Free Software
  *                Foundation; either version 2 of the License, or (at
@@ -35,6 +35,44 @@ const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.3 2001/10/14 22:12:49 jongfoster
  *
  * Revisions   :
  *    $Log: cgiedit.c,v $
+ *    Revision 1.8  2001/11/30 23:35:51  jongfoster
+ *    Renaming actionsfile to ijb.action
+ *
+ *    Revision 1.7  2001/11/13 00:28:24  jongfoster
+ *    - Renaming parameters from edit-actions-for-url so that they only
+ *      contain legal JavaScript characters.  If we wanted to write
+ *      JavaScript that worked with Netscape 4, this is nessacery.
+ *      (Note that at the moment the JavaScript doesn't actually work
+ *      with Netscape 4, but now this is purely a template issue, not
+ *      one affecting code).
+ *    - Adding new CGIs for use by non-JavaScript browsers:
+ *        edit-actions-url-form
+ *        edit-actions-add-url-form
+ *        edit-actions-remove-url-form
+ *    - Fixing || bug.
+ *
+ *    Revision 1.6  2001/10/29 03:48:09  david__schmidt
+ *    OS/2 native needed a snprintf() routine.  Added one to miscutil, brackedted
+ *    by and __OS2__ ifdef.
+ *
+ *    Revision 1.5  2001/10/25 03:40:48  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.4  2001/10/23 21:48:19  jongfoster
+ *    Cleaning up error handling in CGI functions - they now send back
+ *    a HTML error page and should never cause a FATAL error.  (Fixes one
+ *    potential source of "denial of service" attacks).
+ *
+ *    CGI actions file editor that works and is actually useful.
+ *
+ *    Ability to toggle JunkBuster remotely using a CGI call.
+ *
+ *    You can turn off both the above features in the main configuration
+ *    file, e.g. if you are running a multi-user proxy.
+ *
  *    Revision 1.3  2001/10/14 22:12:49  jongfoster
  *    New version of CGI-based actionsfile editor.
  *    Major changes, including:
@@ -68,7 +106,6 @@ const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.3 2001/10/14 22:12:49 jongfoster
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/types.h>
-#include <stdlib.h>
 #include <ctype.h>
 #include <string.h>
 #include <assert.h>
@@ -90,6 +127,7 @@ const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.3 2001/10/14 22:12:49 jongfoster
 #include "errlog.h"
 #include "loadcfg.h"
 /* loadcfg.h is for g_bToggleIJB only */
+#include "urlmatch.h"
 
 const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION;
 
@@ -103,7 +141,7 @@ struct file_line
    char * prefix;
    char * unprocessed;
    int type;
-   
+
    union
    {
       struct action_spec action[1];
@@ -157,7 +195,7 @@ struct editable_file
                                * significant anyway.
                                */
    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.  
+   const char * parse_error_text;  /* On parse error, this is the problem.
                                     * (Statically allocated) */
 };
 
@@ -190,6 +228,8 @@ 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);
 
 /* Internal arbitrary config file support functions */
 static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile);
@@ -215,7 +255,7 @@ static jb_err get_number_param(struct client_state *csp,
 
 /* Internal actionsfile <==> HTML conversion functions */
 static jb_err map_radio(struct map * exports,
-                        const char * optionname, 
+                        const char * optionname,
                         const char * values,
                         char value);
 static jb_err actions_to_radio(struct map * exports,
@@ -224,6 +264,292 @@ static jb_err actions_from_radio(const struct map * parameters,
                                  struct action_spec *action);
 
 
+static jb_err map_copy_parameter_html(struct map *out,
+                                      const struct map *in,
+                                      const char *name);
+static jb_err map_copy_parameter_url(struct map *out,
+                                     const struct map *in,
+                                     const char *name);
+
+
+/*********************************************************************
+ *
+ * Function    :  map_copy_parameter_html
+ *
+ * Description :  Copy a CGI parameter from one map to another, HTML
+ *                encoding it.
+ *
+ * Parameters  :
+ *           1 :  out = target map
+ *           2 :  in = source map
+ *           3 :  name = name of cgi parameter to copy
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the parameter doesn't exist
+ *                                  in the source map
+ *
+ *********************************************************************/
+static jb_err map_copy_parameter_html(struct map *out,
+                                      const struct map *in,
+                                      const char *name)
+{
+   const char * value;
+   jb_err err;
+
+   assert(out);
+   assert(in);
+   assert(name);
+
+   value = lookup(in, name);
+   err = map(out, name, 1, html_encode(value), 0);
+
+   if (err)
+   {
+      /* Out of memory */
+      return err;
+   }
+   else if (*value == '\0')
+   {
+      return JB_ERR_CGI_PARAMS;
+   }
+   else
+   {
+      return JB_ERR_OK;
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  map_copy_parameter_html
+ *
+ * Description :  Copy a CGI parameter from one map to another, URL
+ *                encoding it.
+ *
+ * Parameters  :
+ *           1 :  out = target map
+ *           2 :  in = source map
+ *           3 :  name = name of cgi parameter to copy
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the parameter doesn't exist
+ *                                  in the source map
+ *
+ *********************************************************************/
+static jb_err map_copy_parameter_url(struct map *out,
+                                     const struct map *in,
+                                     const char *name)
+{
+   const char * value;
+   jb_err err;
+
+   assert(out);
+   assert(in);
+   assert(name);
+
+   value = lookup(in, name);
+   err = map(out, name, 1, url_encode(value), 0);
+
+   if (err)
+   {
+      /* Out of memory */
+      return err;
+   }
+   else if (*value == '\0')
+   {
+      return JB_ERR_CGI_PARAMS;
+   }
+   else
+   {
+      return JB_ERR_OK;
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_url_form
+ *
+ * Description :  CGI function that displays a form for
+ *                edit-actions-url
+ *
+ * Parameters  :
+ *           1 :  csp = Current client state (buffers, headers, etc...)
+ *           2 :  rsp = http_response data structure for output
+ *           3 :  parameters = map of cgi parameters
+ *
+ * CGI Parameters
+ *    filename : Identifies the file to edit
+ *         ver : File's last-modified time
+ *     section : Line number of section to edit
+ *     pattern : Line number of pattern to edit
+ *      oldval : Current value for pattern
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_url_form(struct client_state *csp,
+                                 struct http_response *rsp,
+                                 const struct map *parameters)
+{
+   struct map *exports;
+   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);
+   }
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   err = map_copy_parameter_html(exports, parameters, "section");
+   if (!err) err = map_copy_parameter_html(exports, parameters, "pattern");
+   if (!err) err = map_copy_parameter_html(exports, parameters, "ver");
+   if (!err) err = map_copy_parameter_html(exports, parameters, "filename");
+   if (!err) err = map_copy_parameter_html(exports, parameters, "oldval");
+
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   return template_fill_for_cgi(csp, "edit-actions-url-form", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_add_url_form
+ *
+ * Description :  CGI function that displays a form for
+ *                edit-actions-url
+ *
+ * Parameters  :
+ *           1 :  csp = Current client state (buffers, headers, etc...)
+ *           2 :  rsp = http_response data structure for output
+ *           3 :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *    filename : Identifies the file to edit
+ *         ver : File's last-modified time
+ *     section : Line number of section to edit
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_add_url_form(struct client_state *csp,
+                                     struct http_response *rsp,
+                                     const struct map *parameters)
+{
+   struct map *exports;
+   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);
+   }
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   err = map_copy_parameter_html(exports, parameters, "section");
+   if (!err) err = map_copy_parameter_html(exports, parameters, "ver");
+   if (!err) err = map_copy_parameter_html(exports, parameters, "filename");
+
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   return template_fill_for_cgi(csp, "edit-actions-add-url-form", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_remove_url_form
+ *
+ * Description :  CGI function that displays a form for
+ *                edit-actions-url
+ *
+ * Parameters  :
+ *           1 :  csp = Current client state (buffers, headers, etc...)
+ *           2 :  rsp = http_response data structure for output
+ *           3 :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *    filename : Identifies the file to edit
+ *         ver : File's last-modified time
+ *     section : Line number of section to edit
+ *     pattern : Line number of pattern to edit
+ *      oldval : Current value for pattern
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
+                                     struct http_response *rsp,
+                                     const struct map *parameters)
+{
+   struct map *exports;
+   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);
+   }
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   err = map_copy_parameter_url(exports, parameters, "section");
+   if (!err) err = map_copy_parameter_url(exports, parameters, "pattern");
+   if (!err) err = map_copy_parameter_url(exports, parameters, "ver");
+   if (!err) err = map_copy_parameter_url(exports, parameters, "filename");
+   if (!err) err = map_copy_parameter_html(exports, parameters, "oldval");
+
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   return template_fill_for_cgi(csp, "edit-actions-remove-url-form", exports, rsp);
+}
+
+
 /*********************************************************************
  *
  * Function    :  simple_read_line
@@ -262,7 +588,7 @@ static jb_err simple_read_line(char **dest, FILE *fp)
    {
       return JB_ERR_MEMORY;
    }
-   
+
    *buf = '\0';
    len = 0;
 
@@ -289,7 +615,7 @@ static jb_err simple_read_line(char **dest, FILE *fp)
          *dest = buf;
          return JB_ERR_OK;
       }
-      
+
       if (NULL == (newbuf = realloc(buf, len + BUFFER_SIZE)))
       {
          free(buf);
@@ -309,7 +635,7 @@ static jb_err simple_read_line(char **dest, FILE *fp)
  *                and respects escaping of newline and comment char.
  *                Provides the line in 2 alternative forms: raw and
  *                preprocessed.
- *                - raw is the raw data read from the file.  If the 
+ *                - raw is the raw data read from the file.  If the
  *                  line is not modified, then this should be written
  *                  to the new file.
  *                - prefix is any comments and blank lines that were
@@ -396,7 +722,7 @@ static jb_err edit_read_line(FILE *fp, char **raw_out, char **prefix_out, char *
          free(linebuf);
          return JB_ERR_MEMORY;
       }
-      
+
       /* Trim off newline */
       p = linebuf + strlen(linebuf);
       if ((p != linebuf) && ((p[-1] == '\r') || (p[-1] == '\n')))
@@ -499,7 +825,7 @@ static jb_err edit_read_line(FILE *fp, char **raw_out, char **prefix_out, char *
    {
       /* Got at least some data */
 
-      /* Remove trailing whitespace */         
+      /* Remove trailing whitespace */
       chomp(data);
 
       if (raw_out)
@@ -631,7 +957,7 @@ jb_err edit_write_file(struct editable_file * file)
    file->version = (unsigned)statbuf->st_mtime;
 
    /* Correct file->version_str */
-   freez((char *)file->version_str);
+   freez(file->version_str);
    snprintf(version_buf, 22, "%u", file->version);
    version_buf[21] = '\0';
    file->version_str = strdup(version_buf);
@@ -648,7 +974,7 @@ jb_err edit_write_file(struct editable_file * file)
  *
  * Function    :  edit_free_file
  *
- * Description :  Free a complete file in memory.  
+ * Description :  Free a complete file in memory.
  *
  * Parameters  :
  *          1  :  file = Data structure to free.
@@ -665,9 +991,9 @@ void edit_free_file(struct editable_file * file)
    }
 
    edit_free_file_lines(file->lines);
-   freez((char *)file->filename);
-   freez((char *)file->identifier);
-   freez((char *)file->version_str);
+   freez(file->filename);
+   freez(file->identifier);
+   freez(file->version_str);
    file->version = 0;
    file->parse_error_text = NULL; /* Statically allocated */
    file->parse_error = NULL;
@@ -680,7 +1006,7 @@ void edit_free_file(struct editable_file * file)
  *
  * Function    :  edit_free_file
  *
- * Description :  Free an entire linked list of file lines.  
+ * Description :  Free an entire linked list of file lines.
  *
  * Parameters  :
  *          1  :  first_line = Data structure to free.
@@ -737,7 +1063,7 @@ static void edit_free_file_lines(struct file_line * first_line)
  *
  * Function    :  match_actions_file_header_line
  *
- * Description :  Match an actions file {{header}} line 
+ * Description :  Match an actions file {{header}} line
  *
  * Parameters  :
  *          1  :  line - String from file
@@ -795,7 +1121,7 @@ static int match_actions_file_header_line(const char * line, const char * name)
  *
  * Function    :  match_actions_file_header_line
  *
- * Description :  Match an actions file {{header}} line 
+ * Description :  Match an actions file {{header}} line
  *
  * Parameters  :
  *          1  :  line - String from file.  Must not start with
@@ -873,7 +1199,7 @@ static jb_err split_line_on_equals(const char * line, char ** pname, char ** pva
  *
  * Function    :  edit_parse_actions_file
  *
- * Description :  Parse an actions file in memory.  
+ * Description :  Parse an actions file in memory.
  *
  *                Passed linked list must have the "data" member
  *                zeroed, and must contain valid "next" and
@@ -908,7 +1234,7 @@ jb_err edit_parse_actions_file(struct editable_file * file)
 
    cur_line = file->lines;
 
-   /* A note about blank line support: Blank lines should only 
+   /* A note about blank line support: Blank lines should only
     * ever occur as the last line in the file.  This function
     * is more forgiving than that - FILE_LINE_BLANK can occur
     * anywhere.
@@ -1171,7 +1497,7 @@ jb_err edit_parse_actions_file(struct editable_file * file)
  *
  * Function    :  edit_read_file_lines
  *
- * Description :  Read all the lines of a file into memory.  
+ * Description :  Read all the lines of a file into memory.
  *                Handles whitespace, comments and line continuation.
  *
  * Parameters  :
@@ -1371,7 +1697,7 @@ jb_err edit_read_file(struct client_state *csp,
    }
 
    /* Correct file->version_str */
-   freez((char *)file->version_str);
+   freez(file->version_str);
    snprintf(version_buf, 22, "%u", file->version);
    version_buf[21] = '\0';
    file->version_str = strdup(version_buf);
@@ -1658,7 +1984,7 @@ static jb_err get_number_param(struct client_state *csp,
        *
        * <limits.h> defines UINT_MAX
        *
-       * (UINT_MAX - ch) / 10 is the largest number that 
+       * (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))
@@ -1703,7 +2029,7 @@ static jb_err get_number_param(struct client_state *csp,
  *
  *********************************************************************/
 static jb_err map_radio(struct map * exports,
-                        const char * optionname, 
+                        const char * optionname,
                         const char * values,
                         char value)
 {
@@ -1711,7 +2037,7 @@ static jb_err map_radio(struct map * exports,
    char * buf;
    char * p;
    char c;
-   
+
    assert(exports);
    assert(optionname);
    assert(values);
@@ -1881,6 +2207,44 @@ 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
@@ -1901,102 +2265,115 @@ 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;
+   const char * js_name;
 
    assert(parameters);
    assert(action);
 
-#define DEFINE_ACTION_BOOL(name, bit)                 \
-   if (NULL != (param = lookup(parameters, name)))    \
+   /* Statics are generally a potential race condition,
+    * but in this case we're safe and don't need semaphores.
+    * Be careful if you modify this function.
+    * - Jon
+    */
+
+#define JAVASCRIPTIFY(dest_var, string)               \
    {                                                  \
-      ch = toupper((int)param[0]);                    \
-      if (ch == 'Y')                                  \
-      {                                               \
-         action->add  |= bit;                         \
-         action->mask |= bit;                         \
-      }                                               \
-      else if (ch == 'N')                             \
-      {                                               \
-         action->add  &= ~bit;                        \
-         action->mask &= ~bit;                        \
-      }                                               \
-      else if (ch == 'X')                             \
+      static char js_name_arr[] = string;             \
+      if (first_time)                                 \
       {                                               \
-         action->add  &= ~bit;                        \
-         action->mask |= bit;                         \
+         javascriptify(js_name_arr);                  \
       }                                               \
-   }
+      dest_var = js_name_arr;                         \
+   }                                                  \
 
-#define DEFINE_ACTION_STRING(name, bit, index)                    \
-   if (NULL != (param = lookup(parameters, name)))                \
-   {                                                              \
-      ch = toupper((int)param[0]);                                \
-      if (ch == 'Y')                                              \
-      {                                                           \
-         param = lookup(parameters, name "-mode");                \
-         if ((*param == '\0') || (0 == strcmp(param, "CUSTOM")))  \
-         {                                                        \
-            param = lookup(parameters, name "-param");            \
-         }                                                        \
-         if (*param != '\0')                                      \
-         {                                                        \
-            if (NULL == (param_dup = strdup(param)))              \
-            {                                                     \
-               return JB_ERR_MEMORY;                              \
-            }                                                     \
-            freez(action->string[index]);                         \
-            action->add  |= bit;                                  \
-            action->mask |= bit;                                  \
-            action->string[index] = param_dup;                    \
-         }                                                        \
-      }                                                           \
-      else if (ch == 'N')                                         \
-      {                                                           \
-         if (action->add & bit)                                   \
-         {                                                        \
-            freez(action->string[index]);                         \
-         }                                                        \
-         action->add  &= ~bit;                                    \
-         action->mask &= ~bit;                                    \
-      }                                                           \
-      else if (ch == 'X')                                         \
-      {                                                           \
-         if (action->add & bit)                                   \
-         {                                                        \
-            freez(action->string[index]);                         \
-         }                                                        \
-         action->add  &= ~bit;                                    \
-         action->mask |= bit;                                     \
-      }                                                           \
-   }
-
-#define DEFINE_ACTION_MULTI(name, index)                          \
-   if (NULL != (param = lookup(parameters, name)))                \
-   {                                                              \
-      ch = toupper((int)param[0]);                                \
-      if (ch == 'Y')                                              \
-      {                                                           \
-         /* FIXME */                                              \
-      }                                                           \
-      else if (ch == 'N')                                         \
-      {                                                           \
-         list_remove_all(action->multi_add[index]);               \
-         list_remove_all(action->multi_remove[index]);            \
-         action->multi_remove_all[index] = 1;                     \
-      }                                                           \
-      else if (ch == 'X')                                         \
-      {                                                           \
-         list_remove_all(action->multi_add[index]);               \
-         list_remove_all(action->multi_remove[index]);            \
-         action->multi_remove_all[index] = 0;                     \
-      }                                                           \
-   }
-
-#define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val)
-#define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default)
-#define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val)
+#define DEFINE_ACTION_BOOL(name, bit)                 \
+   JAVASCRIPTIFY(js_name, name);                      \
+   param = lookup(parameters, js_name);               \
+   ch = ijb_toupper(param[0]);                        \
+   if (ch == 'Y')                                     \
+   {                                                  \
+      action->add  |= bit;                            \
+      action->mask |= bit;                            \
+   }                                                  \
+   else if (ch == 'N')                                \
+   {                                                  \
+      action->add  &= ~bit;                           \
+      action->mask &= ~bit;                           \
+   }                                                  \
+   else if (ch == 'X')                                \
+   {                                                  \
+      action->add  &= ~bit;                           \
+      action->mask |= bit;                            \
+   }                                                  \
+
+#define DEFINE_ACTION_STRING(name, bit, index)                 \
+   JAVASCRIPTIFY(js_name, name);                               \
+   param = lookup(parameters, js_name);                        \
+   ch = ijb_toupper(param[0]);                                 \
+   if (ch == 'Y')                                              \
+   {                                                           \
+      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 (*param != '\0')                                      \
+      {                                                        \
+         if (NULL == (param_dup = strdup(param)))              \
+         {                                                     \
+            return JB_ERR_MEMORY;                              \
+         }                                                     \
+         freez(action->string[index]);                         \
+         action->add  |= bit;                                  \
+         action->mask |= bit;                                  \
+         action->string[index] = param_dup;                    \
+      }                                                        \
+   }                                                           \
+   else if (ch == 'N')                                         \
+   {                                                           \
+      if (action->add & bit)                                   \
+      {                                                        \
+         freez(action->string[index]);                         \
+      }                                                        \
+      action->add  &= ~bit;                                    \
+      action->mask &= ~bit;                                    \
+   }                                                           \
+   else if (ch == 'X')                                         \
+   {                                                           \
+      if (action->add & bit)                                   \
+      {                                                        \
+         freez(action->string[index]);                         \
+      }                                                        \
+      action->add  &= ~bit;                                    \
+      action->mask |= bit;                                     \
+   }                                                           \
+
+#define DEFINE_ACTION_MULTI(name, index)                       \
+   JAVASCRIPTIFY(js_name, name);                               \
+   param = lookup(parameters, js_name);                        \
+   ch = ijb_toupper((int)param[0]);                            \
+   if (ch == 'Y')                                              \
+   {                                                           \
+      /* FIXME */                                              \
+   }                                                           \
+   else if (ch == 'N')                                         \
+   {                                                           \
+      list_remove_all(action->multi_add[index]);               \
+      list_remove_all(action->multi_remove[index]);            \
+      action->multi_remove_all[index] = 1;                     \
+   }                                                           \
+   else if (ch == 'X')                                         \
+   {                                                           \
+      list_remove_all(action->multi_add[index]);               \
+      list_remove_all(action->multi_remove[index]);            \
+      action->multi_remove_all[index] = 0;                     \
+   }                                                           \
 
 #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
 
@@ -2006,9 +2383,9 @@ static jb_err actions_from_radio(const struct map * parameters,
 #undef DEFINE_ACTION_STRING
 #undef DEFINE_ACTION_BOOL
 #undef DEFINE_ACTION_ALIAS
-#undef DEFINE_CGI_PARAM_CUSTOM
-#undef DEFINE_CGI_PARAM_RADIO
-#undef DEFINE_CGI_PARAM_NO_RADIO
+#undef JAVASCRIPTIFY
+
+   first_time = 0;
 
    return JB_ERR_OK;
 }
@@ -2020,7 +2397,7 @@ static jb_err actions_from_radio(const struct map * parameters,
  *
  * Description :  CGI function that is called when a file is modified
  *                outside the CGI editor.
- *               
+ *
  * Parameters  :
  *           1 :  csp = Current client state (buffers, headers, etc...)
  *           2 :  rsp = http_response data structure for output
@@ -2029,7 +2406,7 @@ static jb_err actions_from_radio(const struct map * parameters,
  * CGI Parameters : none
  *
  * Returns     :  JB_ERR_OK on success
- *                JB_ERR_MEMORY on out-of-memory error.  
+ *                JB_ERR_MEMORY on out-of-memory error.
  *
  *********************************************************************/
 jb_err cgi_error_modified(struct client_state *csp,
@@ -2065,7 +2442,7 @@ jb_err cgi_error_modified(struct client_state *csp,
  *
  * Description :  CGI function that is called when a file cannot
  *                be parsed by the CGI editor.
- *               
+ *
  * Parameters  :
  *           1 :  csp = Current client state (buffers, headers, etc...)
  *           2 :  rsp = http_response data structure for output
@@ -2074,7 +2451,7 @@ jb_err cgi_error_modified(struct client_state *csp,
  * CGI Parameters : none
  *
  * Returns     :  JB_ERR_OK on success
- *                JB_ERR_MEMORY on out-of-memory error.  
+ *                JB_ERR_MEMORY on out-of-memory error.
  *
  *********************************************************************/
 jb_err cgi_error_parse(struct client_state *csp,
@@ -2095,13 +2472,13 @@ jb_err cgi_error_parse(struct client_state *csp,
    }
 
    err = map(exports, "filename", 1, file->identifier, 1);
-   err = err || map(exports, "parse-error", 1, file->parse_error_text, 1);
+   if (!err) err = map(exports, "parse-error", 1, file->parse_error_text, 1);
 
    cur_line = file->parse_error;
    assert(cur_line);
 
-   err = err || map(exports, "line-raw", 1, html_encode(cur_line->raw), 0);
-   err = err || map(exports, "line-data", 1, html_encode(cur_line->unprocessed), 0);
+   if (!err) err = map(exports, "line-raw", 1, html_encode(cur_line->raw), 0);
+   if (!err) err = map(exports, "line-data", 1, html_encode(cur_line->unprocessed), 0);
 
    if (err)
    {
@@ -2119,7 +2496,7 @@ jb_err cgi_error_parse(struct client_state *csp,
  *
  * Description :  CGI function that is called when a file cannot be
  *                opened by the CGI editor.
- *               
+ *
  * Parameters  :
  *           1 :  csp = Current client state (buffers, headers, etc...)
  *           2 :  rsp = http_response data structure for output
@@ -2128,7 +2505,7 @@ jb_err cgi_error_parse(struct client_state *csp,
  * CGI Parameters : none
  *
  * Returns     :  JB_ERR_OK on success
- *                JB_ERR_MEMORY on out-of-memory error.  
+ *                JB_ERR_MEMORY on out-of-memory error.
  *
  *********************************************************************/
 jb_err cgi_error_file(struct client_state *csp,
@@ -2164,7 +2541,7 @@ jb_err cgi_error_file(struct client_state *csp,
  *
  * Description :  CGI function that is called if the parameters
  *                (query string) for a CGI were wrong.
- *               
+ *
  * Parameters  :
  *           1 :  csp = Current client state (buffers, headers, etc...)
  *           2 :  rsp = http_response data structure for output
@@ -2172,7 +2549,7 @@ jb_err cgi_error_file(struct client_state *csp,
  * CGI Parameters : none
  *
  * Returns     :  JB_ERR_OK on success
- *                JB_ERR_MEMORY on out-of-memory error.  
+ *                JB_ERR_MEMORY on out-of-memory error.
  *
  *********************************************************************/
 jb_err cgi_error_disabled(struct client_state *csp,
@@ -2226,7 +2603,8 @@ jb_err cgi_edit_actions(struct client_state *csp,
    {
       return JB_ERR_MEMORY;
    }
-   if (enlist_unique_header(rsp->headers, "Location", "http://ijbswa.sourceforge.net/config/edit-actions-list?filename=edit"))
+   if (enlist_unique_header(rsp->headers, "Location",
+      CGI_PREFIX "edit-actions-list?filename=ijb"))
    {
       free(rsp->status);
       rsp->status = NULL;
@@ -2274,7 +2652,7 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
    struct map * url_exports;
    struct editable_file * file;
    struct file_line * cur_line;
-   int line_number = 0;
+   unsigned line_number = 0;
    int url_1_2;
    jb_err err;
 
@@ -2297,7 +2675,8 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
    }
 
    err = map(exports, "filename", 1, file->identifier, 1);
-   err = err || map(exports, "ver", 1, file->version_str, 1);
+   if (!err) err = map(exports, "ver", 1, file->version_str, 1);
+
    if (err)
    {
       edit_free_file(file);
@@ -2318,7 +2697,7 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
       }
       return err;
    }
-   
+
    err = template_load(csp, &url_template, "edit-actions-list-url");
    if (err)
    {
@@ -2383,14 +2762,15 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
 
       snprintf(buf, 50, "%d", line_number);
       err = map(section_exports, "sectionid", 1, buf, 1);
+      if (!err) err = map(section_exports, "actions", 1,
+                          actions_to_html(cur_line->data.action), 0);
 
-      err = err || map(section_exports, "actions", 1, 
-                       actions_to_html(cur_line->data.action), 0);
-
-      if ((cur_line->next != NULL) && (cur_line->next->type == FILE_LINE_URL))
+      if ( (!err)
+        && (cur_line->next != NULL)
+        && (cur_line->next->type == FILE_LINE_URL))
       {
          /* This section contains at least one URL, don't allow delete */
-         err = err || map_block_killer(section_exports, "empty-section");
+         err = map_block_killer(section_exports, "empty-section");
       }
 
       if (err)
@@ -2440,10 +2820,12 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
          err = map(url_exports, "urlid", 1, buf, 1);
 
          snprintf(buf, 50, "%d", url_1_2);
-         err = err || map(url_exports, "url-1-2", 1, buf, 1);
+         if (!err) err = map(url_exports, "url-1-2", 1, buf, 1);
 
-         err = err || map(url_exports, "url", 1, 
-                          html_encode(cur_line->unprocessed), 0);
+         if (!err) err = map(url_exports, "url-html", 1,
+                             html_encode(cur_line->unprocessed), 0);
+         if (!err) err = map(url_exports, "url", 1,
+                             url_encode(cur_line->unprocessed), 0);
 
          if (err)
          {
@@ -2471,9 +2853,10 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
             return JB_ERR_MEMORY;
          }
 
-         err =        template_fill(&s, section_exports);
-         err = err || template_fill(&s, url_exports);
-         err = err || string_append(&urls, s);
+         err = template_fill(&s, section_exports);
+         if (!err) err = template_fill(&s, url_exports);
+         if (!err) err = string_append(&urls, s);
+
          free_map(url_exports);
          freez(s);
 
@@ -2522,7 +2905,8 @@ jb_err cgi_edit_actions_list(struct client_state *csp,
       }
 
       err = template_fill(&s, section_exports);
-      err = err || string_append(&sections, s);
+      if (!err) err = string_append(&sections, s);
+
       freez(s);
       free_map(section_exports);
 
@@ -2626,10 +3010,10 @@ jb_err cgi_edit_actions_for_url(struct client_state *csp,
    }
 
    err = map(exports, "filename", 1, file->identifier, 1);
-   err = err || map(exports, "ver", 1, file->version_str, 1);
-   err = err || map(exports, "section", 1, lookup(parameters, "section"), 1);
+   if (!err) err = map(exports, "ver", 1, file->version_str, 1);
+   if (!err) err = map(exports, "section", 1, lookup(parameters, "section"), 1);
 
-   err = err || actions_to_radio(exports, cur_line->data.action);
+   if (!err) err = actions_to_radio(exports, cur_line->data.action);
 
    edit_free_file(file);
 
@@ -2666,13 +3050,13 @@ jb_err cgi_edit_actions_submit(struct client_state *csp,
                                struct http_response *rsp,
                                const struct map *parameters)
 {
-   int sectionid;
+   unsigned sectionid;
    char * actiontext;
    char * newtext;
    int len;
    struct editable_file * file;
    struct file_line * cur_line;
-   int line_number;
+   unsigned line_number;
    char * target;
    jb_err err;
 
@@ -2761,7 +3145,7 @@ jb_err cgi_edit_actions_submit(struct client_state *csp,
       return err;
    }
 
-   target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
+   target = strdup(CGI_PREFIX "edit-actions-list?filename=");
    string_append(&target, file->identifier);
 
    edit_free_file(file);
@@ -2830,7 +3214,12 @@ jb_err cgi_edit_actions_url(struct client_state *csp,
    }
 
    err = get_number_param(csp, parameters, "section", &sectionid);
-   err = err || get_number_param(csp, parameters, "pattern", &patternid);
+   if (err)
+   {
+      return err;
+   }
+
+   err = get_number_param(csp, parameters, "pattern", &patternid);
    if (err)
    {
       return err;
@@ -2910,7 +3299,7 @@ jb_err cgi_edit_actions_url(struct client_state *csp,
       return err;
    }
 
-   target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
+   target = strdup(CGI_PREFIX "edit-actions-list?filename=");
    string_append(&target, file->identifier);
 
    edit_free_file(file);
@@ -2963,7 +3352,6 @@ jb_err cgi_edit_actions_add_url(struct client_state *csp,
                                 const struct map *parameters)
 {
    unsigned sectionid;
-   unsigned patternid;
    const char * newval;
    char * new_pattern;
    struct file_line * new_line;
@@ -2972,6 +3360,7 @@ jb_err cgi_edit_actions_add_url(struct client_state *csp,
    unsigned line_number;
    char * target;
    jb_err err;
+   struct url_spec compiled[1];
 
    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
    {
@@ -2986,11 +3375,19 @@ jb_err cgi_edit_actions_add_url(struct client_state *csp,
 
    newval = lookup(parameters, "newval");
 
-   if ((*newval == '\0') || (sectionid < 1U) || (patternid < 1U))
+   if ((*newval == '\0') || (sectionid < 1U))
    {
       return JB_ERR_CGI_PARAMS;
    }
 
+   /* Check that regex is valid */
+   err = create_url_spec(compiled, newval);
+   if (err)
+   {
+      return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
+   }
+   free_url_spec(compiled);
+
    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
    if (err)
    {
@@ -3053,7 +3450,7 @@ jb_err cgi_edit_actions_add_url(struct client_state *csp,
       return err;
    }
 
-   target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
+   target = strdup(CGI_PREFIX "edit-actions-list?filename=");
    string_append(&target, file->identifier);
 
    edit_free_file(file);
@@ -3120,7 +3517,12 @@ jb_err cgi_edit_actions_remove_url(struct client_state *csp,
    }
 
    err = get_number_param(csp, parameters, "section", &sectionid);
-   err = err || get_number_param(csp, parameters, "pattern", &patternid);
+   if (err)
+   {
+      return err;
+   }
+
+   err = get_number_param(csp, parameters, "pattern", &patternid);
    if (err)
    {
       return err;
@@ -3196,7 +3598,7 @@ jb_err cgi_edit_actions_remove_url(struct client_state *csp,
       return err;
    }
 
-   target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
+   target = strdup(CGI_PREFIX "edit-actions-list?filename=");
    string_append(&target, file->identifier);
 
    edit_free_file(file);
@@ -3329,7 +3731,7 @@ jb_err cgi_edit_actions_section_remove(struct client_state *csp,
       return err;
    }
 
-   target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
+   target = strdup(CGI_PREFIX "edit-actions-list?filename=");
    string_append(&target, file->identifier);
 
    edit_free_file(file);
@@ -3503,7 +3905,7 @@ jb_err cgi_edit_actions_section_add(struct client_state *csp,
       return err;
    }
 
-   target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
+   target = strdup(CGI_PREFIX "edit-actions-list?filename=");
    string_append(&target, file->identifier);
 
    edit_free_file(file);
@@ -3602,6 +4004,8 @@ jb_err cgi_toggle(struct client_state *csp,
 
    return template_fill_for_cgi(csp, template_name, exports, rsp);
 }
+
+
 #endif /* def FEATURE_CGI_EDIT_ACTIONS */