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