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