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