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