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