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