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