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