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