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