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