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