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