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