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