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