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