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