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