ff14dbbcafd07e89ae59714a861068bcd242c244
[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,
2801          cur_line->data.action, filter_template, &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                assert(multi_action_index < ACTION_MULTI_COUNT);
2905
2906                filter_name = cur_line->data.action->multi_add[multi_action_index]->first;
2907                while ((filter_name != NULL)
2908                    && (0 != strcmp(filter_group->name, filter_name->str)))
2909                {
2910                     filter_name = filter_name->next;
2911                }
2912
2913                if (filter_name != NULL)
2914                {
2915                   current_mode = 'y';
2916                }
2917                else
2918                {
2919                   filter_name = cur_line->data.action->multi_remove[multi_action_index]->first;
2920                   while ((filter_name != NULL)
2921                       && (0 != strcmp(filter_group->name, filter_name->str)))
2922                   {
2923                        filter_name = filter_name->next;
2924                   }
2925                   if (filter_name != NULL)
2926                   {
2927                      current_mode = 'n';
2928                   }
2929                }
2930
2931                /* Generate a unique serial number */
2932                snprintf(number, sizeof(number), "%x", filter_identifier++);
2933                number[sizeof(number) - 1] = '\0';
2934
2935                line_exports = new_map();
2936                if (line_exports == NULL)
2937                {
2938                   err = JB_ERR_MEMORY;
2939                }
2940                else
2941                {
2942                   char *filter_line;
2943
2944                   if (!err) err = map(line_exports, "index", 1, number, 1);
2945                   if (!err) err = map(line_exports, "name",  1, filter_group->name, 1);
2946                   if (!err) err = map(line_exports, "description",  1, filter_group->description, 1);
2947                   if (!err) err = map_radio(line_exports, "this-filter", "ynx", current_mode);
2948                   if (!err) err = map(line_exports, "filter-type", 1, action_type_info[type].type, 1);
2949                   if (!err) err = map(line_exports, "abbr-action-type", 1, action_type_info[type].abbr_type, 1);
2950                   if (!err) err = map(line_exports, "anchor", 1, action_type_info[type].anchor, 1);
2951
2952                   if (!err)
2953                   {
2954                      filter_line = strdup(filter_template);
2955                      if (filter_line == NULL) err = JB_ERR_MEMORY;
2956                   }
2957                   if (!err) err = template_fill(&filter_line, line_exports);
2958                   if (!err) err = string_join(&prepared_templates[type], filter_line);
2959
2960                   free_map(line_exports);
2961                }
2962             }
2963          }
2964       }
2965       freez(filter_template);
2966
2967       /* Replace all filter macros with the aggregated templates */
2968       for (i = 0; i < MAX_FILTER_TYPES; i++)
2969       {
2970          if (err) break;
2971          err = map(exports, action_type_info[i].macro_name, 1, prepared_templates[i], 0);
2972       }
2973
2974       if (err)
2975       {
2976          /* Free aggregated templates */
2977          for (i = 0; i < MAX_FILTER_TYPES; i++)
2978          {
2979             freez(prepared_templates[i]);
2980          }
2981       }
2982    }
2983
2984    /* Check or uncheck the "disable all of this type" radio buttons. */
2985    for (i = 0; i < MAX_FILTER_TYPES; i++)
2986    {
2987       const int a = action_type_info[i].multi_action_index;
2988       const int disable_all = cur_line->data.action->multi_remove_all[a];
2989       if (err) break;
2990       err = map_radio(exports, action_type_info[i].disable_all_option, "nx", (disable_all ? 'n' : 'x'));
2991    }
2992
2993    edit_free_file(file);
2994
2995    if (err)
2996    {
2997       free_map(exports);
2998       return err;
2999    }
3000
3001    return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
3002 }
3003
3004
3005 /*********************************************************************
3006  *
3007  * Function    :  get_number_of_filters
3008  *
3009  * Description :  Counts the number of filter available.
3010  *
3011  * Parameters  :
3012  *          1  :  csp = Current client state (buffers, headers, etc...)
3013  *
3014  * Returns     :  Number of filters available.
3015  *
3016  *********************************************************************/
3017 static int get_number_of_filters(const struct client_state *csp)
3018 {
3019    int i;
3020    struct re_filterfile_spec *b;
3021    struct file_list *fl;
3022    int number_of_filters = 0;
3023
3024    for (i = 0; i < MAX_AF_FILES; i++)
3025    {
3026      fl = csp->rlist[i];
3027      if ((NULL == fl) || (NULL == fl->f))
3028      {
3029         /*
3030          * Either there are no filter files left or this
3031          * filter file just contains no valid filters.
3032          *
3033          * Continue to be sure we don't miss valid filter
3034          * files that are chained after empty or invalid ones.
3035          */
3036         continue;
3037      }
3038
3039      for (b = fl->f; b != NULL; b = b->next)
3040      {
3041         number_of_filters++;
3042      }
3043    }
3044
3045    return number_of_filters;
3046
3047 }
3048
3049
3050 /*********************************************************************
3051  *
3052  * Function    :  cgi_edit_process_string_action
3053  *
3054  * Description :  Helper CGI function that actually edits the Actions list for
3055  *                the action string parameters.
3056  *
3057  * Parameters  :
3058  *          1  :  csp = Current client state (buffers, headers, etc...)
3059  *          2  :  rsp = http_response data structure for output
3060  *          3  :  parameters = map of cgi parameters
3061  *          4  :  cur_line = current config file line
3062  *          5  :  action_type = string filter type to process
3063  *
3064  * Returns     :  JB_ERR_OK     on success
3065  *                JB_ERR_MEMORY on out-of-memory
3066  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3067  *                                  specified or not valid.
3068  *
3069  *********************************************************************/
3070 static jb_err cgi_edit_process_string_action(struct client_state *csp,
3071                                              struct http_response *rsp,
3072                                              const struct map *parameters,
3073                                              struct file_line *cur_line,
3074                                              enum filter_type action_type)
3075 {
3076    jb_err err = JB_ERR_OK;
3077    const char *abbr_type = action_type_info[action_type].abbr_type;
3078    int action_identifier = 0;
3079
3080    /* process existing string filter actions */
3081    for (action_identifier = 0; !err; action_identifier++)
3082    {
3083       char key_value[30];
3084       char key_name[30];
3085       char old_name[30];
3086       char key_type[30];
3087       const char *name, *new_name;
3088       char value; /*
3089                    * Action state. Valid states are: 'Y' (active),
3090                    * 'N' (inactive) and 'X' (no change).
3091                    * XXX: bad name.
3092                    */
3093       char type;  /*
3094                    * Abbreviated filter type. Valid types are: 'U' (suppress tag), 'H' (add header)
3095                    */
3096       int multi_action_index = 0;
3097
3098       /* Generate the keys */
3099       snprintf(key_value, sizeof(key_value), "string_action_%s_r%x", abbr_type, action_identifier);
3100       snprintf(key_name, sizeof(key_name), "string_action_%s_n%x", abbr_type, action_identifier);
3101       snprintf(old_name, sizeof(old_name), "string_action_%s_o%x", abbr_type, action_identifier);
3102       snprintf(key_type, sizeof(key_type), "string_action_%s_t%x", abbr_type, action_identifier);
3103
3104       err = get_string_param(parameters, old_name, &name);
3105       if (err) break;
3106
3107       if (name == NULL)
3108       {
3109          /* The action identifier isn't present: we're done! */
3110          break;
3111       }
3112
3113       err = get_string_param(parameters, key_name, &new_name);
3114       if (err) break;
3115       if (new_name == NULL) new_name = name;
3116
3117       type = get_char_param(parameters, key_type);
3118       switch (type)
3119       {
3120          case 'U':
3121             multi_action_index = ACTION_MULTI_SUPPRESS_TAG;
3122             break;
3123          case 'H':
3124             multi_action_index = ACTION_MULTI_ADD_HEADER;
3125             break;
3126          default:
3127             log_error(LOG_LEVEL_ERROR,
3128                "Unknown action type: %c for action %s. Action ignored.", type, name);
3129             continue;
3130       }
3131
3132       value = get_char_param(parameters, key_value);
3133       if (value == 'X' || value == 'Y' || value == 'N')
3134       {
3135          list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3136          list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3137       }
3138
3139       if (value == 'Y')
3140       {
3141          err = enlist(cur_line->data.action->multi_add[multi_action_index], new_name);
3142       }
3143       else if (value == 'N')
3144       {
3145          err = enlist(cur_line->data.action->multi_remove[multi_action_index], new_name);
3146       }
3147    }
3148
3149    /* process new string filter actions */
3150    for (action_identifier = 0; !err; action_identifier++)
3151    {
3152       char key_value[30];
3153       char key_name[30];
3154       char key_type[30];
3155       const char *name;
3156       char value; /*
3157                    * Action state. Valid states are: 'Y' (active),
3158                    * 'N' (inactive) and 'X' (no change).
3159                    * XXX: bad name.
3160                    */
3161       char type;  /*
3162                    * Abbreviated filter type. Valid types are: 'U' (suppress tag), 'H' (add header)
3163                    */
3164       int multi_action_index = 0;
3165
3166       /* Generate the keys */
3167       snprintf(key_value, sizeof(key_value), "new_string_action_%s_r%x", abbr_type, action_identifier);
3168       snprintf(key_name, sizeof(key_name), "new_string_action_%s_n%x", abbr_type, action_identifier);
3169       snprintf(key_type, sizeof(key_type), "new_string_action_%s_t%x", abbr_type, action_identifier);
3170
3171       err = get_string_param(parameters, key_name, &name);
3172       if (err) break;
3173
3174       if (name == NULL)
3175       {
3176          /* The action identifier isn't present: we've done! */
3177          break;
3178       }
3179
3180       type = get_char_param(parameters, key_type);
3181       switch (type)
3182       {
3183          case 'U':
3184             multi_action_index = ACTION_MULTI_SUPPRESS_TAG;
3185             break;
3186          case 'H':
3187             multi_action_index = ACTION_MULTI_ADD_HEADER;
3188             break;
3189          default:
3190             log_error(LOG_LEVEL_ERROR,
3191                "Unknown filter type: %c for filter %s. Filter ignored.", type, name);
3192             continue;
3193       }
3194
3195       value = get_char_param(parameters, key_value);
3196       if (value == 'Y')
3197       {
3198          list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3199          if (!err) err = enlist(cur_line->data.action->multi_add[multi_action_index], name);
3200          list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3201       }
3202       else if (value == 'N')
3203       {
3204          list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3205          list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3206          if (!err) err = enlist(cur_line->data.action->multi_remove[multi_action_index], name);
3207       }
3208       /* nothing to do if the value is 'X' */
3209    }
3210    return err;
3211 }
3212
3213
3214 /*********************************************************************
3215  *
3216  * Function    :  cgi_edit_actions_submit
3217  *
3218  * Description :  CGI function that actually edits the Actions list.
3219  *
3220  * Parameters  :
3221  *          1  :  csp = Current client state (buffers, headers, etc...)
3222  *          2  :  rsp = http_response data structure for output
3223  *          3  :  parameters = map of cgi parameters
3224  *
3225  * CGI Parameters : None
3226  *
3227  * Returns     :  JB_ERR_OK     on success
3228  *                JB_ERR_MEMORY on out-of-memory
3229  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3230  *                                  specified or not valid.
3231  *
3232  *********************************************************************/
3233 jb_err cgi_edit_actions_submit(struct client_state *csp,
3234                                struct http_response *rsp,
3235                                const struct map *parameters)
3236 {
3237    unsigned sectionid;
3238    char * actiontext;
3239    char * newtext;
3240    size_t newtext_size;
3241    size_t len;
3242    struct editable_file * file;
3243    struct file_line * cur_line;
3244    unsigned line_number;
3245    char target[1024];
3246    jb_err err;
3247    int filter_identifier;
3248    int i;
3249    const char * action_set_name;
3250    struct file_list * fl;
3251    struct url_actions * b;
3252    int number_of_filters;
3253
3254    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3255    {
3256       return cgi_error_disabled(csp, rsp);
3257    }
3258
3259    err = get_number_param(csp, parameters, "s", &sectionid);
3260    if (err)
3261    {
3262       return err;
3263    }
3264
3265    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3266    if (err)
3267    {
3268       /* No filename specified, can't read file, modified, or out of memory. */
3269       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3270    }
3271
3272    cur_line = file->lines;
3273
3274    for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
3275    {
3276       cur_line = cur_line->next;
3277    }
3278
3279    if ( (cur_line == NULL)
3280      || (line_number != sectionid)
3281      || (sectionid < 1)
3282      || (cur_line->type != FILE_LINE_ACTION))
3283    {
3284       /* Invalid "sectionid" parameter */
3285       edit_free_file(file);
3286       return JB_ERR_CGI_PARAMS;
3287    }
3288
3289    get_string_param(parameters, "p", &action_set_name);
3290    if (action_set_name != NULL)
3291    {
3292       for (filter_identifier = 0; filter_identifier < MAX_AF_FILES; filter_identifier++)
3293       {
3294          if (((fl = csp->actions_list[filter_identifier]) != NULL) && ((b = fl->f) != NULL))
3295          {
3296             for (b = b->next; NULL != b; b = b->next)
3297             {
3298                if (!strncmp(b->url->spec, "standard.", 9) && !strcmp(b->url->spec + 9, action_set_name))
3299                {
3300                   copy_action(cur_line->data.action, b->action);
3301                   goto found;
3302                }
3303             }
3304          }
3305       }
3306       edit_free_file(file);
3307       return JB_ERR_CGI_PARAMS;
3308
3309       found: ;
3310    }
3311    else
3312    {
3313       err = actions_from_radio(parameters, cur_line->data.action);
3314    }
3315
3316    if (err)
3317    {
3318       /* Out of memory */
3319       edit_free_file(file);
3320       return err;
3321    }
3322
3323    /* Check the "disable all of this type" parameters. */
3324    for (i = 0; i < MAX_FILTER_TYPES; i++)
3325    {
3326       const int multi_action_index = action_type_info[i].multi_action_index;
3327       const char ch = get_char_param(parameters, action_type_info[i].disable_all_param);
3328
3329       if (ch == 'N')
3330       {
3331          list_remove_all(cur_line->data.action->multi_add[multi_action_index]);
3332          list_remove_all(cur_line->data.action->multi_remove[multi_action_index]);
3333          cur_line->data.action->multi_remove_all[multi_action_index] = 1;
3334       }
3335       else if (ch == 'X')
3336       {
3337          cur_line->data.action->multi_remove_all[multi_action_index] = 0;
3338       }
3339    }
3340
3341    number_of_filters = get_number_of_filters(csp);
3342
3343    for (filter_identifier = 0; filter_identifier < number_of_filters && !err; filter_identifier++)
3344    {
3345       char key_value[30];
3346       char key_name[30];
3347       char key_type[30];
3348       const char *name;
3349       char value; /*
3350                    * Filter state. Valid states are: 'Y' (active),
3351                    * 'N' (inactive) and 'X' (no change).
3352                    * XXX: bad name.
3353                    */
3354       char type;  /*
3355                    * Abbreviated filter type. Valid types are: 'F' (content filter),
3356                    * 'S' (server-header filter) and 'C' (client-header filter).
3357                    */
3358       int multi_action_index = 0;
3359
3360       /* Generate the keys */
3361       snprintf(key_value, sizeof(key_value), "filter_r%x", filter_identifier);
3362       key_value[sizeof(key_value) - 1] = '\0'; /* XXX: Why? */
3363       snprintf(key_name, sizeof(key_name), "filter_n%x", filter_identifier);
3364       key_name[sizeof(key_name) - 1] = '\0'; /* XXX: Why? */
3365       snprintf(key_type, sizeof(key_type), "filter_t%x", filter_identifier);
3366
3367       err = get_string_param(parameters, key_name, &name);
3368       if (err) break;
3369
3370       if (name == NULL)
3371       {
3372          /* The filter identifier isn't present. Try the next one. */
3373          continue;
3374       }
3375
3376       type = get_char_param(parameters, key_type);
3377       switch (type)
3378       {
3379          case 'F':
3380             multi_action_index = ACTION_MULTI_FILTER;
3381             break;
3382          case 'S':
3383             multi_action_index = ACTION_MULTI_SERVER_HEADER_FILTER;
3384             break;
3385          case 'C':
3386             multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER;
3387             break;
3388          case 'L':
3389             multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER;
3390             break;
3391          case 'E':
3392             multi_action_index = ACTION_MULTI_SERVER_HEADER_TAGGER;
3393             break;
3394          case 'P':
3395             multi_action_index = ACTION_MULTI_CLIENT_BODY_FILTER;
3396             break;
3397          default:
3398             log_error(LOG_LEVEL_ERROR,
3399                "Unknown filter type: %c for filter %s. Filter ignored.", type, name);
3400             continue;
3401       }
3402       assert(multi_action_index);
3403
3404       value = get_char_param(parameters, key_value);
3405       if (value == 'Y')
3406       {
3407          list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3408          if (!err) err = enlist(cur_line->data.action->multi_add[multi_action_index], name);
3409          list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3410       }
3411       else if (value == 'N')
3412       {
3413          list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3414          if (!cur_line->data.action->multi_remove_all[multi_action_index])
3415          {
3416             list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3417             if (!err) err = enlist(cur_line->data.action->multi_remove[multi_action_index], name);
3418          }
3419       }
3420       else if (value == 'X')
3421       {
3422          list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3423          list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3424       }
3425    }
3426
3427    /* process new string filters */
3428    for (i = 0; !err && i < SZ(string_action_type_info); i++)
3429    {
3430       err = cgi_edit_process_string_action(csp, rsp, parameters, cur_line,
3431                                            string_action_type_info[i].action_type);
3432    }
3433
3434    if (err)
3435    {
3436       /* Out of memory */
3437       edit_free_file(file);
3438       return err;
3439    }
3440
3441    if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
3442    {
3443       /* Out of memory */
3444       edit_free_file(file);
3445       return JB_ERR_MEMORY;
3446    }
3447
3448    len = strlen(actiontext);
3449    if (len == 0)
3450    {
3451       /*
3452        * Empty action - must special-case this.
3453        * Simply setting len to 1 is sufficient...
3454        */
3455       len = 1;
3456    }
3457
3458    newtext_size = len + 2;
3459    newtext = malloc_or_die(newtext_size);
3460    strlcpy(newtext, actiontext, newtext_size);
3461    free(actiontext);
3462    newtext[0]       = '{';
3463    newtext[len]     = '}';
3464    newtext[len + 1] = '\0';
3465
3466    freez(cur_line->raw);
3467    freez(cur_line->unprocessed);
3468    cur_line->unprocessed = newtext;
3469
3470    err = edit_write_file(file);
3471    if (err)
3472    {
3473       /* Error writing file */
3474       if (err == JB_ERR_FILE)
3475       {
3476          /* Read-only file. */
3477          err = cgi_error_file_read_only(csp, rsp, file->filename);
3478       }
3479       edit_free_file(file);
3480       return err;
3481    }
3482
3483    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3484             (unsigned long) time(NULL), file->identifier, sectionid);
3485
3486    edit_free_file(file);
3487
3488    return cgi_redirect(rsp, target);
3489 }
3490
3491
3492 /*********************************************************************
3493  *
3494  * Function    :  cgi_edit_actions_url
3495  *
3496  * Description :  CGI function that actually edits a URL pattern in
3497  *                an actions file.
3498  *
3499  * Parameters  :
3500  *          1  :  csp = Current client state (buffers, headers, etc...)
3501  *          2  :  rsp = http_response data structure for output
3502  *          3  :  parameters = map of cgi parameters
3503  *
3504  * CGI Parameters :
3505  *    filename : Identifies the file to edit
3506  *         ver : File's last-modified time
3507  *     section : Line number of section to edit
3508  *     pattern : Line number of pattern to edit
3509  *      newval : New value for pattern
3510  *
3511  * Returns     :  JB_ERR_OK     on success
3512  *                JB_ERR_MEMORY on out-of-memory
3513  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3514  *                                  specified or not valid.
3515  *
3516  *********************************************************************/
3517 jb_err cgi_edit_actions_url(struct client_state *csp,
3518                             struct http_response *rsp,
3519                             const struct map *parameters)
3520 {
3521    unsigned patternid;
3522    char * new_pattern;
3523    struct editable_file * file;
3524    struct file_line * cur_line;
3525    unsigned line_number;
3526    unsigned section_start_line_number = 0;
3527    char target[1024];
3528    jb_err err;
3529
3530    assert(csp);
3531    assert(rsp);
3532    assert(parameters);
3533
3534    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3535    {
3536       return cgi_error_disabled(csp, rsp);
3537    }
3538
3539    err = get_number_param(csp, parameters, "p", &patternid);
3540    if (err)
3541    {
3542       return err;
3543    }
3544    if (patternid < 1U)
3545    {
3546       return JB_ERR_CGI_PARAMS;
3547    }
3548
3549    err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3550    if (err)
3551    {
3552       return err;
3553    }
3554
3555    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3556    if (err)
3557    {
3558       /* No filename specified, can't read file, modified, or out of memory. */
3559       free(new_pattern);
3560       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3561    }
3562
3563    line_number = 1;
3564    cur_line = file->lines;
3565
3566    while ((cur_line != NULL) && (line_number < patternid))
3567    {
3568       if (cur_line->type == FILE_LINE_ACTION)
3569       {
3570          section_start_line_number = line_number;
3571       }
3572       cur_line = cur_line->next;
3573       line_number++;
3574    }
3575
3576    if ((cur_line == NULL)
3577     || (cur_line->type != FILE_LINE_URL))
3578    {
3579       /* Invalid "patternid" parameter */
3580       free(new_pattern);
3581       edit_free_file(file);
3582       return JB_ERR_CGI_PARAMS;
3583    }
3584
3585    /* At this point, the line to edit is in cur_line */
3586
3587    freez(cur_line->raw);
3588    freez(cur_line->unprocessed);
3589    cur_line->unprocessed = new_pattern;
3590
3591    err = edit_write_file(file);
3592    if (err)
3593    {
3594       /* Error writing file */
3595       if (err == JB_ERR_FILE)
3596       {
3597          /* Read-only file. */
3598          err = cgi_error_file_read_only(csp, rsp, file->filename);
3599       }
3600       edit_free_file(file);
3601       return err;
3602    }
3603
3604    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3605             (unsigned long) time(NULL), file->identifier, section_start_line_number);
3606
3607    edit_free_file(file);
3608
3609    return cgi_redirect(rsp, target);
3610 }
3611
3612
3613 /*********************************************************************
3614  *
3615  * Function    :  cgi_edit_actions_add_url
3616  *
3617  * Description :  CGI function that actually adds a URL pattern to
3618  *                an actions file.
3619  *
3620  * Parameters  :
3621  *          1  :  csp = Current client state (buffers, headers, etc...)
3622  *          2  :  rsp = http_response data structure for output
3623  *          3  :  parameters = map of cgi parameters
3624  *
3625  * CGI Parameters :
3626  *    filename : Identifies the file to edit
3627  *         ver : File's last-modified time
3628  *     section : Line number of section to edit
3629  *      newval : New pattern
3630  *
3631  * Returns     :  JB_ERR_OK     on success
3632  *                JB_ERR_MEMORY on out-of-memory
3633  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3634  *                                  specified or not valid.
3635  *
3636  *********************************************************************/
3637 jb_err cgi_edit_actions_add_url(struct client_state *csp,
3638                                 struct http_response *rsp,
3639                                 const struct map *parameters)
3640 {
3641    unsigned sectionid;
3642    char * new_pattern;
3643    struct file_line * new_line;
3644    struct editable_file * file;
3645    struct file_line * cur_line;
3646    unsigned line_number;
3647    char target[1024];
3648    jb_err err;
3649
3650    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3651    {
3652       return cgi_error_disabled(csp, rsp);
3653    }
3654
3655    err = get_number_param(csp, parameters, "s", &sectionid);
3656    if (err)
3657    {
3658       return err;
3659    }
3660    if (sectionid < 1U)
3661    {
3662       return JB_ERR_CGI_PARAMS;
3663    }
3664
3665    err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3666    if (err)
3667    {
3668       return err;
3669    }
3670
3671    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3672    if (err)
3673    {
3674       /* No filename specified, can't read file, modified, or out of memory. */
3675       free(new_pattern);
3676       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3677    }
3678
3679    line_number = 1;
3680    cur_line = file->lines;
3681
3682    while ((cur_line != NULL) && (line_number < sectionid))
3683    {
3684       cur_line = cur_line->next;
3685       line_number++;
3686    }
3687
3688    if ((cur_line == NULL)
3689     || (cur_line->type != FILE_LINE_ACTION))
3690    {
3691       /* Invalid "sectionid" parameter */
3692       free(new_pattern);
3693       edit_free_file(file);
3694       return JB_ERR_CGI_PARAMS;
3695    }
3696
3697    /* At this point, the section header is in cur_line - add after this. */
3698
3699    /* Allocate the new line */
3700    new_line = zalloc_or_die(sizeof(*new_line));
3701
3702    /* Fill in the data members of the new line */
3703    new_line->raw = NULL;
3704    new_line->prefix = NULL;
3705    new_line->unprocessed = new_pattern;
3706    new_line->type = FILE_LINE_URL;
3707
3708    /* Link new_line into the list, after cur_line */
3709    new_line->next = cur_line->next;
3710    cur_line->next = new_line;
3711
3712    /* Done making changes, now commit */
3713
3714    err = edit_write_file(file);
3715    if (err)
3716    {
3717       /* Error writing file */
3718       if (err == JB_ERR_FILE)
3719       {
3720          /* Read-only file. */
3721          err = cgi_error_file_read_only(csp, rsp, file->filename);
3722       }
3723       edit_free_file(file);
3724       return err;
3725    }
3726
3727    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3728             (unsigned long) time(NULL), file->identifier, sectionid);
3729
3730    edit_free_file(file);
3731
3732    return cgi_redirect(rsp, target);
3733 }
3734
3735
3736 /*********************************************************************
3737  *
3738  * Function    :  cgi_edit_actions_remove_url
3739  *
3740  * Description :  CGI function that actually removes a URL pattern from
3741  *                the actions file.
3742  *
3743  * Parameters  :
3744  *          1  :  csp = Current client state (buffers, headers, etc...)
3745  *          2  :  rsp = http_response data structure for output
3746  *          3  :  parameters = map of cgi parameters
3747  *
3748  * CGI Parameters :
3749  *           f : (filename) Identifies the file to edit
3750  *           v : (version) File's last-modified time
3751  *           p : (pattern) Line number of pattern to remove
3752  *
3753  * Returns     :  JB_ERR_OK     on success
3754  *                JB_ERR_MEMORY on out-of-memory
3755  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3756  *                                  specified or not valid.
3757  *
3758  *********************************************************************/
3759 jb_err cgi_edit_actions_remove_url(struct client_state *csp,
3760                                    struct http_response *rsp,
3761                                    const struct map *parameters)
3762 {
3763    unsigned patternid;
3764    struct editable_file * file;
3765    struct file_line * cur_line;
3766    struct file_line * prev_line;
3767    unsigned line_number;
3768    unsigned section_start_line_number = 0;
3769    char target[1024];
3770    jb_err err;
3771
3772    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3773    {
3774       return cgi_error_disabled(csp, rsp);
3775    }
3776
3777    err = get_number_param(csp, parameters, "p", &patternid);
3778    if (err)
3779    {
3780       return err;
3781    }
3782
3783    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3784    if (err)
3785    {
3786       /* No filename specified, can't read file, modified, or out of memory. */
3787       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3788    }
3789
3790    line_number = 1;
3791    prev_line = NULL;
3792    cur_line = file->lines;
3793
3794    while ((cur_line != NULL) && (line_number < patternid))
3795    {
3796       if (cur_line->type == FILE_LINE_ACTION)
3797       {
3798          section_start_line_number = line_number;
3799       }
3800       prev_line = cur_line;
3801       cur_line = cur_line->next;
3802       line_number++;
3803    }
3804
3805    if ( (cur_line == NULL)
3806      || (prev_line == NULL)
3807      || (cur_line->type != FILE_LINE_URL))
3808    {
3809       /* Invalid "patternid" parameter */
3810       edit_free_file(file);
3811       return JB_ERR_CGI_PARAMS;
3812    }
3813
3814    /* At this point, the line to remove is in cur_line, and the previous
3815     * one is in prev_line
3816     */
3817
3818    /* Unlink cur_line */
3819    prev_line->next = cur_line->next;
3820    cur_line->next = NULL;
3821
3822    /* Free cur_line */
3823    edit_free_file_lines(cur_line);
3824
3825    err = edit_write_file(file);
3826    if (err)
3827    {
3828       /* Error writing file */
3829       if (err == JB_ERR_FILE)
3830       {
3831          /* Read-only file. */
3832          err = cgi_error_file_read_only(csp, rsp, file->filename);
3833       }
3834       edit_free_file(file);
3835       return err;
3836    }
3837
3838    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3839             (unsigned long) time(NULL), file->identifier, section_start_line_number);
3840
3841    edit_free_file(file);
3842
3843    return cgi_redirect(rsp, target);
3844 }
3845
3846
3847 /*********************************************************************
3848  *
3849  * Function    :  cgi_edit_actions_section_remove
3850  *
3851  * Description :  CGI function that actually removes a whole section from
3852  *                the actions file.  The section must be empty first
3853  *                (else JB_ERR_CGI_PARAMS).
3854  *
3855  * Parameters  :
3856  *          1  :  csp = Current client state (buffers, headers, etc...)
3857  *          2  :  rsp = http_response data structure for output
3858  *          3  :  parameters = map of cgi parameters
3859  *
3860  * CGI Parameters :
3861  *           f : (filename) Identifies the file to edit
3862  *           v : (version) File's last-modified time
3863  *           s : (section) Line number of section to edit
3864  *
3865  * Returns     :  JB_ERR_OK     on success
3866  *                JB_ERR_MEMORY on out-of-memory
3867  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3868  *                                  specified or not valid.
3869  *
3870  *********************************************************************/
3871 jb_err cgi_edit_actions_section_remove(struct client_state *csp,
3872                                        struct http_response *rsp,
3873                                        const struct map *parameters)
3874 {
3875    unsigned sectionid;
3876    struct editable_file * file;
3877    struct file_line * cur_line;
3878    struct file_line * prev_line;
3879    unsigned line_number;
3880    char target[1024];
3881    jb_err err;
3882
3883    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3884    {
3885       return cgi_error_disabled(csp, rsp);
3886    }
3887
3888    err = get_number_param(csp, parameters, "s", &sectionid);
3889    if (err)
3890    {
3891       return err;
3892    }
3893
3894    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3895    if (err)
3896    {
3897       /* No filename specified, can't read file, modified, or out of memory. */
3898       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3899    }
3900
3901    line_number = 1;
3902    cur_line = file->lines;
3903
3904    prev_line = NULL;
3905    while ((cur_line != NULL) && (line_number < sectionid))
3906    {
3907       prev_line = cur_line;
3908       cur_line = cur_line->next;
3909       line_number++;
3910    }
3911
3912    if ((cur_line == NULL)
3913     || (cur_line->type != FILE_LINE_ACTION))
3914    {
3915       /* Invalid "sectionid" parameter */
3916       edit_free_file(file);
3917       return JB_ERR_CGI_PARAMS;
3918    }
3919
3920    if ((cur_line->next != NULL)
3921     && (cur_line->next->type == FILE_LINE_URL))
3922    {
3923       /* Section not empty. */
3924       edit_free_file(file);
3925       return JB_ERR_CGI_PARAMS;
3926    }
3927
3928    /* At this point, the line to remove is in cur_line, and the previous
3929     * one is in prev_line
3930     */
3931
3932    /* Unlink cur_line */
3933    if (prev_line == NULL)
3934    {
3935       /* Removing the first line from the file */
3936       file->lines = cur_line->next;
3937    }
3938    else
3939    {
3940       prev_line->next = cur_line->next;
3941    }
3942    cur_line->next = NULL;
3943
3944    /* Free cur_line */
3945    edit_free_file_lines(cur_line);
3946
3947    err = edit_write_file(file);
3948    if (err)
3949    {
3950       /* Error writing file */
3951       if (err == JB_ERR_FILE)
3952       {
3953          /* Read-only file. */
3954          err = cgi_error_file_read_only(csp, rsp, file->filename);
3955       }
3956       edit_free_file(file);
3957       return err;
3958    }
3959
3960    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
3961             (unsigned long) time(NULL), file->identifier);
3962
3963    edit_free_file(file);
3964
3965    return cgi_redirect(rsp, target);
3966 }
3967
3968
3969 /*********************************************************************
3970  *
3971  * Function    :  cgi_edit_actions_section_add
3972  *
3973  * Description :  CGI function that adds a new empty section to
3974  *                an actions file.
3975  *
3976  * Parameters  :
3977  *          1  :  csp = Current client state (buffers, headers, etc...)
3978  *          2  :  rsp = http_response data structure for output
3979  *          3  :  parameters = map of cgi parameters
3980  *
3981  * CGI Parameters :
3982  *           f : (filename) Identifies the file to edit
3983  *           v : (version) File's last-modified time
3984  *           s : (section) Line number of section to add after, 0 for
3985  *               start of file.
3986  *
3987  * Returns     :  JB_ERR_OK     on success
3988  *                JB_ERR_MEMORY on out-of-memory
3989  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3990  *                                  specified or not valid.
3991  *
3992  *********************************************************************/
3993 jb_err cgi_edit_actions_section_add(struct client_state *csp,
3994                                     struct http_response *rsp,
3995                                     const struct map *parameters)
3996 {
3997    unsigned sectionid;
3998    struct file_line * new_line;
3999    char * new_text;
4000    struct editable_file * file;
4001    struct file_line * cur_line;
4002    unsigned line_number;
4003    char target[1024];
4004    jb_err err;
4005
4006    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
4007    {
4008       return cgi_error_disabled(csp, rsp);
4009    }
4010
4011    err = get_number_param(csp, parameters, "s", &sectionid);
4012    if (err)
4013    {
4014       return err;
4015    }
4016
4017    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
4018    if (err)
4019    {
4020       /* No filename specified, can't read file, modified, or out of memory. */
4021       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
4022    }
4023
4024    line_number = 1;
4025    cur_line = file->lines;
4026
4027    if (sectionid <= 1U)
4028    {
4029       /* Add to start of file */
4030       if (cur_line != NULL && cur_line->type != FILE_LINE_ACTION)
4031       {
4032          /* There's something in the file, find the line before the first
4033           * action.
4034           */
4035          while ((cur_line->next != NULL)
4036               && (cur_line->next->type != FILE_LINE_ACTION))
4037          {
4038             cur_line = cur_line->next;
4039             line_number++;
4040          }
4041       }
4042       else
4043       {
4044          /* File starts with action line, so insert at top */
4045          cur_line = NULL;
4046       }
4047    }
4048    else
4049    {
4050       /* Add after stated section. */
4051       while ((cur_line != NULL) && (line_number < sectionid))
4052       {
4053          cur_line = cur_line->next;
4054          line_number++;
4055       }
4056
4057       if ((cur_line == NULL)
4058        || (cur_line->type != FILE_LINE_ACTION))
4059       {
4060          /* Invalid "sectionid" parameter */
4061          edit_free_file(file);
4062          return JB_ERR_CGI_PARAMS;
4063       }
4064
4065       /* Skip through the section to find the last line in it. */
4066       while ((cur_line->next != NULL)
4067           && (cur_line->next->type != FILE_LINE_ACTION))
4068       {
4069          cur_line = cur_line->next;
4070          line_number++;
4071       }
4072    }
4073
4074    /* At this point, the last line in the previous section is in cur_line
4075     * - add after this.  (Or if we need to add as the first line, cur_line
4076     * will be NULL).
4077     */
4078
4079    new_text = strdup("{}");
4080    if (NULL == new_text)
4081    {
4082       edit_free_file(file);
4083       return JB_ERR_MEMORY;
4084    }
4085
4086    /* Allocate the new line */
4087    new_line = zalloc_or_die(sizeof(*new_line));
4088
4089    /* Fill in the data members of the new line */
4090    new_line->raw = NULL;
4091    new_line->prefix = NULL;
4092    new_line->unprocessed = new_text;
4093    new_line->type = FILE_LINE_ACTION;
4094
4095    if (cur_line != NULL)
4096    {
4097       /* Link new_line into the list, after cur_line */
4098       new_line->next = cur_line->next;
4099       cur_line->next = new_line;
4100    }
4101    else
4102    {
4103       /* Link new_line into the list, as first line */
4104       new_line->next = file->lines;
4105       file->lines = new_line;
4106    }
4107
4108    /* Done making changes, now commit */
4109
4110    err = edit_write_file(file);
4111    if (err)
4112    {
4113       /* Error writing file */
4114       if (err == JB_ERR_FILE)
4115       {
4116          /* Read-only file. */
4117          err = cgi_error_file_read_only(csp, rsp, file->filename);
4118       }
4119       edit_free_file(file);
4120       return err;
4121    }
4122
4123    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
4124             (unsigned long) time(NULL), file->identifier);
4125
4126    edit_free_file(file);
4127
4128    return cgi_redirect(rsp, target);
4129 }
4130
4131
4132 /*********************************************************************
4133  *
4134  * Function    :  cgi_edit_actions_section_swap
4135  *
4136  * Description :  CGI function that swaps the order of two sections
4137  *                in the actions file.  Note that this CGI can actually
4138  *                swap any two arbitrary sections, but the GUI interface
4139  *                currently only allows consecutive sections to be
4140  *                specified.
4141  *
4142  * Parameters  :
4143  *          1  :  csp = Current client state (buffers, headers, etc...)
4144  *          2  :  rsp = http_response data structure for output
4145  *          3  :  parameters = map of cgi parameters
4146  *
4147  * CGI Parameters :
4148  *           f : (filename) Identifies the file to edit
4149  *           v : (version) File's last-modified time
4150  *          s1 : (section1) Line number of first section to swap
4151  *          s2 : (section2) Line number of second section to swap
4152  *
4153  * Returns     :  JB_ERR_OK     on success
4154  *                JB_ERR_MEMORY on out-of-memory
4155  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
4156  *                                  specified or not valid.
4157  *
4158  *********************************************************************/
4159 jb_err cgi_edit_actions_section_swap(struct client_state *csp,
4160                                      struct http_response *rsp,
4161                                      const struct map *parameters)
4162 {
4163    unsigned section1;
4164    unsigned section2;
4165    struct editable_file * file;
4166    struct file_line * cur_line;
4167    struct file_line * prev_line;
4168    struct file_line * line_before_section1;
4169    struct file_line * line_start_section1;
4170    struct file_line * line_end_section1;
4171    struct file_line * line_after_section1;
4172    struct file_line * line_before_section2;
4173    struct file_line * line_start_section2;
4174    struct file_line * line_end_section2;
4175    struct file_line * line_after_section2;
4176    unsigned line_number;
4177    char target[1024];
4178    jb_err err;
4179
4180    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
4181    {
4182       return cgi_error_disabled(csp, rsp);
4183    }
4184
4185    err = get_number_param(csp, parameters, "s1", &section1);
4186    if (!err) err = get_number_param(csp, parameters, "s2", &section2);
4187    if (err)
4188    {
4189       return err;
4190    }
4191
4192    if (section1 > section2)
4193    {
4194       unsigned temp = section2;
4195       section2 = section1;
4196       section1 = temp;
4197    }
4198
4199    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
4200    if (err)
4201    {
4202       /* No filename specified, can't read file, modified, or out of memory. */
4203       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
4204    }
4205
4206    /* Start at the beginning... */
4207    line_number = 1;
4208    cur_line = file->lines;
4209    prev_line = NULL;
4210
4211    /* ... find section1 ... */
4212    while ((cur_line != NULL) && (line_number < section1))
4213    {
4214       prev_line = cur_line;
4215       cur_line = cur_line->next;
4216       line_number++;
4217    }
4218
4219    if ((cur_line == NULL)
4220     || (cur_line->type != FILE_LINE_ACTION))
4221    {
4222       /* Invalid "section1" parameter */
4223       edit_free_file(file);
4224       return JB_ERR_CGI_PARAMS;
4225    }
4226
4227    /* If no-op, we've validated params and can skip the rest. */
4228    if (section1 != section2)
4229    {
4230       /* ... find the end of section1 ... */
4231       line_before_section1 = prev_line;
4232       line_start_section1 = cur_line;
4233       do
4234       {
4235          prev_line = cur_line;
4236          cur_line = cur_line->next;
4237          line_number++;
4238       }
4239       while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
4240       line_end_section1 = prev_line;
4241       line_after_section1 = cur_line;
4242
4243       /* ... find section2 ... */
4244       while ((cur_line != NULL) && (line_number < section2))
4245       {
4246          prev_line = cur_line;
4247          cur_line = cur_line->next;
4248          line_number++;
4249       }
4250
4251       if ((cur_line == NULL)
4252        || (cur_line->type != FILE_LINE_ACTION))
4253       {
4254          /* Invalid "section2" parameter */
4255          edit_free_file(file);
4256          return JB_ERR_CGI_PARAMS;
4257       }
4258
4259       /* ... find the end of section2 ... */
4260       line_before_section2 = prev_line;
4261       line_start_section2 = cur_line;
4262       do
4263       {
4264          prev_line = cur_line;
4265          cur_line = cur_line->next;
4266          line_number++;
4267       }
4268       while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
4269       line_end_section2 = prev_line;
4270       line_after_section2 = cur_line;
4271
4272       /* Now have all the pointers we need. Do the swap. */
4273
4274       /* Change the pointer to section1 to point to section2 instead */
4275       if (line_before_section1 == NULL)
4276       {
4277          file->lines = line_start_section2;
4278       }
4279       else
4280       {
4281          line_before_section1->next = line_start_section2;
4282       }
4283
4284       if (line_before_section2 == line_end_section1)
4285       {
4286          /* Consecutive sections */
4287          line_end_section2->next = line_start_section1;
4288       }
4289       else
4290       {
4291          line_end_section2->next = line_after_section1;
4292          line_before_section2->next = line_start_section1;
4293       }
4294
4295       /* Set the pointer from the end of section1 to the rest of the file */
4296       line_end_section1->next = line_after_section2;
4297
4298       err = edit_write_file(file);
4299       if (err)
4300       {
4301          /* Error writing file */
4302          if (err == JB_ERR_FILE)
4303          {
4304             /* Read-only file. */
4305             err = cgi_error_file_read_only(csp, rsp, file->filename);
4306          }
4307          edit_free_file(file);
4308          return err;
4309       }
4310    } /* END if (section1 != section2) */
4311
4312    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
4313             (unsigned long) time(NULL), file->identifier);
4314
4315    edit_free_file(file);
4316
4317    return cgi_redirect(rsp, target);
4318 }
4319
4320
4321 /*********************************************************************
4322  *
4323  * Function    :  javascriptify
4324  *
4325  * Description :  Converts a string into a form JavaScript will like.
4326  *
4327  *                Netscape 4's JavaScript sucks - it doesn't use
4328  *                "id" parameters, so you have to set the "name"
4329  *                used to submit a form element to something JavaScript
4330  *                will like.  (Or access the elements by index in an
4331  *                array.  That array contains >60 elements and will
4332  *                be changed whenever we add a new action to the
4333  *                editor, so I'm NOT going to use indexes that have
4334  *                to be figured out by hand.)
4335  *
4336  *                Currently the only thing we have to worry about
4337  *                is "-" ==> "_" conversion.
4338  *
4339  *                This is a length-preserving operation so it is
4340  *                carried out in-place, no memory is allocated
4341  *                or freed.
4342  *
4343  * Parameters  :
4344  *          1  :  identifier = String to make JavaScript-friendly.
4345  *
4346  * Returns     :  N/A
4347  *
4348  *********************************************************************/
4349 static void javascriptify(char * identifier)
4350 {
4351    char * p = identifier;
4352    while (NULL != (p = strchr(p, '-')))
4353    {
4354       *p++ = '_';
4355    }
4356 }
4357
4358
4359 /*********************************************************************
4360  *
4361  * Function    :  actions_to_radio
4362  *
4363  * Description :  Converts a actionsfile entry into settings for
4364  *                radio buttons and edit boxes on a HTML form.
4365  *
4366  * Parameters  :
4367  *          1  :  exports = List of substitutions to add to.
4368  *          2  :  action  = Action to read
4369  *
4370  * Returns     :  JB_ERR_OK     on success
4371  *                JB_ERR_MEMORY on out-of-memory
4372  *
4373  *********************************************************************/
4374 static jb_err actions_to_radio(struct map * exports,
4375                                const struct action_spec *action)
4376 {
4377    unsigned long mask;
4378    unsigned long add;
4379    int mapped_param;
4380    int checked;
4381    char current_mode;
4382
4383    assert(exports);
4384    assert(action);
4385
4386    mask = action->mask;
4387    add  = action->add;
4388
4389    /* sanity - prevents "-feature +feature" */
4390    mask |= add;
4391
4392
4393 #define DEFINE_ACTION_BOOL(name, bit)                 \
4394    if (!(mask & bit))                                 \
4395    {                                                  \
4396       current_mode = 'n';                             \
4397    }                                                  \
4398    else if (add & bit)                                \
4399    {                                                  \
4400       current_mode = 'y';                             \
4401    }                                                  \
4402    else                                               \
4403    {                                                  \
4404       current_mode = 'x';                             \
4405    }                                                  \
4406    if (map_radio(exports, name, "ynx", current_mode)) \
4407    {                                                  \
4408       return JB_ERR_MEMORY;                           \
4409    }
4410
4411 #define DEFINE_ACTION_STRING(name, bit, index)        \
4412    DEFINE_ACTION_BOOL(name, bit);                     \
4413    mapped_param = 0;
4414
4415 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default)  \
4416    if (add & bit)                                                    \
4417    {                                                                 \
4418       checked = !strcmp(action->string[index], value);               \
4419    }                                                                 \
4420    else                                                              \
4421    {                                                                 \
4422       checked = is_default;                                          \
4423    }                                                                 \
4424    mapped_param |= checked;                                          \
4425    if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
4426    {                                                                 \
4427       return JB_ERR_MEMORY;                                          \
4428    }
4429
4430 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val)       \
4431    if (map(exports, name "-param-custom", 1,                         \
4432            ((!mapped_param) ? "checked" : ""), 1))                   \
4433    {                                                                 \
4434       return JB_ERR_MEMORY;                                          \
4435    }                                                                 \
4436    if (map(exports, name "-param", 1,                                \
4437            (((add & bit) && !mapped_param) ?                         \
4438            action->string[index] : default_val), 1))                 \
4439    {                                                                 \
4440       return JB_ERR_MEMORY;                                          \
4441    }
4442
4443 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val)     \
4444    if (map(exports, name "-param", 1,                                \
4445            ((add & bit) ? action->string[index] : default_val), 1))  \
4446    {                                                                 \
4447       return JB_ERR_MEMORY;                                          \
4448    }
4449
4450 #define DEFINE_ACTION_MULTI(name, index)              \
4451    if (action->multi_add[index]->first)               \
4452    {                                                  \
4453       current_mode = 'y';                             \
4454    }                                                  \
4455    else if (action->multi_remove_all[index])          \
4456    {                                                  \
4457       current_mode = 'n';                             \
4458    }                                                  \
4459    else if (action->multi_remove[index]->first)       \
4460    {                                                  \
4461       current_mode = 'y';                             \
4462    }                                                  \
4463    else                                               \
4464    {                                                  \
4465       current_mode = 'x';                             \
4466    }                                                  \
4467    if (map_radio(exports, name, "ynx", current_mode)) \
4468    {                                                  \
4469       return JB_ERR_MEMORY;                           \
4470    }
4471
4472 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
4473
4474 #include "actionlist.h"
4475
4476 #undef DEFINE_ACTION_MULTI
4477 #undef DEFINE_ACTION_STRING
4478 #undef DEFINE_ACTION_BOOL
4479 #undef DEFINE_ACTION_ALIAS
4480 #undef DEFINE_CGI_PARAM_CUSTOM
4481 #undef DEFINE_CGI_PARAM_RADIO
4482 #undef DEFINE_CGI_PARAM_NO_RADIO
4483
4484    return JB_ERR_OK;
4485 }
4486
4487 /*********************************************************************
4488  *
4489  * Function    :  action_render_string_actions_template
4490  *
4491  * Description : Converts an actionsfile entry into HTML template for
4492  *               actions with string filters (currently SUPPRESS-TAG
4493  *               actions only)
4494  *
4495  * Parameters  :
4496  *          1  :  exports = List of substitutions to add to.
4497  *          2  :  action  = Action to read
4498  *          3  :  action_template  = template to fill
4499  *          4  :  type  = filter type info for rendered values/macro name
4500  *
4501  * Returns     :  JB_ERR_OK     on success
4502  *                JB_ERR_MEMORY on out-of-memory
4503  *
4504  *********************************************************************/
4505 static jb_err action_render_string_actions_template(struct map *exports,
4506                                        const struct action_spec *action,
4507                                        const char *action_template,
4508                                        const struct string_action_type_info *string_action_type)
4509 {
4510    jb_err err = JB_ERR_OK;
4511    int filter_identifier = 0;
4512    int i;
4513    char *prepared_template = strdup("");
4514    const struct action_type_info *type = &action_type_info[string_action_type->action_type];
4515
4516    struct action_multi {
4517        char radio;
4518        struct list_entry *list;
4519    };
4520
4521    assert(type->multi_action_index < ACTION_MULTI_COUNT);
4522
4523    struct action_multi desc[] = {
4524        { 'y', action->multi_add[type->multi_action_index][0].first },
4525        { 'n', action->multi_remove[type->multi_action_index][0].first }
4526    };
4527
4528    for (i = 0; i < SZ(desc); ++i)
4529    {
4530       const char radio = desc[i].radio;
4531       struct list_entry *entry = desc[i].list;
4532       for (;(!err) && (entry != NULL); entry = entry->next)
4533       {
4534          char number[20];
4535          struct map *line_exports;
4536
4537          /* Generate a unique serial number */
4538          snprintf(number, sizeof(number), "%x", filter_identifier++);
4539
4540          line_exports = new_map();
4541          if (line_exports == NULL)
4542          {
4543             err = JB_ERR_MEMORY;
4544          }
4545          else
4546          {
4547             char *action_line;
4548             if (!err) err = map(line_exports, "index", 1, number, 1);
4549             if (!err) err = map(line_exports, "name",  1, entry->str, 1);
4550             if (!err) err = map_radio(line_exports, "this-filter", "ynx", radio);
4551             if (!err) err = map(line_exports, "filter-type", 1, type->type, 1);
4552             if (!err) err = map(line_exports, "abbr-action-type", 1, type->abbr_type, 1);
4553             if (!err) err = map(line_exports, "anchor", 1, type->anchor, 1);
4554             if (!err) err = map(line_exports, "desc", 1, string_action_type->description, 1);
4555             if (!err) err = map(line_exports, "input_desc", 1, string_action_type->input_description, 1);
4556             if (!err)
4557             {
4558                action_line = strdup(action_template);
4559                if (action_line == NULL) err = JB_ERR_MEMORY;
4560             }
4561             if (!err) err = template_fill(&action_line, line_exports);
4562             if (!err) err = string_join(&prepared_template, action_line);
4563
4564             free_map(line_exports);
4565         }
4566       }
4567    }
4568    if (!err) map(exports, type->macro_name, 1, prepared_template, 1);
4569    freez(prepared_template);
4570    return err;
4571 }
4572
4573 /*********************************************************************
4574  *
4575  * Function    :  actions_from_radio
4576  *
4577  * Description :  Converts a map of parameters passed to a CGI function
4578  *                into an actionsfile entry.
4579  *
4580  * Parameters  :
4581  *          1  :  parameters = parameters to the CGI call
4582  *          2  :  action  = Action to change.  Must be valid before
4583  *                          the call, actions not specified will be
4584  *                          left unchanged.
4585  *
4586  * Returns     :  JB_ERR_OK     on success
4587  *                JB_ERR_MEMORY on out-of-memory
4588  *
4589  *********************************************************************/
4590 static jb_err actions_from_radio(const struct map * parameters,
4591                                  struct action_spec *action)
4592 {
4593    const char * param;
4594    char * param_dup;
4595    char ch;
4596    const char * js_name;
4597    jb_err err = JB_ERR_OK;
4598
4599    assert(parameters);
4600    assert(action);
4601
4602    /* Statics are generally a potential race condition,
4603     * but in this case we're safe and don't need semaphores.
4604     * Be careful if you modify this function.
4605     * - Jon
4606     * The js_name_arr's are never free()d, but this is no
4607     * problem, since they will only be created once and
4608     * used by all threads thereafter. -oes
4609     */
4610
4611 #define JAVASCRIPTIFY(dest_var, string)               \
4612    {                                                  \
4613      static int first_time = 1;                       \
4614      static char *js_name_arr;                        \
4615       if (first_time)                                 \
4616       {                                               \
4617          js_name_arr = strdup(string);                \
4618          javascriptify(js_name_arr);                  \
4619       }                                               \
4620       dest_var = js_name_arr;                         \
4621       first_time = 0;                                 \
4622    }                                                  \
4623
4624 #define DEFINE_ACTION_BOOL(name, bit)                 \
4625    JAVASCRIPTIFY(js_name, name);                      \
4626    ch = get_char_param(parameters, js_name);          \
4627    if (ch == 'Y')                                     \
4628    {                                                  \
4629       action->add  |= bit;                            \
4630       action->mask |= bit;                            \
4631    }                                                  \
4632    else if (ch == 'N')                                \
4633    {                                                  \
4634       action->add  &= ~bit;                           \
4635       action->mask &= ~bit;                           \
4636    }                                                  \
4637    else if (ch == 'X')                                \
4638    {                                                  \
4639       action->add  &= ~bit;                           \
4640       action->mask |= bit;                            \
4641    }                                                  \
4642
4643 #define DEFINE_ACTION_STRING(name, bit, index)                 \
4644    JAVASCRIPTIFY(js_name, name);                               \
4645    ch = get_char_param(parameters, js_name);                   \
4646    if (ch == 'Y')                                              \
4647    {                                                           \
4648       param = NULL;                                            \
4649       JAVASCRIPTIFY(js_name, name "-mode");                    \
4650       if (!err) err = get_string_param(parameters, js_name, &param);    \
4651       if ((param == NULL) || (0 == strcmp(param, "CUSTOM")))            \
4652       {                                                                 \
4653          JAVASCRIPTIFY(js_name, name "-param");                         \
4654          if (!err) err = get_string_param(parameters, js_name, &param); \
4655       }                                                        \
4656       if (param != NULL)                                       \
4657       {                                                        \
4658          if (NULL == (param_dup = strdup(param)))              \
4659          {                                                     \
4660             return JB_ERR_MEMORY;                              \
4661          }                                                     \
4662          freez(action->string[index]);                         \
4663          action->add  |= bit;                                  \
4664          action->mask |= bit;                                  \
4665          action->string[index] = param_dup;                    \
4666       }                                                        \
4667    }                                                           \
4668    else if (ch == 'N')                                         \
4669    {                                                           \
4670       if (action->add & bit)                                   \
4671       {                                                        \
4672          freez(action->string[index]);                         \
4673       }                                                        \
4674       action->add  &= ~bit;                                    \
4675       action->mask &= ~bit;                                    \
4676    }                                                           \
4677    else if (ch == 'X')                                         \
4678    {                                                           \
4679       if (action->add & bit)                                   \
4680       {                                                        \
4681          freez(action->string[index]);                         \
4682       }                                                        \
4683       action->add  &= ~bit;                                    \
4684       action->mask |= bit;                                     \
4685    }                                                           \
4686
4687 #define DEFINE_ACTION_MULTI(name, index)                       \
4688    JAVASCRIPTIFY(js_name, name);                               \
4689    ch = get_char_param(parameters, js_name);                   \
4690    if (ch == 'Y')                                              \
4691    {                                                           \
4692       /* FIXME */                                              \
4693    }                                                           \
4694    else if (ch == 'N')                                         \
4695    {                                                           \
4696       list_remove_all(action->multi_add[index]);               \
4697       list_remove_all(action->multi_remove[index]);            \
4698       action->multi_remove_all[index] = 1;                     \
4699    }                                                           \
4700    else if (ch == 'X')                                         \
4701    {                                                           \
4702       list_remove_all(action->multi_add[index]);               \
4703       list_remove_all(action->multi_remove[index]);            \
4704       action->multi_remove_all[index] = 0;                     \
4705    }                                                           \
4706
4707 #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
4708
4709 #include "actionlist.h"
4710
4711 #undef DEFINE_ACTION_MULTI
4712 #undef DEFINE_ACTION_STRING
4713 #undef DEFINE_ACTION_BOOL
4714 #undef DEFINE_ACTION_ALIAS
4715 #undef JAVASCRIPTIFY
4716
4717    return err;
4718 }
4719 #endif /* def FEATURE_CGI_EDIT_ACTIONS */
4720
4721
4722 #ifdef FEATURE_TOGGLE
4723 /*********************************************************************
4724  *
4725  * Function    :  cgi_toggle
4726  *
4727  * Description :  CGI function that adds a new empty section to
4728  *                an actions file.
4729  *
4730  * Parameters  :
4731  *          1  :  csp = Current client state (buffers, headers, etc...)
4732  *          2  :  rsp = http_response data structure for output
4733  *          3  :  parameters = map of cgi parameters
4734  *
4735  * CGI Parameters :
4736  *         set : If present, how to change toggle setting:
4737  *               "enable", "disable", "toggle", or none (default).
4738  *        mini : If present, use mini reply template.
4739  *
4740  * Returns     :  JB_ERR_OK     on success
4741  *                JB_ERR_MEMORY on out-of-memory
4742  *
4743  *********************************************************************/
4744 jb_err cgi_toggle(struct client_state *csp,
4745                   struct http_response *rsp,
4746                   const struct map *parameters)
4747 {
4748    struct map *exports;
4749    char mode;
4750    const char *template_name;
4751
4752    assert(csp);
4753    assert(rsp);
4754    assert(parameters);
4755
4756    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE))
4757    {
4758       return cgi_error_disabled(csp, rsp);
4759    }
4760
4761    mode = get_char_param(parameters, "set");
4762
4763    if (mode == 'E')
4764    {
4765       /* Enable */
4766       global_toggle_state = 1;
4767    }
4768    else if (mode == 'D')
4769    {
4770       /* Disable */
4771       global_toggle_state = 0;
4772    }
4773    else if (mode == 'T')
4774    {
4775       /* Toggle */
4776       global_toggle_state = !global_toggle_state;
4777    }
4778
4779    log_error(LOG_LEVEL_INFO, "Now toggled %s.", global_toggle_state ? "ON" : "OFF");
4780
4781    if (NULL == (exports = default_exports(csp, "toggle")))
4782    {
4783       return JB_ERR_MEMORY;
4784    }
4785
4786    template_name = (get_char_param(parameters, "mini")
4787                  ? "toggle-mini"
4788                  : "toggle");
4789
4790    return template_fill_for_cgi(csp, template_name, exports, rsp);
4791 }
4792 #endif /* def FEATURE_TOGGLE */
4793
4794
4795 /*
4796   Local Variables:
4797   tab-width: 3
4798   end:
4799 */