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