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