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