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