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