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