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