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