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