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. https://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 relevant 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 dividing 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 #ifndef FEATURE_HTTPS_INSPECTION
2786    if (!err) err = map_block_killer(exports, "https-inspection");
2787 #endif
2788
2789    if (err)
2790    {
2791       edit_free_file(file);
2792       free_map(exports);
2793       return err;
2794    }
2795
2796    if (0 == have_filters)
2797    {
2798       err = map(exports, "filter-params", 1, "", 1);
2799    }
2800    else
2801    {
2802       /*
2803        * List available filters and their settings.
2804        */
2805       char *filter_template;
2806       int filter_identifier = 0;
2807       char *prepared_templates[MAX_FILTER_TYPES];
2808
2809       for (i = 0; i < MAX_FILTER_TYPES; i++)
2810       {
2811          prepared_templates[i] = strdup("");
2812       }
2813
2814       err = template_load(csp, &filter_template, "edit-actions-for-url-filter", 0);
2815       if (err)
2816       {
2817          edit_free_file(file);
2818          free_map(exports);
2819          if (err == JB_ERR_FILE)
2820          {
2821             return cgi_error_no_template(csp, rsp, "edit-actions-for-url-filter");
2822          }
2823          return err;
2824       }
2825
2826       err = template_fill(&filter_template, exports);
2827
2828       for (i = 0; i < MAX_AF_FILES; i++)
2829       {
2830          if ((csp->rlist[i] != NULL) && (csp->rlist[i]->f != NULL))
2831          {
2832             filter_group = csp->rlist[i]->f;
2833             for (;(!err) && (filter_group != NULL); filter_group = filter_group->next)
2834             {
2835                char current_mode = 'x';
2836                char number[20];
2837                struct list_entry *filter_name;
2838                struct map *line_exports;
2839                const enum filter_type type = filter_group->type;
2840                const int multi_action_index = filter_type_info[type].multi_action_index;
2841
2842                assert(type < MAX_FILTER_TYPES);
2843
2844                filter_name = cur_line->data.action->multi_add[multi_action_index]->first;
2845                while ((filter_name != NULL)
2846                    && (0 != strcmp(filter_group->name, filter_name->str)))
2847                {
2848                     filter_name = filter_name->next;
2849                }
2850
2851                if (filter_name != NULL)
2852                {
2853                   current_mode = 'y';
2854                }
2855                else
2856                {
2857                   filter_name = cur_line->data.action->multi_remove[multi_action_index]->first;
2858                   while ((filter_name != NULL)
2859                       && (0 != strcmp(filter_group->name, filter_name->str)))
2860                   {
2861                        filter_name = filter_name->next;
2862                   }
2863                   if (filter_name != NULL)
2864                   {
2865                      current_mode = 'n';
2866                   }
2867                }
2868
2869                /* Generate a unique serial number */
2870                snprintf(number, sizeof(number), "%x", filter_identifier++);
2871                number[sizeof(number) - 1] = '\0';
2872
2873                line_exports = new_map();
2874                if (line_exports == NULL)
2875                {
2876                   err = JB_ERR_MEMORY;
2877                }
2878                else
2879                {
2880                   char *filter_line;
2881
2882                   if (!err) err = map(line_exports, "index", 1, number, 1);
2883                   if (!err) err = map(line_exports, "name",  1, filter_group->name, 1);
2884                   if (!err) err = map(line_exports, "description",  1, filter_group->description, 1);
2885                   if (!err) err = map_radio(line_exports, "this-filter", "ynx", current_mode);
2886                   if (!err) err = map(line_exports, "filter-type", 1, filter_type_info[type].type, 1);
2887                   if (!err) err = map(line_exports, "abbr-filter-type", 1, filter_type_info[type].abbr_type, 1);
2888                   if (!err) err = map(line_exports, "anchor", 1, filter_type_info[type].anchor, 1);
2889
2890                   if (!err)
2891                   {
2892                      filter_line = strdup(filter_template);
2893                      if (filter_line == NULL) err = JB_ERR_MEMORY;
2894                   }
2895                   if (!err) err = template_fill(&filter_line, line_exports);
2896                   string_join(&prepared_templates[type], filter_line);
2897
2898                   free_map(line_exports);
2899                }
2900             }
2901          }
2902       }
2903       freez(filter_template);
2904
2905       /* Replace all filter macros with the aggregated templates */
2906       for (i = 0; i < MAX_FILTER_TYPES; i++)
2907       {
2908          if (err) break;
2909          err = map(exports, filter_type_info[i].macro_name, 1, prepared_templates[i], 0);
2910       }
2911
2912       if (err)
2913       {
2914          /* Free aggregated templates */
2915          for (i = 0; i < MAX_FILTER_TYPES; i++)
2916          {
2917             freez(prepared_templates[i]);
2918          }
2919       }
2920    }
2921
2922    /* Check or uncheck the "disable all of this type" radio buttons. */
2923    for (i = 0; i < MAX_FILTER_TYPES; i++)
2924    {
2925       const int a = filter_type_info[i].multi_action_index;
2926       const int disable_all = cur_line->data.action->multi_remove_all[a];
2927       if (err) break;
2928       err = map_radio(exports, filter_type_info[i].disable_all_option, "nx", (disable_all ? 'n' : 'x'));
2929    }
2930
2931    edit_free_file(file);
2932
2933    if (err)
2934    {
2935       free_map(exports);
2936       return err;
2937    }
2938
2939    return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
2940 }
2941
2942
2943 /*********************************************************************
2944  *
2945  * Function    :  cgi_edit_actions_submit
2946  *
2947  * Description :  CGI function that actually edits the Actions list.
2948  *
2949  * Parameters  :
2950  *          1  :  csp = Current client state (buffers, headers, etc...)
2951  *          2  :  rsp = http_response data structure for output
2952  *          3  :  parameters = map of cgi parameters
2953  *
2954  * CGI Parameters : None
2955  *
2956  * Returns     :  JB_ERR_OK     on success
2957  *                JB_ERR_MEMORY on out-of-memory
2958  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
2959  *                                  specified or not valid.
2960  *
2961  *********************************************************************/
2962 jb_err cgi_edit_actions_submit(struct client_state *csp,
2963                                struct http_response *rsp,
2964                                const struct map *parameters)
2965 {
2966    unsigned sectionid;
2967    char * actiontext;
2968    char * newtext;
2969    size_t newtext_size;
2970    size_t len;
2971    struct editable_file * file;
2972    struct file_line * cur_line;
2973    unsigned line_number;
2974    char target[1024];
2975    jb_err err;
2976    int filter_identifier;
2977    int i;
2978    const char * action_set_name;
2979    struct file_list * fl;
2980    struct url_actions * b;
2981
2982    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2983    {
2984       return cgi_error_disabled(csp, rsp);
2985    }
2986
2987    err = get_number_param(csp, parameters, "s", &sectionid);
2988    if (err)
2989    {
2990       return err;
2991    }
2992
2993    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2994    if (err)
2995    {
2996       /* No filename specified, can't read file, modified, or out of memory. */
2997       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2998    }
2999
3000    cur_line = file->lines;
3001
3002    for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
3003    {
3004       cur_line = cur_line->next;
3005    }
3006
3007    if ( (cur_line == NULL)
3008      || (line_number != sectionid)
3009      || (sectionid < 1)
3010      || (cur_line->type != FILE_LINE_ACTION))
3011    {
3012       /* Invalid "sectionid" parameter */
3013       edit_free_file(file);
3014       return JB_ERR_CGI_PARAMS;
3015    }
3016
3017    get_string_param(parameters, "p", &action_set_name);
3018    if (action_set_name != NULL)
3019    {
3020       for (filter_identifier = 0; filter_identifier < MAX_AF_FILES; filter_identifier++)
3021       {
3022          if (((fl = csp->actions_list[filter_identifier]) != NULL) && ((b = fl->f) != NULL))
3023          {
3024             for (b = b->next; NULL != b; b = b->next)
3025             {
3026                if (!strncmp(b->url->spec, "standard.", 9) && !strcmp(b->url->spec + 9, action_set_name))
3027                {
3028                   copy_action(cur_line->data.action, b->action);
3029                   goto found;
3030                }
3031             }
3032          }
3033       }
3034       edit_free_file(file);
3035       return JB_ERR_CGI_PARAMS;
3036
3037       found: ;
3038    }
3039    else
3040    {
3041       err = actions_from_radio(parameters, cur_line->data.action);
3042    }
3043
3044    if (err)
3045    {
3046       /* Out of memory */
3047       edit_free_file(file);
3048       return err;
3049    }
3050
3051    /* Check the "disable all of this type" parameters. */
3052    for (i = 0; i < MAX_FILTER_TYPES; i++)
3053    {
3054       const int multi_action_index = filter_type_info[i].multi_action_index;
3055       const char ch = get_char_param(parameters, filter_type_info[i].disable_all_param);
3056
3057       if (ch == 'N')
3058       {
3059          list_remove_all(cur_line->data.action->multi_add[multi_action_index]);
3060          list_remove_all(cur_line->data.action->multi_remove[multi_action_index]);
3061          cur_line->data.action->multi_remove_all[multi_action_index] = 1;
3062       }
3063       else if (ch == 'X')
3064       {
3065          cur_line->data.action->multi_remove_all[multi_action_index] = 0;
3066       }
3067    }
3068
3069    for (filter_identifier = 0; !err; filter_identifier++)
3070    {
3071       char key_value[30];
3072       char key_name[30];
3073       char key_type[30];
3074       const char *name;
3075       char value; /*
3076                    * Filter state. Valid states are: 'Y' (active),
3077                    * 'N' (inactive) and 'X' (no change).
3078                    * XXX: bad name.
3079                    */
3080       char type;  /*
3081                    * Abbreviated filter type. Valid types are: 'F' (content filter),
3082                    * 'S' (server-header filter) and 'C' (client-header filter).
3083                    */
3084       int multi_action_index = 0;
3085
3086       /* Generate the keys */
3087       snprintf(key_value, sizeof(key_value), "filter_r%x", filter_identifier);
3088       key_value[sizeof(key_value) - 1] = '\0'; /* XXX: Why? */
3089       snprintf(key_name, sizeof(key_name), "filter_n%x", filter_identifier);
3090       key_name[sizeof(key_name) - 1] = '\0'; /* XXX: Why? */
3091       snprintf(key_type, sizeof(key_type), "filter_t%x", filter_identifier);
3092
3093       err = get_string_param(parameters, key_name, &name);
3094       if (err) break;
3095
3096       if (name == NULL)
3097       {
3098          /* End of list */
3099          break;
3100       }
3101
3102       type = get_char_param(parameters, key_type);
3103       switch (type)
3104       {
3105          case 'F':
3106             multi_action_index = ACTION_MULTI_FILTER;
3107             break;
3108          case 'S':
3109             multi_action_index = ACTION_MULTI_SERVER_HEADER_FILTER;
3110             break;
3111          case 'C':
3112             multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER;
3113             break;
3114          case 'L':
3115             multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER;
3116             break;
3117          case 'E':
3118             multi_action_index = ACTION_MULTI_SERVER_HEADER_TAGGER;
3119             break;
3120          default:
3121             log_error(LOG_LEVEL_ERROR,
3122                "Unknown filter type: %c for filter %s. Filter ignored.", type, name);
3123             continue;
3124       }
3125       assert(multi_action_index);
3126
3127       value = get_char_param(parameters, key_value);
3128       if (value == 'Y')
3129       {
3130          list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3131          if (!err) err = enlist(cur_line->data.action->multi_add[multi_action_index], name);
3132          list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3133       }
3134       else if (value == 'N')
3135       {
3136          list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3137          if (!cur_line->data.action->multi_remove_all[multi_action_index])
3138          {
3139             list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3140             if (!err) err = enlist(cur_line->data.action->multi_remove[multi_action_index], name);
3141          }
3142       }
3143       else if (value == 'X')
3144       {
3145          list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3146          list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3147       }
3148    }
3149
3150    if (err)
3151    {
3152       /* Out of memory */
3153       edit_free_file(file);
3154       return err;
3155    }
3156
3157    if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
3158    {
3159       /* Out of memory */
3160       edit_free_file(file);
3161       return JB_ERR_MEMORY;
3162    }
3163
3164    len = strlen(actiontext);
3165    if (len == 0)
3166    {
3167       /*
3168        * Empty action - must special-case this.
3169        * Simply setting len to 1 is sufficient...
3170        */
3171       len = 1;
3172    }
3173
3174    newtext_size = len + 2;
3175    newtext = malloc_or_die(newtext_size);
3176    strlcpy(newtext, actiontext, newtext_size);
3177    free(actiontext);
3178    newtext[0]       = '{';
3179    newtext[len]     = '}';
3180    newtext[len + 1] = '\0';
3181
3182    freez(cur_line->raw);
3183    freez(cur_line->unprocessed);
3184    cur_line->unprocessed = newtext;
3185
3186    err = edit_write_file(file);
3187    if (err)
3188    {
3189       /* Error writing file */
3190       if (err == JB_ERR_FILE)
3191       {
3192          /* Read-only file. */
3193          err = cgi_error_file_read_only(csp, rsp, file->filename);
3194       }
3195       edit_free_file(file);
3196       return err;
3197    }
3198
3199    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3200             (unsigned long) time(NULL), file->identifier, sectionid);
3201
3202    edit_free_file(file);
3203
3204    return cgi_redirect(rsp, target);
3205 }
3206
3207
3208 /*********************************************************************
3209  *
3210  * Function    :  cgi_edit_actions_url
3211  *
3212  * Description :  CGI function that actually edits a URL pattern in
3213  *                an actions file.
3214  *
3215  * Parameters  :
3216  *          1  :  csp = Current client state (buffers, headers, etc...)
3217  *          2  :  rsp = http_response data structure for output
3218  *          3  :  parameters = map of cgi parameters
3219  *
3220  * CGI Parameters :
3221  *    filename : Identifies the file to edit
3222  *         ver : File's last-modified time
3223  *     section : Line number of section to edit
3224  *     pattern : Line number of pattern to edit
3225  *      newval : New value for pattern
3226  *
3227  * Returns     :  JB_ERR_OK     on success
3228  *                JB_ERR_MEMORY on out-of-memory
3229  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3230  *                                  specified or not valid.
3231  *
3232  *********************************************************************/
3233 jb_err cgi_edit_actions_url(struct client_state *csp,
3234                             struct http_response *rsp,
3235                             const struct map *parameters)
3236 {
3237    unsigned patternid;
3238    char * new_pattern;
3239    struct editable_file * file;
3240    struct file_line * cur_line;
3241    unsigned line_number;
3242    unsigned section_start_line_number = 0;
3243    char target[1024];
3244    jb_err err;
3245
3246    assert(csp);
3247    assert(rsp);
3248    assert(parameters);
3249
3250    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3251    {
3252       return cgi_error_disabled(csp, rsp);
3253    }
3254
3255    err = get_number_param(csp, parameters, "p", &patternid);
3256    if (err)
3257    {
3258       return err;
3259    }
3260    if (patternid < 1U)
3261    {
3262       return JB_ERR_CGI_PARAMS;
3263    }
3264
3265    err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3266    if (err)
3267    {
3268       return err;
3269    }
3270
3271    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3272    if (err)
3273    {
3274       /* No filename specified, can't read file, modified, or out of memory. */
3275       free(new_pattern);
3276       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3277    }
3278
3279    line_number = 1;
3280    cur_line = file->lines;
3281
3282    while ((cur_line != NULL) && (line_number < patternid))
3283    {
3284       if (cur_line->type == FILE_LINE_ACTION)
3285       {
3286          section_start_line_number = line_number;
3287       }
3288       cur_line = cur_line->next;
3289       line_number++;
3290    }
3291
3292    if ((cur_line == NULL)
3293     || (cur_line->type != FILE_LINE_URL))
3294    {
3295       /* Invalid "patternid" parameter */
3296       free(new_pattern);
3297       edit_free_file(file);
3298       return JB_ERR_CGI_PARAMS;
3299    }
3300
3301    /* At this point, the line to edit is in cur_line */
3302
3303    freez(cur_line->raw);
3304    freez(cur_line->unprocessed);
3305    cur_line->unprocessed = new_pattern;
3306
3307    err = edit_write_file(file);
3308    if (err)
3309    {
3310       /* Error writing file */
3311       if (err == JB_ERR_FILE)
3312       {
3313          /* Read-only file. */
3314          err = cgi_error_file_read_only(csp, rsp, file->filename);
3315       }
3316       edit_free_file(file);
3317       return err;
3318    }
3319
3320    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3321             (unsigned long) time(NULL), file->identifier, section_start_line_number);
3322
3323    edit_free_file(file);
3324
3325    return cgi_redirect(rsp, target);
3326 }
3327
3328
3329 /*********************************************************************
3330  *
3331  * Function    :  cgi_edit_actions_add_url
3332  *
3333  * Description :  CGI function that actually adds a URL pattern to
3334  *                an actions file.
3335  *
3336  * Parameters  :
3337  *          1  :  csp = Current client state (buffers, headers, etc...)
3338  *          2  :  rsp = http_response data structure for output
3339  *          3  :  parameters = map of cgi parameters
3340  *
3341  * CGI Parameters :
3342  *    filename : Identifies the file to edit
3343  *         ver : File's last-modified time
3344  *     section : Line number of section to edit
3345  *      newval : New pattern
3346  *
3347  * Returns     :  JB_ERR_OK     on success
3348  *                JB_ERR_MEMORY on out-of-memory
3349  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3350  *                                  specified or not valid.
3351  *
3352  *********************************************************************/
3353 jb_err cgi_edit_actions_add_url(struct client_state *csp,
3354                                 struct http_response *rsp,
3355                                 const struct map *parameters)
3356 {
3357    unsigned sectionid;
3358    char * new_pattern;
3359    struct file_line * new_line;
3360    struct editable_file * file;
3361    struct file_line * cur_line;
3362    unsigned line_number;
3363    char target[1024];
3364    jb_err err;
3365
3366    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3367    {
3368       return cgi_error_disabled(csp, rsp);
3369    }
3370
3371    err = get_number_param(csp, parameters, "s", &sectionid);
3372    if (err)
3373    {
3374       return err;
3375    }
3376    if (sectionid < 1U)
3377    {
3378       return JB_ERR_CGI_PARAMS;
3379    }
3380
3381    err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3382    if (err)
3383    {
3384       return err;
3385    }
3386
3387    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3388    if (err)
3389    {
3390       /* No filename specified, can't read file, modified, or out of memory. */
3391       free(new_pattern);
3392       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3393    }
3394
3395    line_number = 1;
3396    cur_line = file->lines;
3397
3398    while ((cur_line != NULL) && (line_number < sectionid))
3399    {
3400       cur_line = cur_line->next;
3401       line_number++;
3402    }
3403
3404    if ((cur_line == NULL)
3405     || (cur_line->type != FILE_LINE_ACTION))
3406    {
3407       /* Invalid "sectionid" parameter */
3408       free(new_pattern);
3409       edit_free_file(file);
3410       return JB_ERR_CGI_PARAMS;
3411    }
3412
3413    /* At this point, the section header is in cur_line - add after this. */
3414
3415    /* Allocate the new line */
3416    new_line = zalloc_or_die(sizeof(*new_line));
3417
3418    /* Fill in the data members of the new line */
3419    new_line->raw = NULL;
3420    new_line->prefix = NULL;
3421    new_line->unprocessed = new_pattern;
3422    new_line->type = FILE_LINE_URL;
3423
3424    /* Link new_line into the list, after cur_line */
3425    new_line->next = cur_line->next;
3426    cur_line->next = new_line;
3427
3428    /* Done making changes, now commit */
3429
3430    err = edit_write_file(file);
3431    if (err)
3432    {
3433       /* Error writing file */
3434       if (err == JB_ERR_FILE)
3435       {
3436          /* Read-only file. */
3437          err = cgi_error_file_read_only(csp, rsp, file->filename);
3438       }
3439       edit_free_file(file);
3440       return err;
3441    }
3442
3443    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3444             (unsigned long) time(NULL), file->identifier, sectionid);
3445
3446    edit_free_file(file);
3447
3448    return cgi_redirect(rsp, target);
3449 }
3450
3451
3452 /*********************************************************************
3453  *
3454  * Function    :  cgi_edit_actions_remove_url
3455  *
3456  * Description :  CGI function that actually removes a URL pattern from
3457  *                the actions file.
3458  *
3459  * Parameters  :
3460  *          1  :  csp = Current client state (buffers, headers, etc...)
3461  *          2  :  rsp = http_response data structure for output
3462  *          3  :  parameters = map of cgi parameters
3463  *
3464  * CGI Parameters :
3465  *           f : (filename) Identifies the file to edit
3466  *           v : (version) File's last-modified time
3467  *           p : (pattern) Line number of pattern to remove
3468  *
3469  * Returns     :  JB_ERR_OK     on success
3470  *                JB_ERR_MEMORY on out-of-memory
3471  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3472  *                                  specified or not valid.
3473  *
3474  *********************************************************************/
3475 jb_err cgi_edit_actions_remove_url(struct client_state *csp,
3476                                    struct http_response *rsp,
3477                                    const struct map *parameters)
3478 {
3479    unsigned patternid;
3480    struct editable_file * file;
3481    struct file_line * cur_line;
3482    struct file_line * prev_line;
3483    unsigned line_number;
3484    unsigned section_start_line_number = 0;
3485    char target[1024];
3486    jb_err err;
3487
3488    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3489    {
3490       return cgi_error_disabled(csp, rsp);
3491    }
3492
3493    err = get_number_param(csp, parameters, "p", &patternid);
3494    if (err)
3495    {
3496       return err;
3497    }
3498
3499    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3500    if (err)
3501    {
3502       /* No filename specified, can't read file, modified, or out of memory. */
3503       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3504    }
3505
3506    line_number = 1;
3507    prev_line = NULL;
3508    cur_line = file->lines;
3509
3510    while ((cur_line != NULL) && (line_number < patternid))
3511    {
3512       if (cur_line->type == FILE_LINE_ACTION)
3513       {
3514          section_start_line_number = line_number;
3515       }
3516       prev_line = cur_line;
3517       cur_line = cur_line->next;
3518       line_number++;
3519    }
3520
3521    if ( (cur_line == NULL)
3522      || (prev_line == NULL)
3523      || (cur_line->type != FILE_LINE_URL))
3524    {
3525       /* Invalid "patternid" parameter */
3526       edit_free_file(file);
3527       return JB_ERR_CGI_PARAMS;
3528    }
3529
3530    /* At this point, the line to remove is in cur_line, and the previous
3531     * one is in prev_line
3532     */
3533
3534    /* Unlink cur_line */
3535    prev_line->next = cur_line->next;
3536    cur_line->next = NULL;
3537
3538    /* Free cur_line */
3539    edit_free_file_lines(cur_line);
3540
3541    err = edit_write_file(file);
3542    if (err)
3543    {
3544       /* Error writing file */
3545       if (err == JB_ERR_FILE)
3546       {
3547          /* Read-only file. */
3548          err = cgi_error_file_read_only(csp, rsp, file->filename);
3549       }
3550       edit_free_file(file);
3551       return err;
3552    }
3553
3554    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3555             (unsigned long) time(NULL), file->identifier, section_start_line_number);
3556
3557    edit_free_file(file);
3558
3559    return cgi_redirect(rsp, target);
3560 }
3561
3562
3563 /*********************************************************************
3564  *
3565  * Function    :  cgi_edit_actions_section_remove
3566  *
3567  * Description :  CGI function that actually removes a whole section from
3568  *                the actions file.  The section must be empty first
3569  *                (else JB_ERR_CGI_PARAMS).
3570  *
3571  * Parameters  :
3572  *          1  :  csp = Current client state (buffers, headers, etc...)
3573  *          2  :  rsp = http_response data structure for output
3574  *          3  :  parameters = map of cgi parameters
3575  *
3576  * CGI Parameters :
3577  *           f : (filename) Identifies the file to edit
3578  *           v : (version) File's last-modified time
3579  *           s : (section) Line number of section to edit
3580  *
3581  * Returns     :  JB_ERR_OK     on success
3582  *                JB_ERR_MEMORY on out-of-memory
3583  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3584  *                                  specified or not valid.
3585  *
3586  *********************************************************************/
3587 jb_err cgi_edit_actions_section_remove(struct client_state *csp,
3588                                        struct http_response *rsp,
3589                                        const struct map *parameters)
3590 {
3591    unsigned sectionid;
3592    struct editable_file * file;
3593    struct file_line * cur_line;
3594    struct file_line * prev_line;
3595    unsigned line_number;
3596    char target[1024];
3597    jb_err err;
3598
3599    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3600    {
3601       return cgi_error_disabled(csp, rsp);
3602    }
3603
3604    err = get_number_param(csp, parameters, "s", &sectionid);
3605    if (err)
3606    {
3607       return err;
3608    }
3609
3610    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3611    if (err)
3612    {
3613       /* No filename specified, can't read file, modified, or out of memory. */
3614       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3615    }
3616
3617    line_number = 1;
3618    cur_line = file->lines;
3619
3620    prev_line = NULL;
3621    while ((cur_line != NULL) && (line_number < sectionid))
3622    {
3623       prev_line = cur_line;
3624       cur_line = cur_line->next;
3625       line_number++;
3626    }
3627
3628    if ((cur_line == NULL)
3629     || (cur_line->type != FILE_LINE_ACTION))
3630    {
3631       /* Invalid "sectionid" parameter */
3632       edit_free_file(file);
3633       return JB_ERR_CGI_PARAMS;
3634    }
3635
3636    if ((cur_line->next != NULL)
3637     && (cur_line->next->type == FILE_LINE_URL))
3638    {
3639       /* Section not empty. */
3640       edit_free_file(file);
3641       return JB_ERR_CGI_PARAMS;
3642    }
3643
3644    /* At this point, the line to remove is in cur_line, and the previous
3645     * one is in prev_line
3646     */
3647
3648    /* Unlink cur_line */
3649    if (prev_line == NULL)
3650    {
3651       /* Removing the first line from the file */
3652       file->lines = cur_line->next;
3653    }
3654    else
3655    {
3656       prev_line->next = cur_line->next;
3657    }
3658    cur_line->next = NULL;
3659
3660    /* Free cur_line */
3661    edit_free_file_lines(cur_line);
3662
3663    err = edit_write_file(file);
3664    if (err)
3665    {
3666       /* Error writing file */
3667       if (err == JB_ERR_FILE)
3668       {
3669          /* Read-only file. */
3670          err = cgi_error_file_read_only(csp, rsp, file->filename);
3671       }
3672       edit_free_file(file);
3673       return err;
3674    }
3675
3676    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
3677             (unsigned long) time(NULL), file->identifier);
3678
3679    edit_free_file(file);
3680
3681    return cgi_redirect(rsp, target);
3682 }
3683
3684
3685 /*********************************************************************
3686  *
3687  * Function    :  cgi_edit_actions_section_add
3688  *
3689  * Description :  CGI function that adds a new empty section to
3690  *                an actions file.
3691  *
3692  * Parameters  :
3693  *          1  :  csp = Current client state (buffers, headers, etc...)
3694  *          2  :  rsp = http_response data structure for output
3695  *          3  :  parameters = map of cgi parameters
3696  *
3697  * CGI Parameters :
3698  *           f : (filename) Identifies the file to edit
3699  *           v : (version) File's last-modified time
3700  *           s : (section) Line number of section to add after, 0 for
3701  *               start of file.
3702  *
3703  * Returns     :  JB_ERR_OK     on success
3704  *                JB_ERR_MEMORY on out-of-memory
3705  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3706  *                                  specified or not valid.
3707  *
3708  *********************************************************************/
3709 jb_err cgi_edit_actions_section_add(struct client_state *csp,
3710                                     struct http_response *rsp,
3711                                     const struct map *parameters)
3712 {
3713    unsigned sectionid;
3714    struct file_line * new_line;
3715    char * new_text;
3716    struct editable_file * file;
3717    struct file_line * cur_line;
3718    unsigned line_number;
3719    char target[1024];
3720    jb_err err;
3721
3722    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3723    {
3724       return cgi_error_disabled(csp, rsp);
3725    }
3726
3727    err = get_number_param(csp, parameters, "s", &sectionid);
3728    if (err)
3729    {
3730       return err;
3731    }
3732
3733    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3734    if (err)
3735    {
3736       /* No filename specified, can't read file, modified, or out of memory. */
3737       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3738    }
3739
3740    line_number = 1;
3741    cur_line = file->lines;
3742
3743    if (sectionid <= 1U)
3744    {
3745       /* Add to start of file */
3746       if (cur_line != NULL && cur_line->type != FILE_LINE_ACTION)
3747       {
3748          /* There's something in the file, find the line before the first
3749           * action.
3750           */
3751          while ((cur_line->next != NULL)
3752               && (cur_line->next->type != FILE_LINE_ACTION))
3753          {
3754             cur_line = cur_line->next;
3755             line_number++;
3756          }
3757       }
3758       else
3759       {
3760          /* File starts with action line, so insert at top */
3761          cur_line = NULL;
3762       }
3763    }
3764    else
3765    {
3766       /* Add after stated section. */
3767       while ((cur_line != NULL) && (line_number < sectionid))
3768       {
3769          cur_line = cur_line->next;
3770          line_number++;
3771       }
3772
3773       if ((cur_line == NULL)
3774        || (cur_line->type != FILE_LINE_ACTION))
3775       {
3776          /* Invalid "sectionid" parameter */
3777          edit_free_file(file);
3778          return JB_ERR_CGI_PARAMS;
3779       }
3780
3781       /* Skip through the section to find the last line in it. */
3782       while ((cur_line->next != NULL)
3783           && (cur_line->next->type != FILE_LINE_ACTION))
3784       {
3785          cur_line = cur_line->next;
3786          line_number++;
3787       }
3788    }
3789
3790    /* At this point, the last line in the previous section is in cur_line
3791     * - add after this.  (Or if we need to add as the first line, cur_line
3792     * will be NULL).
3793     */
3794
3795    new_text = strdup("{}");
3796    if (NULL == new_text)
3797    {
3798       edit_free_file(file);
3799       return JB_ERR_MEMORY;
3800    }
3801
3802    /* Allocate the new line */
3803    new_line = zalloc_or_die(sizeof(*new_line));
3804
3805    /* Fill in the data members of the new line */
3806    new_line->raw = NULL;
3807    new_line->prefix = NULL;
3808    new_line->unprocessed = new_text;
3809    new_line->type = FILE_LINE_ACTION;
3810
3811    if (cur_line != NULL)
3812    {
3813       /* Link new_line into the list, after cur_line */
3814       new_line->next = cur_line->next;
3815       cur_line->next = new_line;
3816    }
3817    else
3818    {
3819       /* Link new_line into the list, as first line */
3820       new_line->next = file->lines;
3821       file->lines = new_line;
3822    }
3823
3824    /* Done making changes, now commit */
3825
3826    err = edit_write_file(file);
3827    if (err)
3828    {
3829       /* Error writing file */
3830       if (err == JB_ERR_FILE)
3831       {
3832          /* Read-only file. */
3833          err = cgi_error_file_read_only(csp, rsp, file->filename);
3834       }
3835       edit_free_file(file);
3836       return err;
3837    }
3838
3839    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
3840             (unsigned long) time(NULL), file->identifier);
3841
3842    edit_free_file(file);
3843
3844    return cgi_redirect(rsp, target);
3845 }
3846
3847
3848 /*********************************************************************
3849  *
3850  * Function    :  cgi_edit_actions_section_swap
3851  *
3852  * Description :  CGI function that swaps the order of two sections
3853  *                in the actions file.  Note that this CGI can actually
3854  *                swap any two arbitrary sections, but the GUI interface
3855  *                currently only allows consecutive sections to be
3856  *                specified.
3857  *
3858  * Parameters  :
3859  *          1  :  csp = Current client state (buffers, headers, etc...)
3860  *          2  :  rsp = http_response data structure for output
3861  *          3  :  parameters = map of cgi parameters
3862  *
3863  * CGI Parameters :
3864  *           f : (filename) Identifies the file to edit
3865  *           v : (version) File's last-modified time
3866  *          s1 : (section1) Line number of first section to swap
3867  *          s2 : (section2) Line number of second section to swap
3868  *
3869  * Returns     :  JB_ERR_OK     on success
3870  *                JB_ERR_MEMORY on out-of-memory
3871  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3872  *                                  specified or not valid.
3873  *
3874  *********************************************************************/
3875 jb_err cgi_edit_actions_section_swap(struct client_state *csp,
3876                                      struct http_response *rsp,
3877                                      const struct map *parameters)
3878 {
3879    unsigned section1;
3880    unsigned section2;
3881    struct editable_file * file;
3882    struct file_line * cur_line;
3883    struct file_line * prev_line;
3884    struct file_line * line_before_section1;
3885    struct file_line * line_start_section1;
3886    struct file_line * line_end_section1;
3887    struct file_line * line_after_section1;
3888    struct file_line * line_before_section2;
3889    struct file_line * line_start_section2;
3890    struct file_line * line_end_section2;
3891    struct file_line * line_after_section2;
3892    unsigned line_number;
3893    char target[1024];
3894    jb_err err;
3895
3896    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3897    {
3898       return cgi_error_disabled(csp, rsp);
3899    }
3900
3901    err = get_number_param(csp, parameters, "s1", &section1);
3902    if (!err) err = get_number_param(csp, parameters, "s2", &section2);
3903    if (err)
3904    {
3905       return err;
3906    }
3907
3908    if (section1 > section2)
3909    {
3910       unsigned temp = section2;
3911       section2 = section1;
3912       section1 = temp;
3913    }
3914
3915    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3916    if (err)
3917    {
3918       /* No filename specified, can't read file, modified, or out of memory. */
3919       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3920    }
3921
3922    /* Start at the beginning... */
3923    line_number = 1;
3924    cur_line = file->lines;
3925    prev_line = NULL;
3926
3927    /* ... find section1 ... */
3928    while ((cur_line != NULL) && (line_number < section1))
3929    {
3930       prev_line = cur_line;
3931       cur_line = cur_line->next;
3932       line_number++;
3933    }
3934
3935    if ((cur_line == NULL)
3936     || (cur_line->type != FILE_LINE_ACTION))
3937    {
3938       /* Invalid "section1" parameter */
3939       edit_free_file(file);
3940       return JB_ERR_CGI_PARAMS;
3941    }
3942
3943    /* If no-op, we've validated params and can skip the rest. */
3944    if (section1 != section2)
3945    {
3946       /* ... find the end of section1 ... */
3947       line_before_section1 = prev_line;
3948       line_start_section1 = cur_line;
3949       do
3950       {
3951          prev_line = cur_line;
3952          cur_line = cur_line->next;
3953          line_number++;
3954       }
3955       while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
3956       line_end_section1 = prev_line;
3957       line_after_section1 = cur_line;
3958
3959       /* ... find section2 ... */
3960       while ((cur_line != NULL) && (line_number < section2))
3961       {
3962          prev_line = cur_line;
3963          cur_line = cur_line->next;
3964          line_number++;
3965       }
3966
3967       if ((cur_line == NULL)
3968        || (cur_line->type != FILE_LINE_ACTION))
3969       {
3970          /* Invalid "section2" parameter */
3971          edit_free_file(file);
3972          return JB_ERR_CGI_PARAMS;
3973       }
3974
3975       /* ... find the end of section2 ... */
3976       line_before_section2 = prev_line;
3977       line_start_section2 = cur_line;
3978       do
3979       {
3980          prev_line = cur_line;
3981          cur_line = cur_line->next;
3982          line_number++;
3983       }
3984       while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
3985       line_end_section2 = prev_line;
3986       line_after_section2 = cur_line;
3987
3988       /* Now have all the pointers we need. Do the swap. */
3989
3990       /* Change the pointer to section1 to point to section2 instead */
3991       if (line_before_section1 == NULL)
3992       {
3993          file->lines = line_start_section2;
3994       }
3995       else
3996       {
3997          line_before_section1->next = line_start_section2;
3998       }
3999
4000       if (line_before_section2 == line_end_section1)
4001       {
4002          /* Consecutive sections */
4003          line_end_section2->next = line_start_section1;
4004       }
4005       else
4006       {
4007          line_end_section2->next = line_after_section1;
4008          line_before_section2->next = line_start_section1;
4009       }
4010
4011       /* Set the pointer from the end of section1 to the rest of the file */
4012       line_end_section1->next = line_after_section2;
4013
4014       err = edit_write_file(file);
4015       if (err)
4016       {
4017          /* Error writing file */
4018          if (err == JB_ERR_FILE)
4019          {
4020             /* Read-only file. */
4021             err = cgi_error_file_read_only(csp, rsp, file->filename);
4022          }
4023          edit_free_file(file);
4024          return err;
4025       }
4026    } /* END if (section1 != section2) */
4027
4028    snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
4029             (unsigned long) time(NULL), file->identifier);
4030
4031    edit_free_file(file);
4032
4033    return cgi_redirect(rsp, target);
4034 }
4035
4036
4037 /*********************************************************************
4038  *
4039  * Function    :  javascriptify
4040  *
4041  * Description :  Converts a string into a form JavaScript will like.
4042  *
4043  *                Netscape 4's JavaScript sucks - it doesn't use
4044  *                "id" parameters, so you have to set the "name"
4045  *                used to submit a form element to something JavaScript
4046  *                will like.  (Or access the elements by index in an
4047  *                array.  That array contains >60 elements and will
4048  *                be changed whenever we add a new action to the
4049  *                editor, so I'm NOT going to use indexes that have
4050  *                to be figured out by hand.)
4051  *
4052  *                Currently the only thing we have to worry about
4053  *                is "-" ==> "_" conversion.
4054  *
4055  *                This is a length-preserving operation so it is
4056  *                carried out in-place, no memory is allocated
4057  *                or freed.
4058  *
4059  * Parameters  :
4060  *          1  :  identifier = String to make JavaScript-friendly.
4061  *
4062  * Returns     :  N/A
4063  *
4064  *********************************************************************/
4065 static void javascriptify(char * identifier)
4066 {
4067    char * p = identifier;
4068    while (NULL != (p = strchr(p, '-')))
4069    {
4070       *p++ = '_';
4071    }
4072 }
4073
4074
4075 /*********************************************************************
4076  *
4077  * Function    :  actions_to_radio
4078  *
4079  * Description :  Converts a actionsfile entry into settings for
4080  *                radio buttons and edit boxes on a HTML form.
4081  *
4082  * Parameters  :
4083  *          1  :  exports = List of substitutions to add to.
4084  *          2  :  action  = Action to read
4085  *
4086  * Returns     :  JB_ERR_OK     on success
4087  *                JB_ERR_MEMORY on out-of-memory
4088  *
4089  *********************************************************************/
4090 static jb_err actions_to_radio(struct map * exports,
4091                                const struct action_spec *action)
4092 {
4093    unsigned long mask;
4094    unsigned long add;
4095    int mapped_param;
4096    int checked;
4097    char current_mode;
4098
4099    assert(exports);
4100    assert(action);
4101
4102    mask = action->mask;
4103    add  = action->add;
4104
4105    /* sanity - prevents "-feature +feature" */
4106    mask |= add;
4107
4108
4109 #define DEFINE_ACTION_BOOL(name, bit)                 \
4110    if (!(mask & bit))                                 \
4111    {                                                  \
4112       current_mode = 'n';                             \
4113    }                                                  \
4114    else if (add & bit)                                \
4115    {                                                  \
4116       current_mode = 'y';                             \
4117    }                                                  \
4118    else                                               \
4119    {                                                  \
4120       current_mode = 'x';                             \
4121    }                                                  \
4122    if (map_radio(exports, name, "ynx", current_mode)) \
4123    {                                                  \
4124       return JB_ERR_MEMORY;                           \
4125    }
4126
4127 #define DEFINE_ACTION_STRING(name, bit, index)        \
4128    DEFINE_ACTION_BOOL(name, bit);                     \
4129    mapped_param = 0;
4130
4131 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default)  \
4132    if (add & bit)                                                    \
4133    {                                                                 \
4134       checked = !strcmp(action->string[index], value);               \
4135    }                                                                 \
4136    else                                                              \
4137    {                                                                 \
4138       checked = is_default;                                          \
4139    }                                                                 \
4140    mapped_param |= checked;                                          \
4141    if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
4142    {                                                                 \
4143       return JB_ERR_MEMORY;                                          \
4144    }
4145
4146 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val)       \
4147    if (map(exports, name "-param-custom", 1,                         \
4148            ((!mapped_param) ? "checked" : ""), 1))                   \
4149    {                                                                 \
4150       return JB_ERR_MEMORY;                                          \
4151    }                                                                 \
4152    if (map(exports, name "-param", 1,                                \
4153            (((add & bit) && !mapped_param) ?                         \
4154            action->string[index] : default_val), 1))                 \
4155    {                                                                 \
4156       return JB_ERR_MEMORY;                                          \
4157    }
4158
4159 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val)     \
4160    if (map(exports, name "-param", 1,                                \
4161            ((add & bit) ? action->string[index] : default_val), 1))  \
4162    {                                                                 \
4163       return JB_ERR_MEMORY;                                          \
4164    }
4165
4166 #define DEFINE_ACTION_MULTI(name, index)              \
4167    if (action->multi_add[index]->first)               \
4168    {                                                  \
4169       current_mode = 'y';                             \
4170    }                                                  \
4171    else if (action->multi_remove_all[index])          \
4172    {                                                  \
4173       current_mode = 'n';                             \
4174    }                                                  \
4175    else if (action->multi_remove[index]->first)       \
4176    {                                                  \
4177       current_mode = 'y';                             \
4178    }                                                  \
4179    else                                               \
4180    {                                                  \
4181       current_mode = 'x';                             \
4182    }                                                  \
4183    if (map_radio(exports, name, "ynx", current_mode)) \
4184    {                                                  \
4185       return JB_ERR_MEMORY;                           \
4186    }
4187
4188 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
4189
4190 #include "actionlist.h"
4191
4192 #undef DEFINE_ACTION_MULTI
4193 #undef DEFINE_ACTION_STRING
4194 #undef DEFINE_ACTION_BOOL
4195 #undef DEFINE_ACTION_ALIAS
4196 #undef DEFINE_CGI_PARAM_CUSTOM
4197 #undef DEFINE_CGI_PARAM_RADIO
4198 #undef DEFINE_CGI_PARAM_NO_RADIO
4199
4200    return JB_ERR_OK;
4201 }
4202
4203
4204 /*********************************************************************
4205  *
4206  * Function    :  actions_from_radio
4207  *
4208  * Description :  Converts a map of parameters passed to a CGI function
4209  *                into an actionsfile entry.
4210  *
4211  * Parameters  :
4212  *          1  :  parameters = parameters to the CGI call
4213  *          2  :  action  = Action to change.  Must be valid before
4214  *                          the call, actions not specified will be
4215  *                          left unchanged.
4216  *
4217  * Returns     :  JB_ERR_OK     on success
4218  *                JB_ERR_MEMORY on out-of-memory
4219  *
4220  *********************************************************************/
4221 static jb_err actions_from_radio(const struct map * parameters,
4222                                  struct action_spec *action)
4223 {
4224    const char * param;
4225    char * param_dup;
4226    char ch;
4227    const char * js_name;
4228    jb_err err = JB_ERR_OK;
4229
4230    assert(parameters);
4231    assert(action);
4232
4233    /* Statics are generally a potential race condition,
4234     * but in this case we're safe and don't need semaphores.
4235     * Be careful if you modify this function.
4236     * - Jon
4237     * The js_name_arr's are never free()d, but this is no
4238     * problem, since they will only be created once and
4239     * used by all threads thereafter. -oes
4240     */
4241
4242 #define JAVASCRIPTIFY(dest_var, string)               \
4243    {                                                  \
4244      static int first_time = 1;                       \
4245      static char *js_name_arr;                        \
4246       if (first_time)                                 \
4247       {                                               \
4248          js_name_arr = strdup(string);                \
4249          javascriptify(js_name_arr);                  \
4250       }                                               \
4251       dest_var = js_name_arr;                         \
4252       first_time = 0;                                 \
4253    }                                                  \
4254
4255 #define DEFINE_ACTION_BOOL(name, bit)                 \
4256    JAVASCRIPTIFY(js_name, name);                      \
4257    ch = get_char_param(parameters, js_name);          \
4258    if (ch == 'Y')                                     \
4259    {                                                  \
4260       action->add  |= bit;                            \
4261       action->mask |= bit;                            \
4262    }                                                  \
4263    else if (ch == 'N')                                \
4264    {                                                  \
4265       action->add  &= ~bit;                           \
4266       action->mask &= ~bit;                           \
4267    }                                                  \
4268    else if (ch == 'X')                                \
4269    {                                                  \
4270       action->add  &= ~bit;                           \
4271       action->mask |= bit;                            \
4272    }                                                  \
4273
4274 #define DEFINE_ACTION_STRING(name, bit, index)                 \
4275    JAVASCRIPTIFY(js_name, name);                               \
4276    ch = get_char_param(parameters, js_name);                   \
4277    if (ch == 'Y')                                              \
4278    {                                                           \
4279       param = NULL;                                            \
4280       JAVASCRIPTIFY(js_name, name "-mode");                    \
4281       if (!err) err = get_string_param(parameters, js_name, &param);    \
4282       if ((param == NULL) || (0 == strcmp(param, "CUSTOM")))            \
4283       {                                                                 \
4284          JAVASCRIPTIFY(js_name, name "-param");                         \
4285          if (!err) err = get_string_param(parameters, js_name, &param); \
4286       }                                                        \
4287       if (param != NULL)                                       \
4288       {                                                        \
4289          if (NULL == (param_dup = strdup(param)))              \
4290          {                                                     \
4291             return JB_ERR_MEMORY;                              \
4292          }                                                     \
4293          freez(action->string[index]);                         \
4294          action->add  |= bit;                                  \
4295          action->mask |= bit;                                  \
4296          action->string[index] = param_dup;                    \
4297       }                                                        \
4298    }                                                           \
4299    else if (ch == 'N')                                         \
4300    {                                                           \
4301       if (action->add & bit)                                   \
4302       {                                                        \
4303          freez(action->string[index]);                         \
4304       }                                                        \
4305       action->add  &= ~bit;                                    \
4306       action->mask &= ~bit;                                    \
4307    }                                                           \
4308    else if (ch == 'X')                                         \
4309    {                                                           \
4310       if (action->add & bit)                                   \
4311       {                                                        \
4312          freez(action->string[index]);                         \
4313       }                                                        \
4314       action->add  &= ~bit;                                    \
4315       action->mask |= bit;                                     \
4316    }                                                           \
4317
4318 #define DEFINE_ACTION_MULTI(name, index)                       \
4319    JAVASCRIPTIFY(js_name, name);                               \
4320    ch = get_char_param(parameters, js_name);                   \
4321    if (ch == 'Y')                                              \
4322    {                                                           \
4323       /* FIXME */                                              \
4324    }                                                           \
4325    else if (ch == 'N')                                         \
4326    {                                                           \
4327       list_remove_all(action->multi_add[index]);               \
4328       list_remove_all(action->multi_remove[index]);            \
4329       action->multi_remove_all[index] = 1;                     \
4330    }                                                           \
4331    else if (ch == 'X')                                         \
4332    {                                                           \
4333       list_remove_all(action->multi_add[index]);               \
4334       list_remove_all(action->multi_remove[index]);            \
4335       action->multi_remove_all[index] = 0;                     \
4336    }                                                           \
4337
4338 #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
4339
4340 #include "actionlist.h"
4341
4342 #undef DEFINE_ACTION_MULTI
4343 #undef DEFINE_ACTION_STRING
4344 #undef DEFINE_ACTION_BOOL
4345 #undef DEFINE_ACTION_ALIAS
4346 #undef JAVASCRIPTIFY
4347
4348    return err;
4349 }
4350 #endif /* def FEATURE_CGI_EDIT_ACTIONS */
4351
4352
4353 #ifdef FEATURE_TOGGLE
4354 /*********************************************************************
4355  *
4356  * Function    :  cgi_toggle
4357  *
4358  * Description :  CGI function that adds a new empty section to
4359  *                an actions file.
4360  *
4361  * Parameters  :
4362  *          1  :  csp = Current client state (buffers, headers, etc...)
4363  *          2  :  rsp = http_response data structure for output
4364  *          3  :  parameters = map of cgi parameters
4365  *
4366  * CGI Parameters :
4367  *         set : If present, how to change toggle setting:
4368  *               "enable", "disable", "toggle", or none (default).
4369  *        mini : If present, use mini reply template.
4370  *
4371  * Returns     :  JB_ERR_OK     on success
4372  *                JB_ERR_MEMORY on out-of-memory
4373  *
4374  *********************************************************************/
4375 jb_err cgi_toggle(struct client_state *csp,
4376                   struct http_response *rsp,
4377                   const struct map *parameters)
4378 {
4379    struct map *exports;
4380    char mode;
4381    const char *template_name;
4382
4383    assert(csp);
4384    assert(rsp);
4385    assert(parameters);
4386
4387    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE))
4388    {
4389       return cgi_error_disabled(csp, rsp);
4390    }
4391
4392    mode = get_char_param(parameters, "set");
4393
4394    if (mode == 'E')
4395    {
4396       /* Enable */
4397       global_toggle_state = 1;
4398    }
4399    else if (mode == 'D')
4400    {
4401       /* Disable */
4402       global_toggle_state = 0;
4403    }
4404    else if (mode == 'T')
4405    {
4406       /* Toggle */
4407       global_toggle_state = !global_toggle_state;
4408    }
4409
4410    log_error(LOG_LEVEL_INFO, "Now toggled %s.", global_toggle_state ? "ON" : "OFF");
4411
4412    if (NULL == (exports = default_exports(csp, "toggle")))
4413    {
4414       return JB_ERR_MEMORY;
4415    }
4416
4417    template_name = (get_char_param(parameters, "mini")
4418                  ? "toggle-mini"
4419                  : "toggle");
4420
4421    return template_fill_for_cgi(csp, template_name, exports, rsp);
4422 }
4423 #endif /* def FEATURE_TOGGLE */
4424
4425
4426 /*
4427   Local Variables:
4428   tab-width: 3
4429   end:
4430 */