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