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