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