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