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