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