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