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