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