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