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