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