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