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