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