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