Synced with the stable branch:
[privoxy.git] / src / cgiedit.c
1 const char cgiedit_rcs[] = "$Id: cgiedit.c,v 2.0 2002/06/04 14:34:21 jongfoster Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa//current/src/cgiedit.c,v $
5  *
6  * Purpose     :  CGI-based actionsfile editor.
7  *
8  *                Functions declared include: cgi_edit_*
9  *
10  *                NOTE: The CGIs in this file use parameter names
11  *                such as "f" and "s" which are really *BAD* choices.
12  *                However, I'm trying to save bytes in the
13  *                edit-actions-list HTML page - the standard actions
14  *                file generated a 550kbyte page, which is ridiculous.
15  *
16  *                Stick to the short names in this file for consistency.
17  *
18  * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
19  *                Privoxy team. http://www.privoxy.org/
20  *
21  *                Based on the Internet Junkbuster originally written
22  *                by and Copyright (C) 1997 Anonymous Coders and
23  *                Junkbusters Corporation.  http://www.junkbusters.com
24  *
25  *                This program is free software; you can redistribute it
26  *                and/or modify it under the terms of the GNU General
27  *                Public License as published by the Free Software
28  *                Foundation; either version 2 of the License, or (at
29  *                your option) any later version.
30  *
31  *                This program is distributed in the hope that it will
32  *                be useful, but WITHOUT ANY WARRANTY; without even the
33  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
34  *                PARTICULAR PURPOSE.  See the GNU General Public
35  *                License for more details.
36  *
37  *                The GNU General Public License should be included with
38  *                this file.  If not, you can view it at
39  *                http://www.gnu.org/copyleft/gpl.html
40  *                or write to the Free Software Foundation, Inc., 59
41  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
42  *
43  * Revisions   :
44  *    $Log: cgiedit.c,v $
45  *    Revision 2.0  2002/06/04 14:34:21  jongfoster
46  *    Moving source files to src/
47  *
48  *    Revision 1.41  2002/05/21 19:09:45  oes
49  *     - Made Add/Edit/Remove URL Submit and Cancel
50  *       buttons jump back to relevant section in eal
51  *     - Bugfix: remove-url-form needs p export
52  *
53  *    Revision 1.40  2002/05/19 11:34:35  jongfoster
54  *    Handling read-only actions files better - report the actual
55  *    error, not "Out of memory"!
56  *
57  *    Bug report:
58  *    http://sourceforge.net/tracker/index.php?func=detail
59  *    &aid=557905&group_id=11118&atid=111118
60  *
61  *    Revision 1.39  2002/05/12 21:39:15  jongfoster
62  *    - Adding Doxygen-style comments to structures and #defines.
63  *    - Correcting function comments
64  *
65  *    Revision 1.38  2002/05/03 23:00:38  jongfoster
66  *    Support for templates for "standard actions" buttons.
67  *    See bug #549871
68  *
69  *    Revision 1.37  2002/04/30 11:14:52  oes
70  *    Made csp the first parameter in *action_to_html
71  *
72  *    Revision 1.36  2002/04/26 21:53:30  jongfoster
73  *    Fixing a memory leak.  (Near, but not caused by, my earlier commit).
74  *
75  *    Revision 1.35  2002/04/26 21:50:02  jongfoster
76  *    Honouring default exports in edit-actions-for-url-filter template.
77  *
78  *    Revision 1.34  2002/04/26 12:54:17  oes
79  *    Adaptions to changes in actions.c
80  *
81  *    Revision 1.33  2002/04/24 02:17:47  oes
82  *     - Moved get_char_param, get_string_param and get_number_param to cgi.c
83  *     - Comments
84  *     - Activated Jon's code for editing multiple AFs
85  *     - cgi_edit_list_actions now provides context-sensitive
86  *       help, looks up all action sets from standard.action and
87  *       makes buttons for them in the catchall section
88  *     - cgi_edit_action_submit now honors a p parameter, looks up
89  *       the corresponding action set, and sets the catchall pattern's
90  *       actions accordingly.
91  *
92  *    Revision 1.32  2002/04/19 16:55:31  jongfoster
93  *    Fixing newline problems.  If we do our own text file newline
94  *    mangling, we don't want the library to do any, so we need to
95  *    open the files in *binary* mode.
96  *
97  *    Revision 1.31  2002/04/18 19:21:08  jongfoster
98  *    Added code to detect "conventional" action files, that start
99  *    with a set of actions for all URLs (the pattern "/").
100  *    These are special-cased in the "edit-actions-list" CGI, so
101  *    that a special UI can be written for them.
102  *
103  *    Revision 1.30  2002/04/10 13:38:35  oes
104  *    load_template signature changed
105  *
106  *    Revision 1.29  2002/04/08 16:59:08  oes
107  *    Fixed comment
108  *
109  *    Revision 1.28  2002/03/27 12:30:29  oes
110  *    Deleted unsused variable
111  *
112  *    Revision 1.27  2002/03/26 23:06:04  jongfoster
113  *    Removing duplicate @ifs on the toggle page
114  *
115  *    Revision 1.26  2002/03/26 22:59:17  jongfoster
116  *    Fixing /toggle to display status consistently.
117  *
118  *    Revision 1.25  2002/03/26 22:29:54  swa
119  *    we have a new homepage!
120  *
121  *    Revision 1.24  2002/03/24 15:23:33  jongfoster
122  *    Name changes
123  *
124  *    Revision 1.23  2002/03/24 13:32:41  swa
125  *    name change related issues
126  *
127  *    Revision 1.22  2002/03/24 13:25:43  swa
128  *    name change related issues
129  *
130  *    Revision 1.21  2002/03/22 18:02:48  jongfoster
131  *    Fixing remote toggle
132  *
133  *    Revision 1.20  2002/03/16 20:28:34  oes
134  *    Added descriptions to the filters so users will know what they select in the cgi editor
135  *
136  *    Revision 1.19  2002/03/16 18:38:14  jongfoster
137  *    Stopping stupid or malicious users from breaking the actions
138  *    file using the web-based editor.
139  *
140  *    Revision 1.18  2002/03/16 14:57:44  jongfoster
141  *    Full support for enabling/disabling modular filters.
142  *
143  *    Revision 1.17  2002/03/16 14:26:42  jongfoster
144  *    First version of modular filters support - READ ONLY!
145  *    Fixing a double-free bug in the out-of-memory handling in map_radio().
146  *
147  *    Revision 1.16  2002/03/07 03:46:17  oes
148  *    Fixed compiler warnings
149  *
150  *    Revision 1.15  2002/03/06 22:54:35  jongfoster
151  *    Automated function-comment nitpicking.
152  *
153  *    Revision 1.14  2002/03/05 00:24:51  jongfoster
154  *    Patch to always edit the current actions file.
155  *
156  *    Revision 1.13  2002/03/04 02:07:59  david__schmidt
157  *    Enable web editing of actions file on OS/2 (it had been broken all this time!)
158  *
159  *    Revision 1.12  2002/03/03 09:18:03  joergs
160  *    Made jumbjuster work on AmigaOS again.
161  *
162  *    Revision 1.11  2002/01/23 01:03:31  jongfoster
163  *    Fixing gcc [CygWin] compiler warnings
164  *
165  *    Revision 1.10  2002/01/23 00:22:59  jongfoster
166  *    Adding new function cgi_edit_actions_section_swap(), to reorder
167  *    the actions file.
168  *
169  *    Adding get_url_spec_param() to get a validated URL pattern.
170  *
171  *    Moving edit_read_line() out of this file and into loaders.c.
172  *
173  *    Adding missing html_encode() to many CGI functions.
174  *
175  *    Moving the functions that #include actionlist.h to the end of the file,
176  *    because the Visual C++ 97 debugger gets extremely confused if you try
177  *    to debug any code that comes after them in the file.
178  *
179  *    Major optimizations in cgi_edit_actions_list() to reduce the size of
180  *    the generated HTML (down 40% from 550k to 304k), with major side-effects
181  *    throughout the editor and templates.  In particular, the length of the
182  *    URLs throughout the editor has been drastically reduced, by cutting
183  *    paramater names down to 1 character and CGI names down to 3-4
184  *    characters, by removing all non-essential CGI paramaters even at the
185  *    expense of having to re-read the actions file for the most trivial
186  *    page, and by using relative rather than absolute URLs.  This means
187  *    that this (typical example):
188  *
189  *    <a href="http://ijbswa.sourceforge.net/config/edit-actions-url-form?
190  *    filename=ijb&amp;ver=1011487572&amp;section=12&amp;pattern=13
191  *    &amp;oldval=www.oesterhelt.org%2Fdeanimate-demo">
192  *
193  *    is now this:
194  *
195  *    <a href="eau?f=ijb&amp;v=1011487572&amp;p=13">
196  *
197  *    Revision 1.9  2002/01/17 20:56:22  jongfoster
198  *    Replacing hard references to the URL of the config interface
199  *    with #defines from project.h
200  *
201  *    Revision 1.8  2001/11/30 23:35:51  jongfoster
202  *    Renaming actionsfile to ijb.action
203  *
204  *    Revision 1.7  2001/11/13 00:28:24  jongfoster
205  *    - Renaming parameters from edit-actions-for-url so that they only
206  *      contain legal JavaScript characters.  If we wanted to write
207  *      JavaScript that worked with Netscape 4, this is nessacery.
208  *      (Note that at the moment the JavaScript doesn't actually work
209  *      with Netscape 4, but now this is purely a template issue, not
210  *      one affecting code).
211  *    - Adding new CGIs for use by non-JavaScript browsers:
212  *        edit-actions-url-form
213  *        edit-actions-add-url-form
214  *        edit-actions-remove-url-form
215  *    - Fixing || bug.
216  *
217  *    Revision 1.6  2001/10/29 03:48:09  david__schmidt
218  *    OS/2 native needed a snprintf() routine.  Added one to miscutil, brackedted
219  *    by and __OS2__ ifdef.
220  *
221  *    Revision 1.5  2001/10/25 03:40:48  david__schmidt
222  *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
223  *    threads to call select() simultaneously.  So, it's time to do a real, live,
224  *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
225  *    (native). Both versions will work, but using __OS2__ offers multi-threading.
226  *
227  *    Revision 1.4  2001/10/23 21:48:19  jongfoster
228  *    Cleaning up error handling in CGI functions - they now send back
229  *    a HTML error page and should never cause a FATAL error.  (Fixes one
230  *    potential source of "denial of service" attacks).
231  *
232  *    CGI actions file editor that works and is actually useful.
233  *
234  *    Ability to toggle JunkBuster remotely using a CGI call.
235  *
236  *    You can turn off both the above features in the main configuration
237  *    file, e.g. if you are running a multi-user proxy.
238  *
239  *    Revision 1.3  2001/10/14 22:12:49  jongfoster
240  *    New version of CGI-based actionsfile editor.
241  *    Major changes, including:
242  *    - Completely new file parser and file output routines
243  *    - edit-actions CGI renamed edit-actions-for-url
244  *    - All CGIs now need a filename parameter, except for...
245  *    - New CGI edit-actions which doesn't need a filename,
246  *      to allow you to start the editor up.
247  *    - edit-actions-submit now works, and now automatically
248  *      redirects you back to the main edit-actions-list handler.
249  *
250  *    Revision 1.2  2001/09/16 17:05:14  jongfoster
251  *    Removing unused #include showarg.h
252  *
253  *    Revision 1.1  2001/09/16 15:47:37  jongfoster
254  *    First version of CGI-based edit interface.  This is very much a
255  *    work-in-progress, and you can't actually use it to edit anything
256  *    yet.  You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
257  *    to have any effect.
258  *
259  *
260  **********************************************************************/
261 \f
262
263 #include "config.h"
264
265 /*
266  * FIXME: Following includes copied from cgi.c - which are actually needed?
267  */
268
269 #include <stdio.h>
270 #include <stdlib.h>
271 #include <sys/types.h>
272 #include <ctype.h>
273 #include <string.h>
274 #include <assert.h>
275 #include <sys/stat.h>
276
277 #ifdef _WIN32
278 #define snprintf _snprintf
279 #endif /* def _WIN32 */
280
281 #include "project.h"
282 #include "cgi.h"
283 #include "cgiedit.h"
284 #include "cgisimple.h"
285 #include "list.h"
286 #include "encode.h"
287 #include "actions.h"
288 #include "miscutil.h"
289 #include "errlog.h"
290 #include "loaders.h"
291 #include "loadcfg.h"
292 /* loadcfg.h is for g_bToggleIJB only */
293 #include "urlmatch.h"
294
295 const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION;
296
297
298 #ifdef FEATURE_CGI_EDIT_ACTIONS
299
300 /**
301  * A line in an editable_file.
302  */
303 struct file_line
304 {
305    /** Next entry in the linked list */
306    struct file_line * next;
307    
308    /** The raw data, to write out if this line is unmodified. */
309    char * raw;
310    
311    /** Comments and/or whitespace to put before this line if it's modified
312        and then written out. */
313    char * prefix;
314
315    /** The actual data, as a string.  Line continuation and comment removal
316        are performed on the data read from file before it's stored here, so
317        it will be a single line of data.  */
318    char * unprocessed;
319    
320    /** The type of data on this line.  One of the FILE_LINE_xxx constants. */
321    int type;
322
323    /** The actual data, processed into some sensible data type. */
324    union
325    {
326
327       /** An action specification. */
328       struct action_spec action[1];
329
330       /** A name=value pair. */
331       struct
332       {
333
334          /** The name in the name=value pair. */
335          char * name;
336
337          /** The value in the name=value pair, as a string. */
338          char * svalue;
339
340          /** The value in the name=value pair, as an integer. */
341          int ivalue;
342
343       } setting;
344
345       /* Add more data types here... e.g.
346
347
348       struct url_spec url[1];
349
350       struct
351       {
352          struct action_spec action[1];
353          const char * name;
354       } alias;
355
356       */
357
358    } data;
359
360 };
361
362 /** This file_line has not been processed yet. */
363 #define FILE_LINE_UNPROCESSED           1
364
365 /** This file_line is blank. Can only appear at the end of a file, due to
366     the way the parser works. */
367 #define FILE_LINE_BLANK                 2
368
369 /** This file_line says {{alias}}. */
370 #define FILE_LINE_ALIAS_HEADER          3
371
372 /** This file_line defines an alias. */
373 #define FILE_LINE_ALIAS_ENTRY           4
374
375 /** This file_line defines an {action}. */
376 #define FILE_LINE_ACTION                5
377
378 /** This file_line specifies a URL pattern. */
379 #define FILE_LINE_URL                   6
380
381 /** This file_line says {{settings}}. */
382 #define FILE_LINE_SETTINGS_HEADER       7
383
384 /** This file_line is in a {{settings}} block. */
385 #define FILE_LINE_SETTINGS_ENTRY        8
386
387 /** This file_line says {{description}}. */
388 #define FILE_LINE_DESCRIPTION_HEADER    9
389
390 /** This file_line is in a {{description}} block. */
391 #define FILE_LINE_DESCRIPTION_ENTRY    10
392
393
394 /**
395  * A configuration file, in a format that can be edited and written back to
396  * disk.
397  */
398 struct editable_file
399 {
400    struct file_line * lines;  /**< The contents of the file.  A linked list of lines. */
401    const char * filename;     /**< Full pathname - e.g. "/etc/privoxy/wibble.action". */
402    const char * identifier;   /**< Filename stub - e.g. "wibble".  Use for CGI param. */
403                               /**< Pre-encoded with url_encode() for ease of use. */
404    const char * version_str;  /**< Last modification time, as a string.  For CGI param. */
405                               /**< Can be used in URL without using url_param(). */
406    unsigned version;          /**< Last modification time - prevents chaos with
407                                    the browser's "back" button.  Note that this is a
408                                    time_t cast to an unsigned.  When comparing, always
409                                    cast the time_t to an unsigned, and *NOT* vice-versa.
410                                    This may lose the top few bits, but they're not
411                                    significant anyway. */
412    int newline;               /**< Newline convention - one of the NEWLINE_xxx constants.
413                                    Note that changing this after the file has been
414                                    read in will cause a mess. */
415    struct file_line * parse_error; /**< On parse error, this is the offending line. */
416    const char * parse_error_text;  /**< On parse error, this is the problem.
417                                         (Statically allocated) */
418 };
419
420 /* FIXME: Following non-static functions should be prototyped in .h or made static */
421
422 /* Functions to read and write arbitrary config files */
423 jb_err edit_read_file(struct client_state *csp,
424                       const struct map *parameters,
425                       int require_version,
426                       const char *suffix,
427                       struct editable_file **pfile);
428 jb_err edit_write_file(struct editable_file * file);
429 void   edit_free_file(struct editable_file * file);
430
431 /* Functions to read and write actions files */
432 jb_err edit_parse_actions_file(struct editable_file * file);
433 jb_err edit_read_actions_file(struct client_state *csp,
434                               struct http_response *rsp,
435                               const struct map *parameters,
436                               int require_version,
437                               struct editable_file **pfile);
438
439 /* Error handlers */
440 jb_err cgi_error_modified(struct client_state *csp,
441                           struct http_response *rsp,
442                           const char *filename);
443 jb_err cgi_error_parse(struct client_state *csp,
444                        struct http_response *rsp,
445                        struct editable_file *file);
446 jb_err cgi_error_file(struct client_state *csp,
447                       struct http_response *rsp,
448                       const char *filename);
449 jb_err cgi_error_file_read_only(struct client_state *csp,
450                                 struct http_response *rsp,
451                                 const char *filename);
452 jb_err cgi_error_disabled(struct client_state *csp,
453                           struct http_response *rsp);
454
455 /* Internal arbitrary config file support functions */
456 static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline);
457 static void edit_free_file_lines(struct file_line * first_line);
458
459 /* Internal actions file support functions */
460 static int match_actions_file_header_line(const char * line, const char * name);
461 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue);
462
463 /* Internal parameter parsing functions */
464 static jb_err get_file_name_param(struct client_state *csp,
465                                   const struct map *parameters,
466                                   const char *param_name,
467                                   const char *suffix,
468                                   char **pfilename,
469                                   const char **pparam);
470
471 static jb_err get_url_spec_param(struct client_state *csp,
472                                  const struct map *parameters,
473                                  const char *name,
474                                  char **pvalue);
475
476
477 /* Internal actionsfile <==> HTML conversion functions */
478 static jb_err map_radio(struct map * exports,
479                         const char * optionname,
480                         const char * values,
481                         int value);
482 static jb_err actions_to_radio(struct map * exports,
483                                const struct action_spec *action);
484 static jb_err actions_from_radio(const struct map * parameters,
485                                  struct action_spec *action);
486
487
488 static jb_err map_copy_parameter_html(struct map *out,
489                                       const struct map *in,
490                                       const char *name);
491 #if 0 /* unused function */
492 static jb_err map_copy_parameter_url(struct map *out,
493                                      const struct map *in,
494                                      const char *name);
495 #endif /* unused function */
496
497 /* Internal convenience functions */
498 static char *section_target(const unsigned sectionid);
499
500 /*********************************************************************
501  *
502  * Function    :  section_target
503  *
504  * Description :  Given an unsigned (section id) n, produce a dynamically
505  *                allocated string of the form #l<n>, for use in link
506  *                targets.
507  *
508  * Parameters  :
509  *          1  :  sectionid = start line number of section
510  *
511  * Returns     :  String with link target, or NULL if out of
512  *                memory
513  *
514  *********************************************************************/
515 static char *section_target(const unsigned sectionid)
516 {
517    char buf[30];
518
519    snprintf(buf, 30, "#l%d", sectionid);
520    return(strdup(buf));
521
522 }
523
524
525 /*********************************************************************
526  *
527  * Function    :  map_copy_parameter_html
528  *
529  * Description :  Copy a CGI parameter from one map to another, HTML
530  *                encoding it.
531  *
532  * Parameters  :
533  *          1  :  out = target map
534  *          2  :  in = source map
535  *          3  :  name = name of cgi parameter to copy
536  *
537  * Returns     :  JB_ERR_OK on success
538  *                JB_ERR_MEMORY on out-of-memory
539  *                JB_ERR_CGI_PARAMS if the parameter doesn't exist
540  *                                  in the source map
541  *
542  *********************************************************************/
543 static jb_err map_copy_parameter_html(struct map *out,
544                                       const struct map *in,
545                                       const char *name)
546 {
547    const char * value;
548    jb_err err;
549
550    assert(out);
551    assert(in);
552    assert(name);
553
554    value = lookup(in, name);
555    err = map(out, name, 1, html_encode(value), 0);
556
557    if (err)
558    {
559       /* Out of memory */
560       return err;
561    }
562    else if (*value == '\0')
563    {
564       return JB_ERR_CGI_PARAMS;
565    }
566    else
567    {
568       return JB_ERR_OK;
569    }
570 }
571
572
573 #if 0 /* unused function */
574 /*********************************************************************
575  *
576  * Function    :  map_copy_parameter_url
577  *
578  * Description :  Copy a CGI parameter from one map to another, URL
579  *                encoding it.
580  *
581  * Parameters  :
582  *          1  :  out = target map
583  *          2  :  in = source map
584  *          3  :  name = name of cgi parameter to copy
585  *
586  * Returns     :  JB_ERR_OK on success
587  *                JB_ERR_MEMORY on out-of-memory
588  *                JB_ERR_CGI_PARAMS if the parameter doesn't exist
589  *                                  in the source map
590  *
591  *********************************************************************/
592 static jb_err map_copy_parameter_url(struct map *out,
593                                      const struct map *in,
594                                      const char *name)
595 {
596    const char * value;
597    jb_err err;
598
599    assert(out);
600    assert(in);
601    assert(name);
602
603    value = lookup(in, name);
604    err = map(out, name, 1, url_encode(value), 0);
605
606    if (err)
607    {
608       /* Out of memory */
609       return err;
610    }
611    else if (*value == '\0')
612    {
613       return JB_ERR_CGI_PARAMS;
614    }
615    else
616    {
617       return JB_ERR_OK;
618    }
619 }
620 #endif /* 0 - unused function */
621
622 /*********************************************************************
623  *
624  * Function    :  cgi_edit_actions_url_form
625  *
626  * Description :  CGI function that displays a form for
627  *                edit-actions-url
628  *
629  * Parameters  :
630  *          1  :  csp = Current client state (buffers, headers, etc...)
631  *          2  :  rsp = http_response data structure for output
632  *          3  :  parameters = map of cgi parameters
633  *
634  * CGI Parameters
635  *           f : (filename) Identifies the file to edit
636  *           v : (version) File's last-modified time
637  *           p : (pattern) Line number of pattern to edit
638  *
639  * Returns     :  JB_ERR_OK on success
640  *                JB_ERR_MEMORY on out-of-memory
641  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
642  *                                  specified or not valid.
643  *
644  *********************************************************************/
645 jb_err cgi_edit_actions_url_form(struct client_state *csp,
646                                  struct http_response *rsp,
647                                  const struct map *parameters)
648 {
649    struct map * exports;
650    unsigned patternid;
651    struct editable_file * file;
652    struct file_line * cur_line;
653    unsigned line_number;
654    unsigned section_start_line_number = 0;
655    jb_err err;
656
657    assert(csp);
658    assert(rsp);
659    assert(parameters);
660
661    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
662    {
663       return cgi_error_disabled(csp, rsp);
664    }
665
666    err = get_number_param(csp, parameters, "p", &patternid);
667    if (err)
668    {
669       return err;
670    }
671
672    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
673    if (err)
674    {
675       /* No filename specified, can't read file, modified, or out of memory. */
676       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
677    }
678
679    cur_line = file->lines;
680
681    for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
682    {
683       if (cur_line->type == FILE_LINE_ACTION)
684       {
685          section_start_line_number = line_number;
686       }
687       cur_line = cur_line->next;
688    }
689
690    if ( (cur_line == NULL)
691      || (line_number != patternid)
692      || (patternid < 1)
693      || (cur_line->type != FILE_LINE_URL))
694    {
695       /* Invalid "patternid" parameter */
696       edit_free_file(file);
697       return JB_ERR_CGI_PARAMS;
698    }
699
700    if (NULL == (exports = default_exports(csp, NULL)))
701    {
702       edit_free_file(file);
703       return JB_ERR_MEMORY;
704    }
705
706    err = map(exports, "f", 1, file->identifier, 1);
707    if (!err) err = map(exports, "v", 1, file->version_str, 1);
708    if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
709    if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
710    if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0);
711
712    edit_free_file(file);
713
714    if (err)
715    {
716       free_map(exports);
717       return err;
718    }
719
720    return template_fill_for_cgi(csp, "edit-actions-url-form", exports, rsp);
721 }
722
723
724 /*********************************************************************
725  *
726  * Function    :  cgi_edit_actions_add_url_form
727  *
728  * Description :  CGI function that displays a form for
729  *                edit-actions-url
730  *
731  * Parameters  :
732  *          1  :  csp = Current client state (buffers, headers, etc...)
733  *          2  :  rsp = http_response data structure for output
734  *          3  :  parameters = map of cgi parameters
735  *
736  * CGI Parameters :
737  *           f : (filename) Identifies the file to edit
738  *           v : (version) File's last-modified time
739  *           s : (section) Line number of section to edit
740  *
741  * Returns     :  JB_ERR_OK on success
742  *                JB_ERR_MEMORY on out-of-memory
743  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
744  *                                  specified or not valid.
745  *
746  *********************************************************************/
747 jb_err cgi_edit_actions_add_url_form(struct client_state *csp,
748                                      struct http_response *rsp,
749                                      const struct map *parameters)
750 {
751    struct map *exports;
752    jb_err err;
753
754    assert(csp);
755    assert(rsp);
756    assert(parameters);
757
758    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
759    {
760       return cgi_error_disabled(csp, rsp);
761    }
762
763    if (NULL == (exports = default_exports(csp, NULL)))
764    {
765       return JB_ERR_MEMORY;
766    }
767
768    err = map_copy_parameter_html(exports, parameters, "f");
769    if (!err) err = map_copy_parameter_html(exports, parameters, "v");
770    if (!err) err = map_copy_parameter_html(exports, parameters, "s");
771
772    if (err)
773    {
774       free_map(exports);
775       return err;
776    }
777
778    return template_fill_for_cgi(csp, "edit-actions-add-url-form", exports, rsp);
779 }
780
781
782 /*********************************************************************
783  *
784  * Function    :  cgi_edit_actions_remove_url_form
785  *
786  * Description :  CGI function that displays a form for
787  *                edit-actions-url
788  *
789  * Parameters  :
790  *          1  :  csp = Current client state (buffers, headers, etc...)
791  *          2  :  rsp = http_response data structure for output
792  *          3  :  parameters = map of cgi parameters
793  *
794  * CGI Parameters :
795  *           f : (filename) Identifies the file to edit
796  *           v : (version) File's last-modified time
797  *           p : (pattern) Line number of pattern to edit
798  *
799  * Returns     :  JB_ERR_OK on success
800  *                JB_ERR_MEMORY on out-of-memory
801  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
802  *                                  specified or not valid.
803  *
804  *********************************************************************/
805 jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
806                                      struct http_response *rsp,
807                                      const struct map *parameters)
808 {
809    struct map * exports;
810    unsigned patternid;
811    struct editable_file * file;
812    struct file_line * cur_line;
813    unsigned line_number;
814    unsigned section_start_line_number = 0;
815    jb_err err;
816
817    assert(csp);
818    assert(rsp);
819    assert(parameters);
820
821    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
822    {
823       return cgi_error_disabled(csp, rsp);
824    }
825
826    err = get_number_param(csp, parameters, "p", &patternid);
827    if (err)
828    {
829       return err;
830    }
831
832    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
833    if (err)
834    {
835       /* No filename specified, can't read file, modified, or out of memory. */
836       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
837    }
838
839    cur_line = file->lines;
840
841    for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
842    {
843       if (cur_line->type == FILE_LINE_ACTION)
844       {
845          section_start_line_number = line_number;
846       }      
847       cur_line = cur_line->next;
848    }
849
850    if ( (cur_line == NULL)
851      || (line_number != patternid)
852      || (patternid < 1)
853      || (cur_line->type != FILE_LINE_URL))
854    {
855       /* Invalid "patternid" parameter */
856       edit_free_file(file);
857       return JB_ERR_CGI_PARAMS;
858    }
859
860    if (NULL == (exports = default_exports(csp, NULL)))
861    {
862       edit_free_file(file);
863       return JB_ERR_MEMORY;
864    }
865
866    err = map(exports, "f", 1, file->identifier, 1);
867    if (!err) err = map(exports, "v", 1, file->version_str, 1);
868    if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
869    if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
870    if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0);
871
872    edit_free_file(file);
873
874    if (err)
875    {
876       free_map(exports);
877       return err;
878    }
879
880    return template_fill_for_cgi(csp, "edit-actions-remove-url-form", exports, rsp);
881 }
882
883
884 /*********************************************************************
885  *
886  * Function    :  edit_write_file
887  *
888  * Description :  Write a complete file to disk.
889  *
890  * Parameters  :
891  *          1  :  file = File to write.
892  *
893  * Returns     :  JB_ERR_OK     on success
894  *                JB_ERR_FILE   on error writing to file.
895  *                JB_ERR_MEMORY on out of memory
896  *
897  *********************************************************************/
898 jb_err edit_write_file(struct editable_file * file)
899 {
900    FILE * fp;
901    struct file_line * cur_line;
902    struct stat statbuf[1];
903    char version_buf[22]; /* 22 = ceil(log10(2^64)) + 2 = max number of
904                             digits in time_t, assuming this is a 64-bit
905                             machine, plus null terminator, plus one
906                             for paranoia */
907
908    assert(file);
909    assert(file->filename);
910
911    if (NULL == (fp = fopen(file->filename, "wb")))
912    {
913       return JB_ERR_FILE;
914    }
915
916    cur_line = file->lines;
917    while (cur_line != NULL)
918    {
919       if (cur_line->raw)
920       {
921          if (fputs(cur_line->raw, fp) < 0)
922          {
923             fclose(fp);
924             return JB_ERR_FILE;
925          }
926       }
927       else
928       {
929          if (cur_line->prefix)
930          {
931             if (fputs(cur_line->prefix, fp) < 0)
932             {
933                fclose(fp);
934                return JB_ERR_FILE;
935             }
936          }
937          if (cur_line->unprocessed)
938          {
939
940             if (NULL != strchr(cur_line->unprocessed, '#'))
941             {
942                /* Must quote '#' characters */
943                int numhash = 0;
944                int len;
945                char * src;
946                char * dest;
947                char * str;
948
949                /* Count number of # characters, so we know length of output string */
950                src = cur_line->unprocessed;
951                while (NULL != (src = strchr(src, '#')))
952                {
953                   numhash++;
954                   src++;
955                }
956                assert(numhash > 0);
957
958                /* Allocate new memory for string */
959                len = strlen(cur_line->unprocessed);
960                if (NULL == (str = malloc((size_t) len + 1 + numhash)))
961                {
962                   /* Uh oh, just trashed file! */
963                   fclose(fp);
964                   return JB_ERR_MEMORY;
965                }
966
967                /* Loop through string from end */
968                src  = cur_line->unprocessed + len;
969                dest = str + len + numhash;
970                for ( ; len >= 0; len--)
971                {
972                   if ((*dest-- = *src--) == '#')
973                   {
974                      *dest-- = '\\';
975                      numhash--;
976                      assert(numhash >= 0);
977                   }
978                }
979                assert(numhash == 0);
980                assert(src  + 1 == cur_line->unprocessed);
981                assert(dest + 1 == str);
982
983                if (fputs(str, fp) < 0)
984                {
985                   free(str);
986                   fclose(fp);
987                   return JB_ERR_FILE;
988                }
989
990                free(str);
991             }
992             else
993             {
994                /* Can write without quoting '#' characters. */
995                if (fputs(cur_line->unprocessed, fp) < 0)
996                {
997                   fclose(fp);
998                   return JB_ERR_FILE;
999                }
1000             }
1001             if (fputs(NEWLINE(file->newline), fp) < 0)
1002             {
1003                fclose(fp);
1004                return JB_ERR_FILE;
1005             }
1006          }
1007          else
1008          {
1009             /* FIXME: Write data from file->data->whatever */
1010             assert(0);
1011          }
1012       }
1013       cur_line = cur_line->next;
1014    }
1015
1016    fclose(fp);
1017
1018
1019    /* Update the version stamp in the file structure, since we just
1020     * wrote to the file & changed it's date.
1021     */
1022    if (stat(file->filename, statbuf) < 0)
1023    {
1024       /* Error, probably file not found. */
1025       return JB_ERR_FILE;
1026    }
1027    file->version = (unsigned)statbuf->st_mtime;
1028
1029    /* Correct file->version_str */
1030    freez(file->version_str);
1031    snprintf(version_buf, 22, "%u", file->version);
1032    version_buf[21] = '\0';
1033    file->version_str = strdup(version_buf);
1034    if (version_buf == NULL)
1035    {
1036       return JB_ERR_MEMORY;
1037    }
1038
1039    return JB_ERR_OK;
1040 }
1041
1042
1043 /*********************************************************************
1044  *
1045  * Function    :  edit_free_file
1046  *
1047  * Description :  Free a complete file in memory.
1048  *
1049  * Parameters  :
1050  *          1  :  file = Data structure to free.
1051  *
1052  * Returns     :  N/A
1053  *
1054  *********************************************************************/
1055 void edit_free_file(struct editable_file * file)
1056 {
1057    if (!file)
1058    {
1059       /* Silently ignore NULL pointer */
1060       return;
1061    }
1062
1063    edit_free_file_lines(file->lines);
1064    freez(file->filename);
1065    freez(file->identifier);
1066    freez(file->version_str);
1067    file->version = 0;
1068    file->parse_error_text = NULL; /* Statically allocated */
1069    file->parse_error = NULL;
1070
1071    free(file);
1072 }
1073
1074
1075 /*********************************************************************
1076  *
1077  * Function    :  edit_free_file_lines
1078  *
1079  * Description :  Free an entire linked list of file lines.
1080  *
1081  * Parameters  :
1082  *          1  :  first_line = Data structure to free.
1083  *
1084  * Returns     :  N/A
1085  *
1086  *********************************************************************/
1087 static void edit_free_file_lines(struct file_line * first_line)
1088 {
1089    struct file_line * next_line;
1090
1091    while (first_line != NULL)
1092    {
1093       next_line = first_line->next;
1094       first_line->next = NULL;
1095       freez(first_line->raw);
1096       freez(first_line->prefix);
1097       freez(first_line->unprocessed);
1098       switch(first_line->type)
1099       {
1100          case 0: /* special case if memory zeroed */
1101          case FILE_LINE_UNPROCESSED:
1102          case FILE_LINE_BLANK:
1103          case FILE_LINE_ALIAS_HEADER:
1104          case FILE_LINE_SETTINGS_HEADER:
1105          case FILE_LINE_DESCRIPTION_HEADER:
1106          case FILE_LINE_DESCRIPTION_ENTRY:
1107          case FILE_LINE_ALIAS_ENTRY:
1108          case FILE_LINE_URL:
1109             /* No data is stored for these */
1110             break;
1111
1112          case FILE_LINE_ACTION:
1113             free_action(first_line->data.action);
1114             break;
1115
1116          case FILE_LINE_SETTINGS_ENTRY:
1117             freez(first_line->data.setting.name);
1118             freez(first_line->data.setting.svalue);
1119             break;
1120          default:
1121             /* Should never happen */
1122             assert(0);
1123             break;
1124       }
1125       first_line->type = 0; /* paranoia */
1126       free(first_line);
1127       first_line = next_line;
1128    }
1129 }
1130
1131
1132 /*********************************************************************
1133  *
1134  * Function    :  match_actions_file_header_line
1135  *
1136  * Description :  Match an actions file {{header}} line
1137  *
1138  * Parameters  :
1139  *          1  :  line = String from file
1140  *          2  :  name = Header to match against
1141  *
1142  * Returns     :  0 iff they match.
1143  *
1144  *********************************************************************/
1145 static int match_actions_file_header_line(const char * line, const char * name)
1146 {
1147    size_t len;
1148
1149    assert(line);
1150    assert(name);
1151
1152    /* Look for "{{" */
1153    if ((line[0] != '{') || (line[1] != '{'))
1154    {
1155       return 1;
1156    }
1157    line += 2;
1158
1159    /* Look for optional whitespace */
1160    while ( (*line == ' ') || (*line == '\t') )
1161    {
1162       line++;
1163    }
1164
1165    /* Look for the specified name (case-insensitive) */
1166    len = strlen(name);
1167    if (0 != strncmpic(line, name, len))
1168    {
1169       return 1;
1170    }
1171    line += len;
1172
1173    /* Look for optional whitespace */
1174    while ( (*line == ' ') || (*line == '\t') )
1175    {
1176       line++;
1177    }
1178
1179    /* Look for "}}" and end of string*/
1180    if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0'))
1181    {
1182       return 1;
1183    }
1184
1185    /* It matched!! */
1186    return 0;
1187 }
1188
1189
1190 /*********************************************************************
1191  *
1192  * Function    :  match_actions_file_header_line
1193  *
1194  * Description :  Match an actions file {{header}} line
1195  *
1196  * Parameters  :
1197  *          1  :  line = String from file.  Must not start with
1198  *                       whitespace (else infinite loop!)
1199  *          2  :  pname = Destination for name
1200  *          2  :  pvalue = Destination for value
1201  *
1202  * Returns     :  JB_ERR_OK     on success
1203  *                JB_ERR_MEMORY on out-of-memory
1204  *                JB_ERR_PARSE  if there's no "=" sign, or if there's
1205  *                              nothing before the "=" sign (but empty
1206  *                              values *after* the "=" sign are legal).
1207  *
1208  *********************************************************************/
1209 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue)
1210 {
1211    const char * name_end;
1212    const char * value_start;
1213    size_t name_len;
1214
1215    assert(line);
1216    assert(pname);
1217    assert(pvalue);
1218    assert(*line != ' ');
1219    assert(*line != '\t');
1220
1221    *pname = NULL;
1222    *pvalue = NULL;
1223
1224    value_start = strchr(line, '=');
1225    if ((value_start == NULL) || (value_start == line))
1226    {
1227       return JB_ERR_PARSE;
1228    }
1229
1230    name_end = value_start - 1;
1231
1232    /* Eat any whitespace before the '=' */
1233    while ((*name_end == ' ') || (*name_end == '\t'))
1234    {
1235       /*
1236        * we already know we must have at least 1 non-ws char
1237        * at start of buf - no need to check
1238        */
1239       name_end--;
1240    }
1241
1242    name_len = name_end - line + 1; /* Length excluding \0 */
1243    if (NULL == (*pname = (char *) malloc(name_len + 1)))
1244    {
1245       return JB_ERR_MEMORY;
1246    }
1247    strncpy(*pname, line, name_len);
1248    (*pname)[name_len] = '\0';
1249
1250    /* Eat any the whitespace after the '=' */
1251    value_start++;
1252    while ((*value_start == ' ') || (*value_start == '\t'))
1253    {
1254       value_start++;
1255    }
1256
1257    if (NULL == (*pvalue = strdup(value_start)))
1258    {
1259       free(*pname);
1260       *pname = NULL;
1261       return JB_ERR_MEMORY;
1262    }
1263
1264    return JB_ERR_OK;
1265 }
1266
1267
1268 /*********************************************************************
1269  *
1270  * Function    :  edit_parse_actions_file
1271  *
1272  * Description :  Parse an actions file in memory.
1273  *
1274  *                Passed linked list must have the "data" member
1275  *                zeroed, and must contain valid "next" and
1276  *                "unprocessed" fields.  The "raw" and "prefix"
1277  *                fields are ignored, and "type" is just overwritten.
1278  *
1279  *                Note that on error the file may have been
1280  *                partially parsed.
1281  *
1282  * Parameters  :
1283  *          1  :  file = Actions file to be parsed in-place.
1284  *
1285  * Returns     :  JB_ERR_OK     on success
1286  *                JB_ERR_MEMORY on out-of-memory
1287  *                JB_ERR_PARSE  on error
1288  *
1289  *********************************************************************/
1290 jb_err edit_parse_actions_file(struct editable_file * file)
1291 {
1292    struct file_line * cur_line;
1293    size_t len;
1294    const char * text; /* Text from a line */
1295    char * name;  /* For lines of the form name=value */
1296    char * value; /* For lines of the form name=value */
1297    struct action_alias * alias_list = NULL;
1298    jb_err err = JB_ERR_OK;
1299
1300    /* alias_list contains the aliases defined in this file.
1301     * It might be better to use the "file_line.data" fields
1302     * in the relavent places instead.
1303     */
1304
1305    cur_line = file->lines;
1306
1307    /* A note about blank line support: Blank lines should only
1308     * ever occur as the last line in the file.  This function
1309     * is more forgiving than that - FILE_LINE_BLANK can occur
1310     * anywhere.
1311     */
1312
1313    /* Skip leading blanks.  Should only happen if file is
1314     * empty (which is valid, but pointless).
1315     */
1316    while ( (cur_line != NULL)
1317         && (cur_line->unprocessed[0] == '\0') )
1318    {
1319       /* Blank line */
1320       cur_line->type = FILE_LINE_BLANK;
1321       cur_line = cur_line->next;
1322    }
1323
1324    if ( (cur_line != NULL)
1325      && (cur_line->unprocessed[0] != '{') )
1326    {
1327       /* File doesn't start with a header */
1328       file->parse_error = cur_line;
1329       file->parse_error_text = "First (non-comment) line of the file must contain a header.";
1330       return JB_ERR_PARSE;
1331    }
1332
1333    if ( (cur_line != NULL) && (0 ==
1334       match_actions_file_header_line(cur_line->unprocessed, "settings") ) )
1335    {
1336       cur_line->type = FILE_LINE_SETTINGS_HEADER;
1337
1338       cur_line = cur_line->next;
1339       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1340       {
1341          if (cur_line->unprocessed[0])
1342          {
1343             cur_line->type = FILE_LINE_SETTINGS_ENTRY;
1344
1345             err = split_line_on_equals(cur_line->unprocessed,
1346                      &cur_line->data.setting.name,
1347                      &cur_line->data.setting.svalue);
1348             if (err == JB_ERR_MEMORY)
1349             {
1350                return err;
1351             }
1352             else if (err != JB_ERR_OK)
1353             {
1354                /* Line does not contain a name=value pair */
1355                file->parse_error = cur_line;
1356                file->parse_error_text = "Expected a name=value pair on this {{description}} line, but couldn't find one.";
1357                return JB_ERR_PARSE;
1358             }
1359          }
1360          else
1361          {
1362             cur_line->type = FILE_LINE_BLANK;
1363          }
1364          cur_line = cur_line->next;
1365       }
1366    }
1367
1368    if ( (cur_line != NULL) && (0 ==
1369       match_actions_file_header_line(cur_line->unprocessed, "description") ) )
1370    {
1371       cur_line->type = FILE_LINE_DESCRIPTION_HEADER;
1372
1373       cur_line = cur_line->next;
1374       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1375       {
1376          if (cur_line->unprocessed[0])
1377          {
1378             cur_line->type = FILE_LINE_DESCRIPTION_ENTRY;
1379          }
1380          else
1381          {
1382             cur_line->type = FILE_LINE_BLANK;
1383          }
1384          cur_line = cur_line->next;
1385       }
1386    }
1387
1388    if ( (cur_line != NULL) && (0 ==
1389       match_actions_file_header_line(cur_line->unprocessed, "alias") ) )
1390    {
1391       cur_line->type = FILE_LINE_ALIAS_HEADER;
1392
1393       cur_line = cur_line->next;
1394       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1395       {
1396          if (cur_line->unprocessed[0])
1397          {
1398             /* define an alias */
1399             struct action_alias * new_alias;
1400
1401             cur_line->type = FILE_LINE_ALIAS_ENTRY;
1402
1403             err = split_line_on_equals(cur_line->unprocessed, &name, &value);
1404             if (err == JB_ERR_MEMORY)
1405             {
1406                return err;
1407             }
1408             else if (err != JB_ERR_OK)
1409             {
1410                /* Line does not contain a name=value pair */
1411                file->parse_error = cur_line;
1412                file->parse_error_text = "Expected a name=value pair on this {{alias}} line, but couldn't find one.";
1413                return JB_ERR_PARSE;
1414             }
1415
1416             if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
1417             {
1418                /* Out of memory */
1419                free(name);
1420                free(value);
1421                free_alias_list(alias_list);
1422                return JB_ERR_MEMORY;
1423             }
1424
1425             err = get_actions(value, alias_list, new_alias->action);
1426             if (err)
1427             {
1428                /* Invalid action or out of memory */
1429                free(name);
1430                free(value);
1431                free(new_alias);
1432                free_alias_list(alias_list);
1433                if (err == JB_ERR_MEMORY)
1434                {
1435                   return err;
1436                }
1437                else
1438                {
1439                   /* Line does not contain a name=value pair */
1440                   file->parse_error = cur_line;
1441                   file->parse_error_text = "This alias does not specify a valid set of actions.";
1442                   return JB_ERR_PARSE;
1443                }
1444             }
1445
1446             free(value);
1447
1448             new_alias->name = name;
1449
1450             /* add to list */
1451             new_alias->next = alias_list;
1452             alias_list = new_alias;
1453          }
1454          else
1455          {
1456             cur_line->type = FILE_LINE_BLANK;
1457          }
1458          cur_line = cur_line->next;
1459       }
1460    }
1461
1462    /* Header done, process the main part of the file */
1463    while (cur_line != NULL)
1464    {
1465       /* At this point, (cur_line->unprocessed[0] == '{') */
1466       assert(cur_line->unprocessed[0] == '{');
1467       text = cur_line->unprocessed + 1;
1468       len = strlen(text) - 1;
1469       if (text[len] != '}')
1470       {
1471          /* No closing } on header */
1472          free_alias_list(alias_list);
1473          file->parse_error = cur_line;
1474          file->parse_error_text = "Headers starting with '{' must have a "
1475             "closing bracket ('}').  Headers starting with two brackets ('{{') "
1476             "must close with two brackets ('}}').";
1477          return JB_ERR_PARSE;
1478       }
1479
1480       if (text[0] == '{')
1481       {
1482          /* An invalid {{ header.  */
1483          free_alias_list(alias_list);
1484          file->parse_error = cur_line;
1485          file->parse_error_text = "Unknown or unexpected two-bracket header.  "
1486             "Please remember that the system (two-bracket) headers must "
1487             "appear in the order {{settings}}, {{description}}, {{alias}}, "
1488             "and must appear before any actions (one-bracket) headers.  "
1489             "Also note that system headers may not be repeated.";
1490          return JB_ERR_PARSE;
1491       }
1492
1493       while ( (*text == ' ') || (*text == '\t') )
1494       {
1495          text++;
1496          len--;
1497       }
1498       while ( (len > 0)
1499            && ( (text[len - 1] == ' ')
1500              || (text[len - 1] == '\t') ) )
1501       {
1502          len--;
1503       }
1504
1505       cur_line->type = FILE_LINE_ACTION;
1506
1507       /* Remove {} and make copy */
1508       if (NULL == (value = (char *) malloc(len + 1)))
1509       {
1510          /* Out of memory */
1511          free_alias_list(alias_list);
1512          return JB_ERR_MEMORY;
1513       }
1514       strncpy(value, text, len);
1515       value[len] = '\0';
1516
1517       /* Get actions */
1518       err = get_actions(value, alias_list, cur_line->data.action);
1519       if (err)
1520       {
1521          /* Invalid action or out of memory */
1522          free(value);
1523          free_alias_list(alias_list);
1524          if (err == JB_ERR_MEMORY)
1525          {
1526             return err;
1527          }
1528          else
1529          {
1530             /* Line does not contain a name=value pair */
1531             file->parse_error = cur_line;
1532             file->parse_error_text = "This header does not specify a valid set of actions.";
1533             return JB_ERR_PARSE;
1534          }
1535       }
1536
1537       /* Done with string - it was clobbered anyway */
1538       free(value);
1539
1540       /* Process next line */
1541       cur_line = cur_line->next;
1542
1543       /* Loop processing URL patterns */
1544       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1545       {
1546          if (cur_line->unprocessed[0])
1547          {
1548             /* Could parse URL here, but this isn't currently needed */
1549
1550             cur_line->type = FILE_LINE_URL;
1551          }
1552          else
1553          {
1554             cur_line->type = FILE_LINE_BLANK;
1555          }
1556          cur_line = cur_line->next;
1557       }
1558    } /* End main while(cur_line != NULL) loop */
1559
1560    free_alias_list(alias_list);
1561
1562    return JB_ERR_OK;
1563 }
1564
1565
1566 /*********************************************************************
1567  *
1568  * Function    :  edit_read_file_lines
1569  *
1570  * Description :  Read all the lines of a file into memory.
1571  *                Handles whitespace, comments and line continuation.
1572  *
1573  * Parameters  :
1574  *          1  :  fp = File to read from.  On return, this will be
1575  *                     at EOF but it will not have been closed.
1576  *          2  :  pfile = Destination for a linked list of file_lines.
1577  *                        Will be set to NULL on error.
1578  *          3  :  newline = How to handle newlines.
1579  *
1580  * Returns     :  JB_ERR_OK     on success
1581  *                JB_ERR_MEMORY on out-of-memory
1582  *
1583  *********************************************************************/
1584 jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline)
1585 {
1586    struct file_line * first_line; /* Keep for return value or to free */
1587    struct file_line * cur_line;   /* Current line */
1588    struct file_line * prev_line;  /* Entry with prev_line->next = cur_line */
1589    jb_err rval;
1590
1591    assert(fp);
1592    assert(pfile);
1593
1594    *pfile = NULL;
1595
1596    cur_line = first_line = zalloc(sizeof(struct file_line));
1597    if (cur_line == NULL)
1598    {
1599       return JB_ERR_MEMORY;
1600    }
1601
1602    cur_line->type = FILE_LINE_UNPROCESSED;
1603
1604    rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1605    if (rval)
1606    {
1607       /* Out of memory or empty file. */
1608       /* Note that empty file is not an error we propogate up */
1609       free(cur_line);
1610       return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval);
1611    }
1612
1613    do
1614    {
1615       prev_line = cur_line;
1616       cur_line = prev_line->next = zalloc(sizeof(struct file_line));
1617       if (cur_line == NULL)
1618       {
1619          /* Out of memory */
1620          edit_free_file_lines(first_line);
1621          return JB_ERR_MEMORY;
1622       }
1623
1624       cur_line->type = FILE_LINE_UNPROCESSED;
1625
1626       rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1627       if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
1628       {
1629          /* Out of memory */
1630          edit_free_file_lines(first_line);
1631          return JB_ERR_MEMORY;
1632       }
1633
1634    }
1635    while (rval != JB_ERR_FILE);
1636
1637    /* EOF */
1638
1639    /* We allocated one too many - free it */
1640    prev_line->next = NULL;
1641    free(cur_line);
1642
1643    *pfile = first_line;
1644    return JB_ERR_OK;
1645 }
1646
1647
1648 /*********************************************************************
1649  *
1650  * Function    :  edit_read_file
1651  *
1652  * Description :  Read a complete file into memory.
1653  *                Handles CGI parameter parsing.  If requested, also
1654  *                checks the file's modification timestamp.
1655  *
1656  * Parameters  :
1657  *          1  :  csp = Current client state (buffers, headers, etc...)
1658  *          2  :  parameters = map of cgi parameters.
1659  *          3  :  require_version = true to check "ver" parameter.
1660  *          4  :  suffix = File extension, e.g. ".action".
1661  *          5  :  pfile = Destination for the file.  Will be set
1662  *                        to NULL on error.
1663  *
1664  * CGI Parameters :
1665  *    filename :  The name of the file to read, without the
1666  *                path or ".action" extension.
1667  *         ver :  (Only if require_version is nonzero)
1668  *                Timestamp of the actions file.  If wrong, this
1669  *                function fails with JB_ERR_MODIFIED.
1670  *
1671  * Returns     :  JB_ERR_OK     on success
1672  *                JB_ERR_MEMORY on out-of-memory
1673  *                JB_ERR_CGI_PARAMS if "filename" was not specified
1674  *                                  or is not valid.
1675  *                JB_ERR_FILE   if the file cannot be opened or
1676  *                              contains no data
1677  *                JB_ERR_MODIFIED if version checking was requested and
1678  *                                failed - the file was modified outside
1679  *                                of this CGI editor instance.
1680  *
1681  *********************************************************************/
1682 jb_err edit_read_file(struct client_state *csp,
1683                       const struct map *parameters,
1684                       int require_version,
1685                       const char *suffix,
1686                       struct editable_file **pfile)
1687 {
1688    struct file_line * lines;
1689    FILE * fp;
1690    jb_err err;
1691    char * filename;
1692    const char * identifier;
1693    struct editable_file * file;
1694    unsigned version = 0;
1695    struct stat statbuf[1];
1696    char version_buf[22];
1697    int newline = NEWLINE_UNKNOWN;
1698
1699    assert(csp);
1700    assert(parameters);
1701    assert(pfile);
1702
1703    *pfile = NULL;
1704
1705    err = get_file_name_param(csp, parameters, "f", suffix,
1706                              &filename, &identifier);
1707    if (err)
1708    {
1709       return err;
1710    }
1711
1712    if (stat(filename, statbuf) < 0)
1713    {
1714       /* Error, probably file not found. */
1715       free(filename);
1716       return JB_ERR_FILE;
1717    }
1718    version = (unsigned) statbuf->st_mtime;
1719
1720    if (require_version)
1721    {
1722       unsigned specified_version;
1723       err = get_number_param(csp, parameters, "v", &specified_version);
1724       if (err)
1725       {
1726          free(filename);
1727          return err;
1728       }
1729
1730       if (version != specified_version)
1731       {
1732          return JB_ERR_MODIFIED;
1733       }
1734    }
1735
1736    if (NULL == (fp = fopen(filename,"rb")))
1737    {
1738       free(filename);
1739       return JB_ERR_FILE;
1740    }
1741
1742    err = edit_read_file_lines(fp, &lines, &newline);
1743
1744    fclose(fp);
1745
1746    if (err)
1747    {
1748       free(filename);
1749       return err;
1750    }
1751
1752    file = (struct editable_file *) zalloc(sizeof(*file));
1753    if (err)
1754    {
1755       free(filename);
1756       edit_free_file_lines(lines);
1757       return err;
1758    }
1759
1760    file->lines = lines;
1761    file->newline = newline;
1762    file->filename = filename;
1763    file->version = version;
1764    file->identifier = url_encode(identifier);
1765
1766    if (file->identifier == NULL)
1767    {
1768       edit_free_file(file);
1769       return JB_ERR_MEMORY;
1770    }
1771
1772    /* Correct file->version_str */
1773    freez(file->version_str);
1774    snprintf(version_buf, 22, "%u", file->version);
1775    version_buf[21] = '\0';
1776    file->version_str = strdup(version_buf);
1777    if (version_buf == NULL)
1778    {
1779       edit_free_file(file);
1780       return JB_ERR_MEMORY;
1781    }
1782
1783    *pfile = file;
1784    return JB_ERR_OK;
1785 }
1786
1787
1788 /*********************************************************************
1789  *
1790  * Function    :  edit_read_actions_file
1791  *
1792  * Description :  Read a complete actions file into memory.
1793  *                Handles CGI parameter parsing.  If requested, also
1794  *                checks the file's modification timestamp.
1795  *
1796  *                If this function detects an error in the categories
1797  *                JB_ERR_FILE, JB_ERR_MODIFIED, or JB_ERR_PARSE,
1798  *                then it handles it by filling in the specified
1799  *                response structure and returning JB_ERR_FILE.
1800  *
1801  * Parameters  :
1802  *          1  :  csp = Current client state (buffers, headers, etc...)
1803  *          2  :  rsp = HTTP response.  Only filled in on error.
1804  *          2  :  parameters = map of cgi parameters.
1805  *          3  :  require_version = true to check "ver" parameter.
1806  *          4  :  pfile = Destination for the file.  Will be set
1807  *                        to NULL on error.
1808  *
1809  * CGI Parameters :
1810  *    filename :  The name of the actions file to read, without the
1811  *                path or ".action" extension.
1812  *         ver :  (Only if require_version is nonzero)
1813  *                Timestamp of the actions file.  If wrong, this
1814  *                function fails with JB_ERR_MODIFIED.
1815  *
1816  * Returns     :  JB_ERR_OK     on success
1817  *                JB_ERR_MEMORY on out-of-memory
1818  *                JB_ERR_CGI_PARAMS if "filename" was not specified
1819  *                                  or is not valid.
1820  *                JB_ERR_FILE  if the file does not contain valid data,
1821  *                             or if file cannot be opened or
1822  *                             contains no data, or if version
1823  *                             checking was requested and failed.
1824  *
1825  *********************************************************************/
1826 jb_err edit_read_actions_file(struct client_state *csp,
1827                               struct http_response *rsp,
1828                               const struct map *parameters,
1829                               int require_version,
1830                               struct editable_file **pfile)
1831 {
1832    jb_err err;
1833    struct editable_file *file;
1834
1835    assert(csp);
1836    assert(parameters);
1837    assert(pfile);
1838
1839    *pfile = NULL;
1840
1841    err = edit_read_file(csp, parameters, require_version, ".action", &file);
1842    if (err)
1843    {
1844       /* Try to handle if possible */
1845       if (err == JB_ERR_FILE)
1846       {
1847          err = cgi_error_file(csp, rsp, lookup(parameters, "f"));
1848       }
1849       else if (err == JB_ERR_MODIFIED)
1850       {
1851          err = cgi_error_modified(csp, rsp, lookup(parameters, "f"));
1852       }
1853       if (err == JB_ERR_OK)
1854       {
1855          /*
1856           * Signal to higher-level CGI code that there was a problem but we
1857           * handled it, they should just return JB_ERR_OK.
1858           */
1859          err = JB_ERR_FILE;
1860       }
1861       return err;
1862    }
1863
1864    err = edit_parse_actions_file(file);
1865    if (err)
1866    {
1867       if (err == JB_ERR_PARSE)
1868       {
1869          err = cgi_error_parse(csp, rsp, file);
1870          if (err == JB_ERR_OK)
1871          {
1872             /*
1873              * Signal to higher-level CGI code that there was a problem but we
1874              * handled it, they should just return JB_ERR_OK.
1875              */
1876             err = JB_ERR_FILE;
1877          }
1878       }
1879       edit_free_file(file);
1880       return err;
1881    }
1882
1883    *pfile = file;
1884    return JB_ERR_OK;
1885 }
1886
1887
1888 /*********************************************************************
1889  *
1890  * Function    :  get_file_name_param
1891  *
1892  * Description :  Get the name of the file to edit from the parameters
1893  *                passed to a CGI function.  This function handles
1894  *                security checks such as blocking urls containing
1895  *                "/" or ".", prepending the config file directory,
1896  *                and adding the specified suffix.
1897  *
1898  *                (This is an essential security check, otherwise
1899  *                users may be able to pass "../../../etc/passwd"
1900  *                and overwrite the password file [linux], "prn:"
1901  *                and print random data [Windows], etc...)
1902  *
1903  *                This function only allows filenames contining the
1904  *                characters '-', '_', 'A'-'Z', 'a'-'z', and '0'-'9'.
1905  *                That's probably too restrictive but at least it's
1906  *                secure.
1907  *
1908  * Parameters  :
1909  *          1  :  csp = Current client state (buffers, headers, etc...)
1910  *          2  :  parameters = map of cgi parameters
1911  *          3  :  param_name = The name of the parameter to read
1912  *          4  :  suffix = File extension, e.g. ".actions"
1913  *          5  :  pfilename = destination for full filename.  Caller
1914  *                free()s.  Set to NULL on error.
1915  *          6  :  pparam = destination for partial filename,
1916  *                suitable for use in another URL.  Allocated as part
1917  *                of the map "parameters", so don't free it.
1918  *                Set to NULL if not specified.
1919  *
1920  * Returns     :  JB_ERR_OK         on success
1921  *                JB_ERR_MEMORY     on out-of-memory
1922  *                JB_ERR_CGI_PARAMS if "filename" was not specified
1923  *                                  or is not valid.
1924  *
1925  *********************************************************************/
1926 static jb_err get_file_name_param(struct client_state *csp,
1927                                   const struct map *parameters,
1928                                   const char *param_name,
1929                                   const char *suffix,
1930                                   char **pfilename,
1931                                   const char **pparam)
1932 {
1933    const char *param;
1934    const char *s;
1935    char *name;
1936    char *fullpath;
1937    char ch;
1938    int len;
1939
1940    assert(csp);
1941    assert(parameters);
1942    assert(suffix);
1943    assert(pfilename);
1944    assert(pparam);
1945
1946    *pfilename = NULL;
1947    *pparam = NULL;
1948
1949    param = lookup(parameters, param_name);
1950    if (!*param)
1951    {
1952       return JB_ERR_CGI_PARAMS;
1953    }
1954
1955    *pparam = param;
1956
1957    len = strlen(param);
1958    if (len >= FILENAME_MAX)
1959    {
1960       /* Too long. */
1961       return JB_ERR_CGI_PARAMS;
1962    }
1963
1964    /* Check every character to see if it's legal */
1965    s = param;
1966    while ((ch = *s++) != '\0')
1967    {
1968       if ( ((ch < 'A') || (ch > 'Z'))
1969         && ((ch < 'a') || (ch > 'z'))
1970         && ((ch < '0') || (ch > '9'))
1971         && (ch != '-')
1972         && (ch != '_') )
1973       {
1974          /* Probable hack attempt. */
1975          return JB_ERR_CGI_PARAMS;
1976       }
1977    }
1978
1979    /* Append extension */
1980    name = malloc(len + strlen(suffix) + 1);
1981    if (name == NULL)
1982    {
1983       return JB_ERR_MEMORY;
1984    }
1985    strcpy(name, param);
1986    strcpy(name + len, suffix);
1987
1988    /* Prepend path */
1989    fullpath = make_path(csp->config->confdir, name);
1990    free(name);
1991
1992    if (fullpath == NULL)
1993    {
1994       return JB_ERR_MEMORY;
1995    }
1996
1997    /* Success */
1998    *pfilename = fullpath;
1999
2000    return JB_ERR_OK;
2001 }
2002
2003
2004 /*********************************************************************
2005  *
2006  * Function    :  get_url_spec_param
2007  *
2008  * Description :  Get a URL pattern from the parameters
2009  *                passed to a CGI function.  Removes leading/trailing
2010  *                spaces and validates it.
2011  *
2012  * Parameters  :
2013  *          1  :  csp = Current client state (buffers, headers, etc...)
2014  *          2  :  parameters = map of cgi parameters
2015  *          3  :  name = Name of CGI parameter to read
2016  *          4  :  pvalue = destination for value.  Will be malloc()'d.
2017  *                         Set to NULL on error.
2018  *
2019  * Returns     :  JB_ERR_OK         on success
2020  *                JB_ERR_MEMORY     on out-of-memory
2021  *                JB_ERR_CGI_PARAMS if the parameter was not specified
2022  *                                  or is not valid.
2023  *
2024  *********************************************************************/
2025 static jb_err get_url_spec_param(struct client_state *csp,
2026                                  const struct map *parameters,
2027                                  const char *name,
2028                                  char **pvalue)
2029 {
2030    const char *orig_param;
2031    char *param;
2032    char *s;
2033    struct url_spec compiled[1];
2034    jb_err err;
2035
2036    assert(csp);
2037    assert(parameters);
2038    assert(name);
2039    assert(pvalue);
2040
2041    *pvalue = NULL;
2042
2043    orig_param = lookup(parameters, name);
2044    if (!*orig_param)
2045    {
2046       return JB_ERR_CGI_PARAMS;
2047    }
2048
2049    /* Copy and trim whitespace */
2050    param = strdup(orig_param);
2051    if (param == NULL)
2052    {
2053       return JB_ERR_MEMORY;
2054    }
2055    chomp(param);
2056
2057    /* Must be non-empty, and can't allow 1st character to be '{' */
2058    if (param[0] == '\0' || param[0] == '{')
2059    {
2060       free(param);
2061       return JB_ERR_CGI_PARAMS;
2062    }
2063
2064    /* Check for embedded newlines */
2065    for (s = param; *s != '\0'; s++)
2066    {
2067       if ((*s == '\r') || (*s == '\n'))
2068       {
2069          free(param);
2070          return JB_ERR_CGI_PARAMS;
2071       }
2072    }
2073
2074    /* Check that regex is valid */
2075    s = strdup(param);
2076    if (s == NULL)
2077    {
2078       free(param);
2079       return JB_ERR_MEMORY;
2080    }
2081    err = create_url_spec(compiled, s);
2082    free(s);
2083    if (err)
2084    {
2085       free(param);
2086       return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
2087    }
2088    free_url_spec(compiled);
2089
2090    if (param[strlen(param) - 1] == '\\')
2091    {
2092       /*
2093        * Must protect trailing '\\' from becoming line continuation character.
2094        * Two methods: 1) If it's a domain only, add a trailing '/'.
2095        * 2) For path, add the do-nothing PCRE expression (?:) to the end
2096        */
2097       if (strchr(param, '/') == NULL)
2098       {
2099          err = string_append(&param, "/");
2100       }
2101       else
2102       {
2103          err = string_append(&param, "(?:)");
2104       }
2105       if (err)
2106       {
2107          return err;
2108       }
2109
2110       /* Check that the modified regex is valid */
2111       s = strdup(param);
2112       if (s == NULL)
2113       {
2114          free(param);
2115          return JB_ERR_MEMORY;
2116       }
2117       err = create_url_spec(compiled, s);
2118       free(s);
2119       if (err)
2120       {
2121          free(param);
2122          return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
2123       }
2124       free_url_spec(compiled);
2125    }
2126
2127    *pvalue = param;
2128    return JB_ERR_OK;
2129 }
2130
2131 /*********************************************************************
2132  *
2133  * Function    :  map_radio
2134  *
2135  * Description :  Map a set of radio button values.  E.g. if you have
2136  *                3 radio buttons, declare them as:
2137  *                  <option type="radio" name="xyz" @xyz-a@>
2138  *                  <option type="radio" name="xyz" @xyz-b@>
2139  *                  <option type="radio" name="xyz" @xyz-c@>
2140  *                Then map one of the @xyz-?@ variables to "checked"
2141  *                and all the others to empty by calling:
2142  *                map_radio(exports, "xyz", "abc", sel)
2143  *                Where 'sel' is 'a', 'b', or 'c'.
2144  *
2145  * Parameters  :
2146  *          1  :  exports = Exports map to modify.
2147  *          2  :  optionname = name for map
2148  *          3  :  values = null-terminated list of values;
2149  *          4  :  value = Selected value.
2150  *
2151  * CGI Parameters : None
2152  *
2153  * Returns     :  JB_ERR_OK     on success
2154  *                JB_ERR_MEMORY on out-of-memory
2155  *
2156  *********************************************************************/
2157 static jb_err map_radio(struct map * exports,
2158                         const char * optionname,
2159                         const char * values,
2160                         int value)
2161 {
2162    size_t len;
2163    char * buf;
2164    char * p;
2165    char c;
2166
2167    assert(exports);
2168    assert(optionname);
2169    assert(values);
2170
2171    len = strlen(optionname);
2172    buf = malloc(len + 3);
2173    if (buf == NULL)
2174    {
2175       return JB_ERR_MEMORY;
2176    }
2177
2178    strcpy(buf, optionname);
2179    p = buf + len;
2180    *p++ = '-';
2181    p[1] = '\0';
2182
2183    while ((c = *values++) != '\0')
2184    {
2185       if (c != value)
2186       {
2187          *p = c;
2188          if (map(exports, buf, 1, "", 1))
2189          {
2190             return JB_ERR_MEMORY;
2191          }
2192       }
2193    }
2194
2195    *p = value;
2196    return map(exports, buf, 0, "checked", 1);
2197 }
2198
2199
2200 /*********************************************************************
2201  *
2202  * Function    :  cgi_error_modified
2203  *
2204  * Description :  CGI function that is called when a file is modified
2205  *                outside the CGI editor.
2206  *
2207  * Parameters  :
2208  *          1  :  csp = Current client state (buffers, headers, etc...)
2209  *          2  :  rsp = http_response data structure for output
2210  *          3  :  filename = The file that was modified.
2211  *
2212  * CGI Parameters : none
2213  *
2214  * Returns     :  JB_ERR_OK on success
2215  *                JB_ERR_MEMORY on out-of-memory error.
2216  *
2217  *********************************************************************/
2218 jb_err cgi_error_modified(struct client_state *csp,
2219                           struct http_response *rsp,
2220                           const char *filename)
2221 {
2222    struct map *exports;
2223    jb_err err;
2224
2225    assert(csp);
2226    assert(rsp);
2227    assert(filename);
2228
2229    if (NULL == (exports = default_exports(csp, NULL)))
2230    {
2231       return JB_ERR_MEMORY;
2232    }
2233
2234    err = map(exports, "f", 1, html_encode(filename), 0);
2235    if (err)
2236    {
2237       free_map(exports);
2238       return err;
2239    }
2240
2241    return template_fill_for_cgi(csp, "cgi-error-modified", exports, rsp);
2242 }
2243
2244
2245 /*********************************************************************
2246  *
2247  * Function    :  cgi_error_parse
2248  *
2249  * Description :  CGI function that is called when a file cannot
2250  *                be parsed by the CGI editor.
2251  *
2252  * Parameters  :
2253  *          1  :  csp = Current client state (buffers, headers, etc...)
2254  *          2  :  rsp = http_response data structure for output
2255  *          3  :  file = The file that was modified.
2256  *
2257  * CGI Parameters : none
2258  *
2259  * Returns     :  JB_ERR_OK on success
2260  *                JB_ERR_MEMORY on out-of-memory error.
2261  *
2262  *********************************************************************/
2263 jb_err cgi_error_parse(struct client_state *csp,
2264                        struct http_response *rsp,
2265                        struct editable_file *file)
2266 {
2267    struct map *exports;
2268    jb_err err;
2269    struct file_line *cur_line;
2270
2271    assert(csp);
2272    assert(rsp);
2273    assert(file);
2274
2275    if (NULL == (exports = default_exports(csp, NULL)))
2276    {
2277       return JB_ERR_MEMORY;
2278    }
2279
2280    err = map(exports, "f", 1, file->identifier, 1);
2281    if (!err) err = map(exports, "parse-error", 1, html_encode(file->parse_error_text), 0);
2282
2283    cur_line = file->parse_error;
2284    assert(cur_line);
2285
2286    if (!err) err = map(exports, "line-raw", 1, html_encode(cur_line->raw), 0);
2287    if (!err) err = map(exports, "line-data", 1, html_encode(cur_line->unprocessed), 0);
2288
2289    if (err)
2290    {
2291       free_map(exports);
2292       return err;
2293    }
2294
2295    return template_fill_for_cgi(csp, "cgi-error-parse", exports, rsp);
2296 }
2297
2298
2299 /*********************************************************************
2300  *
2301  * Function    :  cgi_error_file
2302  *
2303  * Description :  CGI function that is called when a file cannot be
2304  *                opened by the CGI editor.
2305  *
2306  * Parameters  :
2307  *          1  :  csp = Current client state (buffers, headers, etc...)
2308  *          2  :  rsp = http_response data structure for output
2309  *          3  :  filename = The file that was modified.
2310  *
2311  * CGI Parameters : none
2312  *
2313  * Returns     :  JB_ERR_OK on success
2314  *                JB_ERR_MEMORY on out-of-memory error.
2315  *
2316  *********************************************************************/
2317 jb_err cgi_error_file(struct client_state *csp,
2318                       struct http_response *rsp,
2319                       const char *filename)
2320 {
2321    struct map *exports;
2322    jb_err err;
2323
2324    assert(csp);
2325    assert(rsp);
2326    assert(filename);
2327
2328    if (NULL == (exports = default_exports(csp, NULL)))
2329    {
2330       return JB_ERR_MEMORY;
2331    }
2332
2333    err = map(exports, "f", 1, html_encode(filename), 0);
2334    if (err)
2335    {
2336       free_map(exports);
2337       return err;
2338    }
2339
2340    return template_fill_for_cgi(csp, "cgi-error-file", exports, rsp);
2341 }
2342
2343
2344 /*********************************************************************
2345  *
2346  * Function    :  cgi_error_file
2347  *
2348  * Description :  CGI function that is called when a file cannot be
2349  *                opened for writing by the CGI editor.
2350  *
2351  * Parameters  :
2352  *          1  :  csp = Current client state (buffers, headers, etc...)
2353  *          2  :  rsp = http_response data structure for output
2354  *          3  :  filename = The file that we can't write to
2355  *
2356  * CGI Parameters : none
2357  *
2358  * Returns     :  JB_ERR_OK on success
2359  *                JB_ERR_MEMORY on out-of-memory error.
2360  *
2361  *********************************************************************/
2362 jb_err cgi_error_file_read_only(struct client_state *csp,
2363                                 struct http_response *rsp,
2364                                 const char *filename)
2365 {
2366    struct map *exports;
2367    jb_err err;
2368
2369    assert(csp);
2370    assert(rsp);
2371    assert(filename);
2372
2373    if (NULL == (exports = default_exports(csp, NULL)))
2374    {
2375       return JB_ERR_MEMORY;
2376    }
2377
2378    err = map(exports, "f", 1, html_encode(filename), 0);
2379    if (err)
2380    {
2381       free_map(exports);
2382       return err;
2383    }
2384
2385    return template_fill_for_cgi(csp, "cgi-error-file-read-only", exports, rsp);
2386 }
2387
2388
2389 /*********************************************************************
2390  *
2391  * Function    :  cgi_error_disabled
2392  *
2393  * Description :  CGI function that is called if the actions editor
2394  *                is called although it's disabled in config
2395  *
2396  * Parameters  :
2397  *          1  :  csp = Current client state (buffers, headers, etc...)
2398  *          2  :  rsp = http_response data structure for output
2399  *
2400  * CGI Parameters : none
2401  *
2402  * Returns     :  JB_ERR_OK on success
2403  *                JB_ERR_MEMORY on out-of-memory error.
2404  *
2405  *********************************************************************/
2406 jb_err cgi_error_disabled(struct client_state *csp,
2407                           struct http_response *rsp)
2408 {
2409    struct map *exports;
2410
2411    assert(csp);
2412    assert(rsp);
2413
2414    if (NULL == (exports = default_exports(csp, NULL)))
2415    {
2416       return JB_ERR_MEMORY;
2417    }
2418
2419    return template_fill_for_cgi(csp, "cgi-error-disabled", exports, rsp);
2420 }
2421
2422
2423 /*********************************************************************
2424  *
2425  * Function    :  cgi_edit_actions
2426  *
2427  * Description :  CGI function that allows the user to choose which
2428  *                actions file to edit.
2429  *
2430  * Parameters  :
2431  *          1  :  csp = Current client state (buffers, headers, etc...)
2432  *          2  :  rsp = http_response data structure for output
2433  *          3  :  parameters = map of cgi parameters
2434  *
2435  * CGI Parameters : None
2436  *
2437  * Returns     :  JB_ERR_OK on success
2438  *                JB_ERR_MEMORY on out-of-memory error
2439  *
2440  *********************************************************************/
2441 jb_err cgi_edit_actions(struct client_state *csp,
2442                         struct http_response *rsp,
2443                         const struct map *parameters)
2444 {
2445
2446    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2447    {
2448       return cgi_error_disabled(csp, rsp);
2449    }
2450
2451    /* FIXME: Incomplete */
2452    rsp->status = strdup("302 Local Redirect from Privoxy");
2453    if (rsp->status == NULL)
2454    {
2455       return JB_ERR_MEMORY;
2456    }
2457    if (enlist_unique_header(rsp->headers, "Location",
2458       CGI_PREFIX "edit-actions-list?f=default"))
2459    {
2460       free(rsp->status);
2461       rsp->status = NULL;
2462       return JB_ERR_MEMORY;
2463    }
2464
2465    return JB_ERR_OK;
2466 }
2467
2468
2469 /*********************************************************************
2470  *
2471  * Function    :  cgi_edit_actions_list
2472  *
2473  * Description :  CGI function that edits the actions list.
2474  *                FIXME: This function shouldn't FATAL ever.
2475  *                FIXME: This function doesn't check the retval of map()
2476  * Parameters  :
2477  *          1  :  csp = Current client state (buffers, headers, etc...)
2478  *          2  :  rsp = http_response data structure for output
2479  *          3  :  parameters = map of cgi parameters
2480  *
2481  * CGI Parameters : filename
2482  *
2483  * Returns     :  JB_ERR_OK     on success
2484  *                JB_ERR_MEMORY on out-of-memory
2485  *                JB_ERR_FILE   if the file cannot be opened or
2486  *                              contains no data
2487  *                JB_ERR_CGI_PARAMS if "filename" was not specified
2488  *                                  or is not valid.
2489  *
2490  *********************************************************************/
2491 jb_err cgi_edit_actions_list(struct client_state *csp,
2492                              struct http_response *rsp,
2493                              const struct map *parameters)
2494 {
2495    char * section_template;
2496    char * url_template;
2497    char * sections;
2498    char * urls;
2499    char buf[150];
2500    char * s;
2501    struct map * exports;
2502    struct map * section_exports;
2503    struct map * url_exports;
2504    struct editable_file * file;
2505    struct file_line * cur_line;
2506    unsigned line_number = 0;
2507    unsigned prev_section_line_number = ((unsigned) (-1));
2508    int i, url_1_2;
2509    struct file_list * fl;
2510    struct url_actions * b;
2511    char * buttons = NULL;
2512    jb_err err;
2513
2514    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2515    {
2516       return cgi_error_disabled(csp, rsp);
2517    }
2518
2519    if (NULL == (exports = default_exports(csp, NULL)))
2520    {
2521       edit_free_file(file);
2522       return JB_ERR_MEMORY;
2523    }
2524
2525    /* Load actions file */
2526    err = edit_read_actions_file(csp, rsp, parameters, 0, &file);
2527    if (err)
2528    {
2529       /* No filename specified, can't read file, or out of memory. */
2530       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2531    }
2532
2533    /* Find start of actions in file */
2534    cur_line = file->lines;
2535    line_number = 1;
2536    while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
2537    {
2538       cur_line = cur_line->next;
2539       line_number++;
2540    }
2541
2542    /*
2543     * Conventional actions files should have a match all block
2544     * at the start:
2545     * cur_line             = {...global actions...}
2546     * cur_line->next       = /
2547     * cur_line->next->next = {...actions...} or EOF
2548     */
2549    if ( (cur_line != NULL)
2550      && (cur_line->type == FILE_LINE_ACTION)
2551      && (cur_line->next != NULL)
2552      && (cur_line->next->type == FILE_LINE_URL)
2553      && (0 == strcmp(cur_line->next->unprocessed, "/"))
2554      && ( (cur_line->next->next == NULL)
2555        || (cur_line->next->next->type != FILE_LINE_URL)
2556       ) )
2557    {
2558       /*
2559        * Generate string with buttons to set actions for "/" to
2560        * any predefined set of actions (named standard.*, probably
2561        * residing in standard.action).
2562        */
2563
2564       err = template_load(csp, &section_template, "edit-actions-list-button", 0);
2565       if (err)
2566       {
2567          edit_free_file(file);
2568          free_map(exports);
2569          if (err == JB_ERR_FILE)
2570          {
2571             return cgi_error_no_template(csp, rsp, "edit-actions-list-button");
2572          }
2573          return err;
2574       }
2575
2576       err = template_fill(&section_template, exports);
2577       if (err)
2578       {
2579          edit_free_file(file);
2580          free_map(exports);
2581          return err;
2582       }
2583
2584       buttons = strdup("");
2585       for (i = 0; i < MAX_ACTION_FILES; i++)
2586       {
2587          if (((fl = csp->actions_list[i]) != NULL) && ((b = fl->f) != NULL))
2588          {
2589             for (b = b->next; NULL != b; b = b->next)
2590             {
2591                if (!strncmp(b->url->spec, "standard.", 9) && *(b->url->spec + 9) != '\0')
2592                {
2593                   if (err || (NULL == (section_exports = new_map())))
2594                   {
2595                      freez(buttons);
2596                      free(section_template);
2597                      edit_free_file(file);
2598                      free_map(exports);
2599                      return JB_ERR_MEMORY;
2600                   }
2601
2602                   err = map(section_exports, "button-name", 1, b->url->spec + 9, 1);
2603
2604                   if (err || (NULL == (s = strdup(section_template))))
2605                   {
2606                      free_map(section_exports);
2607                      freez(buttons);
2608                      free(section_template);
2609                      edit_free_file(file);
2610                      free_map(exports);
2611                      return JB_ERR_MEMORY;
2612                   }
2613
2614                   if (!err) err = template_fill(&s, section_exports);
2615                   free_map(section_exports);
2616                   if (!err) err = string_join(&buttons, s);
2617                }
2618             }
2619          }
2620       }
2621       freez(section_template);
2622       if (!err) err = map(exports, "all-urls-buttons", 1, buttons, 0);
2623
2624       /*
2625        * Conventional actions file, supply extra editing help.
2626        * (e.g. don't allow them to make it an unconventional one).
2627        */
2628       if (!err) err = map_conditional(exports, "all-urls-present", 1);
2629
2630       snprintf(buf, 150, "%d", line_number);
2631       if (!err) err = map(exports, "all-urls-s", 1, buf, 1);
2632       snprintf(buf, 150, "%d", line_number + 2);
2633       if (!err) err = map(exports, "all-urls-s-next", 1, buf, 1);
2634       if (!err) err = map(exports, "all-urls-actions", 1,
2635                           actions_to_html(csp, cur_line->data.action), 0);
2636
2637        /* Skip the 2 lines */
2638       cur_line = cur_line->next->next;
2639       line_number += 2;
2640
2641       /*
2642        * Note that prev_section_line_number is NOT set here.
2643        * This is deliberate and not a bug.  It stops a "Move up"
2644        * option appearing on the next section.  Clicking "Move
2645        * up" would make the actions file unconventional, which
2646        * we don't want, so we hide this option.
2647        */
2648    }
2649    else
2650    {
2651       /*
2652        * Non-standard actions file - does not begin with
2653        * the "All URLs" section.
2654        */
2655       if (!err) err = map_conditional(exports, "all-urls-present", 0);
2656    }
2657
2658    /* Set up global exports */
2659
2660    if (!err) err = map(exports, "f", 1, file->identifier, 1);
2661    if (!err) err = map(exports, "v", 1, file->version_str, 1);
2662
2663    /* Discourage private additions to default.action */
2664
2665    if (!err) err = map_conditional(exports, "default-action",
2666                                    (strcmp("default", lookup(parameters, "f")) == 0));
2667    if (err)
2668    {
2669       edit_free_file(file);
2670       free_map(exports);
2671       return err;
2672    }
2673
2674    /* Should do all global exports above this point */
2675
2676    /* Load templates */
2677
2678    err = template_load(csp, &section_template, "edit-actions-list-section", 0);
2679    if (err)
2680    {
2681       edit_free_file(file);
2682       free_map(exports);
2683       if (err == JB_ERR_FILE)
2684       {
2685          return cgi_error_no_template(csp, rsp, "edit-actions-list-section");
2686       }
2687       return err;
2688    }
2689
2690    err = template_load(csp, &url_template, "edit-actions-list-url", 0);
2691    if (err)
2692    {
2693       free(section_template);
2694       edit_free_file(file);
2695       free_map(exports);
2696       if (err == JB_ERR_FILE)
2697       {
2698          return cgi_error_no_template(csp, rsp, "edit-actions-list-url");
2699       }
2700       return err;
2701    }
2702
2703    err = template_fill(&section_template, exports);
2704    if (err)
2705    {
2706       free(url_template);
2707       edit_free_file(file);
2708       free_map(exports);
2709       free(url_template);
2710       return err;
2711    }
2712
2713    err = template_fill(&url_template, exports);
2714    if (err)
2715    {
2716       free(section_template);
2717       edit_free_file(file);
2718       free_map(exports);
2719       return err;
2720    }
2721
2722    if (NULL == (sections = strdup("")))
2723    {
2724       free(section_template);
2725       free(url_template);
2726       edit_free_file(file);
2727       free_map(exports);
2728       return JB_ERR_MEMORY;
2729    }
2730
2731    while ((cur_line != NULL) && (cur_line->type == FILE_LINE_ACTION))
2732    {
2733       if (NULL == (section_exports = new_map()))
2734       {
2735          free(sections);
2736          free(section_template);
2737          free(url_template);
2738          edit_free_file(file);
2739          free_map(exports);
2740          return JB_ERR_MEMORY;
2741       }
2742
2743       snprintf(buf, 150, "%d", line_number);
2744       err = map(section_exports, "s", 1, buf, 1);
2745       if (!err) err = map(section_exports, "actions", 1,
2746                           actions_to_html(csp, cur_line->data.action), 0);
2747
2748       if ( (!err)
2749         && (cur_line->next != NULL)
2750         && (cur_line->next->type == FILE_LINE_URL))
2751       {
2752          /* This section contains at least one URL, don't allow delete */
2753          err = map_block_killer(section_exports, "empty-section");
2754       }
2755       else
2756       {
2757          if (!err) err = map_block_keep(section_exports, "empty-section");
2758       }
2759
2760       if (prev_section_line_number != ((unsigned)(-1)))
2761       {
2762          /* Not last section */
2763          snprintf(buf, 150, "%d", prev_section_line_number);
2764          if (!err) err = map(section_exports, "s-prev", 1, buf, 1);
2765          if (!err) err = map_block_keep(section_exports, "s-prev-exists");
2766       }
2767       else
2768       {
2769          /* Last section */
2770          if (!err) err = map_block_killer(section_exports, "s-prev-exists");
2771       }
2772       prev_section_line_number = line_number;
2773
2774       if (err)
2775       {
2776          free(sections);
2777          free(section_template);
2778          free(url_template);
2779          edit_free_file(file);
2780          free_map(exports);
2781          free_map(section_exports);
2782          return err;
2783       }
2784
2785       /* Should do all section-specific exports above this point */
2786
2787       if (NULL == (urls = strdup("")))
2788       {
2789          free(sections);
2790          free(section_template);
2791          free(url_template);
2792          edit_free_file(file);
2793          free_map(exports);
2794          free_map(section_exports);
2795          return JB_ERR_MEMORY;
2796       }
2797
2798       url_1_2 = 2;
2799
2800       cur_line = cur_line->next;
2801       line_number++;
2802
2803       while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL))
2804       {
2805          if (NULL == (url_exports = new_map()))
2806          {
2807             free(urls);
2808             free(sections);
2809             free(section_template);
2810             free(url_template);
2811             edit_free_file(file);
2812             free_map(exports);
2813             free_map(section_exports);
2814             return JB_ERR_MEMORY;
2815          }
2816
2817          snprintf(buf, 150, "%d", line_number);
2818          err = map(url_exports, "p", 1, buf, 1);
2819
2820          snprintf(buf, 150, "%d", url_1_2);
2821          if (!err) err = map(url_exports, "url-1-2", 1, buf, 1);
2822
2823          if (!err) err = map(url_exports, "url-html", 1,
2824                              html_encode(cur_line->unprocessed), 0);
2825          if (!err) err = map(url_exports, "url", 1,
2826                              url_encode(cur_line->unprocessed), 0);
2827
2828          if (err)
2829          {
2830             free(urls);
2831             free(sections);
2832             free(section_template);
2833             free(url_template);
2834             edit_free_file(file);
2835             free_map(exports);
2836             free_map(section_exports);
2837             free_map(url_exports);
2838             return err;
2839          }
2840
2841          if (NULL == (s = strdup(url_template)))
2842          {
2843             free(urls);
2844             free(sections);
2845             free(section_template);
2846             free(url_template);
2847             edit_free_file(file);
2848             free_map(exports);
2849             free_map(section_exports);
2850             free_map(url_exports);
2851             return JB_ERR_MEMORY;
2852          }
2853
2854          err = template_fill(&s, section_exports);
2855          if (!err) err = template_fill(&s, url_exports);
2856          if (!err) err = string_append(&urls, s);
2857
2858          free_map(url_exports);
2859          freez(s);
2860
2861          if (err)
2862          {
2863             freez(urls);
2864             free(sections);
2865             free(section_template);
2866             free(url_template);
2867             edit_free_file(file);
2868             free_map(exports);
2869             free_map(section_exports);
2870             return err;
2871          }
2872
2873          url_1_2 = 3 - url_1_2;
2874
2875          cur_line = cur_line->next;
2876          line_number++;
2877       }
2878
2879       err = map(section_exports, "urls", 1, urls, 0);
2880
2881       /* Could also do section-specific exports here, but it wouldn't be as fast */
2882
2883       snprintf(buf, 150, "%d", line_number);
2884       if (!err) err = map(section_exports, "s-next", 1, buf, 1);
2885
2886       if ( (cur_line != NULL)
2887         && (cur_line->type == FILE_LINE_ACTION))
2888       {
2889          /* Not last section */
2890          if (!err) err = map_block_keep(section_exports, "s-next-exists");
2891       }
2892       else
2893       {
2894          /* Last section */
2895          if (!err) err = map_block_killer(section_exports, "s-next-exists");
2896       }
2897
2898       if (err)
2899       {
2900          free(sections);
2901          free(section_template);
2902          free(url_template);
2903          edit_free_file(file);
2904          free_map(exports);
2905          free_map(section_exports);
2906          return err;
2907       }
2908
2909       if (NULL == (s = strdup(section_template)))
2910       {
2911          free(sections);
2912          free(section_template);
2913          free(url_template);
2914          edit_free_file(file);
2915          free_map(exports);
2916          free_map(section_exports);
2917          return JB_ERR_MEMORY;
2918       }
2919
2920       err = template_fill(&s, section_exports);
2921       if (!err) err = string_append(&sections, s);
2922
2923       freez(s);
2924       free_map(section_exports);
2925
2926       if (err)
2927       {
2928          freez(sections);
2929          free(section_template);
2930          free(url_template);
2931          edit_free_file(file);
2932          free_map(exports);
2933          return err;
2934       }
2935    }
2936
2937    edit_free_file(file);
2938    free(section_template);
2939    free(url_template);
2940
2941    err = map(exports, "sections", 1, sections, 0);
2942    if (err)
2943    {
2944       free_map(exports);
2945       return err;
2946    }
2947
2948    /* Could also do global exports here, but it wouldn't be as fast */
2949
2950    return template_fill_for_cgi(csp, "edit-actions-list", exports, rsp);
2951 }
2952
2953
2954 /*********************************************************************
2955  *
2956  * Function    :  cgi_edit_actions_for_url
2957  *
2958  * Description :  CGI function that edits the Actions list.
2959  *
2960  * Parameters  :
2961  *          1  :  csp = Current client state (buffers, headers, etc...)
2962  *          2  :  rsp = http_response data structure for output
2963  *          3  :  parameters = map of cgi parameters
2964  *
2965  * CGI Parameters : None
2966  *
2967  * Returns     :  JB_ERR_OK     on success
2968  *                JB_ERR_MEMORY on out-of-memory
2969  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
2970  *                                  specified or not valid.
2971  *
2972  *********************************************************************/
2973 jb_err cgi_edit_actions_for_url(struct client_state *csp,
2974                                 struct http_response *rsp,
2975                                 const struct map *parameters)
2976 {
2977    struct map * exports;
2978    unsigned sectionid;
2979    struct editable_file * file;
2980    struct file_line * cur_line;
2981    unsigned line_number;
2982    jb_err err;
2983    struct file_list *filter_file;
2984    struct re_filterfile_spec *filter_group;
2985
2986    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2987    {
2988       return cgi_error_disabled(csp, rsp);
2989    }
2990
2991    err = get_number_param(csp, parameters, "s", &sectionid);
2992    if (err)
2993    {
2994       return err;
2995    }
2996
2997    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2998    if (err)
2999    {
3000       /* No filename specified, can't read file, modified, or out of memory. */
3001       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3002    }
3003
3004    cur_line = file->lines;
3005
3006    for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
3007    {
3008       cur_line = cur_line->next;
3009    }
3010
3011    if ( (cur_line == NULL)
3012      || (line_number != sectionid)
3013      || (sectionid < 1)
3014      || (cur_line->type != FILE_LINE_ACTION))
3015    {
3016       /* Invalid "sectionid" parameter */
3017       edit_free_file(file);
3018       return JB_ERR_CGI_PARAMS;
3019    }
3020
3021    if (NULL == (exports = default_exports(csp, NULL)))
3022    {
3023       edit_free_file(file);
3024       return JB_ERR_MEMORY;
3025    }
3026
3027    err = map(exports, "f", 1, file->identifier, 1);
3028    if (!err) err = map(exports, "v", 1, file->version_str, 1);
3029    if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0);
3030
3031    if (!err) err = actions_to_radio(exports, cur_line->data.action);
3032
3033    filter_file = csp->rlist;
3034    filter_group = ((filter_file != NULL) ? filter_file->f : NULL);
3035
3036    if (!err) err = map_conditional(exports, "any-filters-defined", (filter_group != NULL));
3037
3038    if (err)
3039    {
3040       edit_free_file(file);
3041       free_map(exports);
3042       return err;
3043    }
3044
3045    if (filter_group == NULL)
3046    {
3047       err = map(exports, "filter-params", 1, "", 1);
3048    }
3049    else
3050    {
3051       /* We have some entries in the filter list */
3052       char * result;
3053       int index = 0;
3054       char * filter_template;
3055
3056       err = template_load(csp, &filter_template, "edit-actions-for-url-filter", 0);
3057       if (err)
3058       {
3059          edit_free_file(file);
3060          free_map(exports);
3061          if (err == JB_ERR_FILE)
3062          {
3063             return cgi_error_no_template(csp, rsp, "edit-actions-for-url-filter");
3064          }
3065          return err;
3066       }
3067
3068       err = template_fill(&filter_template, exports);
3069
3070       result = strdup("");
3071
3072       for (;(!err) && (filter_group != NULL); filter_group = filter_group->next)
3073       {
3074          char current_mode = 'x';
3075          struct list_entry *filter_name;
3076          char * this_line;
3077          struct map *line_exports;
3078          char number[20];
3079
3080          filter_name = cur_line->data.action->multi_add[ACTION_MULTI_FILTER]->first;
3081          while ((filter_name != NULL)
3082              && (0 != strcmp(filter_group->name, filter_name->str)))
3083          {
3084               filter_name = filter_name->next;
3085          }
3086
3087          if (filter_name != NULL)
3088          {
3089             current_mode = 'y';
3090          }
3091          else
3092          {
3093             filter_name = cur_line->data.action->multi_remove[ACTION_MULTI_FILTER]->first;
3094             while ((filter_name != NULL)
3095                 && (0 != strcmp(filter_group->name, filter_name->str)))
3096             {
3097                  filter_name = filter_name->next;
3098             }
3099             if (filter_name != NULL)
3100             {
3101                current_mode = 'n';
3102             }
3103          }
3104
3105          /* Generate a unique serial number */
3106          snprintf(number, sizeof(number), "%x", index++);
3107          number[sizeof(number) - 1] = '\0';
3108
3109          line_exports = new_map();
3110          if (line_exports == NULL)
3111          {
3112             err = JB_ERR_MEMORY;
3113             freez(result);
3114          }
3115          else
3116          {
3117             if (!err) err = map(line_exports, "index", 1, number, 1);
3118             if (!err) err = map(line_exports, "name",  1, filter_group->name, 1);
3119             if (!err) err = map(line_exports, "description",  1, filter_group->description, 1);
3120             if (!err) err = map_radio(line_exports, "this-filter", "ynx", current_mode);
3121
3122             this_line = NULL;
3123             if (!err)
3124             {
3125                this_line = strdup(filter_template);
3126                if (this_line == NULL) err = JB_ERR_MEMORY;
3127             }
3128             if (!err) err = template_fill(&this_line, line_exports);
3129             string_join(&result, this_line);
3130
3131             free_map(line_exports);
3132          }
3133       }
3134
3135       freez(filter_template);
3136
3137       if (!err)
3138       {
3139          err = map(exports, "filter-params", 1, result, 0);
3140       }
3141       else
3142       {
3143          freez(result);
3144       }
3145    }
3146
3147    if (!err) err = map_radio(exports, "filter-all", "nx",
3148       (cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] ? 'n' : 'x'));
3149
3150    edit_free_file(file);
3151
3152    if (err)
3153    {
3154       free_map(exports);
3155       return err;
3156    }
3157
3158    return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
3159 }
3160
3161
3162 /*********************************************************************
3163  *
3164  * Function    :  cgi_edit_actions_submit
3165  *
3166  * Description :  CGI function that actually edits the Actions list.
3167  *
3168  * Parameters  :
3169  *          1  :  csp = Current client state (buffers, headers, etc...)
3170  *          2  :  rsp = http_response data structure for output
3171  *          3  :  parameters = map of cgi parameters
3172  *
3173  * CGI Parameters : None
3174  *
3175  * Returns     :  JB_ERR_OK     on success
3176  *                JB_ERR_MEMORY on out-of-memory
3177  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3178  *                                  specified or not valid.
3179  *
3180  *********************************************************************/
3181 jb_err cgi_edit_actions_submit(struct client_state *csp,
3182                                struct http_response *rsp,
3183                                const struct map *parameters)
3184 {
3185    unsigned sectionid;
3186    char * actiontext;
3187    char * newtext;
3188    size_t len;
3189    struct editable_file * file;
3190    struct file_line * cur_line;
3191    unsigned line_number;
3192    char * target;
3193    jb_err err;
3194    int index;
3195    const char * action_set_name;
3196    char ch;
3197    struct file_list * fl;
3198    struct url_actions * b;
3199
3200    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3201    {
3202       return cgi_error_disabled(csp, rsp);
3203    }
3204
3205    err = get_number_param(csp, parameters, "s", &sectionid);
3206    if (err)
3207    {
3208       return err;
3209    }
3210
3211    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3212    if (err)
3213    {
3214       /* No filename specified, can't read file, modified, or out of memory. */
3215       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3216    }
3217
3218    cur_line = file->lines;
3219
3220    for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
3221    {
3222       cur_line = cur_line->next;
3223    }
3224
3225    if ( (cur_line == NULL)
3226      || (line_number != sectionid)
3227      || (sectionid < 1)
3228      || (cur_line->type != FILE_LINE_ACTION))
3229    {
3230       /* Invalid "sectionid" parameter */
3231       edit_free_file(file);
3232       return JB_ERR_CGI_PARAMS;
3233    }
3234
3235    get_string_param(parameters, "p", &action_set_name);
3236    if (action_set_name != NULL)
3237    {
3238       for (index = 0; index < MAX_ACTION_FILES; index++)
3239       {
3240          if (((fl = csp->actions_list[index]) != NULL) && ((b = fl->f) != NULL))
3241          {
3242             for (b = b->next; NULL != b; b = b->next)
3243             {
3244                if (!strncmp(b->url->spec, "standard.", 9) && !strcmp(b->url->spec + 9, action_set_name))
3245                {
3246                   copy_action(cur_line->data.action, b->action); 
3247                   goto found;
3248                }
3249             }
3250          }
3251       }
3252       edit_free_file(file);
3253       return JB_ERR_CGI_PARAMS;
3254
3255       found: ;
3256    }
3257    else
3258    {
3259       err = actions_from_radio(parameters, cur_line->data.action);
3260    }
3261
3262    if(err)
3263    {
3264       /* Out of memory */
3265       edit_free_file(file);
3266       return err;
3267    }
3268
3269    ch = get_char_param(parameters, "filter_all");
3270    if (ch == 'N')
3271    {
3272       list_remove_all(cur_line->data.action->multi_add[ACTION_MULTI_FILTER]);
3273       list_remove_all(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER]);
3274       cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] = 1;
3275    }
3276    else if (ch == 'X')
3277    {
3278       cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] = 0;
3279    }
3280
3281    for (index = 0; !err; index++)
3282    {
3283       char key_value[30];
3284       char key_name[30];
3285       const char *name;
3286       char value;
3287
3288       /* Generate the keys */
3289       snprintf(key_value, sizeof(key_value), "filter_r%x", index);
3290       key_value[sizeof(key_value) - 1] = '\0';
3291       snprintf(key_name, sizeof(key_name), "filter_n%x", index);
3292       key_name[sizeof(key_name) - 1] = '\0';
3293
3294       err = get_string_param(parameters, key_name, &name);
3295       if (err) break;
3296
3297       if (name == NULL)
3298       {
3299          /* End of list */
3300          break;
3301       }
3302
3303       value = get_char_param(parameters, key_value);
3304       if (value == 'Y')
3305       {
3306          list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
3307          if (!err) err = enlist(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
3308          list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
3309       }
3310       else if (value == 'N')
3311       {
3312          list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
3313          if (!cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER])
3314          {
3315             list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
3316             if (!err) err = enlist(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
3317          }
3318       }
3319       else if (value == 'X')
3320       {
3321          list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
3322          list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
3323       }
3324    }
3325
3326    if(err)
3327    {
3328       /* Out of memory */
3329       edit_free_file(file);
3330       return err;
3331    }
3332
3333    if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
3334    {
3335       /* Out of memory */
3336       edit_free_file(file);
3337       return JB_ERR_MEMORY;
3338    }
3339
3340    len = strlen(actiontext);
3341    if (len == 0)
3342    {
3343       /*
3344        * Empty action - must special-case this.
3345        * Simply setting len to 1 is sufficient...
3346        */
3347       len = 1;
3348    }
3349
3350    if (NULL == (newtext = malloc(len + 2)))
3351    {
3352       /* Out of memory */
3353       free(actiontext);
3354       edit_free_file(file);
3355       return JB_ERR_MEMORY;
3356    }
3357    strcpy(newtext, actiontext);
3358    free(actiontext);
3359    newtext[0]       = '{';
3360    newtext[len]     = '}';
3361    newtext[len + 1] = '\0';
3362
3363    freez(cur_line->raw);
3364    freez(cur_line->unprocessed);
3365    cur_line->unprocessed = newtext;
3366
3367    err = edit_write_file(file);
3368    if (err)
3369    {
3370       /* Error writing file */
3371       if (err == JB_ERR_FILE)
3372       {
3373          /* Read-only file. */
3374          err = cgi_error_file_read_only(csp, rsp, file->identifier);
3375       }
3376       edit_free_file(file);
3377       return err;
3378    }
3379
3380    target = strdup(CGI_PREFIX "edit-actions-list?f=");
3381    string_append(&target, file->identifier);
3382    string_join(&target, section_target(sectionid));
3383
3384
3385    edit_free_file(file);
3386
3387    if (target == NULL)
3388    {
3389       /* Out of memory */
3390       return JB_ERR_MEMORY;
3391    }
3392
3393    rsp->status = strdup("302 Local Redirect from Privoxy");
3394    if (rsp->status == NULL)
3395    {
3396       free(target);
3397       return JB_ERR_MEMORY;
3398    }
3399    err = enlist_unique_header(rsp->headers, "Location", target);
3400    free(target);
3401
3402    return err;
3403 }
3404
3405
3406 /*********************************************************************
3407  *
3408  * Function    :  cgi_edit_actions_url
3409  *
3410  * Description :  CGI function that actually edits a URL pattern in
3411  *                an actions file.
3412  *
3413  * Parameters  :
3414  *          1  :  csp = Current client state (buffers, headers, etc...)
3415  *          2  :  rsp = http_response data structure for output
3416  *          3  :  parameters = map of cgi parameters
3417  *
3418  * CGI Parameters :
3419  *    filename : Identifies the file to edit
3420  *         ver : File's last-modified time
3421  *     section : Line number of section to edit
3422  *     pattern : Line number of pattern to edit
3423  *      newval : New value for pattern
3424  *
3425  * Returns     :  JB_ERR_OK     on success
3426  *                JB_ERR_MEMORY on out-of-memory
3427  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3428  *                                  specified or not valid.
3429  *
3430  *********************************************************************/
3431 jb_err cgi_edit_actions_url(struct client_state *csp,
3432                             struct http_response *rsp,
3433                             const struct map *parameters)
3434 {
3435    unsigned patternid;
3436    char * new_pattern;
3437    struct editable_file * file;
3438    struct file_line * cur_line;
3439    unsigned line_number;
3440    unsigned section_start_line_number = 0;
3441    char * target;
3442    jb_err err;
3443
3444    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3445    {
3446       return cgi_error_disabled(csp, rsp);
3447    }
3448
3449    err = get_number_param(csp, parameters, "p", &patternid);
3450    if (err)
3451    {
3452       return err;
3453    }
3454    if (patternid < 1U)
3455    {
3456       return JB_ERR_CGI_PARAMS;
3457    }
3458
3459    err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3460    if (err)
3461    {
3462       return err;
3463    }
3464
3465    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3466    if (err)
3467    {
3468       /* No filename specified, can't read file, modified, or out of memory. */
3469       free(new_pattern);
3470       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3471    }
3472
3473    line_number = 1;
3474    cur_line = file->lines;
3475
3476    while ((cur_line != NULL) && (line_number < patternid))
3477    {
3478       if (cur_line->type == FILE_LINE_ACTION)
3479       {
3480          section_start_line_number = line_number;
3481       }      
3482       cur_line = cur_line->next;
3483       line_number++;
3484    }
3485
3486    if ( (cur_line == NULL)
3487      || (cur_line->type != FILE_LINE_URL))
3488    {
3489       /* Invalid "patternid" parameter */
3490       free(new_pattern);
3491       edit_free_file(file);
3492       return JB_ERR_CGI_PARAMS;
3493    }
3494
3495    /* At this point, the line to edit is in cur_line */
3496
3497    freez(cur_line->raw);
3498    freez(cur_line->unprocessed);
3499    cur_line->unprocessed = new_pattern;
3500
3501    err = edit_write_file(file);
3502    if (err)
3503    {
3504       /* Error writing file */
3505       if (err == JB_ERR_FILE)
3506       {
3507          /* Read-only file. */
3508          err = cgi_error_file_read_only(csp, rsp, file->identifier);
3509       }
3510       edit_free_file(file);
3511       return err;
3512    }
3513
3514    target = strdup(CGI_PREFIX "edit-actions-list?f=");
3515    string_append(&target, file->identifier);
3516    string_join(&target, section_target(section_start_line_number));
3517
3518    edit_free_file(file);
3519
3520    if (target == NULL)
3521    {
3522       /* Out of memory */
3523       return JB_ERR_MEMORY;
3524    }
3525
3526    rsp->status = strdup("302 Local Redirect from Privoxy");
3527    if (rsp->status == NULL)
3528    {
3529       free(target);
3530       return JB_ERR_MEMORY;
3531    }
3532    err = enlist_unique_header(rsp->headers, "Location", target);
3533    free(target);
3534
3535    return err;
3536 }
3537
3538
3539 /*********************************************************************
3540  *
3541  * Function    :  cgi_edit_actions_add_url
3542  *
3543  * Description :  CGI function that actually adds a URL pattern to
3544  *                an actions file.
3545  *
3546  * Parameters  :
3547  *          1  :  csp = Current client state (buffers, headers, etc...)
3548  *          2  :  rsp = http_response data structure for output
3549  *          3  :  parameters = map of cgi parameters
3550  *
3551  * CGI Parameters :
3552  *    filename : Identifies the file to edit
3553  *         ver : File's last-modified time
3554  *     section : Line number of section to edit
3555  *      newval : New pattern
3556  *
3557  * Returns     :  JB_ERR_OK     on success
3558  *                JB_ERR_MEMORY on out-of-memory
3559  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3560  *                                  specified or not valid.
3561  *
3562  *********************************************************************/
3563 jb_err cgi_edit_actions_add_url(struct client_state *csp,
3564                                 struct http_response *rsp,
3565                                 const struct map *parameters)
3566 {
3567    unsigned sectionid;
3568    char * new_pattern;
3569    struct file_line * new_line;
3570    struct editable_file * file;
3571    struct file_line * cur_line;
3572    unsigned line_number;
3573    char * target;
3574    jb_err err;
3575
3576    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3577    {
3578       return cgi_error_disabled(csp, rsp);
3579    }
3580
3581    err = get_number_param(csp, parameters, "s", &sectionid);
3582    if (err)
3583    {
3584       return err;
3585    }
3586    if (sectionid < 1U)
3587    {
3588       return JB_ERR_CGI_PARAMS;
3589    }
3590
3591    err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3592    if (err)
3593    {
3594       return err;
3595    }
3596
3597    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3598    if (err)
3599    {
3600       /* No filename specified, can't read file, modified, or out of memory. */
3601       free(new_pattern);
3602       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3603    }
3604
3605    line_number = 1;
3606    cur_line = file->lines;
3607
3608    while ((cur_line != NULL) && (line_number < sectionid))
3609    {
3610       cur_line = cur_line->next;
3611       line_number++;
3612    }
3613
3614    if ( (cur_line == NULL)
3615      || (cur_line->type != FILE_LINE_ACTION))
3616    {
3617       /* Invalid "sectionid" parameter */
3618       free(new_pattern);
3619       edit_free_file(file);
3620       return JB_ERR_CGI_PARAMS;
3621    }
3622
3623    /* At this point, the section header is in cur_line - add after this. */
3624
3625    /* Allocate the new line */
3626    new_line = (struct file_line *)zalloc(sizeof(*new_line));
3627    if (new_line == NULL)
3628    {
3629       free(new_pattern);
3630       edit_free_file(file);
3631       return JB_ERR_MEMORY;
3632    }
3633
3634    /* Fill in the data members of the new line */
3635    new_line->raw = NULL;
3636    new_line->prefix = NULL;
3637    new_line->unprocessed = new_pattern;
3638    new_line->type = FILE_LINE_URL;
3639
3640    /* Link new_line into the list, after cur_line */
3641    new_line->next = cur_line->next;
3642    cur_line->next = new_line;
3643
3644    /* Done making changes, now commit */
3645
3646    err = edit_write_file(file);
3647    if (err)
3648    {
3649       /* Error writing file */
3650       if (err == JB_ERR_FILE)
3651       {
3652          /* Read-only file. */
3653          err = cgi_error_file_read_only(csp, rsp, file->identifier);
3654       }
3655       edit_free_file(file);
3656       return err;
3657    }
3658
3659    target = strdup(CGI_PREFIX "edit-actions-list?f=");
3660    string_append(&target, file->identifier);
3661    string_join(&target, section_target(sectionid));
3662
3663    edit_free_file(file);
3664
3665    if (target == NULL)
3666    {
3667       /* Out of memory */
3668       return JB_ERR_MEMORY;
3669    }
3670
3671    rsp->status = strdup("302 Local Redirect from Privoxy");
3672    if (rsp->status == NULL)
3673    {
3674       free(target);
3675       return JB_ERR_MEMORY;
3676    }
3677    err = enlist_unique_header(rsp->headers, "Location", target);
3678    free(target);
3679
3680    return err;
3681 }
3682
3683
3684 /*********************************************************************
3685  *
3686  * Function    :  cgi_edit_actions_remove_url
3687  *
3688  * Description :  CGI function that actually removes a URL pattern from
3689  *                the actions file.
3690  *
3691  * Parameters  :
3692  *          1  :  csp = Current client state (buffers, headers, etc...)
3693  *          2  :  rsp = http_response data structure for output
3694  *          3  :  parameters = map of cgi parameters
3695  *
3696  * CGI Parameters :
3697  *           f : (filename) Identifies the file to edit
3698  *           v : (version) File's last-modified time
3699  *           p : (pattern) Line number of pattern to remove
3700  *
3701  * Returns     :  JB_ERR_OK     on success
3702  *                JB_ERR_MEMORY on out-of-memory
3703  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3704  *                                  specified or not valid.
3705  *
3706  *********************************************************************/
3707 jb_err cgi_edit_actions_remove_url(struct client_state *csp,
3708                                    struct http_response *rsp,
3709                                    const struct map *parameters)
3710 {
3711    unsigned patternid;
3712    struct editable_file * file;
3713    struct file_line * cur_line;
3714    struct file_line * prev_line;
3715    unsigned line_number;
3716    unsigned section_start_line_number = 0;
3717    char * target;
3718    jb_err err;
3719
3720    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3721    {
3722       return cgi_error_disabled(csp, rsp);
3723    }
3724
3725    err = get_number_param(csp, parameters, "p", &patternid);
3726    if (err)
3727    {
3728       return err;
3729    }
3730
3731    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3732    if (err)
3733    {
3734       /* No filename specified, can't read file, modified, or out of memory. */
3735       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3736    }
3737
3738    line_number = 1;
3739    prev_line = NULL;
3740    cur_line = file->lines;
3741
3742    while ((cur_line != NULL) && (line_number < patternid))
3743    {
3744       if (cur_line->type == FILE_LINE_ACTION)
3745       {
3746          section_start_line_number = line_number;
3747       }
3748       prev_line = cur_line;
3749       cur_line = cur_line->next;
3750       line_number++;
3751    }
3752
3753    if ( (cur_line == NULL)
3754      || (prev_line == NULL)
3755      || (cur_line->type != FILE_LINE_URL))
3756    {
3757       /* Invalid "patternid" parameter */
3758       edit_free_file(file);
3759       return JB_ERR_CGI_PARAMS;
3760    }
3761
3762    /* At this point, the line to remove is in cur_line, and the previous
3763     * one is in prev_line
3764     */
3765
3766    /* Unlink cur_line */
3767    prev_line->next = cur_line->next;
3768    cur_line->next = NULL;
3769
3770    /* Free cur_line */
3771    edit_free_file_lines(cur_line);
3772
3773    err = edit_write_file(file);
3774    if (err)
3775    {
3776       /* Error writing file */
3777       if (err == JB_ERR_FILE)
3778       {
3779          /* Read-only file. */
3780          err = cgi_error_file_read_only(csp, rsp, file->identifier);
3781       }
3782       edit_free_file(file);
3783       return err;
3784    }
3785
3786    target = strdup(CGI_PREFIX "edit-actions-list?f=");
3787    string_append(&target, file->identifier);
3788    string_join(&target, section_target(section_start_line_number));
3789
3790    edit_free_file(file);
3791
3792    if (target == NULL)
3793    {
3794       /* Out of memory */
3795       return JB_ERR_MEMORY;
3796    }
3797
3798    rsp->status = strdup("302 Local Redirect from Privoxy");
3799    if (rsp->status == NULL)
3800    {
3801       free(target);
3802       return JB_ERR_MEMORY;
3803    }
3804    err = enlist_unique_header(rsp->headers, "Location", target);
3805    free(target);
3806
3807    return err;
3808 }
3809
3810
3811 /*********************************************************************
3812  *
3813  * Function    :  cgi_edit_actions_section_remove
3814  *
3815  * Description :  CGI function that actually removes a whole section from
3816  *                the actions file.  The section must be empty first
3817  *                (else JB_ERR_CGI_PARAMS).
3818  *
3819  * Parameters  :
3820  *          1  :  csp = Current client state (buffers, headers, etc...)
3821  *          2  :  rsp = http_response data structure for output
3822  *          3  :  parameters = map of cgi parameters
3823  *
3824  * CGI Parameters :
3825  *           f : (filename) Identifies the file to edit
3826  *           v : (version) File's last-modified time
3827  *           s : (section) Line number of section to edit
3828  *
3829  * Returns     :  JB_ERR_OK     on success
3830  *                JB_ERR_MEMORY on out-of-memory
3831  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3832  *                                  specified or not valid.
3833  *
3834  *********************************************************************/
3835 jb_err cgi_edit_actions_section_remove(struct client_state *csp,
3836                                        struct http_response *rsp,
3837                                        const struct map *parameters)
3838 {
3839    unsigned sectionid;
3840    struct editable_file * file;
3841    struct file_line * cur_line;
3842    struct file_line * prev_line;
3843    unsigned line_number;
3844    char * target;
3845    jb_err err;
3846
3847    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3848    {
3849       return cgi_error_disabled(csp, rsp);
3850    }
3851
3852    err = get_number_param(csp, parameters, "s", &sectionid);
3853    if (err)
3854    {
3855       return err;
3856    }
3857
3858    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3859    if (err)
3860    {
3861       /* No filename specified, can't read file, modified, or out of memory. */
3862       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3863    }
3864
3865    line_number = 1;
3866    cur_line = file->lines;
3867
3868    prev_line = NULL;
3869    while ((cur_line != NULL) && (line_number < sectionid))
3870    {
3871       prev_line = cur_line;
3872       cur_line = cur_line->next;
3873       line_number++;
3874    }
3875
3876    if ( (cur_line == NULL)
3877      || (cur_line->type != FILE_LINE_ACTION) )
3878    {
3879       /* Invalid "sectionid" parameter */
3880       edit_free_file(file);
3881       return JB_ERR_CGI_PARAMS;
3882    }
3883
3884    if ( (cur_line->next != NULL)
3885      && (cur_line->next->type == FILE_LINE_URL) )
3886    {
3887       /* Section not empty. */
3888       edit_free_file(file);
3889       return JB_ERR_CGI_PARAMS;
3890    }
3891
3892    /* At this point, the line to remove is in cur_line, and the previous
3893     * one is in prev_line
3894     */
3895
3896    /* Unlink cur_line */
3897    if (prev_line == NULL)
3898    {
3899       /* Removing the first line from the file */
3900       file->lines = cur_line->next;
3901    }
3902    else
3903    {
3904       prev_line->next = cur_line->next;
3905    }
3906    cur_line->next = NULL;
3907
3908    /* Free cur_line */
3909    edit_free_file_lines(cur_line);
3910
3911    err = edit_write_file(file);
3912    if (err)
3913    {
3914       /* Error writing file */
3915       if (err == JB_ERR_FILE)
3916       {
3917          /* Read-only file. */
3918          err = cgi_error_file_read_only(csp, rsp, file->identifier);
3919       }
3920       edit_free_file(file);
3921       return err;
3922    }
3923
3924    target = strdup(CGI_PREFIX "edit-actions-list?f=");
3925    string_append(&target, file->identifier);
3926
3927    edit_free_file(file);
3928
3929    if (target == NULL)
3930    {
3931       /* Out of memory */
3932       return JB_ERR_MEMORY;
3933    }
3934
3935    rsp->status = strdup("302 Local Redirect from Privoxy");
3936    if (rsp->status == NULL)
3937    {
3938       free(target);
3939       return JB_ERR_MEMORY;
3940    }
3941    err = enlist_unique_header(rsp->headers, "Location", target);
3942    free(target);
3943
3944    return err;
3945 }
3946
3947
3948 /*********************************************************************
3949  *
3950  * Function    :  cgi_edit_actions_section_add
3951  *
3952  * Description :  CGI function that adds a new empty section to
3953  *                an actions file.
3954  *
3955  * Parameters  :
3956  *          1  :  csp = Current client state (buffers, headers, etc...)
3957  *          2  :  rsp = http_response data structure for output
3958  *          3  :  parameters = map of cgi parameters
3959  *
3960  * CGI Parameters :
3961  *           f : (filename) Identifies the file to edit
3962  *           v : (version) File's last-modified time
3963  *           s : (section) Line number of section to add after, 0 for
3964  *               start of file.
3965  *
3966  * Returns     :  JB_ERR_OK     on success
3967  *                JB_ERR_MEMORY on out-of-memory
3968  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3969  *                                  specified or not valid.
3970  *
3971  *********************************************************************/
3972 jb_err cgi_edit_actions_section_add(struct client_state *csp,
3973                                     struct http_response *rsp,
3974                                     const struct map *parameters)
3975 {
3976    unsigned sectionid;
3977    struct file_line * new_line;
3978    char * new_text;
3979    struct editable_file * file;
3980    struct file_line * cur_line;
3981    unsigned line_number;
3982    char * target;
3983    jb_err err;
3984
3985    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3986    {
3987       return cgi_error_disabled(csp, rsp);
3988    }
3989
3990    err = get_number_param(csp, parameters, "s", &sectionid);
3991    if (err)
3992    {
3993       return err;
3994    }
3995
3996    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3997    if (err)
3998    {
3999       /* No filename specified, can't read file, modified, or out of memory. */
4000       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
4001    }
4002
4003    line_number = 1;
4004    cur_line = file->lines;
4005
4006    if (sectionid <= 1U)
4007    {
4008       /* Add to start of file */
4009       if (cur_line != NULL && cur_line->type != FILE_LINE_ACTION)
4010       {
4011          /* There's something in the file, find the line before the first
4012           * action.
4013           */
4014          while ( (cur_line->next != NULL)
4015               && (cur_line->next->type != FILE_LINE_ACTION) )
4016          {
4017             cur_line = cur_line->next;
4018             line_number++;
4019          }
4020       }
4021       else
4022       {
4023          /* File starts with action line, so insert at top */
4024          cur_line = NULL;
4025       }
4026    }
4027    else
4028    {
4029       /* Add after stated section. */
4030       while ((cur_line != NULL) && (line_number < sectionid))
4031       {
4032          cur_line = cur_line->next;
4033          line_number++;
4034       }
4035
4036       if ( (cur_line == NULL)
4037         || (cur_line->type != FILE_LINE_ACTION))
4038       {
4039          /* Invalid "sectionid" parameter */
4040          edit_free_file(file);
4041          return JB_ERR_CGI_PARAMS;
4042       }
4043
4044       /* Skip through the section to find the last line in it. */
4045       while ( (cur_line->next != NULL)
4046            && (cur_line->next->type != FILE_LINE_ACTION) )
4047       {
4048          cur_line = cur_line->next;
4049          line_number++;
4050       }
4051    }
4052
4053    /* At this point, the last line in the previous section is in cur_line
4054     * - add after this.  (Or if we need to add as the first line, cur_line
4055     * will be NULL).
4056     */
4057
4058    new_text = strdup("{}");
4059    if (NULL == new_text)
4060    {
4061       edit_free_file(file);
4062       return JB_ERR_MEMORY;
4063    }
4064
4065    /* Allocate the new line */
4066    new_line = (struct file_line *)zalloc(sizeof(*new_line));
4067    if (new_line == NULL)
4068    {
4069       free(new_text);
4070       edit_free_file(file);
4071       return JB_ERR_MEMORY;
4072    }
4073
4074    /* Fill in the data members of the new line */
4075    new_line->raw = NULL;
4076    new_line->prefix = NULL;
4077    new_line->unprocessed = new_text;
4078    new_line->type = FILE_LINE_ACTION;
4079
4080    if (cur_line != NULL)
4081    {
4082       /* Link new_line into the list, after cur_line */
4083       new_line->next = cur_line->next;
4084       cur_line->next = new_line;
4085    }
4086    else
4087    {
4088       /* Link new_line into the list, as first line */
4089       new_line->next = file->lines;
4090       file->lines = new_line;
4091    }
4092
4093    /* Done making changes, now commit */
4094
4095    err = edit_write_file(file);
4096    if (err)
4097    {
4098       /* Error writing file */
4099       if (err == JB_ERR_FILE)
4100       {
4101          /* Read-only file. */
4102          err = cgi_error_file_read_only(csp, rsp, file->identifier);
4103       }
4104       edit_free_file(file);
4105       return err;
4106    }
4107
4108    target = strdup(CGI_PREFIX "edit-actions-list?f=");
4109    string_append(&target, file->identifier);
4110
4111    edit_free_file(file);
4112
4113    if (target == NULL)
4114    {
4115       /* Out of memory */
4116       return JB_ERR_MEMORY;
4117    }
4118
4119    rsp->status = strdup("302 Local Redirect from Privoxy");
4120    if (rsp->status == NULL)
4121    {
4122       free(target);
4123       return JB_ERR_MEMORY;
4124    }
4125    err = enlist_unique_header(rsp->headers, "Location", target);
4126    free(target);
4127
4128    return err;
4129 }
4130
4131
4132 /*********************************************************************
4133  *
4134  * Function    :  cgi_edit_actions_section_swap
4135  *
4136  * Description :  CGI function that swaps the order of two sections
4137  *                in the actions file.  Note that this CGI can actually
4138  *                swap any two arbitrary sections, but the GUI interface
4139  *                currently only allows consecutive sections to be
4140  *                specified.
4141  *
4142  * Parameters  :
4143  *          1  :  csp = Current client state (buffers, headers, etc...)
4144  *          2  :  rsp = http_response data structure for output
4145  *          3  :  parameters = map of cgi parameters
4146  *
4147  * CGI Parameters :
4148  *           f : (filename) Identifies the file to edit
4149  *           v : (version) File's last-modified time
4150  *          s1 : (section1) Line number of first section to swap
4151  *          s2 : (section2) Line number of second section to swap
4152  *
4153  * Returns     :  JB_ERR_OK     on success
4154  *                JB_ERR_MEMORY on out-of-memory
4155  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
4156  *                                  specified or not valid.
4157  *
4158  *********************************************************************/
4159 jb_err cgi_edit_actions_section_swap(struct client_state *csp,
4160                                      struct http_response *rsp,
4161                                      const struct map *parameters)
4162 {
4163    unsigned section1;
4164    unsigned section2;
4165    struct editable_file * file;
4166    struct file_line * cur_line;
4167    struct file_line * prev_line;
4168    struct file_line * line_before_section1;
4169    struct file_line * line_start_section1;
4170    struct file_line * line_end_section1;
4171    struct file_line * line_after_section1;
4172    struct file_line * line_before_section2;
4173    struct file_line * line_start_section2;
4174    struct file_line * line_end_section2;
4175    struct file_line * line_after_section2;
4176    unsigned line_number;
4177    char * target;
4178    jb_err err;
4179
4180    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
4181    {
4182       return cgi_error_disabled(csp, rsp);
4183    }
4184
4185    err = get_number_param(csp, parameters, "s1", &section1);
4186    if (!err) err = get_number_param(csp, parameters, "s2", &section2);
4187    if (err)
4188    {
4189       return err;
4190    }
4191
4192    if (section1 > section2)
4193    {
4194       unsigned temp = section2;
4195       section2 = section1;
4196       section1 = temp;
4197    }
4198
4199    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
4200    if (err)
4201    {
4202       /* No filename specified, can't read file, modified, or out of memory. */
4203       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
4204    }
4205
4206    /* Start at the beginning... */
4207    line_number = 1;
4208    cur_line = file->lines;
4209    prev_line = NULL;
4210
4211    /* ... find section1 ... */
4212    while ((cur_line != NULL) && (line_number < section1))
4213    {
4214       prev_line = cur_line;
4215       cur_line = cur_line->next;
4216       line_number++;
4217    }
4218
4219    if ( (cur_line == NULL)
4220      || (cur_line->type != FILE_LINE_ACTION) )
4221    {
4222       /* Invalid "section1" parameter */
4223       edit_free_file(file);
4224       return JB_ERR_CGI_PARAMS;
4225    }
4226
4227    /* If no-op, we've validated params and can skip the rest. */
4228    if (section1 != section2)
4229    {
4230       /* ... find the end of section1 ... */
4231       line_before_section1 = prev_line;
4232       line_start_section1 = cur_line;
4233       do
4234       {
4235          prev_line = cur_line;
4236          cur_line = cur_line->next;
4237          line_number++;
4238       }
4239       while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
4240       line_end_section1 = prev_line;
4241       line_after_section1 = cur_line;
4242
4243       /* ... find section2 ... */
4244       while ((cur_line != NULL) && (line_number < section2))
4245       {
4246          prev_line = cur_line;
4247          cur_line = cur_line->next;
4248          line_number++;
4249       }
4250
4251       if ( (cur_line == NULL)
4252         || (cur_line->type != FILE_LINE_ACTION) )
4253       {
4254          /* Invalid "section2" parameter */
4255          edit_free_file(file);
4256          return JB_ERR_CGI_PARAMS;
4257       }
4258
4259       /* ... find the end of section2 ... */
4260       line_before_section2 = prev_line;
4261       line_start_section2 = cur_line;
4262       do
4263       {
4264          prev_line = cur_line;
4265          cur_line = cur_line->next;
4266          line_number++;
4267       }
4268       while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
4269       line_end_section2 = prev_line;
4270       line_after_section2 = cur_line;
4271
4272       /* Now have all the pointers we need. Do the swap. */
4273
4274       /* Change the pointer to section1 to point to section2 instead */
4275       if (line_before_section1 == NULL)
4276       {
4277          file->lines = line_start_section2;
4278       }
4279       else
4280       {
4281          line_before_section1->next = line_start_section2;
4282       }
4283
4284       if (line_before_section2 == line_end_section1)
4285       {
4286          /* Consecutive sections */
4287          line_end_section2->next = line_start_section1;
4288       }
4289       else
4290       {
4291          line_end_section2->next = line_after_section1;
4292          line_before_section2->next = line_start_section1;
4293       }
4294
4295       /* Set the pointer from the end of section1 to the rest of the file */
4296       line_end_section1->next = line_after_section2;
4297
4298       err = edit_write_file(file);
4299       if (err)
4300       {
4301          /* Error writing file */
4302          if (err == JB_ERR_FILE)
4303          {
4304             /* Read-only file. */
4305             err = cgi_error_file_read_only(csp, rsp, file->identifier);
4306          }
4307          edit_free_file(file);
4308          return err;
4309       }
4310    } /* END if (section1 != section2) */
4311
4312    target = strdup(CGI_PREFIX "edit-actions-list?f=");
4313    string_append(&target, file->identifier);
4314
4315    edit_free_file(file);
4316
4317    if (target == NULL)
4318    {
4319       /* Out of memory */
4320       return JB_ERR_MEMORY;
4321    }
4322
4323    rsp->status = strdup("302 Local Redirect from Privoxy");
4324    if (rsp->status == NULL)
4325    {
4326       free(target);
4327       return JB_ERR_MEMORY;
4328    }
4329    err = enlist_unique_header(rsp->headers, "Location", target);
4330    free(target);
4331
4332    return err;
4333 }
4334
4335
4336 /*********************************************************************
4337  *
4338  * Function    :  cgi_toggle
4339  *
4340  * Description :  CGI function that adds a new empty section to
4341  *                an actions file.
4342  *
4343  * Parameters  :
4344  *          1  :  csp = Current client state (buffers, headers, etc...)
4345  *          2  :  rsp = http_response data structure for output
4346  *          3  :  parameters = map of cgi parameters
4347  *
4348  * CGI Parameters :
4349  *         set : If present, how to change toggle setting:
4350  *               "enable", "disable", "toggle", or none (default).
4351  *        mini : If present, use mini reply template.
4352  *
4353  * Returns     :  JB_ERR_OK     on success
4354  *                JB_ERR_MEMORY on out-of-memory
4355  *
4356  *********************************************************************/
4357 jb_err cgi_toggle(struct client_state *csp,
4358                   struct http_response *rsp,
4359                   const struct map *parameters)
4360 {
4361    struct map *exports;
4362    char mode;
4363    const char *template_name;
4364
4365    assert(csp);
4366    assert(rsp);
4367    assert(parameters);
4368
4369    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE))
4370    {
4371       return cgi_error_disabled(csp, rsp);
4372    }
4373
4374    mode = get_char_param(parameters, "set");
4375
4376    if (mode == 'E')
4377    {
4378       /* Enable */
4379       g_bToggleIJB = 1;
4380    }
4381    else if (mode == 'D')
4382    {
4383       /* Disable */
4384       g_bToggleIJB = 0;
4385    }
4386    else if (mode == 'T')
4387    {
4388       /* Toggle */
4389       g_bToggleIJB = !g_bToggleIJB;
4390    }
4391
4392    if (NULL == (exports = default_exports(csp, "toggle")))
4393    {
4394       return JB_ERR_MEMORY;
4395    }
4396
4397    template_name = (get_char_param(parameters, "mini")
4398                  ? "toggle-mini"
4399                  : "toggle");
4400
4401    return template_fill_for_cgi(csp, template_name, exports, rsp);
4402 }
4403
4404
4405 /*********************************************************************
4406  *
4407  * Function    :  javascriptify
4408  *
4409  * Description :  Converts a string into a form JavaScript will like.
4410  *
4411  *                Netscape 4's JavaScript sucks - it doesn't use 
4412  *                "id" parameters, so you have to set the "name"
4413  *                used to submit a form element to something JavaScript
4414  *                will like.  (Or access the elements by index in an
4415  *                array.  That array contains >60 elements and will
4416  *                be changed whenever we add a new action to the
4417  *                editor, so I'm NOT going to use indexes that have
4418  *                to be figured out by hand.)
4419  *
4420  *                Currently the only thing we have to worry about
4421  *                is "-" ==> "_" conversion.
4422  *
4423  *                This is a length-preserving operation so it is
4424  *                carried out in-place, no memory is allocated
4425  *                or freed.
4426  *
4427  * Parameters  :
4428  *          1  :  identifier = String to make JavaScript-friendly.
4429  *
4430  * Returns     :  N/A
4431  *
4432  *********************************************************************/
4433 static void javascriptify(char * identifier)
4434 {
4435    char * p = identifier;
4436    while (NULL != (p = strchr(p, '-')))
4437    {
4438       *p++ = '_';
4439    }
4440 }
4441
4442
4443 /*********************************************************************
4444  *
4445  * Function    :  actions_to_radio
4446  *
4447  * Description :  Converts a actionsfile entry into settings for
4448  *                radio buttons and edit boxes on a HTML form.
4449  *
4450  * Parameters  :
4451  *          1  :  exports = List of substitutions to add to.
4452  *          2  :  action  = Action to read
4453  *
4454  * Returns     :  JB_ERR_OK     on success
4455  *                JB_ERR_MEMORY on out-of-memory
4456  *
4457  *********************************************************************/
4458 static jb_err actions_to_radio(struct map * exports,
4459                                const struct action_spec *action)
4460 {
4461    unsigned mask = action->mask;
4462    unsigned add  = action->add;
4463    int mapped_param;
4464    int checked;
4465    char current_mode;
4466
4467    assert(exports);
4468    assert(action);
4469
4470    mask = action->mask;
4471    add  = action->add;
4472
4473    /* sanity - prevents "-feature +feature" */
4474    mask |= add;
4475
4476
4477 #define DEFINE_ACTION_BOOL(name, bit)                 \
4478    if (!(mask & bit))                                 \
4479    {                                                  \
4480       current_mode = 'n';                             \
4481    }                                                  \
4482    else if (add & bit)                                \
4483    {                                                  \
4484       current_mode = 'y';                             \
4485    }                                                  \
4486    else                                               \
4487    {                                                  \
4488       current_mode = 'x';                             \
4489    }                                                  \
4490    if (map_radio(exports, name, "ynx", current_mode)) \
4491    {                                                  \
4492       return JB_ERR_MEMORY;                           \
4493    }
4494
4495 #define DEFINE_ACTION_STRING(name, bit, index)        \
4496    DEFINE_ACTION_BOOL(name, bit);                     \
4497    mapped_param = 0;
4498
4499 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default)  \
4500    if (add & bit)                                                    \
4501    {                                                                 \
4502       checked = !strcmp(action->string[index], value);               \
4503    }                                                                 \
4504    else                                                              \
4505    {                                                                 \
4506       checked = is_default;                                          \
4507    }                                                                 \
4508    mapped_param |= checked;                                          \
4509    if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
4510    {                                                                 \
4511       return JB_ERR_MEMORY;                                          \
4512    }
4513
4514 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val)       \
4515    if (map(exports, name "-param-custom", 1,                         \
4516            ((!mapped_param) ? "checked" : ""), 1))                   \
4517    {                                                                 \
4518       return JB_ERR_MEMORY;                                          \
4519    }                                                                 \
4520    if (map(exports, name "-param", 1,                                \
4521            (((add & bit) && !mapped_param) ?                         \
4522            action->string[index] : default_val), 1))                 \
4523    {                                                                 \
4524       return JB_ERR_MEMORY;                                          \
4525    }
4526
4527 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val)     \
4528    if (map(exports, name "-param", 1,                                \
4529            ((add & bit) ? action->string[index] : default_val), 1))  \
4530    {                                                                 \
4531       return JB_ERR_MEMORY;                                          \
4532    }
4533
4534 #define DEFINE_ACTION_MULTI(name, index)              \
4535    if (action->multi_add[index]->first)               \
4536    {                                                  \
4537       current_mode = 'y';                             \
4538    }                                                  \
4539    else if (action->multi_remove_all[index])          \
4540    {                                                  \
4541       current_mode = 'n';                             \
4542    }                                                  \
4543    else if (action->multi_remove[index]->first)       \
4544    {                                                  \
4545       current_mode = 'y';                             \
4546    }                                                  \
4547    else                                               \
4548    {                                                  \
4549       current_mode = 'x';                             \
4550    }                                                  \
4551    if (map_radio(exports, name, "ynx", current_mode)) \
4552    {                                                  \
4553       return JB_ERR_MEMORY;                           \
4554    }
4555
4556 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
4557
4558 #include "actionlist.h"
4559
4560 #undef DEFINE_ACTION_MULTI
4561 #undef DEFINE_ACTION_STRING
4562 #undef DEFINE_ACTION_BOOL
4563 #undef DEFINE_ACTION_ALIAS
4564 #undef DEFINE_CGI_PARAM_CUSTOM
4565 #undef DEFINE_CGI_PARAM_RADIO
4566 #undef DEFINE_CGI_PARAM_NO_RADIO
4567
4568    return JB_ERR_OK;
4569 }
4570
4571
4572 /*********************************************************************
4573  *
4574  * Function    :  actions_from_radio
4575  *
4576  * Description :  Converts a map of parameters passed to a CGI function
4577  *                into an actionsfile entry.
4578  *
4579  * Parameters  :
4580  *          1  :  parameters = parameters to the CGI call
4581  *          2  :  action  = Action to change.  Must be valid before
4582  *                          the call, actions not specified will be
4583  *                          left unchanged.
4584  *
4585  * Returns     :  JB_ERR_OK     on success
4586  *                JB_ERR_MEMORY on out-of-memory
4587  *
4588  *********************************************************************/
4589 static jb_err actions_from_radio(const struct map * parameters,
4590                                  struct action_spec *action)
4591 {
4592    const char * param;
4593    char * param_dup;
4594    char ch;
4595    const char * js_name;
4596    jb_err err = JB_ERR_OK;
4597
4598    assert(parameters);
4599    assert(action);
4600
4601    /* Statics are generally a potential race condition,
4602     * but in this case we're safe and don't need semaphores.
4603     * Be careful if you modify this function.
4604     * - Jon
4605     * The js_name_arr's are never free()d, but this is no
4606     * problem, since they will only be created once and
4607     * used by all threads thereafter. -oes
4608     */
4609
4610 #define JAVASCRIPTIFY(dest_var, string)               \
4611    {                                                  \
4612      static int first_time = 1;                       \
4613      static char *js_name_arr;                        \
4614       if (first_time)                                 \
4615       {                                               \
4616          js_name_arr = strdup(string);                \
4617          javascriptify(js_name_arr);                  \
4618       }                                               \
4619       dest_var = js_name_arr;                         \
4620       first_time = 0;                                 \
4621    }                                                  \
4622
4623 #define DEFINE_ACTION_BOOL(name, bit)                 \
4624    JAVASCRIPTIFY(js_name, name);                      \
4625    ch = get_char_param(parameters, js_name);          \
4626    if (ch == 'Y')                                     \
4627    {                                                  \
4628       action->add  |= bit;                            \
4629       action->mask |= bit;                            \
4630    }                                                  \
4631    else if (ch == 'N')                                \
4632    {                                                  \
4633       action->add  &= ~bit;                           \
4634       action->mask &= ~bit;                           \
4635    }                                                  \
4636    else if (ch == 'X')                                \
4637    {                                                  \
4638       action->add  &= ~bit;                           \
4639       action->mask |= bit;                            \
4640    }                                                  \
4641
4642 #define DEFINE_ACTION_STRING(name, bit, index)                 \
4643    JAVASCRIPTIFY(js_name, name);                               \
4644    ch = get_char_param(parameters, js_name);                   \
4645    if (ch == 'Y')                                              \
4646    {                                                           \
4647       param = NULL;                                            \
4648       JAVASCRIPTIFY(js_name, name "-mode");                    \
4649       if (!err) err = get_string_param(parameters, js_name, &param);    \
4650       if ((param == NULL) || (0 == strcmp(param, "CUSTOM")))            \
4651       {                                                                 \
4652          JAVASCRIPTIFY(js_name, name "-param");                         \
4653          if (!err) err = get_string_param(parameters, js_name, &param); \
4654       }                                                        \
4655       if (param != NULL)                                       \
4656       {                                                        \
4657          if (NULL == (param_dup = strdup(param)))              \
4658          {                                                     \
4659             return JB_ERR_MEMORY;                              \
4660          }                                                     \
4661          freez(action->string[index]);                         \
4662          action->add  |= bit;                                  \
4663          action->mask |= bit;                                  \
4664          action->string[index] = param_dup;                    \
4665       }                                                        \
4666    }                                                           \
4667    else if (ch == 'N')                                         \
4668    {                                                           \
4669       if (action->add & bit)                                   \
4670       {                                                        \
4671          freez(action->string[index]);                         \
4672       }                                                        \
4673       action->add  &= ~bit;                                    \
4674       action->mask &= ~bit;                                    \
4675    }                                                           \
4676    else if (ch == 'X')                                         \
4677    {                                                           \
4678       if (action->add & bit)                                   \
4679       {                                                        \
4680          freez(action->string[index]);                         \
4681       }                                                        \
4682       action->add  &= ~bit;                                    \
4683       action->mask |= bit;                                     \
4684    }                                                           \
4685
4686 #define DEFINE_ACTION_MULTI(name, index)                       \
4687    JAVASCRIPTIFY(js_name, name);                               \
4688    ch = get_char_param(parameters, js_name);                   \
4689    if (ch == 'Y')                                              \
4690    {                                                           \
4691       /* FIXME */                                              \
4692    }                                                           \
4693    else if (ch == 'N')                                         \
4694    {                                                           \
4695       list_remove_all(action->multi_add[index]);               \
4696       list_remove_all(action->multi_remove[index]);            \
4697       action->multi_remove_all[index] = 1;                     \
4698    }                                                           \
4699    else if (ch == 'X')                                         \
4700    {                                                           \
4701       list_remove_all(action->multi_add[index]);               \
4702       list_remove_all(action->multi_remove[index]);            \
4703       action->multi_remove_all[index] = 0;                     \
4704    }                                                           \
4705
4706 #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
4707
4708 #include "actionlist.h"
4709
4710 #undef DEFINE_ACTION_MULTI
4711 #undef DEFINE_ACTION_STRING
4712 #undef DEFINE_ACTION_BOOL
4713 #undef DEFINE_ACTION_ALIAS
4714 #undef JAVASCRIPTIFY
4715
4716    return err;
4717 }
4718
4719
4720 #endif /* def FEATURE_CGI_EDIT_ACTIONS */
4721
4722
4723 /*
4724   Local Variables:
4725   tab-width: 3
4726   end:
4727 */