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