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