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