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