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