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