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