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