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