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