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