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