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