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