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