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