1 const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.39 2002/05/12 21:39:15 jongfoster Exp $";
2 /*********************************************************************
4 * File : $Source: /cvsroot/ijbswa/current/cgiedit.c,v $
6 * Purpose : CGI-based actionsfile editor.
8 * Functions declared include: cgi_edit_*
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.
16 * Stick to the short names in this file for consistency.
18 * Copyright : Written by and Copyright (C) 2001 the SourceForge
19 * Privoxy team. http://www.privoxy.org/
21 * Based on the Internet Junkbuster originally written
22 * by and Copyright (C) 1997 Anonymous Coders and
23 * Junkbusters Corporation. http://www.junkbusters.com
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.
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.
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.
45 * Revision 1.39 2002/05/12 21:39:15 jongfoster
46 * - Adding Doxygen-style comments to structures and #defines.
47 * - Correcting function comments
49 * Revision 1.38 2002/05/03 23:00:38 jongfoster
50 * Support for templates for "standard actions" buttons.
53 * Revision 1.37 2002/04/30 11:14:52 oes
54 * Made csp the first parameter in *action_to_html
56 * Revision 1.36 2002/04/26 21:53:30 jongfoster
57 * Fixing a memory leak. (Near, but not caused by, my earlier commit).
59 * Revision 1.35 2002/04/26 21:50:02 jongfoster
60 * Honouring default exports in edit-actions-for-url-filter template.
62 * Revision 1.34 2002/04/26 12:54:17 oes
63 * Adaptions to changes in actions.c
65 * Revision 1.33 2002/04/24 02:17:47 oes
66 * - Moved get_char_param, get_string_param and get_number_param to cgi.c
68 * - Activated Jon's code for editing multiple AFs
69 * - cgi_edit_list_actions now provides context-sensitive
70 * help, looks up all action sets from standard.action and
71 * makes buttons for them in the catchall section
72 * - cgi_edit_action_submit now honors a p parameter, looks up
73 * the corresponding action set, and sets the catchall pattern's
74 * actions accordingly.
76 * Revision 1.32 2002/04/19 16:55:31 jongfoster
77 * Fixing newline problems. If we do our own text file newline
78 * mangling, we don't want the library to do any, so we need to
79 * open the files in *binary* mode.
81 * Revision 1.31 2002/04/18 19:21:08 jongfoster
82 * Added code to detect "conventional" action files, that start
83 * with a set of actions for all URLs (the pattern "/").
84 * These are special-cased in the "edit-actions-list" CGI, so
85 * that a special UI can be written for them.
87 * Revision 1.30 2002/04/10 13:38:35 oes
88 * load_template signature changed
90 * Revision 1.29 2002/04/08 16:59:08 oes
93 * Revision 1.28 2002/03/27 12:30:29 oes
94 * Deleted unsused variable
96 * Revision 1.27 2002/03/26 23:06:04 jongfoster
97 * Removing duplicate @ifs on the toggle page
99 * Revision 1.26 2002/03/26 22:59:17 jongfoster
100 * Fixing /toggle to display status consistently.
102 * Revision 1.25 2002/03/26 22:29:54 swa
103 * we have a new homepage!
105 * Revision 1.24 2002/03/24 15:23:33 jongfoster
108 * Revision 1.23 2002/03/24 13:32:41 swa
109 * name change related issues
111 * Revision 1.22 2002/03/24 13:25:43 swa
112 * name change related issues
114 * Revision 1.21 2002/03/22 18:02:48 jongfoster
115 * Fixing remote toggle
117 * Revision 1.20 2002/03/16 20:28:34 oes
118 * Added descriptions to the filters so users will know what they select in the cgi editor
120 * Revision 1.19 2002/03/16 18:38:14 jongfoster
121 * Stopping stupid or malicious users from breaking the actions
122 * file using the web-based editor.
124 * Revision 1.18 2002/03/16 14:57:44 jongfoster
125 * Full support for enabling/disabling modular filters.
127 * Revision 1.17 2002/03/16 14:26:42 jongfoster
128 * First version of modular filters support - READ ONLY!
129 * Fixing a double-free bug in the out-of-memory handling in map_radio().
131 * Revision 1.16 2002/03/07 03:46:17 oes
132 * Fixed compiler warnings
134 * Revision 1.15 2002/03/06 22:54:35 jongfoster
135 * Automated function-comment nitpicking.
137 * Revision 1.14 2002/03/05 00:24:51 jongfoster
138 * Patch to always edit the current actions file.
140 * Revision 1.13 2002/03/04 02:07:59 david__schmidt
141 * Enable web editing of actions file on OS/2 (it had been broken all this time!)
143 * Revision 1.12 2002/03/03 09:18:03 joergs
144 * Made jumbjuster work on AmigaOS again.
146 * Revision 1.11 2002/01/23 01:03:31 jongfoster
147 * Fixing gcc [CygWin] compiler warnings
149 * Revision 1.10 2002/01/23 00:22:59 jongfoster
150 * Adding new function cgi_edit_actions_section_swap(), to reorder
153 * Adding get_url_spec_param() to get a validated URL pattern.
155 * Moving edit_read_line() out of this file and into loaders.c.
157 * Adding missing html_encode() to many CGI functions.
159 * Moving the functions that #include actionlist.h to the end of the file,
160 * because the Visual C++ 97 debugger gets extremely confused if you try
161 * to debug any code that comes after them in the file.
163 * Major optimizations in cgi_edit_actions_list() to reduce the size of
164 * the generated HTML (down 40% from 550k to 304k), with major side-effects
165 * throughout the editor and templates. In particular, the length of the
166 * URLs throughout the editor has been drastically reduced, by cutting
167 * paramater names down to 1 character and CGI names down to 3-4
168 * characters, by removing all non-essential CGI paramaters even at the
169 * expense of having to re-read the actions file for the most trivial
170 * page, and by using relative rather than absolute URLs. This means
171 * that this (typical example):
173 * <a href="http://ijbswa.sourceforge.net/config/edit-actions-url-form?
174 * filename=ijb&ver=1011487572&section=12&pattern=13
175 * &oldval=www.oesterhelt.org%2Fdeanimate-demo">
179 * <a href="eau?f=ijb&v=1011487572&p=13">
181 * Revision 1.9 2002/01/17 20:56:22 jongfoster
182 * Replacing hard references to the URL of the config interface
183 * with #defines from project.h
185 * Revision 1.8 2001/11/30 23:35:51 jongfoster
186 * Renaming actionsfile to ijb.action
188 * Revision 1.7 2001/11/13 00:28:24 jongfoster
189 * - Renaming parameters from edit-actions-for-url so that they only
190 * contain legal JavaScript characters. If we wanted to write
191 * JavaScript that worked with Netscape 4, this is nessacery.
192 * (Note that at the moment the JavaScript doesn't actually work
193 * with Netscape 4, but now this is purely a template issue, not
194 * one affecting code).
195 * - Adding new CGIs for use by non-JavaScript browsers:
196 * edit-actions-url-form
197 * edit-actions-add-url-form
198 * edit-actions-remove-url-form
201 * Revision 1.6 2001/10/29 03:48:09 david__schmidt
202 * OS/2 native needed a snprintf() routine. Added one to miscutil, brackedted
203 * by and __OS2__ ifdef.
205 * Revision 1.5 2001/10/25 03:40:48 david__schmidt
206 * Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
207 * threads to call select() simultaneously. So, it's time to do a real, live,
208 * native OS/2 port. See defines for __EMX__ (the porting layer) vs. __OS2__
209 * (native). Both versions will work, but using __OS2__ offers multi-threading.
211 * Revision 1.4 2001/10/23 21:48:19 jongfoster
212 * Cleaning up error handling in CGI functions - they now send back
213 * a HTML error page and should never cause a FATAL error. (Fixes one
214 * potential source of "denial of service" attacks).
216 * CGI actions file editor that works and is actually useful.
218 * Ability to toggle JunkBuster remotely using a CGI call.
220 * You can turn off both the above features in the main configuration
221 * file, e.g. if you are running a multi-user proxy.
223 * Revision 1.3 2001/10/14 22:12:49 jongfoster
224 * New version of CGI-based actionsfile editor.
225 * Major changes, including:
226 * - Completely new file parser and file output routines
227 * - edit-actions CGI renamed edit-actions-for-url
228 * - All CGIs now need a filename parameter, except for...
229 * - New CGI edit-actions which doesn't need a filename,
230 * to allow you to start the editor up.
231 * - edit-actions-submit now works, and now automatically
232 * redirects you back to the main edit-actions-list handler.
234 * Revision 1.2 2001/09/16 17:05:14 jongfoster
235 * Removing unused #include showarg.h
237 * Revision 1.1 2001/09/16 15:47:37 jongfoster
238 * First version of CGI-based edit interface. This is very much a
239 * work-in-progress, and you can't actually use it to edit anything
240 * yet. You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
241 * to have any effect.
244 **********************************************************************/
250 * FIXME: Following includes copied from cgi.c - which are actually needed?
255 #include <sys/types.h>
259 #include <sys/stat.h>
262 #define snprintf _snprintf
263 #endif /* def _WIN32 */
268 #include "cgisimple.h"
272 #include "miscutil.h"
276 /* loadcfg.h is for g_bToggleIJB only */
277 #include "urlmatch.h"
279 const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION;
282 #ifdef FEATURE_CGI_EDIT_ACTIONS
285 * A line in an editable_file.
289 /** Next entry in the linked list */
290 struct file_line * next;
292 /** The raw data, to write out if this line is unmodified. */
295 /** Comments and/or whitespace to put before this line if it's modified
296 and then written out. */
299 /** The actual data, as a string. Line continuation and comment removal
300 are performed on the data read from file before it's stored here, so
301 it will be a single line of data. */
304 /** The type of data on this line. One of the FILE_LINE_xxx constants. */
307 /** The actual data, processed into some sensible data type. */
311 /** An action specification. */
312 struct action_spec action[1];
314 /** A name=value pair. */
318 /** The name in the name=value pair. */
321 /** The value in the name=value pair, as a string. */
324 /** The value in the name=value pair, as an integer. */
329 /* Add more data types here... e.g.
332 struct url_spec url[1];
336 struct action_spec action[1];
346 /** This file_line has not been processed yet. */
347 #define FILE_LINE_UNPROCESSED 1
349 /** This file_line is blank. Can only appear at the end of a file, due to
350 the way the parser works. */
351 #define FILE_LINE_BLANK 2
353 /** This file_line says {{alias}}. */
354 #define FILE_LINE_ALIAS_HEADER 3
356 /** This file_line defines an alias. */
357 #define FILE_LINE_ALIAS_ENTRY 4
359 /** This file_line defines an {action}. */
360 #define FILE_LINE_ACTION 5
362 /** This file_line specifies a URL pattern. */
363 #define FILE_LINE_URL 6
365 /** This file_line says {{settings}}. */
366 #define FILE_LINE_SETTINGS_HEADER 7
368 /** This file_line is in a {{settings}} block. */
369 #define FILE_LINE_SETTINGS_ENTRY 8
371 /** This file_line says {{description}}. */
372 #define FILE_LINE_DESCRIPTION_HEADER 9
374 /** This file_line is in a {{description}} block. */
375 #define FILE_LINE_DESCRIPTION_ENTRY 10
379 * A configuration file, in a format that can be edited and written back to
384 struct file_line * lines; /**< The contents of the file. A linked list of lines. */
385 const char * filename; /**< Full pathname - e.g. "/etc/privoxy/wibble.action". */
386 const char * identifier; /**< Filename stub - e.g. "wibble". Use for CGI param. */
387 /**< Pre-encoded with url_encode() for ease of use. */
388 const char * version_str; /**< Last modification time, as a string. For CGI param. */
389 /**< Can be used in URL without using url_param(). */
390 unsigned version; /**< Last modification time - prevents chaos with
391 the browser's "back" button. Note that this is a
392 time_t cast to an unsigned. When comparing, always
393 cast the time_t to an unsigned, and *NOT* vice-versa.
394 This may lose the top few bits, but they're not
395 significant anyway. */
396 int newline; /**< Newline convention - one of the NEWLINE_xxx constants.
397 Note that changing this after the file has been
398 read in will cause a mess. */
399 struct file_line * parse_error; /**< On parse error, this is the offending line. */
400 const char * parse_error_text; /**< On parse error, this is the problem.
401 (Statically allocated) */
404 /* FIXME: Following non-static functions should be prototyped in .h or made static */
406 /* Functions to read and write arbitrary config files */
407 jb_err edit_read_file(struct client_state *csp,
408 const struct map *parameters,
411 struct editable_file **pfile);
412 jb_err edit_write_file(struct editable_file * file);
413 void edit_free_file(struct editable_file * file);
415 /* Functions to read and write actions files */
416 jb_err edit_parse_actions_file(struct editable_file * file);
417 jb_err edit_read_actions_file(struct client_state *csp,
418 struct http_response *rsp,
419 const struct map *parameters,
421 struct editable_file **pfile);
424 jb_err cgi_error_modified(struct client_state *csp,
425 struct http_response *rsp,
426 const char *filename);
427 jb_err cgi_error_parse(struct client_state *csp,
428 struct http_response *rsp,
429 struct editable_file *file);
430 jb_err cgi_error_file(struct client_state *csp,
431 struct http_response *rsp,
432 const char *filename);
433 jb_err cgi_error_file_read_only(struct client_state *csp,
434 struct http_response *rsp,
435 const char *filename);
436 jb_err cgi_error_disabled(struct client_state *csp,
437 struct http_response *rsp);
439 /* Internal arbitrary config file support functions */
440 static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline);
441 static void edit_free_file_lines(struct file_line * first_line);
443 /* Internal actions file support functions */
444 static int match_actions_file_header_line(const char * line, const char * name);
445 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue);
447 /* Internal parameter parsing functions */
448 static jb_err get_file_name_param(struct client_state *csp,
449 const struct map *parameters,
450 const char *param_name,
453 const char **pparam);
455 static jb_err get_url_spec_param(struct client_state *csp,
456 const struct map *parameters,
461 /* Internal actionsfile <==> HTML conversion functions */
462 static jb_err map_radio(struct map * exports,
463 const char * optionname,
466 static jb_err actions_to_radio(struct map * exports,
467 const struct action_spec *action);
468 static jb_err actions_from_radio(const struct map * parameters,
469 struct action_spec *action);
472 static jb_err map_copy_parameter_html(struct map *out,
473 const struct map *in,
475 #if 0 /* unused function */
476 static jb_err map_copy_parameter_url(struct map *out,
477 const struct map *in,
479 #endif /* unused function */
481 /*********************************************************************
483 * Function : map_copy_parameter_html
485 * Description : Copy a CGI parameter from one map to another, HTML
489 * 1 : out = target map
490 * 2 : in = source map
491 * 3 : name = name of cgi parameter to copy
493 * Returns : JB_ERR_OK on success
494 * JB_ERR_MEMORY on out-of-memory
495 * JB_ERR_CGI_PARAMS if the parameter doesn't exist
498 *********************************************************************/
499 static jb_err map_copy_parameter_html(struct map *out,
500 const struct map *in,
510 value = lookup(in, name);
511 err = map(out, name, 1, html_encode(value), 0);
518 else if (*value == '\0')
520 return JB_ERR_CGI_PARAMS;
529 #if 0 /* unused function */
530 /*********************************************************************
532 * Function : map_copy_parameter_url
534 * Description : Copy a CGI parameter from one map to another, URL
538 * 1 : out = target map
539 * 2 : in = source map
540 * 3 : name = name of cgi parameter to copy
542 * Returns : JB_ERR_OK on success
543 * JB_ERR_MEMORY on out-of-memory
544 * JB_ERR_CGI_PARAMS if the parameter doesn't exist
547 *********************************************************************/
548 static jb_err map_copy_parameter_url(struct map *out,
549 const struct map *in,
559 value = lookup(in, name);
560 err = map(out, name, 1, url_encode(value), 0);
567 else if (*value == '\0')
569 return JB_ERR_CGI_PARAMS;
576 #endif /* 0 - unused function */
578 /*********************************************************************
580 * Function : cgi_edit_actions_url_form
582 * Description : CGI function that displays a form for
586 * 1 : csp = Current client state (buffers, headers, etc...)
587 * 2 : rsp = http_response data structure for output
588 * 3 : parameters = map of cgi parameters
591 * f : (filename) Identifies the file to edit
592 * v : (version) File's last-modified time
593 * p : (pattern) Line number of pattern to edit
595 * Returns : JB_ERR_OK on success
596 * JB_ERR_MEMORY on out-of-memory
597 * JB_ERR_CGI_PARAMS if the CGI parameters are not
598 * specified or not valid.
600 *********************************************************************/
601 jb_err cgi_edit_actions_url_form(struct client_state *csp,
602 struct http_response *rsp,
603 const struct map *parameters)
605 struct map * exports;
607 struct editable_file * file;
608 struct file_line * cur_line;
609 unsigned line_number;
616 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
618 return cgi_error_disabled(csp, rsp);
621 err = get_number_param(csp, parameters, "p", &patternid);
627 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
630 /* No filename specified, can't read file, modified, or out of memory. */
631 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
634 cur_line = file->lines;
636 for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
638 cur_line = cur_line->next;
641 if ( (cur_line == NULL)
642 || (line_number != patternid)
644 || (cur_line->type != FILE_LINE_URL))
646 /* Invalid "patternid" parameter */
647 edit_free_file(file);
648 return JB_ERR_CGI_PARAMS;
651 if (NULL == (exports = default_exports(csp, NULL)))
653 edit_free_file(file);
654 return JB_ERR_MEMORY;
657 err = map(exports, "f", 1, file->identifier, 1);
658 if (!err) err = map(exports, "v", 1, file->version_str, 1);
659 if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
660 if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
662 edit_free_file(file);
670 return template_fill_for_cgi(csp, "edit-actions-url-form", exports, rsp);
674 /*********************************************************************
676 * Function : cgi_edit_actions_add_url_form
678 * Description : CGI function that displays a form for
682 * 1 : csp = Current client state (buffers, headers, etc...)
683 * 2 : rsp = http_response data structure for output
684 * 3 : parameters = map of cgi parameters
687 * f : (filename) Identifies the file to edit
688 * v : (version) File's last-modified time
689 * s : (section) Line number of section to edit
691 * Returns : JB_ERR_OK on success
692 * JB_ERR_MEMORY on out-of-memory
693 * JB_ERR_CGI_PARAMS if the CGI parameters are not
694 * specified or not valid.
696 *********************************************************************/
697 jb_err cgi_edit_actions_add_url_form(struct client_state *csp,
698 struct http_response *rsp,
699 const struct map *parameters)
708 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
710 return cgi_error_disabled(csp, rsp);
713 if (NULL == (exports = default_exports(csp, NULL)))
715 return JB_ERR_MEMORY;
718 err = map_copy_parameter_html(exports, parameters, "f");
719 if (!err) err = map_copy_parameter_html(exports, parameters, "v");
720 if (!err) err = map_copy_parameter_html(exports, parameters, "s");
728 return template_fill_for_cgi(csp, "edit-actions-add-url-form", exports, rsp);
732 /*********************************************************************
734 * Function : cgi_edit_actions_remove_url_form
736 * Description : CGI function that displays a form for
740 * 1 : csp = Current client state (buffers, headers, etc...)
741 * 2 : rsp = http_response data structure for output
742 * 3 : parameters = map of cgi parameters
745 * f : (filename) Identifies the file to edit
746 * v : (version) File's last-modified time
747 * p : (pattern) Line number of pattern to edit
749 * Returns : JB_ERR_OK on success
750 * JB_ERR_MEMORY on out-of-memory
751 * JB_ERR_CGI_PARAMS if the CGI parameters are not
752 * specified or not valid.
754 *********************************************************************/
755 jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
756 struct http_response *rsp,
757 const struct map *parameters)
759 struct map * exports;
761 struct editable_file * file;
762 struct file_line * cur_line;
763 unsigned line_number;
770 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
772 return cgi_error_disabled(csp, rsp);
775 err = get_number_param(csp, parameters, "p", &patternid);
781 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
784 /* No filename specified, can't read file, modified, or out of memory. */
785 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
788 cur_line = file->lines;
790 for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
792 cur_line = cur_line->next;
795 if ( (cur_line == NULL)
796 || (line_number != patternid)
798 || (cur_line->type != FILE_LINE_URL))
800 /* Invalid "patternid" parameter */
801 edit_free_file(file);
802 return JB_ERR_CGI_PARAMS;
805 if (NULL == (exports = default_exports(csp, NULL)))
807 edit_free_file(file);
808 return JB_ERR_MEMORY;
811 err = map(exports, "f", 1, file->identifier, 1);
812 if (!err) err = map(exports, "v", 1, file->version_str, 1);
813 if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0);
814 if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
816 edit_free_file(file);
824 return template_fill_for_cgi(csp, "edit-actions-remove-url-form", exports, rsp);
828 /*********************************************************************
830 * Function : edit_write_file
832 * Description : Write a complete file to disk.
835 * 1 : file = File to write.
837 * Returns : JB_ERR_OK on success
838 * JB_ERR_FILE on error writing to file.
839 * JB_ERR_MEMORY on out of memory
841 *********************************************************************/
842 jb_err edit_write_file(struct editable_file * file)
845 struct file_line * cur_line;
846 struct stat statbuf[1];
847 char version_buf[22]; /* 22 = ceil(log10(2^64)) + 2 = max number of
848 digits in time_t, assuming this is a 64-bit
849 machine, plus null terminator, plus one
853 assert(file->filename);
855 if (NULL == (fp = fopen(file->filename, "wb")))
860 cur_line = file->lines;
861 while (cur_line != NULL)
865 if (fputs(cur_line->raw, fp) < 0)
873 if (cur_line->prefix)
875 if (fputs(cur_line->prefix, fp) < 0)
881 if (cur_line->unprocessed)
884 if (NULL != strchr(cur_line->unprocessed, '#'))
886 /* Must quote '#' characters */
893 /* Count number of # characters, so we know length of output string */
894 src = cur_line->unprocessed;
895 while (NULL != (src = strchr(src, '#')))
902 /* Allocate new memory for string */
903 len = strlen(cur_line->unprocessed);
904 if (NULL == (str = malloc((size_t) len + 1 + numhash)))
906 /* Uh oh, just trashed file! */
908 return JB_ERR_MEMORY;
911 /* Loop through string from end */
912 src = cur_line->unprocessed + len;
913 dest = str + len + numhash;
914 for ( ; len >= 0; len--)
916 if ((*dest-- = *src--) == '#')
920 assert(numhash >= 0);
923 assert(numhash == 0);
924 assert(src + 1 == cur_line->unprocessed);
925 assert(dest + 1 == str);
927 if (fputs(str, fp) < 0)
938 /* Can write without quoting '#' characters. */
939 if (fputs(cur_line->unprocessed, fp) < 0)
945 if (fputs(NEWLINE(file->newline), fp) < 0)
953 /* FIXME: Write data from file->data->whatever */
957 cur_line = cur_line->next;
963 /* Update the version stamp in the file structure, since we just
964 * wrote to the file & changed it's date.
966 if (stat(file->filename, statbuf) < 0)
968 /* Error, probably file not found. */
971 file->version = (unsigned)statbuf->st_mtime;
973 /* Correct file->version_str */
974 freez(file->version_str);
975 snprintf(version_buf, 22, "%u", file->version);
976 version_buf[21] = '\0';
977 file->version_str = strdup(version_buf);
978 if (version_buf == NULL)
980 return JB_ERR_MEMORY;
987 /*********************************************************************
989 * Function : edit_free_file
991 * Description : Free a complete file in memory.
994 * 1 : file = Data structure to free.
998 *********************************************************************/
999 void edit_free_file(struct editable_file * file)
1003 /* Silently ignore NULL pointer */
1007 edit_free_file_lines(file->lines);
1008 freez(file->filename);
1009 freez(file->identifier);
1010 freez(file->version_str);
1012 file->parse_error_text = NULL; /* Statically allocated */
1013 file->parse_error = NULL;
1019 /*********************************************************************
1021 * Function : edit_free_file_lines
1023 * Description : Free an entire linked list of file lines.
1026 * 1 : first_line = Data structure to free.
1030 *********************************************************************/
1031 static void edit_free_file_lines(struct file_line * first_line)
1033 struct file_line * next_line;
1035 while (first_line != NULL)
1037 next_line = first_line->next;
1038 first_line->next = NULL;
1039 freez(first_line->raw);
1040 freez(first_line->prefix);
1041 freez(first_line->unprocessed);
1042 switch(first_line->type)
1044 case 0: /* special case if memory zeroed */
1045 case FILE_LINE_UNPROCESSED:
1046 case FILE_LINE_BLANK:
1047 case FILE_LINE_ALIAS_HEADER:
1048 case FILE_LINE_SETTINGS_HEADER:
1049 case FILE_LINE_DESCRIPTION_HEADER:
1050 case FILE_LINE_DESCRIPTION_ENTRY:
1051 case FILE_LINE_ALIAS_ENTRY:
1053 /* No data is stored for these */
1056 case FILE_LINE_ACTION:
1057 free_action(first_line->data.action);
1060 case FILE_LINE_SETTINGS_ENTRY:
1061 freez(first_line->data.setting.name);
1062 freez(first_line->data.setting.svalue);
1065 /* Should never happen */
1069 first_line->type = 0; /* paranoia */
1071 first_line = next_line;
1076 /*********************************************************************
1078 * Function : match_actions_file_header_line
1080 * Description : Match an actions file {{header}} line
1083 * 1 : line = String from file
1084 * 2 : name = Header to match against
1086 * Returns : 0 iff they match.
1088 *********************************************************************/
1089 static int match_actions_file_header_line(const char * line, const char * name)
1097 if ((line[0] != '{') || (line[1] != '{'))
1103 /* Look for optional whitespace */
1104 while ( (*line == ' ') || (*line == '\t') )
1109 /* Look for the specified name (case-insensitive) */
1111 if (0 != strncmpic(line, name, len))
1117 /* Look for optional whitespace */
1118 while ( (*line == ' ') || (*line == '\t') )
1123 /* Look for "}}" and end of string*/
1124 if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0'))
1134 /*********************************************************************
1136 * Function : match_actions_file_header_line
1138 * Description : Match an actions file {{header}} line
1141 * 1 : line = String from file. Must not start with
1142 * whitespace (else infinite loop!)
1143 * 2 : pname = Destination for name
1144 * 2 : pvalue = Destination for value
1146 * Returns : JB_ERR_OK on success
1147 * JB_ERR_MEMORY on out-of-memory
1148 * JB_ERR_PARSE if there's no "=" sign, or if there's
1149 * nothing before the "=" sign (but empty
1150 * values *after* the "=" sign are legal).
1152 *********************************************************************/
1153 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue)
1155 const char * name_end;
1156 const char * value_start;
1162 assert(*line != ' ');
1163 assert(*line != '\t');
1168 value_start = strchr(line, '=');
1169 if ((value_start == NULL) || (value_start == line))
1171 return JB_ERR_PARSE;
1174 name_end = value_start - 1;
1176 /* Eat any whitespace before the '=' */
1177 while ((*name_end == ' ') || (*name_end == '\t'))
1180 * we already know we must have at least 1 non-ws char
1181 * at start of buf - no need to check
1186 name_len = name_end - line + 1; /* Length excluding \0 */
1187 if (NULL == (*pname = (char *) malloc(name_len + 1)))
1189 return JB_ERR_MEMORY;
1191 strncpy(*pname, line, name_len);
1192 (*pname)[name_len] = '\0';
1194 /* Eat any the whitespace after the '=' */
1196 while ((*value_start == ' ') || (*value_start == '\t'))
1201 if (NULL == (*pvalue = strdup(value_start)))
1205 return JB_ERR_MEMORY;
1212 /*********************************************************************
1214 * Function : edit_parse_actions_file
1216 * Description : Parse an actions file in memory.
1218 * Passed linked list must have the "data" member
1219 * zeroed, and must contain valid "next" and
1220 * "unprocessed" fields. The "raw" and "prefix"
1221 * fields are ignored, and "type" is just overwritten.
1223 * Note that on error the file may have been
1227 * 1 : file = Actions file to be parsed in-place.
1229 * Returns : JB_ERR_OK on success
1230 * JB_ERR_MEMORY on out-of-memory
1231 * JB_ERR_PARSE on error
1233 *********************************************************************/
1234 jb_err edit_parse_actions_file(struct editable_file * file)
1236 struct file_line * cur_line;
1238 const char * text; /* Text from a line */
1239 char * name; /* For lines of the form name=value */
1240 char * value; /* For lines of the form name=value */
1241 struct action_alias * alias_list = NULL;
1242 jb_err err = JB_ERR_OK;
1244 /* alias_list contains the aliases defined in this file.
1245 * It might be better to use the "file_line.data" fields
1246 * in the relavent places instead.
1249 cur_line = file->lines;
1251 /* A note about blank line support: Blank lines should only
1252 * ever occur as the last line in the file. This function
1253 * is more forgiving than that - FILE_LINE_BLANK can occur
1257 /* Skip leading blanks. Should only happen if file is
1258 * empty (which is valid, but pointless).
1260 while ( (cur_line != NULL)
1261 && (cur_line->unprocessed[0] == '\0') )
1264 cur_line->type = FILE_LINE_BLANK;
1265 cur_line = cur_line->next;
1268 if ( (cur_line != NULL)
1269 && (cur_line->unprocessed[0] != '{') )
1271 /* File doesn't start with a header */
1272 file->parse_error = cur_line;
1273 file->parse_error_text = "First (non-comment) line of the file must contain a header.";
1274 return JB_ERR_PARSE;
1277 if ( (cur_line != NULL) && (0 ==
1278 match_actions_file_header_line(cur_line->unprocessed, "settings") ) )
1280 cur_line->type = FILE_LINE_SETTINGS_HEADER;
1282 cur_line = cur_line->next;
1283 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1285 if (cur_line->unprocessed[0])
1287 cur_line->type = FILE_LINE_SETTINGS_ENTRY;
1289 err = split_line_on_equals(cur_line->unprocessed,
1290 &cur_line->data.setting.name,
1291 &cur_line->data.setting.svalue);
1292 if (err == JB_ERR_MEMORY)
1296 else if (err != JB_ERR_OK)
1298 /* Line does not contain a name=value pair */
1299 file->parse_error = cur_line;
1300 file->parse_error_text = "Expected a name=value pair on this {{description}} line, but couldn't find one.";
1301 return JB_ERR_PARSE;
1306 cur_line->type = FILE_LINE_BLANK;
1308 cur_line = cur_line->next;
1312 if ( (cur_line != NULL) && (0 ==
1313 match_actions_file_header_line(cur_line->unprocessed, "description") ) )
1315 cur_line->type = FILE_LINE_DESCRIPTION_HEADER;
1317 cur_line = cur_line->next;
1318 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1320 if (cur_line->unprocessed[0])
1322 cur_line->type = FILE_LINE_DESCRIPTION_ENTRY;
1326 cur_line->type = FILE_LINE_BLANK;
1328 cur_line = cur_line->next;
1332 if ( (cur_line != NULL) && (0 ==
1333 match_actions_file_header_line(cur_line->unprocessed, "alias") ) )
1335 cur_line->type = FILE_LINE_ALIAS_HEADER;
1337 cur_line = cur_line->next;
1338 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1340 if (cur_line->unprocessed[0])
1342 /* define an alias */
1343 struct action_alias * new_alias;
1345 cur_line->type = FILE_LINE_ALIAS_ENTRY;
1347 err = split_line_on_equals(cur_line->unprocessed, &name, &value);
1348 if (err == JB_ERR_MEMORY)
1352 else if (err != JB_ERR_OK)
1354 /* Line does not contain a name=value pair */
1355 file->parse_error = cur_line;
1356 file->parse_error_text = "Expected a name=value pair on this {{alias}} line, but couldn't find one.";
1357 return JB_ERR_PARSE;
1360 if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
1365 free_alias_list(alias_list);
1366 return JB_ERR_MEMORY;
1369 err = get_actions(value, alias_list, new_alias->action);
1372 /* Invalid action or out of memory */
1376 free_alias_list(alias_list);
1377 if (err == JB_ERR_MEMORY)
1383 /* Line does not contain a name=value pair */
1384 file->parse_error = cur_line;
1385 file->parse_error_text = "This alias does not specify a valid set of actions.";
1386 return JB_ERR_PARSE;
1392 new_alias->name = name;
1395 new_alias->next = alias_list;
1396 alias_list = new_alias;
1400 cur_line->type = FILE_LINE_BLANK;
1402 cur_line = cur_line->next;
1406 /* Header done, process the main part of the file */
1407 while (cur_line != NULL)
1409 /* At this point, (cur_line->unprocessed[0] == '{') */
1410 assert(cur_line->unprocessed[0] == '{');
1411 text = cur_line->unprocessed + 1;
1412 len = strlen(text) - 1;
1413 if (text[len] != '}')
1415 /* No closing } on header */
1416 free_alias_list(alias_list);
1417 file->parse_error = cur_line;
1418 file->parse_error_text = "Headers starting with '{' must have a "
1419 "closing bracket ('}'). Headers starting with two brackets ('{{') "
1420 "must close with two brackets ('}}').";
1421 return JB_ERR_PARSE;
1426 /* An invalid {{ header. */
1427 free_alias_list(alias_list);
1428 file->parse_error = cur_line;
1429 file->parse_error_text = "Unknown or unexpected two-bracket header. "
1430 "Please remember that the system (two-bracket) headers must "
1431 "appear in the order {{settings}}, {{description}}, {{alias}}, "
1432 "and must appear before any actions (one-bracket) headers. "
1433 "Also note that system headers may not be repeated.";
1434 return JB_ERR_PARSE;
1437 while ( (*text == ' ') || (*text == '\t') )
1443 && ( (text[len - 1] == ' ')
1444 || (text[len - 1] == '\t') ) )
1449 cur_line->type = FILE_LINE_ACTION;
1451 /* Remove {} and make copy */
1452 if (NULL == (value = (char *) malloc(len + 1)))
1455 free_alias_list(alias_list);
1456 return JB_ERR_MEMORY;
1458 strncpy(value, text, len);
1462 err = get_actions(value, alias_list, cur_line->data.action);
1465 /* Invalid action or out of memory */
1467 free_alias_list(alias_list);
1468 if (err == JB_ERR_MEMORY)
1474 /* Line does not contain a name=value pair */
1475 file->parse_error = cur_line;
1476 file->parse_error_text = "This header does not specify a valid set of actions.";
1477 return JB_ERR_PARSE;
1481 /* Done with string - it was clobbered anyway */
1484 /* Process next line */
1485 cur_line = cur_line->next;
1487 /* Loop processing URL patterns */
1488 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1490 if (cur_line->unprocessed[0])
1492 /* Could parse URL here, but this isn't currently needed */
1494 cur_line->type = FILE_LINE_URL;
1498 cur_line->type = FILE_LINE_BLANK;
1500 cur_line = cur_line->next;
1502 } /* End main while(cur_line != NULL) loop */
1504 free_alias_list(alias_list);
1510 /*********************************************************************
1512 * Function : edit_read_file_lines
1514 * Description : Read all the lines of a file into memory.
1515 * Handles whitespace, comments and line continuation.
1518 * 1 : fp = File to read from. On return, this will be
1519 * at EOF but it will not have been closed.
1520 * 2 : pfile = Destination for a linked list of file_lines.
1521 * Will be set to NULL on error.
1522 * 3 : newline = How to handle newlines.
1524 * Returns : JB_ERR_OK on success
1525 * JB_ERR_MEMORY on out-of-memory
1527 *********************************************************************/
1528 jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline)
1530 struct file_line * first_line; /* Keep for return value or to free */
1531 struct file_line * cur_line; /* Current line */
1532 struct file_line * prev_line; /* Entry with prev_line->next = cur_line */
1540 cur_line = first_line = zalloc(sizeof(struct file_line));
1541 if (cur_line == NULL)
1543 return JB_ERR_MEMORY;
1546 cur_line->type = FILE_LINE_UNPROCESSED;
1548 rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1551 /* Out of memory or empty file. */
1552 /* Note that empty file is not an error we propogate up */
1554 return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval);
1559 prev_line = cur_line;
1560 cur_line = prev_line->next = zalloc(sizeof(struct file_line));
1561 if (cur_line == NULL)
1564 edit_free_file_lines(first_line);
1565 return JB_ERR_MEMORY;
1568 cur_line->type = FILE_LINE_UNPROCESSED;
1570 rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1571 if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
1574 edit_free_file_lines(first_line);
1575 return JB_ERR_MEMORY;
1579 while (rval != JB_ERR_FILE);
1583 /* We allocated one too many - free it */
1584 prev_line->next = NULL;
1587 *pfile = first_line;
1592 /*********************************************************************
1594 * Function : edit_read_file
1596 * Description : Read a complete file into memory.
1597 * Handles CGI parameter parsing. If requested, also
1598 * checks the file's modification timestamp.
1601 * 1 : csp = Current client state (buffers, headers, etc...)
1602 * 2 : parameters = map of cgi parameters.
1603 * 3 : require_version = true to check "ver" parameter.
1604 * 4 : suffix = File extension, e.g. ".action".
1605 * 5 : pfile = Destination for the file. Will be set
1609 * filename : The name of the file to read, without the
1610 * path or ".action" extension.
1611 * ver : (Only if require_version is nonzero)
1612 * Timestamp of the actions file. If wrong, this
1613 * function fails with JB_ERR_MODIFIED.
1615 * Returns : JB_ERR_OK on success
1616 * JB_ERR_MEMORY on out-of-memory
1617 * JB_ERR_CGI_PARAMS if "filename" was not specified
1619 * JB_ERR_FILE if the file cannot be opened or
1621 * JB_ERR_MODIFIED if version checking was requested and
1622 * failed - the file was modified outside
1623 * of this CGI editor instance.
1625 *********************************************************************/
1626 jb_err edit_read_file(struct client_state *csp,
1627 const struct map *parameters,
1628 int require_version,
1630 struct editable_file **pfile)
1632 struct file_line * lines;
1636 const char * identifier;
1637 struct editable_file * file;
1638 unsigned version = 0;
1639 struct stat statbuf[1];
1640 char version_buf[22];
1641 int newline = NEWLINE_UNKNOWN;
1649 err = get_file_name_param(csp, parameters, "f", suffix,
1650 &filename, &identifier);
1656 if (stat(filename, statbuf) < 0)
1658 /* Error, probably file not found. */
1662 version = (unsigned) statbuf->st_mtime;
1664 if (require_version)
1666 unsigned specified_version;
1667 err = get_number_param(csp, parameters, "v", &specified_version);
1674 if (version != specified_version)
1676 return JB_ERR_MODIFIED;
1680 if (NULL == (fp = fopen(filename,"rb")))
1686 err = edit_read_file_lines(fp, &lines, &newline);
1696 file = (struct editable_file *) zalloc(sizeof(*file));
1700 edit_free_file_lines(lines);
1704 file->lines = lines;
1705 file->newline = newline;
1706 file->filename = filename;
1707 file->version = version;
1708 file->identifier = url_encode(identifier);
1710 if (file->identifier == NULL)
1712 edit_free_file(file);
1713 return JB_ERR_MEMORY;
1716 /* Correct file->version_str */
1717 freez(file->version_str);
1718 snprintf(version_buf, 22, "%u", file->version);
1719 version_buf[21] = '\0';
1720 file->version_str = strdup(version_buf);
1721 if (version_buf == NULL)
1723 edit_free_file(file);
1724 return JB_ERR_MEMORY;
1732 /*********************************************************************
1734 * Function : edit_read_actions_file
1736 * Description : Read a complete actions file into memory.
1737 * Handles CGI parameter parsing. If requested, also
1738 * checks the file's modification timestamp.
1740 * If this function detects an error in the categories
1741 * JB_ERR_FILE, JB_ERR_MODIFIED, or JB_ERR_PARSE,
1742 * then it handles it by filling in the specified
1743 * response structure and returning JB_ERR_FILE.
1746 * 1 : csp = Current client state (buffers, headers, etc...)
1747 * 2 : rsp = HTTP response. Only filled in on error.
1748 * 2 : parameters = map of cgi parameters.
1749 * 3 : require_version = true to check "ver" parameter.
1750 * 4 : pfile = Destination for the file. Will be set
1754 * filename : The name of the actions file to read, without the
1755 * path or ".action" extension.
1756 * ver : (Only if require_version is nonzero)
1757 * Timestamp of the actions file. If wrong, this
1758 * function fails with JB_ERR_MODIFIED.
1760 * Returns : JB_ERR_OK on success
1761 * JB_ERR_MEMORY on out-of-memory
1762 * JB_ERR_CGI_PARAMS if "filename" was not specified
1764 * JB_ERR_FILE if the file does not contain valid data,
1765 * or if file cannot be opened or
1766 * contains no data, or if version
1767 * checking was requested and failed.
1769 *********************************************************************/
1770 jb_err edit_read_actions_file(struct client_state *csp,
1771 struct http_response *rsp,
1772 const struct map *parameters,
1773 int require_version,
1774 struct editable_file **pfile)
1777 struct editable_file *file;
1785 err = edit_read_file(csp, parameters, require_version, ".action", &file);
1788 /* Try to handle if possible */
1789 if (err == JB_ERR_FILE)
1791 err = cgi_error_file(csp, rsp, lookup(parameters, "f"));
1793 else if (err == JB_ERR_MODIFIED)
1795 err = cgi_error_modified(csp, rsp, lookup(parameters, "f"));
1797 if (err == JB_ERR_OK)
1800 * Signal to higher-level CGI code that there was a problem but we
1801 * handled it, they should just return JB_ERR_OK.
1808 err = edit_parse_actions_file(file);
1811 if (err == JB_ERR_PARSE)
1813 err = cgi_error_parse(csp, rsp, file);
1814 if (err == JB_ERR_OK)
1817 * Signal to higher-level CGI code that there was a problem but we
1818 * handled it, they should just return JB_ERR_OK.
1823 edit_free_file(file);
1832 /*********************************************************************
1834 * Function : get_file_name_param
1836 * Description : Get the name of the file to edit from the parameters
1837 * passed to a CGI function. This function handles
1838 * security checks such as blocking urls containing
1839 * "/" or ".", prepending the config file directory,
1840 * and adding the specified suffix.
1842 * (This is an essential security check, otherwise
1843 * users may be able to pass "../../../etc/passwd"
1844 * and overwrite the password file [linux], "prn:"
1845 * and print random data [Windows], etc...)
1847 * This function only allows filenames contining the
1848 * characters '-', '_', 'A'-'Z', 'a'-'z', and '0'-'9'.
1849 * That's probably too restrictive but at least it's
1853 * 1 : csp = Current client state (buffers, headers, etc...)
1854 * 2 : parameters = map of cgi parameters
1855 * 3 : param_name = The name of the parameter to read
1856 * 4 : suffix = File extension, e.g. ".actions"
1857 * 5 : pfilename = destination for full filename. Caller
1858 * free()s. Set to NULL on error.
1859 * 6 : pparam = destination for partial filename,
1860 * suitable for use in another URL. Allocated as part
1861 * of the map "parameters", so don't free it.
1862 * Set to NULL if not specified.
1864 * Returns : JB_ERR_OK on success
1865 * JB_ERR_MEMORY on out-of-memory
1866 * JB_ERR_CGI_PARAMS if "filename" was not specified
1869 *********************************************************************/
1870 static jb_err get_file_name_param(struct client_state *csp,
1871 const struct map *parameters,
1872 const char *param_name,
1875 const char **pparam)
1893 param = lookup(parameters, param_name);
1896 return JB_ERR_CGI_PARAMS;
1901 len = strlen(param);
1902 if (len >= FILENAME_MAX)
1905 return JB_ERR_CGI_PARAMS;
1908 /* Check every character to see if it's legal */
1910 while ((ch = *s++) != '\0')
1912 if ( ((ch < 'A') || (ch > 'Z'))
1913 && ((ch < 'a') || (ch > 'z'))
1914 && ((ch < '0') || (ch > '9'))
1918 /* Probable hack attempt. */
1919 return JB_ERR_CGI_PARAMS;
1923 /* Append extension */
1924 name = malloc(len + strlen(suffix) + 1);
1927 return JB_ERR_MEMORY;
1929 strcpy(name, param);
1930 strcpy(name + len, suffix);
1933 fullpath = make_path(csp->config->confdir, name);
1936 if (fullpath == NULL)
1938 return JB_ERR_MEMORY;
1942 *pfilename = fullpath;
1948 /*********************************************************************
1950 * Function : get_url_spec_param
1952 * Description : Get a URL pattern from the parameters
1953 * passed to a CGI function. Removes leading/trailing
1954 * spaces and validates it.
1957 * 1 : csp = Current client state (buffers, headers, etc...)
1958 * 2 : parameters = map of cgi parameters
1959 * 3 : name = Name of CGI parameter to read
1960 * 4 : pvalue = destination for value. Will be malloc()'d.
1961 * Set to NULL on error.
1963 * Returns : JB_ERR_OK on success
1964 * JB_ERR_MEMORY on out-of-memory
1965 * JB_ERR_CGI_PARAMS if the parameter was not specified
1968 *********************************************************************/
1969 static jb_err get_url_spec_param(struct client_state *csp,
1970 const struct map *parameters,
1974 const char *orig_param;
1977 struct url_spec compiled[1];
1987 orig_param = lookup(parameters, name);
1990 return JB_ERR_CGI_PARAMS;
1993 /* Copy and trim whitespace */
1994 param = strdup(orig_param);
1997 return JB_ERR_MEMORY;
2001 /* Must be non-empty, and can't allow 1st character to be '{' */
2002 if (param[0] == '\0' || param[0] == '{')
2005 return JB_ERR_CGI_PARAMS;
2008 /* Check for embedded newlines */
2009 for (s = param; *s != '\0'; s++)
2011 if ((*s == '\r') || (*s == '\n'))
2014 return JB_ERR_CGI_PARAMS;
2018 /* Check that regex is valid */
2023 return JB_ERR_MEMORY;
2025 err = create_url_spec(compiled, s);
2030 return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
2032 free_url_spec(compiled);
2034 if (param[strlen(param) - 1] == '\\')
2037 * Must protect trailing '\\' from becoming line continuation character.
2038 * Two methods: 1) If it's a domain only, add a trailing '/'.
2039 * 2) For path, add the do-nothing PCRE expression (?:) to the end
2041 if (strchr(param, '/') == NULL)
2043 err = string_append(¶m, "/");
2047 err = string_append(¶m, "(?:)");
2054 /* Check that the modified regex is valid */
2059 return JB_ERR_MEMORY;
2061 err = create_url_spec(compiled, s);
2066 return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
2068 free_url_spec(compiled);
2075 /*********************************************************************
2077 * Function : map_radio
2079 * Description : Map a set of radio button values. E.g. if you have
2080 * 3 radio buttons, declare them as:
2081 * <option type="radio" name="xyz" @xyz-a@>
2082 * <option type="radio" name="xyz" @xyz-b@>
2083 * <option type="radio" name="xyz" @xyz-c@>
2084 * Then map one of the @xyz-?@ variables to "checked"
2085 * and all the others to empty by calling:
2086 * map_radio(exports, "xyz", "abc", sel)
2087 * Where 'sel' is 'a', 'b', or 'c'.
2090 * 1 : exports = Exports map to modify.
2091 * 2 : optionname = name for map
2092 * 3 : values = null-terminated list of values;
2093 * 4 : value = Selected value.
2095 * CGI Parameters : None
2097 * Returns : JB_ERR_OK on success
2098 * JB_ERR_MEMORY on out-of-memory
2100 *********************************************************************/
2101 static jb_err map_radio(struct map * exports,
2102 const char * optionname,
2103 const char * values,
2115 len = strlen(optionname);
2116 buf = malloc(len + 3);
2119 return JB_ERR_MEMORY;
2122 strcpy(buf, optionname);
2127 while ((c = *values++) != '\0')
2132 if (map(exports, buf, 1, "", 1))
2134 return JB_ERR_MEMORY;
2140 return map(exports, buf, 0, "checked", 1);
2144 /*********************************************************************
2146 * Function : cgi_error_modified
2148 * Description : CGI function that is called when a file is modified
2149 * outside the CGI editor.
2152 * 1 : csp = Current client state (buffers, headers, etc...)
2153 * 2 : rsp = http_response data structure for output
2154 * 3 : filename = The file that was modified.
2156 * CGI Parameters : none
2158 * Returns : JB_ERR_OK on success
2159 * JB_ERR_MEMORY on out-of-memory error.
2161 *********************************************************************/
2162 jb_err cgi_error_modified(struct client_state *csp,
2163 struct http_response *rsp,
2164 const char *filename)
2166 struct map *exports;
2173 if (NULL == (exports = default_exports(csp, NULL)))
2175 return JB_ERR_MEMORY;
2178 err = map(exports, "f", 1, html_encode(filename), 0);
2185 return template_fill_for_cgi(csp, "cgi-error-modified", exports, rsp);
2189 /*********************************************************************
2191 * Function : cgi_error_parse
2193 * Description : CGI function that is called when a file cannot
2194 * be parsed by the CGI editor.
2197 * 1 : csp = Current client state (buffers, headers, etc...)
2198 * 2 : rsp = http_response data structure for output
2199 * 3 : file = The file that was modified.
2201 * CGI Parameters : none
2203 * Returns : JB_ERR_OK on success
2204 * JB_ERR_MEMORY on out-of-memory error.
2206 *********************************************************************/
2207 jb_err cgi_error_parse(struct client_state *csp,
2208 struct http_response *rsp,
2209 struct editable_file *file)
2211 struct map *exports;
2213 struct file_line *cur_line;
2219 if (NULL == (exports = default_exports(csp, NULL)))
2221 return JB_ERR_MEMORY;
2224 err = map(exports, "f", 1, file->identifier, 1);
2225 if (!err) err = map(exports, "parse-error", 1, html_encode(file->parse_error_text), 0);
2227 cur_line = file->parse_error;
2230 if (!err) err = map(exports, "line-raw", 1, html_encode(cur_line->raw), 0);
2231 if (!err) err = map(exports, "line-data", 1, html_encode(cur_line->unprocessed), 0);
2239 return template_fill_for_cgi(csp, "cgi-error-parse", exports, rsp);
2243 /*********************************************************************
2245 * Function : cgi_error_file
2247 * Description : CGI function that is called when a file cannot be
2248 * opened by the CGI editor.
2251 * 1 : csp = Current client state (buffers, headers, etc...)
2252 * 2 : rsp = http_response data structure for output
2253 * 3 : filename = The file that was modified.
2255 * CGI Parameters : none
2257 * Returns : JB_ERR_OK on success
2258 * JB_ERR_MEMORY on out-of-memory error.
2260 *********************************************************************/
2261 jb_err cgi_error_file(struct client_state *csp,
2262 struct http_response *rsp,
2263 const char *filename)
2265 struct map *exports;
2272 if (NULL == (exports = default_exports(csp, NULL)))
2274 return JB_ERR_MEMORY;
2277 err = map(exports, "f", 1, html_encode(filename), 0);
2284 return template_fill_for_cgi(csp, "cgi-error-file", exports, rsp);
2288 /*********************************************************************
2290 * Function : cgi_error_file
2292 * Description : CGI function that is called when a file cannot be
2293 * opened for writing by the CGI editor.
2296 * 1 : csp = Current client state (buffers, headers, etc...)
2297 * 2 : rsp = http_response data structure for output
2298 * 3 : filename = The file that we can't write to
2300 * CGI Parameters : none
2302 * Returns : JB_ERR_OK on success
2303 * JB_ERR_MEMORY on out-of-memory error.
2305 *********************************************************************/
2306 jb_err cgi_error_file_read_only(struct client_state *csp,
2307 struct http_response *rsp,
2308 const char *filename)
2310 struct map *exports;
2317 if (NULL == (exports = default_exports(csp, NULL)))
2319 return JB_ERR_MEMORY;
2322 err = map(exports, "f", 1, html_encode(filename), 0);
2329 return template_fill_for_cgi(csp, "cgi-error-file-read-only", exports, rsp);
2333 /*********************************************************************
2335 * Function : cgi_error_disabled
2337 * Description : CGI function that is called if the actions editor
2338 * is called although it's disabled in config
2341 * 1 : csp = Current client state (buffers, headers, etc...)
2342 * 2 : rsp = http_response data structure for output
2344 * CGI Parameters : none
2346 * Returns : JB_ERR_OK on success
2347 * JB_ERR_MEMORY on out-of-memory error.
2349 *********************************************************************/
2350 jb_err cgi_error_disabled(struct client_state *csp,
2351 struct http_response *rsp)
2353 struct map *exports;
2358 if (NULL == (exports = default_exports(csp, NULL)))
2360 return JB_ERR_MEMORY;
2363 return template_fill_for_cgi(csp, "cgi-error-disabled", exports, rsp);
2367 /*********************************************************************
2369 * Function : cgi_edit_actions
2371 * Description : CGI function that allows the user to choose which
2372 * actions file to edit.
2375 * 1 : csp = Current client state (buffers, headers, etc...)
2376 * 2 : rsp = http_response data structure for output
2377 * 3 : parameters = map of cgi parameters
2379 * CGI Parameters : None
2381 * Returns : JB_ERR_OK on success
2382 * JB_ERR_MEMORY on out-of-memory error
2384 *********************************************************************/
2385 jb_err cgi_edit_actions(struct client_state *csp,
2386 struct http_response *rsp,
2387 const struct map *parameters)
2390 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2392 return cgi_error_disabled(csp, rsp);
2395 /* FIXME: Incomplete */
2396 rsp->status = strdup("302 Local Redirect from Privoxy");
2397 if (rsp->status == NULL)
2399 return JB_ERR_MEMORY;
2401 if (enlist_unique_header(rsp->headers, "Location",
2402 CGI_PREFIX "edit-actions-list?f=default"))
2406 return JB_ERR_MEMORY;
2413 /*********************************************************************
2415 * Function : cgi_edit_actions_list
2417 * Description : CGI function that edits the actions list.
2418 * FIXME: This function shouldn't FATAL ever.
2419 * FIXME: This function doesn't check the retval of map()
2421 * 1 : csp = Current client state (buffers, headers, etc...)
2422 * 2 : rsp = http_response data structure for output
2423 * 3 : parameters = map of cgi parameters
2425 * CGI Parameters : filename
2427 * Returns : JB_ERR_OK on success
2428 * JB_ERR_MEMORY on out-of-memory
2429 * JB_ERR_FILE if the file cannot be opened or
2431 * JB_ERR_CGI_PARAMS if "filename" was not specified
2434 *********************************************************************/
2435 jb_err cgi_edit_actions_list(struct client_state *csp,
2436 struct http_response *rsp,
2437 const struct map *parameters)
2439 char * section_template;
2440 char * url_template;
2445 struct map * exports;
2446 struct map * section_exports;
2447 struct map * url_exports;
2448 struct editable_file * file;
2449 struct file_line * cur_line;
2450 unsigned line_number = 0;
2451 unsigned prev_section_line_number = ((unsigned) (-1));
2453 struct file_list * fl;
2454 struct url_actions * b;
2455 char * buttons = NULL;
2458 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2460 return cgi_error_disabled(csp, rsp);
2463 if (NULL == (exports = default_exports(csp, NULL)))
2465 edit_free_file(file);
2466 return JB_ERR_MEMORY;
2469 /* Load actions file */
2470 err = edit_read_actions_file(csp, rsp, parameters, 0, &file);
2473 /* No filename specified, can't read file, or out of memory. */
2474 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2477 /* Find start of actions in file */
2478 cur_line = file->lines;
2480 while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
2482 cur_line = cur_line->next;
2487 * Conventional actions files should have a match all block
2489 * cur_line = {...global actions...}
2490 * cur_line->next = /
2491 * cur_line->next->next = {...actions...} or EOF
2493 if ( (cur_line != NULL)
2494 && (cur_line->type == FILE_LINE_ACTION)
2495 && (cur_line->next != NULL)
2496 && (cur_line->next->type == FILE_LINE_URL)
2497 && (0 == strcmp(cur_line->next->unprocessed, "/"))
2498 && ( (cur_line->next->next == NULL)
2499 || (cur_line->next->next->type != FILE_LINE_URL)
2503 * Generate string with buttons to set actions for "/" to
2504 * any predefined set of actions (named standard.*, probably
2505 * residing in standard.action).
2508 err = template_load(csp, §ion_template, "edit-actions-list-button", 0);
2511 edit_free_file(file);
2513 if (err == JB_ERR_FILE)
2515 return cgi_error_no_template(csp, rsp, "edit-actions-list-button");
2520 err = template_fill(§ion_template, exports);
2523 edit_free_file(file);
2528 buttons = strdup("");
2529 for (i = 0; i < MAX_ACTION_FILES; i++)
2531 if (((fl = csp->actions_list[i]) != NULL) && ((b = fl->f) != NULL))
2533 for (b = b->next; NULL != b; b = b->next)
2535 if (!strncmp(b->url->spec, "standard.", 9) && *(b->url->spec + 9) != '\0')
2537 if (err || (NULL == (section_exports = new_map())))
2540 free(section_template);
2541 edit_free_file(file);
2543 return JB_ERR_MEMORY;
2546 err = map(section_exports, "button-name", 1, b->url->spec + 9, 1);
2548 if (err || (NULL == (s = strdup(section_template))))
2550 free_map(section_exports);
2552 free(section_template);
2553 edit_free_file(file);
2555 return JB_ERR_MEMORY;
2558 if (!err) err = template_fill(&s, section_exports);
2559 free_map(section_exports);
2560 if (!err) err = string_join(&buttons, s);
2565 freez(section_template);
2566 if (!err) err = map(exports, "all-urls-buttons", 1, buttons, 0);
2569 * Conventional actions file, supply extra editing help.
2570 * (e.g. don't allow them to make it an unconventional one).
2572 if (!err) err = map_conditional(exports, "all-urls-present", 1);
2574 snprintf(buf, 150, "%d", line_number);
2575 if (!err) err = map(exports, "all-urls-s", 1, buf, 1);
2576 snprintf(buf, 150, "%d", line_number + 2);
2577 if (!err) err = map(exports, "all-urls-s-next", 1, buf, 1);
2578 if (!err) err = map(exports, "all-urls-actions", 1,
2579 actions_to_html(csp, cur_line->data.action), 0);
2581 /* Skip the 2 lines */
2582 cur_line = cur_line->next->next;
2586 * Note that prev_section_line_number is NOT set here.
2587 * This is deliberate and not a bug. It stops a "Move up"
2588 * option appearing on the next section. Clicking "Move
2589 * up" would make the actions file unconventional, which
2590 * we don't want, so we hide this option.
2596 * Non-standard actions file - does not begin with
2597 * the "All URLs" section.
2599 if (!err) err = map_conditional(exports, "all-urls-present", 0);
2602 /* Set up global exports */
2604 if (!err) err = map(exports, "f", 1, file->identifier, 1);
2605 if (!err) err = map(exports, "v", 1, file->version_str, 1);
2607 /* Discourage private additions to default.action */
2609 if (!err) err = map_conditional(exports, "default-action",
2610 (strcmp("default", lookup(parameters, "f")) == 0));
2613 edit_free_file(file);
2618 /* Should do all global exports above this point */
2620 /* Load templates */
2622 err = template_load(csp, §ion_template, "edit-actions-list-section", 0);
2625 edit_free_file(file);
2627 if (err == JB_ERR_FILE)
2629 return cgi_error_no_template(csp, rsp, "edit-actions-list-section");
2634 err = template_load(csp, &url_template, "edit-actions-list-url", 0);
2637 free(section_template);
2638 edit_free_file(file);
2640 if (err == JB_ERR_FILE)
2642 return cgi_error_no_template(csp, rsp, "edit-actions-list-url");
2647 err = template_fill(§ion_template, exports);
2651 edit_free_file(file);
2657 err = template_fill(&url_template, exports);
2660 free(section_template);
2661 edit_free_file(file);
2666 if (NULL == (sections = strdup("")))
2668 free(section_template);
2670 edit_free_file(file);
2672 return JB_ERR_MEMORY;
2675 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_ACTION))
2677 if (NULL == (section_exports = new_map()))
2680 free(section_template);
2682 edit_free_file(file);
2684 return JB_ERR_MEMORY;
2687 snprintf(buf, 150, "%d", line_number);
2688 err = map(section_exports, "s", 1, buf, 1);
2689 if (!err) err = map(section_exports, "actions", 1,
2690 actions_to_html(csp, cur_line->data.action), 0);
2693 && (cur_line->next != NULL)
2694 && (cur_line->next->type == FILE_LINE_URL))
2696 /* This section contains at least one URL, don't allow delete */
2697 err = map_block_killer(section_exports, "empty-section");
2701 if (!err) err = map_block_keep(section_exports, "empty-section");
2704 if (prev_section_line_number != ((unsigned)(-1)))
2706 /* Not last section */
2707 snprintf(buf, 150, "%d", prev_section_line_number);
2708 if (!err) err = map(section_exports, "s-prev", 1, buf, 1);
2709 if (!err) err = map_block_keep(section_exports, "s-prev-exists");
2714 if (!err) err = map_block_killer(section_exports, "s-prev-exists");
2716 prev_section_line_number = line_number;
2721 free(section_template);
2723 edit_free_file(file);
2725 free_map(section_exports);
2729 /* Should do all section-specific exports above this point */
2731 if (NULL == (urls = strdup("")))
2734 free(section_template);
2736 edit_free_file(file);
2738 free_map(section_exports);
2739 return JB_ERR_MEMORY;
2744 cur_line = cur_line->next;
2747 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL))
2749 if (NULL == (url_exports = new_map()))
2753 free(section_template);
2755 edit_free_file(file);
2757 free_map(section_exports);
2758 return JB_ERR_MEMORY;
2761 snprintf(buf, 150, "%d", line_number);
2762 err = map(url_exports, "p", 1, buf, 1);
2764 snprintf(buf, 150, "%d", url_1_2);
2765 if (!err) err = map(url_exports, "url-1-2", 1, buf, 1);
2767 if (!err) err = map(url_exports, "url-html", 1,
2768 html_encode(cur_line->unprocessed), 0);
2769 if (!err) err = map(url_exports, "url", 1,
2770 url_encode(cur_line->unprocessed), 0);
2776 free(section_template);
2778 edit_free_file(file);
2780 free_map(section_exports);
2781 free_map(url_exports);
2785 if (NULL == (s = strdup(url_template)))
2789 free(section_template);
2791 edit_free_file(file);
2793 free_map(section_exports);
2794 free_map(url_exports);
2795 return JB_ERR_MEMORY;
2798 err = template_fill(&s, section_exports);
2799 if (!err) err = template_fill(&s, url_exports);
2800 if (!err) err = string_append(&urls, s);
2802 free_map(url_exports);
2809 free(section_template);
2811 edit_free_file(file);
2813 free_map(section_exports);
2817 url_1_2 = 3 - url_1_2;
2819 cur_line = cur_line->next;
2823 err = map(section_exports, "urls", 1, urls, 0);
2825 /* Could also do section-specific exports here, but it wouldn't be as fast */
2827 if ( (cur_line != NULL)
2828 && (cur_line->type == FILE_LINE_ACTION))
2830 /* Not last section */
2831 snprintf(buf, 150, "%d", line_number);
2832 if (!err) err = map(section_exports, "s-next", 1, buf, 1);
2833 if (!err) err = map_block_keep(section_exports, "s-next-exists");
2838 if (!err) err = map_block_killer(section_exports, "s-next-exists");
2844 free(section_template);
2846 edit_free_file(file);
2848 free_map(section_exports);
2852 if (NULL == (s = strdup(section_template)))
2855 free(section_template);
2857 edit_free_file(file);
2859 free_map(section_exports);
2860 return JB_ERR_MEMORY;
2863 err = template_fill(&s, section_exports);
2864 if (!err) err = string_append(§ions, s);
2867 free_map(section_exports);
2872 free(section_template);
2874 edit_free_file(file);
2880 edit_free_file(file);
2881 free(section_template);
2884 err = map(exports, "sections", 1, sections, 0);
2891 /* Could also do global exports here, but it wouldn't be as fast */
2893 return template_fill_for_cgi(csp, "edit-actions-list", exports, rsp);
2897 /*********************************************************************
2899 * Function : cgi_edit_actions_for_url
2901 * Description : CGI function that edits the Actions list.
2904 * 1 : csp = Current client state (buffers, headers, etc...)
2905 * 2 : rsp = http_response data structure for output
2906 * 3 : parameters = map of cgi parameters
2908 * CGI Parameters : None
2910 * Returns : JB_ERR_OK on success
2911 * JB_ERR_MEMORY on out-of-memory
2912 * JB_ERR_CGI_PARAMS if the CGI parameters are not
2913 * specified or not valid.
2915 *********************************************************************/
2916 jb_err cgi_edit_actions_for_url(struct client_state *csp,
2917 struct http_response *rsp,
2918 const struct map *parameters)
2920 struct map * exports;
2922 struct editable_file * file;
2923 struct file_line * cur_line;
2924 unsigned line_number;
2926 struct file_list *filter_file;
2927 struct re_filterfile_spec *filter_group;
2929 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2931 return cgi_error_disabled(csp, rsp);
2934 err = get_number_param(csp, parameters, "s", §ionid);
2940 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2943 /* No filename specified, can't read file, modified, or out of memory. */
2944 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2947 cur_line = file->lines;
2949 for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2951 cur_line = cur_line->next;
2954 if ( (cur_line == NULL)
2955 || (line_number != sectionid)
2957 || (cur_line->type != FILE_LINE_ACTION))
2959 /* Invalid "sectionid" parameter */
2960 edit_free_file(file);
2961 return JB_ERR_CGI_PARAMS;
2964 if (NULL == (exports = default_exports(csp, NULL)))
2966 edit_free_file(file);
2967 return JB_ERR_MEMORY;
2970 err = map(exports, "f", 1, file->identifier, 1);
2971 if (!err) err = map(exports, "v", 1, file->version_str, 1);
2972 if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0);
2974 if (!err) err = actions_to_radio(exports, cur_line->data.action);
2976 filter_file = csp->rlist;
2977 filter_group = ((filter_file != NULL) ? filter_file->f : NULL);
2979 if (!err) err = map_conditional(exports, "any-filters-defined", (filter_group != NULL));
2983 edit_free_file(file);
2988 if (filter_group == NULL)
2990 err = map(exports, "filter-params", 1, "", 1);
2994 /* We have some entries in the filter list */
2997 char * filter_template;
2999 err = template_load(csp, &filter_template, "edit-actions-for-url-filter", 0);
3002 edit_free_file(file);
3004 if (err == JB_ERR_FILE)
3006 return cgi_error_no_template(csp, rsp, "edit-actions-for-url-filter");
3011 err = template_fill(&filter_template, exports);
3013 result = strdup("");
3015 for (;(!err) && (filter_group != NULL); filter_group = filter_group->next)
3017 char current_mode = 'x';
3018 struct list_entry *filter_name;
3020 struct map *line_exports;
3023 filter_name = cur_line->data.action->multi_add[ACTION_MULTI_FILTER]->first;
3024 while ((filter_name != NULL)
3025 && (0 != strcmp(filter_group->name, filter_name->str)))
3027 filter_name = filter_name->next;
3030 if (filter_name != NULL)
3036 filter_name = cur_line->data.action->multi_remove[ACTION_MULTI_FILTER]->first;
3037 while ((filter_name != NULL)
3038 && (0 != strcmp(filter_group->name, filter_name->str)))
3040 filter_name = filter_name->next;
3042 if (filter_name != NULL)
3048 /* Generate a unique serial number */
3049 snprintf(number, sizeof(number), "%x", index++);
3050 number[sizeof(number) - 1] = '\0';
3052 line_exports = new_map();
3053 if (line_exports == NULL)
3055 err = JB_ERR_MEMORY;
3060 if (!err) err = map(line_exports, "index", 1, number, 1);
3061 if (!err) err = map(line_exports, "name", 1, filter_group->name, 1);
3062 if (!err) err = map(line_exports, "description", 1, filter_group->description, 1);
3063 if (!err) err = map_radio(line_exports, "this-filter", "ynx", current_mode);
3068 this_line = strdup(filter_template);
3069 if (this_line == NULL) err = JB_ERR_MEMORY;
3071 if (!err) err = template_fill(&this_line, line_exports);
3072 string_join(&result, this_line);
3074 free_map(line_exports);
3078 freez(filter_template);
3082 err = map(exports, "filter-params", 1, result, 0);
3090 if (!err) err = map_radio(exports, "filter-all", "nx",
3091 (cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] ? 'n' : 'x'));
3093 edit_free_file(file);
3101 return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
3105 /*********************************************************************
3107 * Function : cgi_edit_actions_submit
3109 * Description : CGI function that actually edits the Actions list.
3112 * 1 : csp = Current client state (buffers, headers, etc...)
3113 * 2 : rsp = http_response data structure for output
3114 * 3 : parameters = map of cgi parameters
3116 * CGI Parameters : None
3118 * Returns : JB_ERR_OK on success
3119 * JB_ERR_MEMORY on out-of-memory
3120 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3121 * specified or not valid.
3123 *********************************************************************/
3124 jb_err cgi_edit_actions_submit(struct client_state *csp,
3125 struct http_response *rsp,
3126 const struct map *parameters)
3132 struct editable_file * file;
3133 struct file_line * cur_line;
3134 unsigned line_number;
3138 const char * action_set_name;
3140 struct file_list * fl;
3141 struct url_actions * b;
3143 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3145 return cgi_error_disabled(csp, rsp);
3148 err = get_number_param(csp, parameters, "s", §ionid);
3154 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3157 /* No filename specified, can't read file, modified, or out of memory. */
3158 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3161 cur_line = file->lines;
3163 for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
3165 cur_line = cur_line->next;
3168 if ( (cur_line == NULL)
3169 || (line_number != sectionid)
3171 || (cur_line->type != FILE_LINE_ACTION))
3173 /* Invalid "sectionid" parameter */
3174 edit_free_file(file);
3175 return JB_ERR_CGI_PARAMS;
3178 get_string_param(parameters, "p", &action_set_name);
3179 if (action_set_name != NULL)
3181 for (index = 0; index < MAX_ACTION_FILES; index++)
3183 if (((fl = csp->actions_list[index]) != NULL) && ((b = fl->f) != NULL))
3185 for (b = b->next; NULL != b; b = b->next)
3187 if (!strncmp(b->url->spec, "standard.", 9) && !strcmp(b->url->spec + 9, action_set_name))
3189 copy_action(cur_line->data.action, b->action);
3195 edit_free_file(file);
3196 return JB_ERR_CGI_PARAMS;
3202 err = actions_from_radio(parameters, cur_line->data.action);
3208 edit_free_file(file);
3212 ch = get_char_param(parameters, "filter_all");
3215 list_remove_all(cur_line->data.action->multi_add[ACTION_MULTI_FILTER]);
3216 list_remove_all(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER]);
3217 cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] = 1;
3221 cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] = 0;
3224 for (index = 0; !err; index++)
3231 /* Generate the keys */
3232 snprintf(key_value, sizeof(key_value), "filter_r%x", index);
3233 key_value[sizeof(key_value) - 1] = '\0';
3234 snprintf(key_name, sizeof(key_name), "filter_n%x", index);
3235 key_name[sizeof(key_name) - 1] = '\0';
3237 err = get_string_param(parameters, key_name, &name);
3246 value = get_char_param(parameters, key_value);
3249 list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
3250 if (!err) err = enlist(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
3251 list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
3253 else if (value == 'N')
3255 list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
3256 if (!cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER])
3258 list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
3259 if (!err) err = enlist(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
3262 else if (value == 'X')
3264 list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
3265 list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
3272 edit_free_file(file);
3276 if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
3279 edit_free_file(file);
3280 return JB_ERR_MEMORY;
3283 len = strlen(actiontext);
3287 * Empty action - must special-case this.
3288 * Simply setting len to 1 is sufficient...
3293 if (NULL == (newtext = malloc(len + 2)))
3297 edit_free_file(file);
3298 return JB_ERR_MEMORY;
3300 strcpy(newtext, actiontext);
3304 newtext[len + 1] = '\0';
3306 freez(cur_line->raw);
3307 freez(cur_line->unprocessed);
3308 cur_line->unprocessed = newtext;
3310 err = edit_write_file(file);
3313 /* Error writing file */
3314 if (err == JB_ERR_FILE)
3316 /* Read-only file. */
3317 err = cgi_error_file_read_only(csp, rsp, file->identifier);
3319 edit_free_file(file);
3323 target = strdup(CGI_PREFIX "edit-actions-list?f=");
3324 string_append(&target, file->identifier);
3326 edit_free_file(file);
3331 return JB_ERR_MEMORY;
3334 rsp->status = strdup("302 Local Redirect from Privoxy");
3335 if (rsp->status == NULL)
3338 return JB_ERR_MEMORY;
3340 err = enlist_unique_header(rsp->headers, "Location", target);
3347 /*********************************************************************
3349 * Function : cgi_edit_actions_url
3351 * Description : CGI function that actually edits a URL pattern in
3355 * 1 : csp = Current client state (buffers, headers, etc...)
3356 * 2 : rsp = http_response data structure for output
3357 * 3 : parameters = map of cgi parameters
3360 * filename : Identifies the file to edit
3361 * ver : File's last-modified time
3362 * section : Line number of section to edit
3363 * pattern : Line number of pattern to edit
3364 * newval : New value for pattern
3366 * Returns : JB_ERR_OK on success
3367 * JB_ERR_MEMORY on out-of-memory
3368 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3369 * specified or not valid.
3371 *********************************************************************/
3372 jb_err cgi_edit_actions_url(struct client_state *csp,
3373 struct http_response *rsp,
3374 const struct map *parameters)
3378 struct editable_file * file;
3379 struct file_line * cur_line;
3380 unsigned line_number;
3384 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3386 return cgi_error_disabled(csp, rsp);
3389 err = get_number_param(csp, parameters, "p", &patternid);
3396 return JB_ERR_CGI_PARAMS;
3399 err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3405 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3408 /* No filename specified, can't read file, modified, or out of memory. */
3410 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3414 cur_line = file->lines;
3416 while ((cur_line != NULL) && (line_number < patternid))
3418 cur_line = cur_line->next;
3422 if ( (cur_line == NULL)
3423 || (cur_line->type != FILE_LINE_URL))
3425 /* Invalid "patternid" parameter */
3427 edit_free_file(file);
3428 return JB_ERR_CGI_PARAMS;
3431 /* At this point, the line to edit is in cur_line */
3433 freez(cur_line->raw);
3434 freez(cur_line->unprocessed);
3435 cur_line->unprocessed = new_pattern;
3437 err = edit_write_file(file);
3440 /* Error writing file */
3441 if (err == JB_ERR_FILE)
3443 /* Read-only file. */
3444 err = cgi_error_file_read_only(csp, rsp, file->identifier);
3446 edit_free_file(file);
3450 target = strdup(CGI_PREFIX "edit-actions-list?f=");
3451 string_append(&target, file->identifier);
3453 edit_free_file(file);
3458 return JB_ERR_MEMORY;
3461 rsp->status = strdup("302 Local Redirect from Privoxy");
3462 if (rsp->status == NULL)
3465 return JB_ERR_MEMORY;
3467 err = enlist_unique_header(rsp->headers, "Location", target);
3474 /*********************************************************************
3476 * Function : cgi_edit_actions_add_url
3478 * Description : CGI function that actually adds a URL pattern to
3482 * 1 : csp = Current client state (buffers, headers, etc...)
3483 * 2 : rsp = http_response data structure for output
3484 * 3 : parameters = map of cgi parameters
3487 * filename : Identifies the file to edit
3488 * ver : File's last-modified time
3489 * section : Line number of section to edit
3490 * newval : New pattern
3492 * Returns : JB_ERR_OK on success
3493 * JB_ERR_MEMORY on out-of-memory
3494 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3495 * specified or not valid.
3497 *********************************************************************/
3498 jb_err cgi_edit_actions_add_url(struct client_state *csp,
3499 struct http_response *rsp,
3500 const struct map *parameters)
3504 struct file_line * new_line;
3505 struct editable_file * file;
3506 struct file_line * cur_line;
3507 unsigned line_number;
3511 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3513 return cgi_error_disabled(csp, rsp);
3516 err = get_number_param(csp, parameters, "s", §ionid);
3523 return JB_ERR_CGI_PARAMS;
3526 err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3532 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3535 /* No filename specified, can't read file, modified, or out of memory. */
3537 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3541 cur_line = file->lines;
3543 while ((cur_line != NULL) && (line_number < sectionid))
3545 cur_line = cur_line->next;
3549 if ( (cur_line == NULL)
3550 || (cur_line->type != FILE_LINE_ACTION))
3552 /* Invalid "sectionid" parameter */
3554 edit_free_file(file);
3555 return JB_ERR_CGI_PARAMS;
3558 /* At this point, the section header is in cur_line - add after this. */
3560 /* Allocate the new line */
3561 new_line = (struct file_line *)zalloc(sizeof(*new_line));
3562 if (new_line == NULL)
3565 edit_free_file(file);
3566 return JB_ERR_MEMORY;
3569 /* Fill in the data members of the new line */
3570 new_line->raw = NULL;
3571 new_line->prefix = NULL;
3572 new_line->unprocessed = new_pattern;
3573 new_line->type = FILE_LINE_URL;
3575 /* Link new_line into the list, after cur_line */
3576 new_line->next = cur_line->next;
3577 cur_line->next = new_line;
3579 /* Done making changes, now commit */
3581 err = edit_write_file(file);
3584 /* Error writing file */
3585 if (err == JB_ERR_FILE)
3587 /* Read-only file. */
3588 err = cgi_error_file_read_only(csp, rsp, file->identifier);
3590 edit_free_file(file);
3594 target = strdup(CGI_PREFIX "edit-actions-list?f=");
3595 string_append(&target, file->identifier);
3597 edit_free_file(file);
3602 return JB_ERR_MEMORY;
3605 rsp->status = strdup("302 Local Redirect from Privoxy");
3606 if (rsp->status == NULL)
3609 return JB_ERR_MEMORY;
3611 err = enlist_unique_header(rsp->headers, "Location", target);
3618 /*********************************************************************
3620 * Function : cgi_edit_actions_remove_url
3622 * Description : CGI function that actually removes a URL pattern from
3626 * 1 : csp = Current client state (buffers, headers, etc...)
3627 * 2 : rsp = http_response data structure for output
3628 * 3 : parameters = map of cgi parameters
3631 * f : (filename) Identifies the file to edit
3632 * v : (version) File's last-modified time
3633 * p : (pattern) Line number of pattern to remove
3635 * Returns : JB_ERR_OK on success
3636 * JB_ERR_MEMORY on out-of-memory
3637 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3638 * specified or not valid.
3640 *********************************************************************/
3641 jb_err cgi_edit_actions_remove_url(struct client_state *csp,
3642 struct http_response *rsp,
3643 const struct map *parameters)
3646 struct editable_file * file;
3647 struct file_line * cur_line;
3648 struct file_line * prev_line;
3649 unsigned line_number;
3653 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3655 return cgi_error_disabled(csp, rsp);
3658 err = get_number_param(csp, parameters, "p", &patternid);
3664 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3667 /* No filename specified, can't read file, modified, or out of memory. */
3668 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3673 cur_line = file->lines;
3675 while ((cur_line != NULL) && (line_number < patternid))
3677 prev_line = cur_line;
3678 cur_line = cur_line->next;
3682 if ( (cur_line == NULL)
3683 || (prev_line == NULL)
3684 || (cur_line->type != FILE_LINE_URL))
3686 /* Invalid "patternid" parameter */
3687 edit_free_file(file);
3688 return JB_ERR_CGI_PARAMS;
3691 /* At this point, the line to remove is in cur_line, and the previous
3692 * one is in prev_line
3695 /* Unlink cur_line */
3696 prev_line->next = cur_line->next;
3697 cur_line->next = NULL;
3700 edit_free_file_lines(cur_line);
3702 err = edit_write_file(file);
3705 /* Error writing file */
3706 if (err == JB_ERR_FILE)
3708 /* Read-only file. */
3709 err = cgi_error_file_read_only(csp, rsp, file->identifier);
3711 edit_free_file(file);
3715 target = strdup(CGI_PREFIX "edit-actions-list?f=");
3716 string_append(&target, file->identifier);
3718 edit_free_file(file);
3723 return JB_ERR_MEMORY;
3726 rsp->status = strdup("302 Local Redirect from Privoxy");
3727 if (rsp->status == NULL)
3730 return JB_ERR_MEMORY;
3732 err = enlist_unique_header(rsp->headers, "Location", target);
3739 /*********************************************************************
3741 * Function : cgi_edit_actions_section_remove
3743 * Description : CGI function that actually removes a whole section from
3744 * the actions file. The section must be empty first
3745 * (else JB_ERR_CGI_PARAMS).
3748 * 1 : csp = Current client state (buffers, headers, etc...)
3749 * 2 : rsp = http_response data structure for output
3750 * 3 : parameters = map of cgi parameters
3753 * f : (filename) Identifies the file to edit
3754 * v : (version) File's last-modified time
3755 * s : (section) Line number of section to edit
3757 * Returns : JB_ERR_OK on success
3758 * JB_ERR_MEMORY on out-of-memory
3759 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3760 * specified or not valid.
3762 *********************************************************************/
3763 jb_err cgi_edit_actions_section_remove(struct client_state *csp,
3764 struct http_response *rsp,
3765 const struct map *parameters)
3768 struct editable_file * file;
3769 struct file_line * cur_line;
3770 struct file_line * prev_line;
3771 unsigned line_number;
3775 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3777 return cgi_error_disabled(csp, rsp);
3780 err = get_number_param(csp, parameters, "s", §ionid);
3786 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3789 /* No filename specified, can't read file, modified, or out of memory. */
3790 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3794 cur_line = file->lines;
3797 while ((cur_line != NULL) && (line_number < sectionid))
3799 prev_line = cur_line;
3800 cur_line = cur_line->next;
3804 if ( (cur_line == NULL)
3805 || (cur_line->type != FILE_LINE_ACTION) )
3807 /* Invalid "sectionid" parameter */
3808 edit_free_file(file);
3809 return JB_ERR_CGI_PARAMS;
3812 if ( (cur_line->next != NULL)
3813 && (cur_line->next->type == FILE_LINE_URL) )
3815 /* Section not empty. */
3816 edit_free_file(file);
3817 return JB_ERR_CGI_PARAMS;
3820 /* At this point, the line to remove is in cur_line, and the previous
3821 * one is in prev_line
3824 /* Unlink cur_line */
3825 if (prev_line == NULL)
3827 /* Removing the first line from the file */
3828 file->lines = cur_line->next;
3832 prev_line->next = cur_line->next;
3834 cur_line->next = NULL;
3837 edit_free_file_lines(cur_line);
3839 err = edit_write_file(file);
3842 /* Error writing file */
3843 if (err == JB_ERR_FILE)
3845 /* Read-only file. */
3846 err = cgi_error_file_read_only(csp, rsp, file->identifier);
3848 edit_free_file(file);
3852 target = strdup(CGI_PREFIX "edit-actions-list?f=");
3853 string_append(&target, file->identifier);
3855 edit_free_file(file);
3860 return JB_ERR_MEMORY;
3863 rsp->status = strdup("302 Local Redirect from Privoxy");
3864 if (rsp->status == NULL)
3867 return JB_ERR_MEMORY;
3869 err = enlist_unique_header(rsp->headers, "Location", target);
3876 /*********************************************************************
3878 * Function : cgi_edit_actions_section_add
3880 * Description : CGI function that adds a new empty section to
3884 * 1 : csp = Current client state (buffers, headers, etc...)
3885 * 2 : rsp = http_response data structure for output
3886 * 3 : parameters = map of cgi parameters
3889 * f : (filename) Identifies the file to edit
3890 * v : (version) File's last-modified time
3891 * s : (section) Line number of section to add after, 0 for
3894 * Returns : JB_ERR_OK on success
3895 * JB_ERR_MEMORY on out-of-memory
3896 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3897 * specified or not valid.
3899 *********************************************************************/
3900 jb_err cgi_edit_actions_section_add(struct client_state *csp,
3901 struct http_response *rsp,
3902 const struct map *parameters)
3905 struct file_line * new_line;
3907 struct editable_file * file;
3908 struct file_line * cur_line;
3909 unsigned line_number;
3913 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3915 return cgi_error_disabled(csp, rsp);
3918 err = get_number_param(csp, parameters, "s", §ionid);
3924 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3927 /* No filename specified, can't read file, modified, or out of memory. */
3928 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3932 cur_line = file->lines;
3936 /* Add to start of file */
3937 if (cur_line != NULL)
3939 /* There's something in the file, find the line before the first
3942 while ( (cur_line->next != NULL)
3943 && (cur_line->next->type != FILE_LINE_ACTION) )
3945 cur_line = cur_line->next;
3952 /* Add after stated section. */
3953 while ((cur_line != NULL) && (line_number < sectionid))
3955 cur_line = cur_line->next;
3959 if ( (cur_line == NULL)
3960 || (cur_line->type != FILE_LINE_ACTION))
3962 /* Invalid "sectionid" parameter */
3963 edit_free_file(file);
3964 return JB_ERR_CGI_PARAMS;
3967 /* Skip through the section to find the last line in it. */
3968 while ( (cur_line->next != NULL)
3969 && (cur_line->next->type != FILE_LINE_ACTION) )
3971 cur_line = cur_line->next;
3976 /* At this point, the last line in the previous section is in cur_line
3977 * - add after this. (Or if we need to add as the first line, cur_line
3981 new_text = strdup("{}");
3982 if (NULL == new_text)
3984 edit_free_file(file);
3985 return JB_ERR_MEMORY;
3988 /* Allocate the new line */
3989 new_line = (struct file_line *)zalloc(sizeof(*new_line));
3990 if (new_line == NULL)
3993 edit_free_file(file);
3994 return JB_ERR_MEMORY;
3997 /* Fill in the data members of the new line */
3998 new_line->raw = NULL;
3999 new_line->prefix = NULL;
4000 new_line->unprocessed = new_text;
4001 new_line->type = FILE_LINE_ACTION;
4003 if (cur_line != NULL)
4005 /* Link new_line into the list, after cur_line */
4006 new_line->next = cur_line->next;
4007 cur_line->next = new_line;
4011 /* Link new_line into the list, as first line */
4012 new_line->next = file->lines;
4013 file->lines = new_line;
4016 /* Done making changes, now commit */
4018 err = edit_write_file(file);
4021 /* Error writing file */
4022 if (err == JB_ERR_FILE)
4024 /* Read-only file. */
4025 err = cgi_error_file_read_only(csp, rsp, file->identifier);
4027 edit_free_file(file);
4031 target = strdup(CGI_PREFIX "edit-actions-list?f=");
4032 string_append(&target, file->identifier);
4034 edit_free_file(file);
4039 return JB_ERR_MEMORY;
4042 rsp->status = strdup("302 Local Redirect from Privoxy");
4043 if (rsp->status == NULL)
4046 return JB_ERR_MEMORY;
4048 err = enlist_unique_header(rsp->headers, "Location", target);
4055 /*********************************************************************
4057 * Function : cgi_edit_actions_section_swap
4059 * Description : CGI function that swaps the order of two sections
4060 * in the actions file. Note that this CGI can actually
4061 * swap any two arbitrary sections, but the GUI interface
4062 * currently only allows consecutive sections to be
4066 * 1 : csp = Current client state (buffers, headers, etc...)
4067 * 2 : rsp = http_response data structure for output
4068 * 3 : parameters = map of cgi parameters
4071 * f : (filename) Identifies the file to edit
4072 * v : (version) File's last-modified time
4073 * s1 : (section1) Line number of first section to swap
4074 * s2 : (section2) Line number of second section to swap
4076 * Returns : JB_ERR_OK on success
4077 * JB_ERR_MEMORY on out-of-memory
4078 * JB_ERR_CGI_PARAMS if the CGI parameters are not
4079 * specified or not valid.
4081 *********************************************************************/
4082 jb_err cgi_edit_actions_section_swap(struct client_state *csp,
4083 struct http_response *rsp,
4084 const struct map *parameters)
4088 struct editable_file * file;
4089 struct file_line * cur_line;
4090 struct file_line * prev_line;
4091 struct file_line * line_before_section1;
4092 struct file_line * line_start_section1;
4093 struct file_line * line_end_section1;
4094 struct file_line * line_after_section1;
4095 struct file_line * line_before_section2;
4096 struct file_line * line_start_section2;
4097 struct file_line * line_end_section2;
4098 struct file_line * line_after_section2;
4099 unsigned line_number;
4103 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
4105 return cgi_error_disabled(csp, rsp);
4108 err = get_number_param(csp, parameters, "s1", §ion1);
4109 if (!err) err = get_number_param(csp, parameters, "s2", §ion2);
4115 if (section1 > section2)
4117 unsigned temp = section2;
4118 section2 = section1;
4122 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
4125 /* No filename specified, can't read file, modified, or out of memory. */
4126 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
4129 /* Start at the beginning... */
4131 cur_line = file->lines;
4134 /* ... find section1 ... */
4135 while ((cur_line != NULL) && (line_number < section1))
4137 prev_line = cur_line;
4138 cur_line = cur_line->next;
4142 if ( (cur_line == NULL)
4143 || (cur_line->type != FILE_LINE_ACTION) )
4145 /* Invalid "section1" parameter */
4146 edit_free_file(file);
4147 return JB_ERR_CGI_PARAMS;
4150 /* If no-op, we've validated params and can skip the rest. */
4151 if (section1 != section2)
4153 /* ... find the end of section1 ... */
4154 line_before_section1 = prev_line;
4155 line_start_section1 = cur_line;
4158 prev_line = cur_line;
4159 cur_line = cur_line->next;
4162 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
4163 line_end_section1 = prev_line;
4164 line_after_section1 = cur_line;
4166 /* ... find section2 ... */
4167 while ((cur_line != NULL) && (line_number < section2))
4169 prev_line = cur_line;
4170 cur_line = cur_line->next;
4174 if ( (cur_line == NULL)
4175 || (cur_line->type != FILE_LINE_ACTION) )
4177 /* Invalid "section2" parameter */
4178 edit_free_file(file);
4179 return JB_ERR_CGI_PARAMS;
4182 /* ... find the end of section2 ... */
4183 line_before_section2 = prev_line;
4184 line_start_section2 = cur_line;
4187 prev_line = cur_line;
4188 cur_line = cur_line->next;
4191 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
4192 line_end_section2 = prev_line;
4193 line_after_section2 = cur_line;
4195 /* Now have all the pointers we need. Do the swap. */
4197 /* Change the pointer to section1 to point to section2 instead */
4198 if (line_before_section1 == NULL)
4200 file->lines = line_start_section2;
4204 line_before_section1->next = line_start_section2;
4207 if (line_before_section2 == line_end_section1)
4209 /* Consecutive sections */
4210 line_end_section2->next = line_start_section1;
4214 line_end_section2->next = line_after_section1;
4215 line_before_section2->next = line_start_section1;
4218 /* Set the pointer from the end of section1 to the rest of the file */
4219 line_end_section1->next = line_after_section2;
4221 err = edit_write_file(file);
4224 /* Error writing file */
4225 if (err == JB_ERR_FILE)
4227 /* Read-only file. */
4228 err = cgi_error_file_read_only(csp, rsp, file->identifier);
4230 edit_free_file(file);
4233 } /* END if (section1 != section2) */
4235 target = strdup(CGI_PREFIX "edit-actions-list?f=");
4236 string_append(&target, file->identifier);
4238 edit_free_file(file);
4243 return JB_ERR_MEMORY;
4246 rsp->status = strdup("302 Local Redirect from Privoxy");
4247 if (rsp->status == NULL)
4250 return JB_ERR_MEMORY;
4252 err = enlist_unique_header(rsp->headers, "Location", target);
4259 /*********************************************************************
4261 * Function : cgi_toggle
4263 * Description : CGI function that adds a new empty section to
4267 * 1 : csp = Current client state (buffers, headers, etc...)
4268 * 2 : rsp = http_response data structure for output
4269 * 3 : parameters = map of cgi parameters
4272 * set : If present, how to change toggle setting:
4273 * "enable", "disable", "toggle", or none (default).
4274 * mini : If present, use mini reply template.
4276 * Returns : JB_ERR_OK on success
4277 * JB_ERR_MEMORY on out-of-memory
4279 *********************************************************************/
4280 jb_err cgi_toggle(struct client_state *csp,
4281 struct http_response *rsp,
4282 const struct map *parameters)
4284 struct map *exports;
4286 const char *template_name;
4292 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE))
4294 return cgi_error_disabled(csp, rsp);
4297 mode = get_char_param(parameters, "set");
4304 else if (mode == 'D')
4309 else if (mode == 'T')
4312 g_bToggleIJB = !g_bToggleIJB;
4315 if (NULL == (exports = default_exports(csp, "toggle")))
4317 return JB_ERR_MEMORY;
4320 template_name = (get_char_param(parameters, "mini")
4324 return template_fill_for_cgi(csp, template_name, exports, rsp);
4328 /*********************************************************************
4330 * Function : javascriptify
4332 * Description : Converts a string into a form JavaScript will like.
4334 * Netscape 4's JavaScript sucks - it doesn't use
4335 * "id" parameters, so you have to set the "name"
4336 * used to submit a form element to something JavaScript
4337 * will like. (Or access the elements by index in an
4338 * array. That array contains >60 elements and will
4339 * be changed whenever we add a new action to the
4340 * editor, so I'm NOT going to use indexes that have
4341 * to be figured out by hand.)
4343 * Currently the only thing we have to worry about
4344 * is "-" ==> "_" conversion.
4346 * This is a length-preserving operation so it is
4347 * carried out in-place, no memory is allocated
4351 * 1 : identifier = String to make JavaScript-friendly.
4355 *********************************************************************/
4356 static void javascriptify(char * identifier)
4358 char * p = identifier;
4359 while (NULL != (p = strchr(p, '-')))
4366 /*********************************************************************
4368 * Function : actions_to_radio
4370 * Description : Converts a actionsfile entry into settings for
4371 * radio buttons and edit boxes on a HTML form.
4374 * 1 : exports = List of substitutions to add to.
4375 * 2 : action = Action to read
4377 * Returns : JB_ERR_OK on success
4378 * JB_ERR_MEMORY on out-of-memory
4380 *********************************************************************/
4381 static jb_err actions_to_radio(struct map * exports,
4382 const struct action_spec *action)
4384 unsigned mask = action->mask;
4385 unsigned add = action->add;
4393 mask = action->mask;
4396 /* sanity - prevents "-feature +feature" */
4400 #define DEFINE_ACTION_BOOL(name, bit) \
4401 if (!(mask & bit)) \
4403 current_mode = 'n'; \
4405 else if (add & bit) \
4407 current_mode = 'y'; \
4411 current_mode = 'x'; \
4413 if (map_radio(exports, name, "ynx", current_mode)) \
4415 return JB_ERR_MEMORY; \
4418 #define DEFINE_ACTION_STRING(name, bit, index) \
4419 DEFINE_ACTION_BOOL(name, bit); \
4422 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default) \
4425 checked = !strcmp(action->string[index], value); \
4429 checked = is_default; \
4431 mapped_param |= checked; \
4432 if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
4434 return JB_ERR_MEMORY; \
4437 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val) \
4438 if (map(exports, name "-param-custom", 1, \
4439 ((!mapped_param) ? "checked" : ""), 1)) \
4441 return JB_ERR_MEMORY; \
4443 if (map(exports, name "-param", 1, \
4444 (((add & bit) && !mapped_param) ? \
4445 action->string[index] : default_val), 1)) \
4447 return JB_ERR_MEMORY; \
4450 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val) \
4451 if (map(exports, name "-param", 1, \
4452 ((add & bit) ? action->string[index] : default_val), 1)) \
4454 return JB_ERR_MEMORY; \
4457 #define DEFINE_ACTION_MULTI(name, index) \
4458 if (action->multi_add[index]->first) \
4460 current_mode = 'y'; \
4462 else if (action->multi_remove_all[index]) \
4464 current_mode = 'n'; \
4466 else if (action->multi_remove[index]->first) \
4468 current_mode = 'y'; \
4472 current_mode = 'x'; \
4474 if (map_radio(exports, name, "ynx", current_mode)) \
4476 return JB_ERR_MEMORY; \
4479 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
4481 #include "actionlist.h"
4483 #undef DEFINE_ACTION_MULTI
4484 #undef DEFINE_ACTION_STRING
4485 #undef DEFINE_ACTION_BOOL
4486 #undef DEFINE_ACTION_ALIAS
4487 #undef DEFINE_CGI_PARAM_CUSTOM
4488 #undef DEFINE_CGI_PARAM_RADIO
4489 #undef DEFINE_CGI_PARAM_NO_RADIO
4495 /*********************************************************************
4497 * Function : actions_from_radio
4499 * Description : Converts a map of parameters passed to a CGI function
4500 * into an actionsfile entry.
4503 * 1 : parameters = parameters to the CGI call
4504 * 2 : action = Action to change. Must be valid before
4505 * the call, actions not specified will be
4508 * Returns : JB_ERR_OK on success
4509 * JB_ERR_MEMORY on out-of-memory
4511 *********************************************************************/
4512 static jb_err actions_from_radio(const struct map * parameters,
4513 struct action_spec *action)
4515 static int first_time = 1;
4519 const char * js_name;
4520 jb_err err = JB_ERR_OK;
4525 /* Statics are generally a potential race condition,
4526 * but in this case we're safe and don't need semaphores.
4527 * Be careful if you modify this function.
4531 #define JAVASCRIPTIFY(dest_var, string) \
4533 static char js_name_arr[] = string; \
4536 javascriptify(js_name_arr); \
4538 dest_var = js_name_arr; \
4541 #define DEFINE_ACTION_BOOL(name, bit) \
4542 JAVASCRIPTIFY(js_name, name); \
4543 ch = get_char_param(parameters, js_name); \
4546 action->add |= bit; \
4547 action->mask |= bit; \
4549 else if (ch == 'N') \
4551 action->add &= ~bit; \
4552 action->mask &= ~bit; \
4554 else if (ch == 'X') \
4556 action->add &= ~bit; \
4557 action->mask |= bit; \
4560 #define DEFINE_ACTION_STRING(name, bit, index) \
4561 JAVASCRIPTIFY(js_name, name); \
4562 ch = get_char_param(parameters, js_name); \
4566 JAVASCRIPTIFY(js_name, name "-mode"); \
4567 if (!err) err = get_string_param(parameters, js_name, ¶m); \
4568 if ((param == NULL) || (0 == strcmp(param, "CUSTOM"))) \
4570 JAVASCRIPTIFY(js_name, name "-param"); \
4571 if (!err) err = get_string_param(parameters, js_name, ¶m); \
4573 if (param != NULL) \
4575 if (NULL == (param_dup = strdup(param))) \
4577 return JB_ERR_MEMORY; \
4579 freez(action->string[index]); \
4580 action->add |= bit; \
4581 action->mask |= bit; \
4582 action->string[index] = param_dup; \
4585 else if (ch == 'N') \
4587 if (action->add & bit) \
4589 freez(action->string[index]); \
4591 action->add &= ~bit; \
4592 action->mask &= ~bit; \
4594 else if (ch == 'X') \
4596 if (action->add & bit) \
4598 freez(action->string[index]); \
4600 action->add &= ~bit; \
4601 action->mask |= bit; \
4604 #define DEFINE_ACTION_MULTI(name, index) \
4605 JAVASCRIPTIFY(js_name, name); \
4606 ch = get_char_param(parameters, js_name); \
4611 else if (ch == 'N') \
4613 list_remove_all(action->multi_add[index]); \
4614 list_remove_all(action->multi_remove[index]); \
4615 action->multi_remove_all[index] = 1; \
4617 else if (ch == 'X') \
4619 list_remove_all(action->multi_add[index]); \
4620 list_remove_all(action->multi_remove[index]); \
4621 action->multi_remove_all[index] = 0; \
4624 #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
4626 #include "actionlist.h"
4628 #undef DEFINE_ACTION_MULTI
4629 #undef DEFINE_ACTION_STRING
4630 #undef DEFINE_ACTION_BOOL
4631 #undef DEFINE_ACTION_ALIAS
4632 #undef JAVASCRIPTIFY
4640 #endif /* def FEATURE_CGI_EDIT_ACTIONS */