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