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