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