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