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