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