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