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