Automated function-comment nitpicking.
[privoxy.git] / cgiedit.c
1 const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.14 2002/03/05 00:24:51 jongfoster Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/cgiedit.c,v $
5  *
6  * Purpose     :  CGI-based actionsfile editor.
7  *
8  *                Functions declared include: cgi_edit_*
9  *
10  *                NOTE: The CGIs in this file use parameter names
11  *                such as "f" and "s" which are really *BAD* choices.
12  *                However, I'm trying to save bytes in the
13  *                edit-actions-list HTML page - the standard actions
14  *                file generated a 550kbyte page, which is ridiculous.
15  *
16  *                Stick to the short names in this file for consistency.
17  *
18  * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
19  *                IJBSWA team.  http://ijbswa.sourceforge.net
20  *
21  *                Based on the Internet Junkbuster originally written
22  *                by and Copyright (C) 1997 Anonymous Coders and
23  *                Junkbusters Corporation.  http://www.junkbusters.com
24  *
25  *                This program is free software; you can redistribute it
26  *                and/or modify it under the terms of the GNU General
27  *                Public License as published by the Free Software
28  *                Foundation; either version 2 of the License, or (at
29  *                your option) any later version.
30  *
31  *                This program is distributed in the hope that it will
32  *                be useful, but WITHOUT ANY WARRANTY; without even the
33  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
34  *                PARTICULAR PURPOSE.  See the GNU General Public
35  *                License for more details.
36  *
37  *                The GNU General Public License should be included with
38  *                this file.  If not, you can view it at
39  *                http://www.gnu.org/copyleft/gpl.html
40  *                or write to the Free Software Foundation, Inc., 59
41  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
42  *
43  * Revisions   :
44  *    $Log: cgiedit.c,v $
45  *    Revision 1.14  2002/03/05 00:24:51  jongfoster
46  *    Patch to always edit the current actions file.
47  *
48  *    Revision 1.13  2002/03/04 02:07:59  david__schmidt
49  *    Enable web editing of actions file on OS/2 (it had been broken all this time!)
50  *
51  *    Revision 1.12  2002/03/03 09:18:03  joergs
52  *    Made jumbjuster work on AmigaOS again.
53  *
54  *    Revision 1.11  2002/01/23 01:03:31  jongfoster
55  *    Fixing gcc [CygWin] compiler warnings
56  *
57  *    Revision 1.10  2002/01/23 00:22:59  jongfoster
58  *    Adding new function cgi_edit_actions_section_swap(), to reorder
59  *    the actions file.
60  *
61  *    Adding get_url_spec_param() to get a validated URL pattern.
62  *
63  *    Moving edit_read_line() out of this file and into loaders.c.
64  *
65  *    Adding missing html_encode() to many CGI functions.
66  *
67  *    Moving the functions that #include actionlist.h to the end of the file,
68  *    because the Visual C++ 97 debugger gets extremely confused if you try
69  *    to debug any code that comes after them in the file.
70  *
71  *    Major optimizations in cgi_edit_actions_list() to reduce the size of
72  *    the generated HTML (down 40% from 550k to 304k), with major side-effects
73  *    throughout the editor and templates.  In particular, the length of the
74  *    URLs throughout the editor has been drastically reduced, by cutting
75  *    paramater names down to 1 character and CGI names down to 3-4
76  *    characters, by removing all non-essential CGI paramaters even at the
77  *    expense of having to re-read the actions file for the most trivial
78  *    page, and by using relative rather than absolute URLs.  This means
79  *    that this (typical example):
80  *
81  *    <a href="http://ijbswa.sourceforge.net/config/edit-actions-url-form?
82  *    filename=ijb&amp;ver=1011487572&amp;section=12&amp;pattern=13
83  *    &amp;oldval=www.oesterhelt.org%2Fdeanimate-demo">
84  *
85  *    is now this:
86  *
87  *    <a href="eau?f=ijb&amp;v=1011487572&amp;p=13">
88  *
89  *    Revision 1.9  2002/01/17 20:56:22  jongfoster
90  *    Replacing hard references to the URL of the config interface
91  *    with #defines from project.h
92  *
93  *    Revision 1.8  2001/11/30 23:35:51  jongfoster
94  *    Renaming actionsfile to ijb.action
95  *
96  *    Revision 1.7  2001/11/13 00:28:24  jongfoster
97  *    - Renaming parameters from edit-actions-for-url so that they only
98  *      contain legal JavaScript characters.  If we wanted to write
99  *      JavaScript that worked with Netscape 4, this is nessacery.
100  *      (Note that at the moment the JavaScript doesn't actually work
101  *      with Netscape 4, but now this is purely a template issue, not
102  *      one affecting code).
103  *    - Adding new CGIs for use by non-JavaScript browsers:
104  *        edit-actions-url-form
105  *        edit-actions-add-url-form
106  *        edit-actions-remove-url-form
107  *    - Fixing || bug.
108  *
109  *    Revision 1.6  2001/10/29 03:48:09  david__schmidt
110  *    OS/2 native needed a snprintf() routine.  Added one to miscutil, brackedted
111  *    by and __OS2__ ifdef.
112  *
113  *    Revision 1.5  2001/10/25 03:40:48  david__schmidt
114  *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
115  *    threads to call select() simultaneously.  So, it's time to do a real, live,
116  *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
117  *    (native). Both versions will work, but using __OS2__ offers multi-threading.
118  *
119  *    Revision 1.4  2001/10/23 21:48:19  jongfoster
120  *    Cleaning up error handling in CGI functions - they now send back
121  *    a HTML error page and should never cause a FATAL error.  (Fixes one
122  *    potential source of "denial of service" attacks).
123  *
124  *    CGI actions file editor that works and is actually useful.
125  *
126  *    Ability to toggle JunkBuster remotely using a CGI call.
127  *
128  *    You can turn off both the above features in the main configuration
129  *    file, e.g. if you are running a multi-user proxy.
130  *
131  *    Revision 1.3  2001/10/14 22:12:49  jongfoster
132  *    New version of CGI-based actionsfile editor.
133  *    Major changes, including:
134  *    - Completely new file parser and file output routines
135  *    - edit-actions CGI renamed edit-actions-for-url
136  *    - All CGIs now need a filename parameter, except for...
137  *    - New CGI edit-actions which doesn't need a filename,
138  *      to allow you to start the editor up.
139  *    - edit-actions-submit now works, and now automatically
140  *      redirects you back to the main edit-actions-list handler.
141  *
142  *    Revision 1.2  2001/09/16 17:05:14  jongfoster
143  *    Removing unused #include showarg.h
144  *
145  *    Revision 1.1  2001/09/16 15:47:37  jongfoster
146  *    First version of CGI-based edit interface.  This is very much a
147  *    work-in-progress, and you can't actually use it to edit anything
148  *    yet.  You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
149  *    to have any effect.
150  *
151  *
152  **********************************************************************/
153 \f
154
155 #include "config.h"
156
157 /*
158  * FIXME: Following includes copied from cgi.c - which are actually needed?
159  */
160
161 #include <stdio.h>
162 #include <stdlib.h>
163 #include <sys/types.h>
164 #include <ctype.h>
165 #include <string.h>
166 #include <assert.h>
167 #include <limits.h>
168 #include <sys/stat.h>
169
170 #ifdef _WIN32
171 #define snprintf _snprintf
172 #endif /* def _WIN32 */
173
174 #include "project.h"
175 #include "cgi.h"
176 #include "cgiedit.h"
177 #include "cgisimple.h"
178 #include "list.h"
179 #include "encode.h"
180 #include "actions.h"
181 #include "miscutil.h"
182 #include "errlog.h"
183 #include "loaders.h"
184 #include "loadcfg.h"
185 /* loadcfg.h is for g_bToggleIJB only */
186 #include "urlmatch.h"
187
188 const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION;
189
190
191 #ifdef FEATURE_CGI_EDIT_ACTIONS
192
193 struct file_line
194 {
195    struct file_line * next;
196    char * raw;
197    char * prefix;
198    char * unprocessed;
199    int type;
200
201    union
202    {
203       struct action_spec action[1];
204
205       struct
206       {
207          char * name;
208          char * svalue;
209          int ivalue;
210       } setting;
211
212       /* Add more data types here... e.g.
213
214
215       struct url_spec url[1];
216
217       struct
218       {
219          struct action_spec action[1];
220          const char * name;
221       } alias;
222
223       */
224
225    } data;
226 };
227
228 #define FILE_LINE_UNPROCESSED           1
229 #define FILE_LINE_BLANK                 2
230 #define FILE_LINE_ALIAS_HEADER          3
231 #define FILE_LINE_ALIAS_ENTRY           4
232 #define FILE_LINE_ACTION                5
233 #define FILE_LINE_URL                   6
234 #define FILE_LINE_SETTINGS_HEADER       7
235 #define FILE_LINE_SETTINGS_ENTRY        8
236 #define FILE_LINE_DESCRIPTION_HEADER    9
237 #define FILE_LINE_DESCRIPTION_ENTRY    10
238
239
240 struct editable_file
241 {
242    struct file_line * lines;
243    const char * filename;     /* Full pathname - e.g. "/etc/junkbuster/wibble.action" */
244    const char * identifier;   /* Filename stub - e.g. "wibble".  Use for CGI param. */
245                               /* Pre-encoded with url_encode() for ease of use. */
246    const char * version_str;  /* Last modification time, as a string.  For CGI param */
247                               /* Can be used in URL without using url_param(). */
248    unsigned version;          /* Last modification time - prevents chaos with
249                                * the browser's "back" button.  Note that this is a
250                                * time_t cast to an unsigned.  When comparing, always
251                                * cast the time_t to an unsigned, and *NOT* vice-versa.
252                                * This may lose the top few bits, but they're not
253                                * significant anyway.
254                                */
255    int newline;               /* Newline convention - one of the NEWLINE_xxx constants.
256                                * Note that changing this after the file has been
257                                * read in will cause a mess.
258                                */
259    struct file_line * parse_error; /* On parse error, this is the offending line. */
260    const char * parse_error_text;  /* On parse error, this is the problem.
261                                     * (Statically allocated) */
262 };
263
264 /* FIXME: Following non-static functions should be prototyped in .h or made static */
265
266 /* Functions to read and write arbitrary config files */
267 jb_err edit_read_file(struct client_state *csp,
268                       const struct map *parameters,
269                       int require_version,
270                       const char *suffix,
271                       struct editable_file **pfile);
272 jb_err edit_write_file(struct editable_file * file);
273 void   edit_free_file(struct editable_file * file);
274
275 /* Functions to read and write actions files */
276 jb_err edit_parse_actions_file(struct editable_file * file);
277 jb_err edit_read_actions_file(struct client_state *csp,
278                               struct http_response *rsp,
279                               const struct map *parameters,
280                               int require_version,
281                               struct editable_file **pfile);
282
283 /* Error handlers */
284 jb_err cgi_error_modified(struct client_state *csp,
285                           struct http_response *rsp,
286                           const char *filename);
287 jb_err cgi_error_parse(struct client_state *csp,
288                        struct http_response *rsp,
289                        struct editable_file *file);
290 jb_err cgi_error_file(struct client_state *csp,
291                       struct http_response *rsp,
292                       const char *filename);
293 jb_err cgi_error_disabled(struct client_state *csp,
294                           struct http_response *rsp);
295
296 /* Internal arbitrary config file support functions */
297 static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline);
298 static void edit_free_file_lines(struct file_line * first_line);
299
300 /* Internal actions file support functions */
301 static int match_actions_file_header_line(const char * line, const char * name);
302 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue);
303
304 /* Internal parameter parsing functions */
305 static jb_err get_file_name_param(struct client_state *csp,
306                                   const struct map *parameters,
307                                   const char *param_name,
308                                   const char *suffix,
309                                   char **pfilename,
310                                   const char **pparam);
311 static jb_err get_number_param(struct client_state *csp,
312                                const struct map *parameters,
313                                char *name,
314                                unsigned *pvalue);
315 static jb_err get_url_spec_param(struct client_state *csp,
316                                  const struct map *parameters,
317                                  const char *name,
318                                  char **pvalue);
319
320 /* Internal actionsfile <==> HTML conversion functions */
321 static jb_err map_radio(struct map * exports,
322                         const char * optionname,
323                         const char * values,
324                         char value);
325 static jb_err actions_to_radio(struct map * exports,
326                                const struct action_spec *action);
327 static jb_err actions_from_radio(const struct map * parameters,
328                                  struct action_spec *action);
329
330
331 static jb_err map_copy_parameter_html(struct map *out,
332                                       const struct map *in,
333                                       const char *name);
334 #if 0 /* unused function */
335 static jb_err map_copy_parameter_url(struct map *out,
336                                      const struct map *in,
337                                      const char *name);
338 #endif /* unused function */
339
340 /*********************************************************************
341  *
342  * Function    :  map_copy_parameter_html
343  *
344  * Description :  Copy a CGI parameter from one map to another, HTML
345  *                encoding it.
346  *
347  * Parameters  :
348  *          1  :  out = target map
349  *          2  :  in = source map
350  *          3  :  name = name of cgi parameter to copy
351  *
352  * Returns     :  JB_ERR_OK on success
353  *                JB_ERR_MEMORY on out-of-memory
354  *                JB_ERR_CGI_PARAMS if the parameter doesn't exist
355  *                                  in the source map
356  *
357  *********************************************************************/
358 static jb_err map_copy_parameter_html(struct map *out,
359                                       const struct map *in,
360                                       const char *name)
361 {
362    const char * value;
363    jb_err err;
364
365    assert(out);
366    assert(in);
367    assert(name);
368
369    value = lookup(in, name);
370    err = map(out, name, 1, html_encode(value), 0);
371
372    if (err)
373    {
374       /* Out of memory */
375       return err;
376    }
377    else if (*value == '\0')
378    {
379       return JB_ERR_CGI_PARAMS;
380    }
381    else
382    {
383       return JB_ERR_OK;
384    }
385 }
386
387
388 #if 0 /* unused function */
389 /*********************************************************************
390  *
391  * Function    :  map_copy_parameter_html
392  *
393  * Description :  Copy a CGI parameter from one map to another, URL
394  *                encoding it.
395  *
396  * Parameters  :
397  *          1  :  out = target map
398  *          2  :  in = source map
399  *          3  :  name = name of cgi parameter to copy
400  *
401  * Returns     :  JB_ERR_OK on success
402  *                JB_ERR_MEMORY on out-of-memory
403  *                JB_ERR_CGI_PARAMS if the parameter doesn't exist
404  *                                  in the source map
405  *
406  *********************************************************************/
407 static jb_err map_copy_parameter_url(struct map *out,
408                                      const struct map *in,
409                                      const char *name)
410 {
411    const char * value;
412    jb_err err;
413
414    assert(out);
415    assert(in);
416    assert(name);
417
418    value = lookup(in, name);
419    err = map(out, name, 1, url_encode(value), 0);
420
421    if (err)
422    {
423       /* Out of memory */
424       return err;
425    }
426    else if (*value == '\0')
427    {
428       return JB_ERR_CGI_PARAMS;
429    }
430    else
431    {
432       return JB_ERR_OK;
433    }
434 }
435 #endif /* 0 - unused function */
436
437 /*********************************************************************
438  *
439  * Function    :  cgi_edit_actions_url_form
440  *
441  * Description :  CGI function that displays a form for
442  *                edit-actions-url
443  *
444  * Parameters  :
445  *          1  :  csp = Current client state (buffers, headers, etc...)
446  *          2  :  rsp = http_response data structure for output
447  *          3  :  parameters = map of cgi parameters
448  *
449  * CGI Parameters
450  *           f : (filename) Identifies the file to edit
451  *           v : (version) File's last-modified time
452  *           p : (pattern) Line number of pattern to edit
453  *
454  * Returns     :  JB_ERR_OK on success
455  *                JB_ERR_MEMORY on out-of-memory
456  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
457  *                                  specified or not valid.
458  *
459  *********************************************************************/
460 jb_err cgi_edit_actions_url_form(struct client_state *csp,
461                                  struct http_response *rsp,
462                                  const struct map *parameters)
463 {
464    struct map * exports;
465    unsigned patternid;
466    struct editable_file * file;
467    struct file_line * cur_line;
468    unsigned line_number;
469    jb_err err;
470
471    assert(csp);
472    assert(rsp);
473    assert(parameters);
474
475    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
476    {
477       return cgi_error_disabled(csp, rsp);
478    }
479
480    err = get_number_param(csp, parameters, "p", &patternid);
481    if (err)
482    {
483       return err;
484    }
485
486    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
487    if (err)
488    {
489       /* No filename specified, can't read file, modified, or out of memory. */
490       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
491    }
492
493    cur_line = file->lines;
494
495    for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
496    {
497       cur_line = cur_line->next;
498    }
499
500    if ( (cur_line == NULL)
501      || (line_number != patternid)
502      || (patternid < 1)
503      || (cur_line->type != FILE_LINE_URL))
504    {
505       /* Invalid "patternid" parameter */
506       edit_free_file(file);
507       return JB_ERR_CGI_PARAMS;
508    }
509
510    if (NULL == (exports = default_exports(csp, NULL)))
511    {
512       edit_free_file(file);
513       return JB_ERR_MEMORY;
514    }
515
516    err = map(exports, "f", 1, file->identifier, 1);
517    if (!err) err = map(exports, "v", 1, file->version_str, 1);
518    if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
519    if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
520
521    edit_free_file(file);
522
523    if (err)
524    {
525       free_map(exports);
526       return err;
527    }
528
529    return template_fill_for_cgi(csp, "edit-actions-url-form", exports, rsp);
530 }
531
532
533 /*********************************************************************
534  *
535  * Function    :  cgi_edit_actions_add_url_form
536  *
537  * Description :  CGI function that displays a form for
538  *                edit-actions-url
539  *
540  * Parameters  :
541  *          1  :  csp = Current client state (buffers, headers, etc...)
542  *          2  :  rsp = http_response data structure for output
543  *          3  :  parameters = map of cgi parameters
544  *
545  * CGI Parameters :
546  *           f : (filename) Identifies the file to edit
547  *           v : (version) File's last-modified time
548  *           s : (section) Line number of section to edit
549  *
550  * Returns     :  JB_ERR_OK on success
551  *                JB_ERR_MEMORY on out-of-memory
552  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
553  *                                  specified or not valid.
554  *
555  *********************************************************************/
556 jb_err cgi_edit_actions_add_url_form(struct client_state *csp,
557                                      struct http_response *rsp,
558                                      const struct map *parameters)
559 {
560    struct map *exports;
561    jb_err err;
562
563    assert(csp);
564    assert(rsp);
565    assert(parameters);
566
567    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
568    {
569       return cgi_error_disabled(csp, rsp);
570    }
571
572    if (NULL == (exports = default_exports(csp, NULL)))
573    {
574       return JB_ERR_MEMORY;
575    }
576
577    err = map_copy_parameter_html(exports, parameters, "f");
578    if (!err) err = map_copy_parameter_html(exports, parameters, "v");
579    if (!err) err = map_copy_parameter_html(exports, parameters, "s");
580
581    if (err)
582    {
583       free_map(exports);
584       return err;
585    }
586
587    return template_fill_for_cgi(csp, "edit-actions-add-url-form", exports, rsp);
588 }
589
590
591 /*********************************************************************
592  *
593  * Function    :  cgi_edit_actions_remove_url_form
594  *
595  * Description :  CGI function that displays a form for
596  *                edit-actions-url
597  *
598  * Parameters  :
599  *          1  :  csp = Current client state (buffers, headers, etc...)
600  *          2  :  rsp = http_response data structure for output
601  *          3  :  parameters = map of cgi parameters
602  *
603  * CGI Parameters :
604  *           f : (filename) Identifies the file to edit
605  *           v : (version) File's last-modified time
606  *           p : (pattern) Line number of pattern to edit
607  *
608  * Returns     :  JB_ERR_OK on success
609  *                JB_ERR_MEMORY on out-of-memory
610  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
611  *                                  specified or not valid.
612  *
613  *********************************************************************/
614 jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
615                                      struct http_response *rsp,
616                                      const struct map *parameters)
617 {
618    struct map * exports;
619    unsigned patternid;
620    struct editable_file * file;
621    struct file_line * cur_line;
622    unsigned line_number;
623    jb_err err;
624
625    assert(csp);
626    assert(rsp);
627    assert(parameters);
628
629    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
630    {
631       return cgi_error_disabled(csp, rsp);
632    }
633
634    err = get_number_param(csp, parameters, "p", &patternid);
635    if (err)
636    {
637       return err;
638    }
639
640    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
641    if (err)
642    {
643       /* No filename specified, can't read file, modified, or out of memory. */
644       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
645    }
646
647    cur_line = file->lines;
648
649    for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
650    {
651       cur_line = cur_line->next;
652    }
653
654    if ( (cur_line == NULL)
655      || (line_number != patternid)
656      || (patternid < 1)
657      || (cur_line->type != FILE_LINE_URL))
658    {
659       /* Invalid "patternid" parameter */
660       edit_free_file(file);
661       return JB_ERR_CGI_PARAMS;
662    }
663
664    if (NULL == (exports = default_exports(csp, NULL)))
665    {
666       edit_free_file(file);
667       return JB_ERR_MEMORY;
668    }
669
670    err = map(exports, "f", 1, file->identifier, 1);
671    if (!err) err = map(exports, "v", 1, file->version_str, 1);
672    if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0);
673    if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
674
675    edit_free_file(file);
676
677    if (err)
678    {
679       free_map(exports);
680       return err;
681    }
682
683    return template_fill_for_cgi(csp, "edit-actions-remove-url-form", exports, rsp);
684 }
685
686
687 /*********************************************************************
688  *
689  * Function    :  edit_write_file
690  *
691  * Description :  Write a complete file to disk.
692  *
693  * Parameters  :
694  *          1  :  filename = File to write to.
695  *          2  :  file = Data structure to write.
696  *
697  * Returns     :  JB_ERR_OK     on success
698  *                JB_ERR_FILE   on error writing to file.
699  *                JB_ERR_MEMORY on out of memory
700  *
701  *********************************************************************/
702 jb_err edit_write_file(struct editable_file * file)
703 {
704    FILE * fp;
705    struct file_line * cur_line;
706    struct stat statbuf[1];
707    char version_buf[22]; /* 22 = ceil(log10(2^64)) + 2 = max number of
708                             digits in time_t, assuming this is a 64-bit
709                             machine, plus null terminator, plus one
710                             for paranoia */
711
712    assert(file);
713    assert(file->filename);
714
715 #if defined(AMIGA) || defined(__OS2__)
716    if (NULL == (fp = fopen(file->filename, "w")))
717 #else
718    if (NULL == (fp = fopen(file->filename, "wt")))
719 #endif /* def AMIGA */
720    {
721       return JB_ERR_FILE;
722    }
723
724    cur_line = file->lines;
725    while (cur_line != NULL)
726    {
727       if (cur_line->raw)
728       {
729          if (fputs(cur_line->raw, fp) < 0)
730          {
731             fclose(fp);
732             return JB_ERR_FILE;
733          }
734       }
735       else
736       {
737          if (cur_line->prefix)
738          {
739             if (fputs(cur_line->prefix, fp) < 0)
740             {
741                fclose(fp);
742                return JB_ERR_FILE;
743             }
744          }
745          if (cur_line->unprocessed)
746          {
747             /* This should be a single line - sanity check. */
748             assert(NULL == strchr(cur_line->unprocessed, '\r'));
749             assert(NULL == strchr(cur_line->unprocessed, '\n'));
750
751             if (NULL != strchr(cur_line->unprocessed, '#'))
752             {
753                /* Must quote '#' characters */
754                int numhash = 0;
755                int len;
756                char * src;
757                char * dest;
758                char * str;
759
760                /* Count number of # characters, so we know length of output string */
761                src = cur_line->unprocessed;
762                while (NULL != (src = strchr(src, '#')))
763                {
764                   numhash++;
765                   src++;
766                }
767                assert(numhash > 0);
768
769                /* Allocate new memory for string */
770                len = strlen(cur_line->unprocessed);
771                if (NULL == (str = malloc(len + 1 + numhash)))
772                {
773                   /* Uh oh, just trashed file! */
774                   fclose(fp);
775                   return JB_ERR_MEMORY;
776                }
777
778                /* Loop through string from end */
779                src  = cur_line->unprocessed + len;
780                dest = str + len + numhash;
781                for ( ; len >= 0; len--)
782                {
783                   if ((*dest-- = *src--) == '#')
784                   {
785                      *dest-- = '\\';
786                      numhash--;
787                      assert(numhash >= 0);
788                   }
789                }
790                assert(numhash == 0);
791                assert(src  + 1 == cur_line->unprocessed);
792                assert(dest + 1 == str);
793
794                if (fputs(str, fp) < 0)
795                {
796                   free(str);
797                   fclose(fp);
798                   return JB_ERR_FILE;
799                }
800
801                free(str);
802             }
803             else
804             {
805                /* Can write without quoting '#' characters. */
806                if (fputs(cur_line->unprocessed, fp) < 0)
807                {
808                   fclose(fp);
809                   return JB_ERR_FILE;
810                }
811             }
812             if (fputs(NEWLINE(file->newline), fp) < 0)
813             {
814                fclose(fp);
815                return JB_ERR_FILE;
816             }
817          }
818          else
819          {
820             /* FIXME: Write data from file->data->whatever */
821             assert(0);
822          }
823       }
824       cur_line = cur_line->next;
825    }
826
827    fclose(fp);
828
829
830    /* Update the version stamp in the file structure, since we just
831     * wrote to the file & changed it's date.
832     */
833    if (stat(file->filename, statbuf) < 0)
834    {
835       /* Error, probably file not found. */
836       return JB_ERR_FILE;
837    }
838    file->version = (unsigned)statbuf->st_mtime;
839
840    /* Correct file->version_str */
841    freez(file->version_str);
842    snprintf(version_buf, 22, "%u", file->version);
843    version_buf[21] = '\0';
844    file->version_str = strdup(version_buf);
845    if (version_buf == NULL)
846    {
847       return JB_ERR_MEMORY;
848    }
849
850    return JB_ERR_OK;
851 }
852
853
854 /*********************************************************************
855  *
856  * Function    :  edit_free_file
857  *
858  * Description :  Free a complete file in memory.
859  *
860  * Parameters  :
861  *          1  :  file = Data structure to free.
862  *
863  * Returns     :  N/A
864  *
865  *********************************************************************/
866 void edit_free_file(struct editable_file * file)
867 {
868    if (!file)
869    {
870       /* Silently ignore NULL pointer */
871       return;
872    }
873
874    edit_free_file_lines(file->lines);
875    freez(file->filename);
876    freez(file->identifier);
877    freez(file->version_str);
878    file->version = 0;
879    file->parse_error_text = NULL; /* Statically allocated */
880    file->parse_error = NULL;
881
882    free(file);
883 }
884
885
886 /*********************************************************************
887  *
888  * Function    :  edit_free_file
889  *
890  * Description :  Free an entire linked list of file lines.
891  *
892  * Parameters  :
893  *          1  :  first_line = Data structure to free.
894  *
895  * Returns     :  N/A
896  *
897  *********************************************************************/
898 static void edit_free_file_lines(struct file_line * first_line)
899 {
900    struct file_line * next_line;
901
902    while (first_line != NULL)
903    {
904       next_line = first_line->next;
905       first_line->next = NULL;
906       freez(first_line->raw);
907       freez(first_line->prefix);
908       freez(first_line->unprocessed);
909       switch(first_line->type)
910       {
911          case 0: /* special case if memory zeroed */
912          case FILE_LINE_UNPROCESSED:
913          case FILE_LINE_BLANK:
914          case FILE_LINE_ALIAS_HEADER:
915          case FILE_LINE_SETTINGS_HEADER:
916          case FILE_LINE_DESCRIPTION_HEADER:
917          case FILE_LINE_DESCRIPTION_ENTRY:
918          case FILE_LINE_ALIAS_ENTRY:
919          case FILE_LINE_URL:
920             /* No data is stored for these */
921             break;
922
923          case FILE_LINE_ACTION:
924             free_action(first_line->data.action);
925             break;
926
927          case FILE_LINE_SETTINGS_ENTRY:
928             freez(first_line->data.setting.name);
929             freez(first_line->data.setting.svalue);
930             break;
931          default:
932             /* Should never happen */
933             assert(0);
934             break;
935       }
936       first_line->type = 0; /* paranoia */
937       free(first_line);
938       first_line = next_line;
939    }
940 }
941
942
943 /*********************************************************************
944  *
945  * Function    :  match_actions_file_header_line
946  *
947  * Description :  Match an actions file {{header}} line
948  *
949  * Parameters  :
950  *          1  :  line = String from file
951  *          2  :  name = Header to match against
952  *
953  * Returns     :  0 iff they match.
954  *
955  *********************************************************************/
956 static int match_actions_file_header_line(const char * line, const char * name)
957 {
958    int len;
959
960    assert(line);
961    assert(name);
962
963    /* Look for "{{" */
964    if ((line[0] != '{') || (line[1] != '{'))
965    {
966       return 1;
967    }
968    line += 2;
969
970    /* Look for optional whitespace */
971    while ( (*line == ' ') || (*line == '\t') )
972    {
973       line++;
974    }
975
976    /* Look for the specified name (case-insensitive) */
977    len = strlen(name);
978    if (0 != strncmpic(line, name, len))
979    {
980       return 1;
981    }
982    line += len;
983
984    /* Look for optional whitespace */
985    while ( (*line == ' ') || (*line == '\t') )
986    {
987       line++;
988    }
989
990    /* Look for "}}" and end of string*/
991    if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0'))
992    {
993       return 1;
994    }
995
996    /* It matched!! */
997    return 0;
998 }
999
1000
1001 /*********************************************************************
1002  *
1003  * Function    :  match_actions_file_header_line
1004  *
1005  * Description :  Match an actions file {{header}} line
1006  *
1007  * Parameters  :
1008  *          1  :  line = String from file.  Must not start with
1009  *                       whitespace (else infinite loop!)
1010  *          2  :  name = Destination for name
1011  *          2  :  name = Destination for value
1012  *
1013  * Returns     :  JB_ERR_OK     on success
1014  *                JB_ERR_MEMORY on out-of-memory
1015  *                JB_ERR_PARSE  if there's no "=" sign, or if there's
1016  *                              nothing before the "=" sign (but empty
1017  *                              values *after* the "=" sign are legal).
1018  *
1019  *********************************************************************/
1020 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue)
1021 {
1022    const char * name_end;
1023    const char * value_start;
1024    int name_len;
1025
1026    assert(line);
1027    assert(pname);
1028    assert(pvalue);
1029    assert(*line != ' ');
1030    assert(*line != '\t');
1031
1032    *pname = NULL;
1033    *pvalue = NULL;
1034
1035    value_start = strchr(line, '=');
1036    if ((value_start == NULL) || (value_start == line))
1037    {
1038       return JB_ERR_PARSE;
1039    }
1040
1041    name_end = value_start - 1;
1042
1043    /* Eat any whitespace before the '=' */
1044    while ((*name_end == ' ') || (*name_end == '\t'))
1045    {
1046       /*
1047        * we already know we must have at least 1 non-ws char
1048        * at start of buf - no need to check
1049        */
1050       name_end--;
1051    }
1052
1053    name_len = name_end - line + 1; /* Length excluding \0 */
1054    if (NULL == (*pname = (char *) malloc(name_len + 1)))
1055    {
1056       return JB_ERR_MEMORY;
1057    }
1058    strncpy(*pname, line, name_len);
1059    (*pname)[name_len] = '\0';
1060
1061    /* Eat any the whitespace after the '=' */
1062    value_start++;
1063    while ((*value_start == ' ') || (*value_start == '\t'))
1064    {
1065       value_start++;
1066    }
1067
1068    if (NULL == (*pvalue = strdup(value_start)))
1069    {
1070       free(*pname);
1071       *pname = NULL;
1072       return JB_ERR_MEMORY;
1073    }
1074
1075    return JB_ERR_OK;
1076 }
1077
1078
1079 /*********************************************************************
1080  *
1081  * Function    :  edit_parse_actions_file
1082  *
1083  * Description :  Parse an actions file in memory.
1084  *
1085  *                Passed linked list must have the "data" member
1086  *                zeroed, and must contain valid "next" and
1087  *                "unprocessed" fields.  The "raw" and "prefix"
1088  *                fields are ignored, and "type" is just overwritten.
1089  *
1090  *                Note that on error the file may have been
1091  *                partially parsed.
1092  *
1093  * Parameters  :
1094  *          1  :  file = Actions file to be parsed in-place.
1095  *
1096  * Returns     :  JB_ERR_OK     on success
1097  *                JB_ERR_MEMORY on out-of-memory
1098  *                JB_ERR_PARSE  on error
1099  *
1100  *********************************************************************/
1101 jb_err edit_parse_actions_file(struct editable_file * file)
1102 {
1103    struct file_line * cur_line;
1104    int len;
1105    const char * text; /* Text from a line */
1106    char * name;  /* For lines of the form name=value */
1107    char * value; /* For lines of the form name=value */
1108    struct action_alias * alias_list = NULL;
1109    jb_err err = JB_ERR_OK;
1110
1111    /* alias_list contains the aliases defined in this file.
1112     * It might be better to use the "file_line.data" fields
1113     * in the relavent places instead.
1114     */
1115
1116    cur_line = file->lines;
1117
1118    /* A note about blank line support: Blank lines should only
1119     * ever occur as the last line in the file.  This function
1120     * is more forgiving than that - FILE_LINE_BLANK can occur
1121     * anywhere.
1122     */
1123
1124    /* Skip leading blanks.  Should only happen if file is
1125     * empty (which is valid, but pointless).
1126     */
1127    while ( (cur_line != NULL)
1128         && (cur_line->unprocessed[0] == '\0') )
1129    {
1130       /* Blank line */
1131       cur_line->type = FILE_LINE_BLANK;
1132       cur_line = cur_line->next;
1133    }
1134
1135    if ( (cur_line != NULL)
1136      && (cur_line->unprocessed[0] != '{') )
1137    {
1138       /* File doesn't start with a header */
1139       file->parse_error = cur_line;
1140       file->parse_error_text = "First (non-comment) line of the file must contain a header.";
1141       return JB_ERR_PARSE;
1142    }
1143
1144    if ( (cur_line != NULL) && (0 ==
1145       match_actions_file_header_line(cur_line->unprocessed, "settings") ) )
1146    {
1147       cur_line->type = FILE_LINE_SETTINGS_HEADER;
1148
1149       cur_line = cur_line->next;
1150       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1151       {
1152          if (cur_line->unprocessed[0])
1153          {
1154             cur_line->type = FILE_LINE_SETTINGS_ENTRY;
1155
1156             err = split_line_on_equals(cur_line->unprocessed,
1157                      &cur_line->data.setting.name,
1158                      &cur_line->data.setting.svalue);
1159             if (err == JB_ERR_MEMORY)
1160             {
1161                return err;
1162             }
1163             else if (err != JB_ERR_OK)
1164             {
1165                /* Line does not contain a name=value pair */
1166                file->parse_error = cur_line;
1167                file->parse_error_text = "Expected a name=value pair on this {{description}} line, but couldn't find one.";
1168                return JB_ERR_PARSE;
1169             }
1170          }
1171          else
1172          {
1173             cur_line->type = FILE_LINE_BLANK;
1174          }
1175          cur_line = cur_line->next;
1176       }
1177    }
1178
1179    if ( (cur_line != NULL) && (0 ==
1180       match_actions_file_header_line(cur_line->unprocessed, "description") ) )
1181    {
1182       cur_line->type = FILE_LINE_DESCRIPTION_HEADER;
1183
1184       cur_line = cur_line->next;
1185       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1186       {
1187          if (cur_line->unprocessed[0])
1188          {
1189             cur_line->type = FILE_LINE_DESCRIPTION_ENTRY;
1190          }
1191          else
1192          {
1193             cur_line->type = FILE_LINE_BLANK;
1194          }
1195          cur_line = cur_line->next;
1196       }
1197    }
1198
1199    if ( (cur_line != NULL) && (0 ==
1200       match_actions_file_header_line(cur_line->unprocessed, "alias") ) )
1201    {
1202       cur_line->type = FILE_LINE_ALIAS_HEADER;
1203
1204       cur_line = cur_line->next;
1205       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1206       {
1207          if (cur_line->unprocessed[0])
1208          {
1209             /* define an alias */
1210             struct action_alias * new_alias;
1211
1212             cur_line->type = FILE_LINE_ALIAS_ENTRY;
1213
1214             err = split_line_on_equals(cur_line->unprocessed, &name, &value);
1215             if (err == JB_ERR_MEMORY)
1216             {
1217                return err;
1218             }
1219             else if (err != JB_ERR_OK)
1220             {
1221                /* Line does not contain a name=value pair */
1222                file->parse_error = cur_line;
1223                file->parse_error_text = "Expected a name=value pair on this {{alias}} line, but couldn't find one.";
1224                return JB_ERR_PARSE;
1225             }
1226
1227             if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
1228             {
1229                /* Out of memory */
1230                free(name);
1231                free(value);
1232                free_alias_list(alias_list);
1233                return JB_ERR_MEMORY;
1234             }
1235
1236             err = get_actions(value, alias_list, new_alias->action);
1237             if (err)
1238             {
1239                /* Invalid action or out of memory */
1240                free(name);
1241                free(value);
1242                free(new_alias);
1243                free_alias_list(alias_list);
1244                if (err == JB_ERR_MEMORY)
1245                {
1246                   return err;
1247                }
1248                else
1249                {
1250                   /* Line does not contain a name=value pair */
1251                   file->parse_error = cur_line;
1252                   file->parse_error_text = "This alias does not specify a valid set of actions.";
1253                   return JB_ERR_PARSE;
1254                }
1255             }
1256
1257             free(value);
1258
1259             new_alias->name = name;
1260
1261             /* add to list */
1262             new_alias->next = alias_list;
1263             alias_list = new_alias;
1264          }
1265          else
1266          {
1267             cur_line->type = FILE_LINE_BLANK;
1268          }
1269          cur_line = cur_line->next;
1270       }
1271    }
1272
1273    /* Header done, process the main part of the file */
1274    while (cur_line != NULL)
1275    {
1276       /* At this point, (cur_line->unprocessed[0] == '{') */
1277       assert(cur_line->unprocessed[0] == '{');
1278       text = cur_line->unprocessed + 1;
1279       len = strlen(text) - 1;
1280       if (text[len] != '}')
1281       {
1282          /* No closing } on header */
1283          free_alias_list(alias_list);
1284          file->parse_error = cur_line;
1285          file->parse_error_text = "Headers starting with '{' must have a "
1286             "closing bracket ('}').  Headers starting with two brackets ('{{') "
1287             "must close with two brackets ('}}').";
1288          return JB_ERR_PARSE;
1289       }
1290
1291       if (text[0] == '{')
1292       {
1293          /* An invalid {{ header.  */
1294          free_alias_list(alias_list);
1295          file->parse_error = cur_line;
1296          file->parse_error_text = "Unknown or unexpected two-bracket header.  "
1297             "Please remember that the system (two-bracket) headers must "
1298             "appear in the order {{settings}}, {{description}}, {{alias}}, "
1299             "and must appear before any actions (one-bracket) headers.  "
1300             "Also note that system headers may not be repeated.";
1301          return JB_ERR_PARSE;
1302       }
1303
1304       while ( (*text == ' ') || (*text == '\t') )
1305       {
1306          text++;
1307          len--;
1308       }
1309       while ( (len > 0)
1310            && ( (text[len - 1] == ' ')
1311              || (text[len - 1] == '\t') ) )
1312       {
1313          len--;
1314       }
1315
1316       cur_line->type = FILE_LINE_ACTION;
1317
1318       /* Remove {} and make copy */
1319       if (NULL == (value = (char *) malloc(len + 1)))
1320       {
1321          /* Out of memory */
1322          free_alias_list(alias_list);
1323          return JB_ERR_MEMORY;
1324       }
1325       strncpy(value, text, len);
1326       value[len] = '\0';
1327
1328       /* Get actions */
1329       err = get_actions(value, alias_list, cur_line->data.action);
1330       if (err)
1331       {
1332          /* Invalid action or out of memory */
1333          free(value);
1334          free_alias_list(alias_list);
1335          if (err == JB_ERR_MEMORY)
1336          {
1337             return err;
1338          }
1339          else
1340          {
1341             /* Line does not contain a name=value pair */
1342             file->parse_error = cur_line;
1343             file->parse_error_text = "This header does not specify a valid set of actions.";
1344             return JB_ERR_PARSE;
1345          }
1346       }
1347
1348       /* Done with string - it was clobbered anyway */
1349       free(value);
1350
1351       /* Process next line */
1352       cur_line = cur_line->next;
1353
1354       /* Loop processing URL patterns */
1355       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1356       {
1357          if (cur_line->unprocessed[0])
1358          {
1359             /* Could parse URL here, but this isn't currently needed */
1360
1361             cur_line->type = FILE_LINE_URL;
1362          }
1363          else
1364          {
1365             cur_line->type = FILE_LINE_BLANK;
1366          }
1367          cur_line = cur_line->next;
1368       }
1369    } /* End main while(cur_line != NULL) loop */
1370
1371    free_alias_list(alias_list);
1372
1373    return JB_ERR_OK;
1374 }
1375
1376
1377 /*********************************************************************
1378  *
1379  * Function    :  edit_read_file_lines
1380  *
1381  * Description :  Read all the lines of a file into memory.
1382  *                Handles whitespace, comments and line continuation.
1383  *
1384  * Parameters  :
1385  *          1  :  fp = File to read from.  On return, this will be
1386  *                     at EOF but it will not have been closed.
1387  *          2  :  pfile = Destination for a linked list of file_lines.
1388  *                        Will be set to NULL on error.
1389  *
1390  * Returns     :  JB_ERR_OK     on success
1391  *                JB_ERR_MEMORY on out-of-memory
1392  *
1393  *********************************************************************/
1394 jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline)
1395 {
1396    struct file_line * first_line; /* Keep for return value or to free */
1397    struct file_line * cur_line;   /* Current line */
1398    struct file_line * prev_line;  /* Entry with prev_line->next = cur_line */
1399    jb_err rval;
1400
1401    assert(fp);
1402    assert(pfile);
1403
1404    *pfile = NULL;
1405
1406    cur_line = first_line = zalloc(sizeof(struct file_line));
1407    if (cur_line == NULL)
1408    {
1409       return JB_ERR_MEMORY;
1410    }
1411
1412    cur_line->type = FILE_LINE_UNPROCESSED;
1413
1414    rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1415    if (rval)
1416    {
1417       /* Out of memory or empty file. */
1418       /* Note that empty file is not an error we propogate up */
1419       free(cur_line);
1420       return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval);
1421    }
1422
1423    do
1424    {
1425       prev_line = cur_line;
1426       cur_line = prev_line->next = zalloc(sizeof(struct file_line));
1427       if (cur_line == NULL)
1428       {
1429          /* Out of memory */
1430          edit_free_file_lines(first_line);
1431          return JB_ERR_MEMORY;
1432       }
1433
1434       cur_line->type = FILE_LINE_UNPROCESSED;
1435
1436       rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1437       if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
1438       {
1439          /* Out of memory */
1440          edit_free_file_lines(first_line);
1441          return JB_ERR_MEMORY;
1442       }
1443
1444    }
1445    while (rval != JB_ERR_FILE);
1446
1447    /* EOF */
1448
1449    /* We allocated one too many - free it */
1450    prev_line->next = NULL;
1451    free(cur_line);
1452
1453    *pfile = first_line;
1454    return JB_ERR_OK;
1455 }
1456
1457
1458 /*********************************************************************
1459  *
1460  * Function    :  edit_read_file
1461  *
1462  * Description :  Read a complete file into memory.
1463  *                Handles CGI parameter parsing.  If requested, also
1464  *                checks the file's modification timestamp.
1465  *
1466  * Parameters  :
1467  *          1  :  csp = Current client state (buffers, headers, etc...)
1468  *          2  :  parameters = map of cgi parameters.
1469  *          3  :  require_version = true to check "ver" parameter.
1470  *          4  :  suffix = File extension, e.g. ".action".
1471  *          5  :  pfile = Destination for the file.  Will be set
1472  *                        to NULL on error.
1473  *
1474  * CGI Parameters :
1475  *    filename :  The name of the file to read, without the
1476  *                path or ".action" extension.
1477  *         ver :  (Only if require_version is nonzero)
1478  *                Timestamp of the actions file.  If wrong, this
1479  *                function fails with JB_ERR_MODIFIED.
1480  *
1481  * Returns     :  JB_ERR_OK     on success
1482  *                JB_ERR_MEMORY on out-of-memory
1483  *                JB_ERR_CGI_PARAMS if "filename" was not specified
1484  *                                  or is not valid.
1485  *                JB_ERR_FILE   if the file cannot be opened or
1486  *                              contains no data
1487  *                JB_ERR_MODIFIED if version checking was requested and
1488  *                                failed - the file was modified outside
1489  *                                of this CGI editor instance.
1490  *
1491  *********************************************************************/
1492 jb_err edit_read_file(struct client_state *csp,
1493                       const struct map *parameters,
1494                       int require_version,
1495                       const char *suffix,
1496                       struct editable_file **pfile)
1497 {
1498    struct file_line * lines;
1499    FILE * fp;
1500    jb_err err;
1501    char * filename;
1502    const char * identifier;
1503    struct editable_file * file;
1504    unsigned version = 0;
1505    struct stat statbuf[1];
1506    char version_buf[22];
1507    int newline = NEWLINE_UNKNOWN;
1508
1509    assert(csp);
1510    assert(parameters);
1511    assert(pfile);
1512
1513    *pfile = NULL;
1514
1515    err = get_file_name_param(csp, parameters, "f", suffix,
1516                              &filename, &identifier);
1517    if (err)
1518    {
1519       return err;
1520    }
1521
1522    if (stat(filename, statbuf) < 0)
1523    {
1524       /* Error, probably file not found. */
1525       free(filename);
1526       return JB_ERR_FILE;
1527    }
1528    version = (unsigned) statbuf->st_mtime;
1529
1530    if (require_version)
1531    {
1532       unsigned specified_version;
1533       err = get_number_param(csp, parameters, "v", &specified_version);
1534       if (err)
1535       {
1536          free(filename);
1537          return err;
1538       }
1539
1540       if (version != specified_version)
1541       {
1542          return JB_ERR_MODIFIED;
1543       }
1544    }
1545
1546 #if defined(AMIGA) || defined(__OS2__)
1547    if (NULL == (fp = fopen(filename,"r")))
1548 #else
1549    if (NULL == (fp = fopen(filename,"rt")))
1550 #endif /* def AMIGA */
1551    {
1552       free(filename);
1553       return JB_ERR_FILE;
1554    }
1555
1556    err = edit_read_file_lines(fp, &lines, &newline);
1557
1558    fclose(fp);
1559
1560    if (err)
1561    {
1562       free(filename);
1563       return err;
1564    }
1565
1566    file = (struct editable_file *) zalloc(sizeof(*file));
1567    if (err)
1568    {
1569       free(filename);
1570       edit_free_file_lines(lines);
1571       return err;
1572    }
1573
1574    file->lines = lines;
1575    file->newline = newline;
1576    file->filename = filename;
1577    file->version = version;
1578    file->identifier = url_encode(identifier);
1579
1580    if (file->identifier == NULL)
1581    {
1582       edit_free_file(file);
1583       return JB_ERR_MEMORY;
1584    }
1585
1586    /* Correct file->version_str */
1587    freez(file->version_str);
1588    snprintf(version_buf, 22, "%u", file->version);
1589    version_buf[21] = '\0';
1590    file->version_str = strdup(version_buf);
1591    if (version_buf == NULL)
1592    {
1593       edit_free_file(file);
1594       return JB_ERR_MEMORY;
1595    }
1596
1597    *pfile = file;
1598    return JB_ERR_OK;
1599 }
1600
1601
1602 /*********************************************************************
1603  *
1604  * Function    :  edit_read_actions_file
1605  *
1606  * Description :  Read a complete actions file into memory.
1607  *                Handles CGI parameter parsing.  If requested, also
1608  *                checks the file's modification timestamp.
1609  *
1610  *                If this function detects an error in the categories
1611  *                JB_ERR_FILE, JB_ERR_MODIFIED, or JB_ERR_PARSE,
1612  *                then it handles it by filling in the specified
1613  *                response structure and returning JB_ERR_FILE.
1614  *
1615  * Parameters  :
1616  *          1  :  csp = Current client state (buffers, headers, etc...)
1617  *          2  :  rsp = HTTP response.  Only filled in on error.
1618  *          2  :  parameters = map of cgi parameters.
1619  *          3  :  require_version = true to check "ver" parameter.
1620  *          4  :  pfile = Destination for the file.  Will be set
1621  *                        to NULL on error.
1622  *
1623  * CGI Parameters :
1624  *    filename :  The name of the actions file to read, without the
1625  *                path or ".action" extension.
1626  *         ver :  (Only if require_version is nonzero)
1627  *                Timestamp of the actions file.  If wrong, this
1628  *                function fails with JB_ERR_MODIFIED.
1629  *
1630  * Returns     :  JB_ERR_OK     on success
1631  *                JB_ERR_MEMORY on out-of-memory
1632  *                JB_ERR_CGI_PARAMS if "filename" was not specified
1633  *                                  or is not valid.
1634  *                JB_ERR_FILE  if the file does not contain valid data,
1635  *                             or if file cannot be opened or
1636  *                             contains no data, or if version
1637  *                             checking was requested and failed.
1638  *
1639  *********************************************************************/
1640 jb_err edit_read_actions_file(struct client_state *csp,
1641                               struct http_response *rsp,
1642                               const struct map *parameters,
1643                               int require_version,
1644                               struct editable_file **pfile)
1645 {
1646    jb_err err;
1647    struct editable_file *file;
1648
1649    assert(csp);
1650    assert(parameters);
1651    assert(pfile);
1652
1653    *pfile = NULL;
1654
1655    err = edit_read_file(csp, parameters, require_version, ".action", &file);
1656    if (err)
1657    {
1658       /* Try to handle if possible */
1659       if (err == JB_ERR_FILE)
1660       {
1661          err = cgi_error_file(csp, rsp, lookup(parameters, "f"));
1662       }
1663       else if (err == JB_ERR_MODIFIED)
1664       {
1665          err = cgi_error_modified(csp, rsp, lookup(parameters, "f"));
1666       }
1667       if (err == JB_ERR_OK)
1668       {
1669          /*
1670           * Signal to higher-level CGI code that there was a problem but we
1671           * handled it, they should just return JB_ERR_OK.
1672           */
1673          err = JB_ERR_FILE;
1674       }
1675       return err;
1676    }
1677
1678    err = edit_parse_actions_file(file);
1679    if (err)
1680    {
1681       if (err == JB_ERR_PARSE)
1682       {
1683          err = cgi_error_parse(csp, rsp, file);
1684          if (err == JB_ERR_OK)
1685          {
1686             /*
1687              * Signal to higher-level CGI code that there was a problem but we
1688              * handled it, they should just return JB_ERR_OK.
1689              */
1690             err = JB_ERR_FILE;
1691          }
1692       }
1693       edit_free_file(file);
1694       return err;
1695    }
1696
1697    *pfile = file;
1698    return JB_ERR_OK;
1699 }
1700
1701
1702 /*********************************************************************
1703  *
1704  * Function    :  get_file_name_param
1705  *
1706  * Description :  Get the name of the file to edit from the parameters
1707  *                passed to a CGI function.  This function handles
1708  *                security checks such as blocking urls containing
1709  *                "/" or ".", prepending the config file directory,
1710  *                and adding the specified suffix.
1711  *
1712  *                (This is an essential security check, otherwise
1713  *                users may be able to pass "../../../etc/passwd"
1714  *                and overwrite the password file [linux], "prn:"
1715  *                and print random data [Windows], etc...)
1716  *
1717  *                This function only allows filenames contining the
1718  *                characters '-', '_', 'A'-'Z', 'a'-'z', and '0'-'9'.
1719  *                That's probably too restrictive but at least it's
1720  *                secure.
1721  *
1722  * Parameters  :
1723  *          1  :  csp = Current client state (buffers, headers, etc...)
1724  *          2  :  parameters = map of cgi parameters
1725  *          3  :  param_name = The name of the parameter to read
1726  *          4  :  suffix = File extension, e.g. ".actions"
1727  *          5  :  pfilename = destination for full filename.  Caller
1728  *                free()s.  Set to NULL on error.
1729  *          6  :  pparam = destination for partial filename,
1730  *                suitable for use in another URL.  Allocated as part
1731  *                of the map "parameters", so don't free it.
1732  *                Set to NULL if not specified.
1733  *
1734  * Returns     :  JB_ERR_OK         on success
1735  *                JB_ERR_MEMORY     on out-of-memory
1736  *                JB_ERR_CGI_PARAMS if "filename" was not specified
1737  *                                  or is not valid.
1738  *
1739  *********************************************************************/
1740 static jb_err get_file_name_param(struct client_state *csp,
1741                                   const struct map *parameters,
1742                                   const char *param_name,
1743                                   const char *suffix,
1744                                   char **pfilename,
1745                                   const char **pparam)
1746 {
1747    const char *param;
1748    const char *s;
1749 #if 0 /* Patch to make 3.0.0 work properly. */
1750    char *name;
1751 #endif /* 0 - Patch to make 3.0.0 work properly. */
1752    char *fullpath;
1753    char ch;
1754    int len;
1755
1756    assert(csp);
1757    assert(parameters);
1758    assert(suffix);
1759    assert(pfilename);
1760    assert(pparam);
1761
1762    *pfilename = NULL;
1763    *pparam = NULL;
1764
1765    param = lookup(parameters, param_name);
1766    if (!*param)
1767    {
1768       return JB_ERR_CGI_PARAMS;
1769    }
1770
1771    *pparam = param;
1772
1773    len = strlen(param);
1774    if (len >= FILENAME_MAX)
1775    {
1776       /* Too long. */
1777       return JB_ERR_CGI_PARAMS;
1778    }
1779
1780    /* Check every character to see if it's legal */
1781    s = param;
1782    while ((ch = *s++) != '\0')
1783    {
1784       if ( ((ch < 'A') || (ch > 'Z'))
1785         && ((ch < 'a') || (ch > 'z'))
1786         && ((ch < '0') || (ch > '9'))
1787         && (ch != '-')
1788         && (ch != '_') )
1789       {
1790          /* Probable hack attempt. */
1791          return JB_ERR_CGI_PARAMS;
1792       }
1793    }
1794
1795    /*
1796     * FIXME Following is a hack to make 3.0.0 work properly.
1797     * Change "#if 0" --> "#if 1" below when we have modular action
1798     * files.
1799     *    -- Jon
1800     */
1801 #if 0 /* Patch to make 3.0.0 work properly. */
1802    /* Append extension */
1803    name = malloc(len + strlen(suffix) + 1);
1804    if (name == NULL)
1805    {
1806       return JB_ERR_MEMORY;
1807    }
1808    strcpy(name, param);
1809    strcpy(name + len, suffix);
1810
1811    /* Prepend path */
1812    fullpath = make_path(csp->config->confdir, name);
1813    free(name);
1814 #else /* 1 - Patch to make 3.0.0 work properly. */
1815    if ((csp->actions_list == NULL)
1816     || (csp->actions_list->filename == NULL))
1817    {
1818       return JB_ERR_CGI_PARAMS;
1819    }
1820
1821    fullpath = ( (csp->actions_list && csp->actions_list->filename)
1822              ? strdup(csp->actions_list->filename) : NULL);
1823 #endif /* 1 - Patch to make 3.0.0 work properly. */
1824    if (fullpath == NULL)
1825    {
1826       return JB_ERR_MEMORY;
1827    }
1828
1829    /* Success */
1830    *pfilename = fullpath;
1831
1832    return JB_ERR_OK;
1833 }
1834
1835
1836 /*********************************************************************
1837  *
1838  * Function    :  get_number_param
1839  *
1840  * Description :  Get a non-negative integer from the parameters
1841  *                passed to a CGI function.
1842  *
1843  * Parameters  :
1844  *          1  :  csp = Current client state (buffers, headers, etc...)
1845  *          2  :  parameters = map of cgi parameters
1846  *          3  :  name = Name of CGI parameter to read
1847  *          4  :  pvalue = destination for value.
1848  *                         Set to -1 on error.
1849  *
1850  * Returns     :  JB_ERR_OK         on success
1851  *                JB_ERR_MEMORY     on out-of-memory
1852  *                JB_ERR_CGI_PARAMS if the parameter was not specified
1853  *                                  or is not valid.
1854  *
1855  *********************************************************************/
1856 static jb_err get_number_param(struct client_state *csp,
1857                                const struct map *parameters,
1858                                char *name,
1859                                unsigned *pvalue)
1860 {
1861    const char *param;
1862    char ch;
1863    unsigned value;
1864
1865    assert(csp);
1866    assert(parameters);
1867    assert(name);
1868    assert(pvalue);
1869
1870    *pvalue = -1;
1871
1872    param = lookup(parameters, name);
1873    if (!*param)
1874    {
1875       return JB_ERR_CGI_PARAMS;
1876    }
1877
1878    /* We don't use atoi because I want to check this carefully... */
1879
1880    value = 0;
1881    while ((ch = *param++) != '\0')
1882    {
1883       if ((ch < '0') || (ch > '9'))
1884       {
1885          return JB_ERR_CGI_PARAMS;
1886       }
1887
1888       ch -= '0';
1889
1890       /* Note:
1891        *
1892        * <limits.h> defines UINT_MAX
1893        *
1894        * (UINT_MAX - ch) / 10 is the largest number that
1895        *     can be safely multiplied by 10 then have ch added.
1896        */
1897       if (value > ((UINT_MAX - (unsigned)ch) / 10U))
1898       {
1899          return JB_ERR_CGI_PARAMS;
1900       }
1901
1902       value = value * 10 + ch;
1903    }
1904
1905    /* Success */
1906    *pvalue = value;
1907
1908    return JB_ERR_OK;
1909 }
1910
1911
1912 /*********************************************************************
1913  *
1914  * Function    :  get_url_spec_param
1915  *
1916  * Description :  Get a URL pattern from the parameters
1917  *                passed to a CGI function.  Removes leading/trailing
1918  *                spaces and validates it.
1919  *
1920  * Parameters  :
1921  *          1  :  csp = Current client state (buffers, headers, etc...)
1922  *          2  :  parameters = map of cgi parameters
1923  *          3  :  name = Name of CGI parameter to read
1924  *          4  :  pvalue = destination for value.  Will be malloc()'d.
1925  *                         Set to NULL on error.
1926  *
1927  * Returns     :  JB_ERR_OK         on success
1928  *                JB_ERR_MEMORY     on out-of-memory
1929  *                JB_ERR_CGI_PARAMS if the parameter was not specified
1930  *                                  or is not valid.
1931  *
1932  *********************************************************************/
1933 static jb_err get_url_spec_param(struct client_state *csp,
1934                                  const struct map *parameters,
1935                                  const char *name,
1936                                  char **pvalue)
1937 {
1938    const char *orig_param;
1939    char *param;
1940    char *s;
1941    struct url_spec compiled[1];
1942    jb_err err;
1943
1944    assert(csp);
1945    assert(parameters);
1946    assert(name);
1947    assert(pvalue);
1948
1949    *pvalue = NULL;
1950
1951    orig_param = lookup(parameters, name);
1952    if (!*orig_param)
1953    {
1954       return JB_ERR_CGI_PARAMS;
1955    }
1956
1957    /* Copy and trim whitespace */
1958    param = strdup(orig_param);
1959    if (param == NULL)
1960    {
1961       return JB_ERR_MEMORY;
1962    }
1963    chomp(param);
1964
1965    /* Must be non-empty, and can't allow 1st character to be '{' */
1966    if (param[0] == '\0' || param[0] == '{')
1967    {
1968       free(param);
1969       return JB_ERR_CGI_PARAMS;
1970    }
1971
1972    /* Check for embedded newlines */
1973    for (s = param; *s != '\0'; s++)
1974    {
1975       if ((*s == '\r') || (*s == '\n'))
1976       {
1977          free(param);
1978          return JB_ERR_CGI_PARAMS;
1979       }
1980    }
1981
1982    /* Check that regex is valid */
1983    s = strdup(param);
1984    if (s == NULL)
1985    {
1986       free(param);
1987       return JB_ERR_MEMORY;
1988    }
1989    err = create_url_spec(compiled, s);
1990    free(s);
1991    if (err)
1992    {
1993       free(param);
1994       return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
1995    }
1996    free_url_spec(compiled);
1997
1998    if (param[strlen(param) - 1] == '\\')
1999    {
2000       /*
2001        * Must protect trailing '\\' from becoming line continuation character.
2002        * Two methods: 1) If it's a domain only, add a trailing '/'.
2003        * 2) For path, add the do-nothing PCRE expression (?:) to the end
2004        */
2005       if (strchr(param, '/') == NULL)
2006       {
2007          err = string_append(&param, "/");
2008       }
2009       else
2010       {
2011          err = string_append(&param, "(?:)");
2012       }
2013       if (err)
2014       {
2015          return err;
2016       }
2017
2018       /* Check that the modified regex is valid */
2019       s = strdup(param);
2020       if (s == NULL)
2021       {
2022          free(param);
2023          return JB_ERR_MEMORY;
2024       }
2025       err = create_url_spec(compiled, s);
2026       free(s);
2027       if (err)
2028       {
2029          free(param);
2030          return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
2031       }
2032       free_url_spec(compiled);
2033    }
2034
2035    *pvalue = param;
2036    return JB_ERR_OK;
2037 }
2038
2039 /*********************************************************************
2040  *
2041  * Function    :  map_radio
2042  *
2043  * Description :  Map a set of radio button values.  E.g. if you have
2044  *                3 radio buttons, declare them as:
2045  *                  <option type="radio" name="xyz" @xyz-a@>
2046  *                  <option type="radio" name="xyz" @xyz-b@>
2047  *                  <option type="radio" name="xyz" @xyz-c@>
2048  *                Then map one of the @xyz-?@ variables to "checked"
2049  *                and all the others to empty by calling:
2050  *                map_radio(exports, "xyz", "abc", sel)
2051  *                Where 'sel' is 'a', 'b', or 'c'.
2052  *
2053  * Parameters  :
2054  *          1  :  exports = Exports map to modify.
2055  *          2  :  optionname = name for map
2056  *          3  :  values = null-terminated list of values;
2057  *          4  :  value = Selected value.
2058  *
2059  * CGI Parameters : None
2060  *
2061  * Returns     :  JB_ERR_OK     on success
2062  *                JB_ERR_MEMORY on out-of-memory
2063  *
2064  *********************************************************************/
2065 static jb_err map_radio(struct map * exports,
2066                         const char * optionname,
2067                         const char * values,
2068                         char value)
2069 {
2070    int len;
2071    char * buf;
2072    char * p;
2073    char c;
2074
2075    assert(exports);
2076    assert(optionname);
2077    assert(values);
2078
2079    len = strlen(optionname);
2080    buf = malloc(len + 3);
2081    if (buf == NULL)
2082    {
2083       return JB_ERR_MEMORY;
2084    }
2085
2086    strcpy(buf, optionname);
2087    p = buf + len;
2088    *p++ = '-';
2089    p[1] = '\0';
2090
2091    while ((c = *values++) != '\0')
2092    {
2093       if (c != value)
2094       {
2095          *p = c;
2096          if (map(exports, buf, 1, "", 1))
2097          {
2098             free(buf);
2099             return JB_ERR_MEMORY;
2100          }
2101       }
2102    }
2103
2104    *p = value;
2105    if (map(exports, buf, 0, "checked", 1))
2106    {
2107       free(buf);
2108       return JB_ERR_MEMORY;
2109    }
2110
2111    return JB_ERR_OK;
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
2725    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2726    {
2727       return cgi_error_disabled(csp, rsp);
2728    }
2729
2730    err = get_number_param(csp, parameters, "s", &sectionid);
2731    if (err)
2732    {
2733       return err;
2734    }
2735
2736    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2737    if (err)
2738    {
2739       /* No filename specified, can't read file, modified, or out of memory. */
2740       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2741    }
2742
2743    cur_line = file->lines;
2744
2745    for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2746    {
2747       cur_line = cur_line->next;
2748    }
2749
2750    if ( (cur_line == NULL)
2751      || (line_number != sectionid)
2752      || (sectionid < 1)
2753      || (cur_line->type != FILE_LINE_ACTION))
2754    {
2755       /* Invalid "sectionid" parameter */
2756       edit_free_file(file);
2757       return JB_ERR_CGI_PARAMS;
2758    }
2759
2760    if (NULL == (exports = default_exports(csp, NULL)))
2761    {
2762       edit_free_file(file);
2763       return JB_ERR_MEMORY;
2764    }
2765
2766    err = map(exports, "f", 1, file->identifier, 1);
2767    if (!err) err = map(exports, "v", 1, file->version_str, 1);
2768    if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0);
2769
2770    if (!err) err = actions_to_radio(exports, cur_line->data.action);
2771
2772    edit_free_file(file);
2773
2774    if (err)
2775    {
2776       free_map(exports);
2777       return err;
2778    }
2779
2780    return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
2781 }
2782
2783
2784 /*********************************************************************
2785  *
2786  * Function    :  cgi_edit_actions_submit
2787  *
2788  * Description :  CGI function that actually edits the Actions list.
2789  *
2790  * Parameters  :
2791  *          1  :  csp = Current client state (buffers, headers, etc...)
2792  *          2  :  rsp = http_response data structure for output
2793  *          3  :  parameters = map of cgi parameters
2794  *
2795  * CGI Parameters : None
2796  *
2797  * Returns     :  JB_ERR_OK     on success
2798  *                JB_ERR_MEMORY on out-of-memory
2799  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
2800  *                                  specified or not valid.
2801  *
2802  *********************************************************************/
2803 jb_err cgi_edit_actions_submit(struct client_state *csp,
2804                                struct http_response *rsp,
2805                                const struct map *parameters)
2806 {
2807    unsigned sectionid;
2808    char * actiontext;
2809    char * newtext;
2810    int len;
2811    struct editable_file * file;
2812    struct file_line * cur_line;
2813    unsigned line_number;
2814    char * target;
2815    jb_err err;
2816
2817    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2818    {
2819       return cgi_error_disabled(csp, rsp);
2820    }
2821
2822    err = get_number_param(csp, parameters, "s", &sectionid);
2823    if (err)
2824    {
2825       return err;
2826    }
2827
2828    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2829    if (err)
2830    {
2831       /* No filename specified, can't read file, modified, or out of memory. */
2832       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2833    }
2834
2835    cur_line = file->lines;
2836
2837    for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2838    {
2839       cur_line = cur_line->next;
2840    }
2841
2842    if ( (cur_line == NULL)
2843      || (line_number != sectionid)
2844      || (sectionid < 1)
2845      || (cur_line->type != FILE_LINE_ACTION))
2846    {
2847       /* Invalid "sectionid" parameter */
2848       edit_free_file(file);
2849       return JB_ERR_CGI_PARAMS;
2850    }
2851
2852    err = actions_from_radio(parameters, cur_line->data.action);
2853    if(err)
2854    {
2855       /* Out of memory */
2856       edit_free_file(file);
2857       return err;
2858    }
2859
2860    if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
2861    {
2862       /* Out of memory */
2863       edit_free_file(file);
2864       return JB_ERR_MEMORY;
2865    }
2866
2867    len = strlen(actiontext);
2868    if (len == 0)
2869    {
2870       /*
2871        * Empty action - must special-case this.
2872        * Simply setting len to 1 is sufficient...
2873        */
2874       len = 1;
2875    }
2876
2877    if (NULL == (newtext = malloc(len + 2)))
2878    {
2879       /* Out of memory */
2880       free(actiontext);
2881       edit_free_file(file);
2882       return JB_ERR_MEMORY;
2883    }
2884    strcpy(newtext, actiontext);
2885    free(actiontext);
2886    newtext[0]       = '{';
2887    newtext[len]     = '}';
2888    newtext[len + 1] = '\0';
2889
2890    freez(cur_line->raw);
2891    freez(cur_line->unprocessed);
2892    cur_line->unprocessed = newtext;
2893
2894    err = edit_write_file(file);
2895    if (err)
2896    {
2897       /* Error writing file */
2898       edit_free_file(file);
2899       return err;
2900    }
2901
2902    target = strdup(CGI_PREFIX "edit-actions-list?f=");
2903    string_append(&target, file->identifier);
2904
2905    edit_free_file(file);
2906
2907    if (target == NULL)
2908    {
2909       /* Out of memory */
2910       return JB_ERR_MEMORY;
2911    }
2912
2913    rsp->status = strdup("302 Local Redirect from Junkbuster");
2914    if (rsp->status == NULL)
2915    {
2916       free(target);
2917       return JB_ERR_MEMORY;
2918    }
2919    err = enlist_unique_header(rsp->headers, "Location", target);
2920    free(target);
2921
2922    return err;
2923 }
2924
2925
2926 /*********************************************************************
2927  *
2928  * Function    :  cgi_edit_actions_url
2929  *
2930  * Description :  CGI function that actually edits a URL pattern in
2931  *                an actions file.
2932  *
2933  * Parameters  :
2934  *          1  :  csp = Current client state (buffers, headers, etc...)
2935  *          2  :  rsp = http_response data structure for output
2936  *          3  :  parameters = map of cgi parameters
2937  *
2938  * CGI Parameters :
2939  *    filename : Identifies the file to edit
2940  *         ver : File's last-modified time
2941  *     section : Line number of section to edit
2942  *     pattern : Line number of pattern to edit
2943  *      newval : New value for pattern
2944  *
2945  * Returns     :  JB_ERR_OK     on success
2946  *                JB_ERR_MEMORY on out-of-memory
2947  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
2948  *                                  specified or not valid.
2949  *
2950  *********************************************************************/
2951 jb_err cgi_edit_actions_url(struct client_state *csp,
2952                             struct http_response *rsp,
2953                             const struct map *parameters)
2954 {
2955    unsigned patternid;
2956    char * new_pattern;
2957    struct editable_file * file;
2958    struct file_line * cur_line;
2959    unsigned line_number;
2960    char * target;
2961    jb_err err;
2962
2963    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2964    {
2965       return cgi_error_disabled(csp, rsp);
2966    }
2967
2968    err = get_number_param(csp, parameters, "p", &patternid);
2969    if (err)
2970    {
2971       return err;
2972    }
2973    if (patternid < 1U)
2974    {
2975       return JB_ERR_CGI_PARAMS;
2976    }
2977
2978    err = get_url_spec_param(csp, parameters, "u", &new_pattern);
2979    if (err)
2980    {
2981       return err;
2982    }
2983
2984    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2985    if (err)
2986    {
2987       /* No filename specified, can't read file, modified, or out of memory. */
2988       free(new_pattern);
2989       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2990    }
2991
2992    line_number = 1;
2993    cur_line = file->lines;
2994
2995    while ((cur_line != NULL) && (line_number < patternid))
2996    {
2997       cur_line = cur_line->next;
2998       line_number++;
2999    }
3000
3001    if ( (cur_line == NULL)
3002      || (cur_line->type != FILE_LINE_URL))
3003    {
3004       /* Invalid "patternid" parameter */
3005       free(new_pattern);
3006       edit_free_file(file);
3007       return JB_ERR_CGI_PARAMS;
3008    }
3009
3010    /* At this point, the line to edit is in cur_line */
3011
3012    freez(cur_line->raw);
3013    freez(cur_line->unprocessed);
3014    cur_line->unprocessed = new_pattern;
3015
3016    err = edit_write_file(file);
3017    if (err)
3018    {
3019       /* Error writing file */
3020       edit_free_file(file);
3021       return err;
3022    }
3023
3024    target = strdup(CGI_PREFIX "edit-actions-list?f=");
3025    string_append(&target, file->identifier);
3026
3027    edit_free_file(file);
3028
3029    if (target == NULL)
3030    {
3031       /* Out of memory */
3032       return JB_ERR_MEMORY;
3033    }
3034
3035    rsp->status = strdup("302 Local Redirect from Junkbuster");
3036    if (rsp->status == NULL)
3037    {
3038       free(target);
3039       return JB_ERR_MEMORY;
3040    }
3041    err = enlist_unique_header(rsp->headers, "Location", target);
3042    free(target);
3043
3044    return err;
3045 }
3046
3047
3048 /*********************************************************************
3049  *
3050  * Function    :  cgi_edit_actions_add_url
3051  *
3052  * Description :  CGI function that actually adds a URL pattern to
3053  *                an actions file.
3054  *
3055  * Parameters  :
3056  *          1  :  csp = Current client state (buffers, headers, etc...)
3057  *          2  :  rsp = http_response data structure for output
3058  *          3  :  parameters = map of cgi parameters
3059  *
3060  * CGI Parameters :
3061  *    filename : Identifies the file to edit
3062  *         ver : File's last-modified time
3063  *     section : Line number of section to edit
3064  *      newval : New pattern
3065  *
3066  * Returns     :  JB_ERR_OK     on success
3067  *                JB_ERR_MEMORY on out-of-memory
3068  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3069  *                                  specified or not valid.
3070  *
3071  *********************************************************************/
3072 jb_err cgi_edit_actions_add_url(struct client_state *csp,
3073                                 struct http_response *rsp,
3074                                 const struct map *parameters)
3075 {
3076    unsigned sectionid;
3077    char * new_pattern;
3078    struct file_line * new_line;
3079    struct editable_file * file;
3080    struct file_line * cur_line;
3081    unsigned line_number;
3082    char * target;
3083    jb_err err;
3084
3085    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3086    {
3087       return cgi_error_disabled(csp, rsp);
3088    }
3089
3090    err = get_number_param(csp, parameters, "s", &sectionid);
3091    if (err)
3092    {
3093       return err;
3094    }
3095    if (sectionid < 1U)
3096    {
3097       return JB_ERR_CGI_PARAMS;
3098    }
3099
3100    err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3101    if (err)
3102    {
3103       return err;
3104    }
3105
3106    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3107    if (err)
3108    {
3109       /* No filename specified, can't read file, modified, or out of memory. */
3110       free(new_pattern);
3111       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3112    }
3113
3114    line_number = 1;
3115    cur_line = file->lines;
3116
3117    while ((cur_line != NULL) && (line_number < sectionid))
3118    {
3119       cur_line = cur_line->next;
3120       line_number++;
3121    }
3122
3123    if ( (cur_line == NULL)
3124      || (cur_line->type != FILE_LINE_ACTION))
3125    {
3126       /* Invalid "sectionid" parameter */
3127       free(new_pattern);
3128       edit_free_file(file);
3129       return JB_ERR_CGI_PARAMS;
3130    }
3131
3132    /* At this point, the section header is in cur_line - add after this. */
3133
3134    /* Allocate the new line */
3135    new_line = (struct file_line *)zalloc(sizeof(*new_line));
3136    if (new_line == NULL)
3137    {
3138       free(new_pattern);
3139       edit_free_file(file);
3140       return JB_ERR_MEMORY;
3141    }
3142
3143    /* Fill in the data members of the new line */
3144    new_line->raw = NULL;
3145    new_line->prefix = NULL;
3146    new_line->unprocessed = new_pattern;
3147    new_line->type = FILE_LINE_URL;
3148
3149    /* Link new_line into the list, after cur_line */
3150    new_line->next = cur_line->next;
3151    cur_line->next = new_line;
3152
3153    /* Done making changes, now commit */
3154
3155    err = edit_write_file(file);
3156    if (err)
3157    {
3158       /* Error writing file */
3159       edit_free_file(file);
3160       return err;
3161    }
3162
3163    target = strdup(CGI_PREFIX "edit-actions-list?f=");
3164    string_append(&target, file->identifier);
3165
3166    edit_free_file(file);
3167
3168    if (target == NULL)
3169    {
3170       /* Out of memory */
3171       return JB_ERR_MEMORY;
3172    }
3173
3174    rsp->status = strdup("302 Local Redirect from Junkbuster");
3175    if (rsp->status == NULL)
3176    {
3177       free(target);
3178       return JB_ERR_MEMORY;
3179    }
3180    err = enlist_unique_header(rsp->headers, "Location", target);
3181    free(target);
3182
3183    return err;
3184 }
3185
3186
3187 /*********************************************************************
3188  *
3189  * Function    :  cgi_edit_actions_remove_url
3190  *
3191  * Description :  CGI function that actually removes a URL pattern from
3192  *                the actions file.
3193  *
3194  * Parameters  :
3195  *          1  :  csp = Current client state (buffers, headers, etc...)
3196  *          2  :  rsp = http_response data structure for output
3197  *          3  :  parameters = map of cgi parameters
3198  *
3199  * CGI Parameters :
3200  *           f : (filename) Identifies the file to edit
3201  *           v : (version) File's last-modified time
3202  *           p : (pattern) Line number of pattern to remove
3203  *
3204  * Returns     :  JB_ERR_OK     on success
3205  *                JB_ERR_MEMORY on out-of-memory
3206  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3207  *                                  specified or not valid.
3208  *
3209  *********************************************************************/
3210 jb_err cgi_edit_actions_remove_url(struct client_state *csp,
3211                                    struct http_response *rsp,
3212                                    const struct map *parameters)
3213 {
3214    unsigned patternid;
3215    struct editable_file * file;
3216    struct file_line * cur_line;
3217    struct file_line * prev_line;
3218    unsigned line_number;
3219    char * target;
3220    jb_err err;
3221
3222    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3223    {
3224       return cgi_error_disabled(csp, rsp);
3225    }
3226
3227    err = get_number_param(csp, parameters, "p", &patternid);
3228    if (err)
3229    {
3230       return err;
3231    }
3232
3233    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3234    if (err)
3235    {
3236       /* No filename specified, can't read file, modified, or out of memory. */
3237       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3238    }
3239
3240    line_number = 1;
3241    prev_line = NULL;
3242    cur_line = file->lines;
3243
3244    while ((cur_line != NULL) && (line_number < patternid))
3245    {
3246       prev_line = cur_line;
3247       cur_line = cur_line->next;
3248       line_number++;
3249    }
3250
3251    if ( (cur_line == NULL)
3252      || (prev_line == NULL)
3253      || (cur_line->type != FILE_LINE_URL))
3254    {
3255       /* Invalid "patternid" parameter */
3256       edit_free_file(file);
3257       return JB_ERR_CGI_PARAMS;
3258    }
3259
3260    /* At this point, the line to remove is in cur_line, and the previous
3261     * one is in prev_line
3262     */
3263
3264    /* Unlink cur_line */
3265    prev_line->next = cur_line->next;
3266    cur_line->next = NULL;
3267
3268    /* Free cur_line */
3269    edit_free_file_lines(cur_line);
3270
3271    err = edit_write_file(file);
3272    if (err)
3273    {
3274       /* Error writing file */
3275       edit_free_file(file);
3276       return err;
3277    }
3278
3279    target = strdup(CGI_PREFIX "edit-actions-list?f=");
3280    string_append(&target, file->identifier);
3281
3282    edit_free_file(file);
3283
3284    if (target == NULL)
3285    {
3286       /* Out of memory */
3287       return JB_ERR_MEMORY;
3288    }
3289
3290    rsp->status = strdup("302 Local Redirect from Junkbuster");
3291    if (rsp->status == NULL)
3292    {
3293       free(target);
3294       return JB_ERR_MEMORY;
3295    }
3296    err = enlist_unique_header(rsp->headers, "Location", target);
3297    free(target);
3298
3299    return err;
3300 }
3301
3302
3303 /*********************************************************************
3304  *
3305  * Function    :  cgi_edit_actions_section_remove
3306  *
3307  * Description :  CGI function that actually removes a whole section from
3308  *                the actions file.  The section must be empty first
3309  *                (else JB_ERR_CGI_PARAMS).
3310  *
3311  * Parameters  :
3312  *          1  :  csp = Current client state (buffers, headers, etc...)
3313  *          2  :  rsp = http_response data structure for output
3314  *          3  :  parameters = map of cgi parameters
3315  *
3316  * CGI Parameters :
3317  *           f : (filename) Identifies the file to edit
3318  *           v : (version) File's last-modified time
3319  *           s : (section) Line number of section to edit
3320  *
3321  * Returns     :  JB_ERR_OK     on success
3322  *                JB_ERR_MEMORY on out-of-memory
3323  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3324  *                                  specified or not valid.
3325  *
3326  *********************************************************************/
3327 jb_err cgi_edit_actions_section_remove(struct client_state *csp,
3328                                        struct http_response *rsp,
3329                                        const struct map *parameters)
3330 {
3331    unsigned sectionid;
3332    struct editable_file * file;
3333    struct file_line * cur_line;
3334    struct file_line * prev_line;
3335    unsigned line_number;
3336    char * target;
3337    jb_err err;
3338
3339    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3340    {
3341       return cgi_error_disabled(csp, rsp);
3342    }
3343
3344    err = get_number_param(csp, parameters, "s", &sectionid);
3345    if (err)
3346    {
3347       return err;
3348    }
3349
3350    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3351    if (err)
3352    {
3353       /* No filename specified, can't read file, modified, or out of memory. */
3354       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3355    }
3356
3357    line_number = 1;
3358    cur_line = file->lines;
3359
3360    prev_line = NULL;
3361    while ((cur_line != NULL) && (line_number < sectionid))
3362    {
3363       prev_line = cur_line;
3364       cur_line = cur_line->next;
3365       line_number++;
3366    }
3367
3368    if ( (cur_line == NULL)
3369      || (cur_line->type != FILE_LINE_ACTION) )
3370    {
3371       /* Invalid "sectionid" parameter */
3372       edit_free_file(file);
3373       return JB_ERR_CGI_PARAMS;
3374    }
3375
3376    if ( (cur_line->next != NULL)
3377      && (cur_line->next->type == FILE_LINE_URL) )
3378    {
3379       /* Section not empty. */
3380       edit_free_file(file);
3381       return JB_ERR_CGI_PARAMS;
3382    }
3383
3384    /* At this point, the line to remove is in cur_line, and the previous
3385     * one is in prev_line
3386     */
3387
3388    /* Unlink cur_line */
3389    if (prev_line == NULL)
3390    {
3391       /* Removing the first line from the file */
3392       file->lines = cur_line->next;
3393    }
3394    else
3395    {
3396       prev_line->next = cur_line->next;
3397    }
3398    cur_line->next = NULL;
3399
3400    /* Free cur_line */
3401    edit_free_file_lines(cur_line);
3402
3403    err = edit_write_file(file);
3404    if (err)
3405    {
3406       /* Error writing file */
3407       edit_free_file(file);
3408       return err;
3409    }
3410
3411    target = strdup(CGI_PREFIX "edit-actions-list?f=");
3412    string_append(&target, file->identifier);
3413
3414    edit_free_file(file);
3415
3416    if (target == NULL)
3417    {
3418       /* Out of memory */
3419       return JB_ERR_MEMORY;
3420    }
3421
3422    rsp->status = strdup("302 Local Redirect from Junkbuster");
3423    if (rsp->status == NULL)
3424    {
3425       free(target);
3426       return JB_ERR_MEMORY;
3427    }
3428    err = enlist_unique_header(rsp->headers, "Location", target);
3429    free(target);
3430
3431    return err;
3432 }
3433
3434
3435 /*********************************************************************
3436  *
3437  * Function    :  cgi_edit_actions_section_add
3438  *
3439  * Description :  CGI function that adds a new empty section to
3440  *                an actions file.
3441  *
3442  * Parameters  :
3443  *          1  :  csp = Current client state (buffers, headers, etc...)
3444  *          2  :  rsp = http_response data structure for output
3445  *          3  :  parameters = map of cgi parameters
3446  *
3447  * CGI Parameters :
3448  *           f : (filename) Identifies the file to edit
3449  *           v : (version) File's last-modified time
3450  *           s : (section) Line number of section to add after, 0 for
3451  *               start of file.
3452  *
3453  * Returns     :  JB_ERR_OK     on success
3454  *                JB_ERR_MEMORY on out-of-memory
3455  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3456  *                                  specified or not valid.
3457  *
3458  *********************************************************************/
3459 jb_err cgi_edit_actions_section_add(struct client_state *csp,
3460                                     struct http_response *rsp,
3461                                     const struct map *parameters)
3462 {
3463    unsigned sectionid;
3464    struct file_line * new_line;
3465    char * new_text;
3466    struct editable_file * file;
3467    struct file_line * cur_line;
3468    unsigned line_number;
3469    char * target;
3470    jb_err err;
3471
3472    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3473    {
3474       return cgi_error_disabled(csp, rsp);
3475    }
3476
3477    err = get_number_param(csp, parameters, "s", &sectionid);
3478    if (err)
3479    {
3480       return err;
3481    }
3482
3483    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3484    if (err)
3485    {
3486       /* No filename specified, can't read file, modified, or out of memory. */
3487       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3488    }
3489
3490    line_number = 1;
3491    cur_line = file->lines;
3492
3493    if (sectionid < 1U)
3494    {
3495       /* Add to start of file */
3496       if (cur_line != NULL)
3497       {
3498          /* There's something in the file, find the line before the first
3499           * action.
3500           */
3501          while ( (cur_line->next != NULL)
3502               && (cur_line->next->type != FILE_LINE_ACTION) )
3503          {
3504             cur_line = cur_line->next;
3505             line_number++;
3506          }
3507       }
3508    }
3509    else
3510    {
3511       /* Add after stated section. */
3512       while ((cur_line != NULL) && (line_number < sectionid))
3513       {
3514          cur_line = cur_line->next;
3515          line_number++;
3516       }
3517
3518       if ( (cur_line == NULL)
3519         || (cur_line->type != FILE_LINE_ACTION))
3520       {
3521          /* Invalid "sectionid" parameter */
3522          edit_free_file(file);
3523          return JB_ERR_CGI_PARAMS;
3524       }
3525
3526       /* Skip through the section to find the last line in it. */
3527       while ( (cur_line->next != NULL)
3528            && (cur_line->next->type != FILE_LINE_ACTION) )
3529       {
3530          cur_line = cur_line->next;
3531          line_number++;
3532       }
3533    }
3534
3535    /* At this point, the last line in the previous section is in cur_line
3536     * - add after this.  (Or if we need to add as the first line, cur_line
3537     * will be NULL).
3538     */
3539
3540    new_text = strdup("{}");
3541    if (NULL == new_text)
3542    {
3543       edit_free_file(file);
3544       return JB_ERR_MEMORY;
3545    }
3546
3547    /* Allocate the new line */
3548    new_line = (struct file_line *)zalloc(sizeof(*new_line));
3549    if (new_line == NULL)
3550    {
3551       free(new_text);
3552       edit_free_file(file);
3553       return JB_ERR_MEMORY;
3554    }
3555
3556    /* Fill in the data members of the new line */
3557    new_line->raw = NULL;
3558    new_line->prefix = NULL;
3559    new_line->unprocessed = new_text;
3560    new_line->type = FILE_LINE_ACTION;
3561
3562    if (cur_line != NULL)
3563    {
3564       /* Link new_line into the list, after cur_line */
3565       new_line->next = cur_line->next;
3566       cur_line->next = new_line;
3567    }
3568    else
3569    {
3570       /* Link new_line into the list, as first line */
3571       new_line->next = file->lines;
3572       file->lines = new_line;
3573    }
3574
3575    /* Done making changes, now commit */
3576
3577    err = edit_write_file(file);
3578    if (err)
3579    {
3580       /* Error writing file */
3581       edit_free_file(file);
3582       return err;
3583    }
3584
3585    target = strdup(CGI_PREFIX "edit-actions-list?f=");
3586    string_append(&target, file->identifier);
3587
3588    edit_free_file(file);
3589
3590    if (target == NULL)
3591    {
3592       /* Out of memory */
3593       return JB_ERR_MEMORY;
3594    }
3595
3596    rsp->status = strdup("302 Local Redirect from Junkbuster");
3597    if (rsp->status == NULL)
3598    {
3599       free(target);
3600       return JB_ERR_MEMORY;
3601    }
3602    err = enlist_unique_header(rsp->headers, "Location", target);
3603    free(target);
3604
3605    return err;
3606 }
3607
3608
3609 /*********************************************************************
3610  *
3611  * Function    :  cgi_edit_actions_section_swap
3612  *
3613  * Description :  CGI function that swaps the order of two sections
3614  *                in the actions file.  Note that this CGI can actually
3615  *                swap any two arbitrary sections, but the GUI interface
3616  *                currently only allows consecutive sections to be
3617  *                specified.
3618  *
3619  * Parameters  :
3620  *          1  :  csp = Current client state (buffers, headers, etc...)
3621  *          2  :  rsp = http_response data structure for output
3622  *          3  :  parameters = map of cgi parameters
3623  *
3624  * CGI Parameters :
3625  *           f : (filename) Identifies the file to edit
3626  *           v : (version) File's last-modified time
3627  *          s1 : (section1) Line number of first section to swap
3628  *          s2 : (section2) Line number of second section to swap
3629  *
3630  * Returns     :  JB_ERR_OK     on success
3631  *                JB_ERR_MEMORY on out-of-memory
3632  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3633  *                                  specified or not valid.
3634  *
3635  *********************************************************************/
3636 jb_err cgi_edit_actions_section_swap(struct client_state *csp,
3637                                      struct http_response *rsp,
3638                                      const struct map *parameters)
3639 {
3640    unsigned section1;
3641    unsigned section2;
3642    struct editable_file * file;
3643    struct file_line * cur_line;
3644    struct file_line * prev_line;
3645    struct file_line * line_before_section1;
3646    struct file_line * line_start_section1;
3647    struct file_line * line_end_section1;
3648    struct file_line * line_after_section1;
3649    struct file_line * line_before_section2;
3650    struct file_line * line_start_section2;
3651    struct file_line * line_end_section2;
3652    struct file_line * line_after_section2;
3653    unsigned line_number;
3654    char * target;
3655    jb_err err;
3656
3657    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3658    {
3659       return cgi_error_disabled(csp, rsp);
3660    }
3661
3662    err = get_number_param(csp, parameters, "s1", &section1);
3663    if (!err) err = get_number_param(csp, parameters, "s2", &section2);
3664    if (err)
3665    {
3666       return err;
3667    }
3668
3669    if (section1 > section2)
3670    {
3671       unsigned temp = section2;
3672       section2 = section1;
3673       section1 = temp;
3674    }
3675
3676    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3677    if (err)
3678    {
3679       /* No filename specified, can't read file, modified, or out of memory. */
3680       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3681    }
3682
3683    /* Start at the beginning... */
3684    line_number = 1;
3685    cur_line = file->lines;
3686    prev_line = NULL;
3687
3688    /* ... find section1 ... */
3689    while ((cur_line != NULL) && (line_number < section1))
3690    {
3691       prev_line = cur_line;
3692       cur_line = cur_line->next;
3693       line_number++;
3694    }
3695
3696    if ( (cur_line == NULL)
3697      || (cur_line->type != FILE_LINE_ACTION) )
3698    {
3699       /* Invalid "section1" parameter */
3700       edit_free_file(file);
3701       return JB_ERR_CGI_PARAMS;
3702    }
3703
3704    /* If no-op, we've validated params and can skip the rest. */
3705    if (section1 != section2)
3706    {
3707       /* ... find the end of section1 ... */
3708       line_before_section1 = prev_line;
3709       line_start_section1 = cur_line;
3710       do
3711       {
3712          prev_line = cur_line;
3713          cur_line = cur_line->next;
3714          line_number++;
3715       }
3716       while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
3717       line_end_section1 = prev_line;
3718       line_after_section1 = cur_line;
3719
3720       /* ... find section2 ... */
3721       while ((cur_line != NULL) && (line_number < section2))
3722       {
3723          prev_line = cur_line;
3724          cur_line = cur_line->next;
3725          line_number++;
3726       }
3727
3728       if ( (cur_line == NULL)
3729         || (cur_line->type != FILE_LINE_ACTION) )
3730       {
3731          /* Invalid "section2" parameter */
3732          edit_free_file(file);
3733          return JB_ERR_CGI_PARAMS;
3734       }
3735
3736       /* ... find the end of section2 ... */
3737       line_before_section2 = prev_line;
3738       line_start_section2 = cur_line;
3739       do
3740       {
3741          prev_line = cur_line;
3742          cur_line = cur_line->next;
3743          line_number++;
3744       }
3745       while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
3746       line_end_section2 = prev_line;
3747       line_after_section2 = cur_line;
3748
3749       /* Now have all the pointers we need. Do the swap. */
3750
3751       /* Change the pointer to section1 to point to section2 instead */
3752       if (line_before_section1 == NULL)
3753       {
3754          file->lines = line_start_section2;
3755       }
3756       else
3757       {
3758          line_before_section1->next = line_start_section2;
3759       }
3760
3761       if (line_before_section2 == line_end_section1)
3762       {
3763          /* Consecutive sections */
3764          line_end_section2->next = line_start_section1;
3765       }
3766       else
3767       {
3768          line_end_section2->next = line_after_section1;
3769          line_before_section2->next = line_start_section1;
3770       }
3771
3772       /* Set the pointer from the end of section1 to the rest of the file */
3773       line_end_section1->next = line_after_section2;
3774
3775       err = edit_write_file(file);
3776       if (err)
3777       {
3778          /* Error writing file */
3779          edit_free_file(file);
3780          return err;
3781       }
3782    } /* END if (section1 != section2) */
3783
3784    target = strdup(CGI_PREFIX "edit-actions-list?f=");
3785    string_append(&target, file->identifier);
3786
3787    edit_free_file(file);
3788
3789    if (target == NULL)
3790    {
3791       /* Out of memory */
3792       return JB_ERR_MEMORY;
3793    }
3794
3795    rsp->status = strdup("302 Local Redirect from Junkbuster");
3796    if (rsp->status == NULL)
3797    {
3798       free(target);
3799       return JB_ERR_MEMORY;
3800    }
3801    err = enlist_unique_header(rsp->headers, "Location", target);
3802    free(target);
3803
3804    return err;
3805 }
3806
3807
3808 /*********************************************************************
3809  *
3810  * Function    :  cgi_toggle
3811  *
3812  * Description :  CGI function that adds a new empty section to
3813  *                an actions file.
3814  *
3815  * Parameters  :
3816  *          1  :  csp = Current client state (buffers, headers, etc...)
3817  *          2  :  rsp = http_response data structure for output
3818  *          3  :  parameters = map of cgi parameters
3819  *
3820  * CGI Parameters :
3821  *         set : If present, how to change toggle setting:
3822  *               "enable", "disable", "toggle", or none (default).
3823  *        mini : If present, use mini reply template.
3824  *
3825  * Returns     :  JB_ERR_OK     on success
3826  *                JB_ERR_MEMORY on out-of-memory
3827  *
3828  *********************************************************************/
3829 jb_err cgi_toggle(struct client_state *csp,
3830         &n