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