1 /*********************************************************************
3 * File : $Source: /cvsroot/ijbswa/current/cgiedit.c,v $
5 * Purpose : CGI-based actionsfile editor.
7 * NOTE: The CGIs in this file use parameter names
8 * such as "f" and "s" which are really *BAD* choices.
9 * However, I'm trying to save bytes in the
10 * edit-actions-list HTML page - the standard actions
11 * file generated a 550kbyte page, which is ridiculous.
13 * Stick to the short names in this file for consistency.
15 * Copyright : Written by and Copyright (C) 2001-2014 the
16 * Privoxy team. https://www.privoxy.org/
18 * Based on the Internet Junkbuster originally written
19 * by and Copyright (C) 1997 Anonymous Coders and
20 * Junkbusters Corporation. http://www.junkbusters.com
22 * This program is free software; you can redistribute it
23 * and/or modify it under the terms of the GNU General
24 * Public License as published by the Free Software
25 * Foundation; either version 2 of the License, or (at
26 * your option) any later version.
28 * This program is distributed in the hope that it will
29 * be useful, but WITHOUT ANY WARRANTY; without even the
30 * implied warranty of MERCHANTABILITY or FITNESS FOR A
31 * PARTICULAR PURPOSE. See the GNU General Public
32 * License for more details.
34 * The GNU General Public License should be included with
35 * this file. If not, you can view it at
36 * http://www.gnu.org/copyleft/gpl.html
37 * or write to the Free Software Foundation, Inc., 59
38 * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
40 **********************************************************************/
46 * FIXME: Following includes copied from cgi.c - which are actually needed?
51 #include <sys/types.h>
60 #include "cgisimple.h"
68 /* loadcfg.h is for global_toggle_state only */
70 #endif /* def FEATURE_TOGGLE */
74 #ifdef FEATURE_CGI_EDIT_ACTIONS
77 * A line in an editable_file.
81 /** Next entry in the linked list */
82 struct file_line * next;
84 /** The raw data, to write out if this line is unmodified. */
87 /** Comments and/or whitespace to put before this line if it's modified
88 and then written out. */
91 /** The actual data, as a string. Line continuation and comment removal
92 are performed on the data read from file before it's stored here, so
93 it will be a single line of data. */
96 /** The type of data on this line. One of the FILE_LINE_xxx constants. */
99 /** The actual data, processed into some sensible data type. */
103 /** An action specification. */
104 struct action_spec action[1];
106 /** A name=value pair. */
110 /** The name in the name=value pair. */
113 /** The value in the name=value pair, as a string. */
116 /** The value in the name=value pair, as an integer. */
125 /** This file_line has not been processed yet. */
126 #define FILE_LINE_UNPROCESSED 1
128 /** This file_line is blank. Can only appear at the end of a file, due to
129 the way the parser works. */
130 #define FILE_LINE_BLANK 2
132 /** This file_line says {{alias}}. */
133 #define FILE_LINE_ALIAS_HEADER 3
135 /** This file_line defines an alias. */
136 #define FILE_LINE_ALIAS_ENTRY 4
138 /** This file_line defines an {action}. */
139 #define FILE_LINE_ACTION 5
141 /** This file_line specifies a URL pattern. */
142 #define FILE_LINE_URL 6
144 /** This file_line says {{settings}}. */
145 #define FILE_LINE_SETTINGS_HEADER 7
147 /** This file_line is in a {{settings}} block. */
148 #define FILE_LINE_SETTINGS_ENTRY 8
150 /** This file_line says {{description}}. */
151 #define FILE_LINE_DESCRIPTION_HEADER 9
153 /** This file_line is in a {{description}} block. */
154 #define FILE_LINE_DESCRIPTION_ENTRY 10
157 * Number of file modification time mismatches
158 * before the CGI editor gets turned off.
160 #define ACCEPTABLE_TIMESTAMP_MISMATCHES 3
163 * A configuration file, in a format that can be edited and written back to
168 struct file_line * lines; /**< The contents of the file. A linked list of lines. */
169 const char * filename; /**< Full pathname - e.g. "/etc/privoxy/wibble.action". */
170 unsigned identifier; /**< The file name's position in csp->config->actions_file[]. */
171 const char * version_str; /**< Last modification time, as a string. For CGI param. */
172 /**< Can be used in URL without using url_param(). */
173 unsigned version; /**< Last modification time - prevents chaos with
174 the browser's "back" button. Note that this is a
175 time_t cast to an unsigned. When comparing, always
176 cast the time_t to an unsigned, and *NOT* vice-versa.
177 This may lose the top few bits, but they're not
178 significant anyway. */
179 int newline; /**< Newline convention - one of the NEWLINE_xxx constants.
180 Note that changing this after the file has been
181 read in will cause a mess. */
182 struct file_line * parse_error; /**< On parse error, this is the offending line. */
183 const char * parse_error_text; /**< On parse error, this is the problem.
184 (Statically allocated) */
188 * Information about the filter types.
189 * Used for macro replacement in cgi_edit_actions_for_url.
191 struct filter_type_info
193 const int multi_action_index; /**< The multi action index as defined in project.h */
194 const char *macro_name; /**< Name of the macro that has to be replaced
195 with the prepared templates.
196 For example "content-filter-params" */
197 const char *type; /**< Name of the filter type,
198 for example "server-header-filter". */
199 /* XXX: check if these two can be combined. */
200 const char *disable_all_option; /**< Name of the catch-all radio option that has
201 to be checked or unchecked for this filter type. */
202 const char *disable_all_param; /**< Name of the parameter that causes all filters of
203 this type to be disabled. */
204 const char *abbr_type; /**< Abbreviation of the filter type, usually the
205 first or second character capitalized */
206 const char *anchor; /**< Anchor for the User Manual link,
207 for example "SERVER-HEADER-FILTER" */
210 /* Accessed by index, keep the order in the way the FT_ macros are defined. */
211 static const struct filter_type_info filter_type_info[] =
215 "content-filter-params", "filter",
216 "filter-all", "filter_all",
220 ACTION_MULTI_CLIENT_HEADER_FILTER,
221 "client-header-filter-params", "client-header-filter",
222 "client-header-filter-all", "client_header_filter_all",
223 "C", "CLIENT-HEADER-FILTER"
226 ACTION_MULTI_SERVER_HEADER_FILTER,
227 "server-header-filter-params", "server-header-filter",
228 "server-header-filter-all", "server_header_filter_all",
229 "S", "SERVER-HEADER-FILTER"
232 ACTION_MULTI_CLIENT_HEADER_TAGGER,
233 "client-header-tagger-params", "client-header-tagger",
234 "client-header-tagger-all", "client_header_tagger_all",
235 "L", "CLIENT-HEADER-TAGGER"
238 ACTION_MULTI_SERVER_HEADER_TAGGER,
239 "server-header-tagger-params", "server-header-tagger",
240 "server-header-tagger-all", "server_header_tagger_all",
241 "E", "SERVER-HEADER-TAGGER"
244 ACTION_MULTI_SUPPRESS_TAG,
245 "suppress-tag-params", "suppress-tag",
246 "suppress-tag-all", "suppress_tag_all",
250 ACTION_MULTI_CLIENT_BODY_FILTER,
251 "client-body-filter-params", "client-body-filter",
252 "client-body-filter-all", "client_body_filter_all",
253 "P", "CLIENT-BODY-FILTER"
255 #ifdef FEATURE_EXTERNAL_FILTERS
257 ACTION_MULTI_EXTERNAL_FILTER,
258 "external-content-filter-params", "external-filter",
259 "external-content-filter-all", "external_content_filter_all",
260 "E", "EXTERNAL-CONTENT-FILTER"
265 /* FIXME: Following non-static functions should be prototyped in .h or made static */
267 /* Functions to read and write arbitrary config files */
268 jb_err edit_read_file(struct client_state *csp,
269 const struct map *parameters,
271 struct editable_file **pfile);
272 jb_err edit_write_file(struct editable_file * file);
273 void edit_free_file(struct editable_file * file);
275 /* Functions to read and write actions files */
276 jb_err edit_parse_actions_file(struct editable_file * file);
277 jb_err edit_read_actions_file(struct client_state *csp,
278 struct http_response *rsp,
279 const struct map *parameters,
281 struct editable_file **pfile);
284 jb_err cgi_error_modified(struct client_state *csp,
285 struct http_response *rsp,
286 const char *filename);
287 jb_err cgi_error_parse(struct client_state *csp,
288 struct http_response *rsp,
289 struct editable_file *file);
290 jb_err cgi_error_file(struct client_state *csp,
291 struct http_response *rsp,
292 const char *filename);
293 jb_err cgi_error_file_read_only(struct client_state *csp,
294 struct http_response *rsp,
295 const char *filename);
297 /* Internal arbitrary config file support functions */
298 static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline);
299 static void edit_free_file_lines(struct file_line * first_line);
301 /* Internal actions file support functions */
302 static int match_actions_file_header_line(const char * line, const char * name);
303 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue);
305 /* Internal parameter parsing functions */
306 static jb_err get_url_spec_param(struct client_state *csp,
307 const struct map *parameters,
312 /* Internal actionsfile <==> HTML conversion functions */
313 static jb_err map_radio(struct map * exports,
314 const char * optionname,
317 static jb_err actions_to_radio(struct map * exports,
318 const struct action_spec *action);
319 static jb_err actions_from_radio(const struct map * parameters,
320 struct action_spec *action);
321 static jb_err action_render_string_filters_template(struct map * exports,
322 const struct action_spec *action,
323 const char* flter_template,
324 const struct filter_type_info *type);
327 static jb_err map_copy_parameter_html(struct map *out,
328 const struct map *in,
331 static jb_err get_file_name_param(struct client_state *csp,
332 const struct map *parameters,
333 const char *param_name,
334 const char **pfilename);
336 /* Internal convenience functions */
337 static char *section_target(const unsigned sectionid);
339 /*********************************************************************
341 * Function : section_target
343 * Description : Given an unsigned (section id) n, produce a dynamically
344 * allocated string of the form #l<n>, for use in link
347 * XXX: The hash should be moved into the templates
348 * to make this function more generic and render
349 * stringify() obsolete.
352 * 1 : sectionid = start line number of section
354 * Returns : String with link target, or NULL if out of
357 *********************************************************************/
358 static char *section_target(const unsigned sectionid)
362 snprintf(buf, sizeof(buf), "#l%u", sectionid);
368 /*********************************************************************
370 * Function : stringify
372 * Description : Convert a number into a dynamically allocated string.
375 * 1 : number = The number to convert.
377 * Returns : String with link target, or NULL if out of memory
379 *********************************************************************/
380 static char *stringify(const unsigned number)
384 snprintf(buf, sizeof(buf), "%u", number);
389 /*********************************************************************
391 * Function : map_copy_parameter_html
393 * Description : Copy a CGI parameter from one map to another, HTML
397 * 1 : out = target map
398 * 2 : in = source map
399 * 3 : name = name of cgi parameter to copy
401 * Returns : JB_ERR_OK on success
402 * JB_ERR_MEMORY on out-of-memory
403 * JB_ERR_CGI_PARAMS if the parameter doesn't exist
406 *********************************************************************/
407 static jb_err map_copy_parameter_html(struct map *out,
408 const struct map *in,
418 value = lookup(in, name);
419 err = map(out, name, 1, html_encode(value), 0);
426 else if (*value == '\0')
428 return JB_ERR_CGI_PARAMS;
437 /*********************************************************************
439 * Function : cgi_edit_actions_url_form
441 * Description : CGI function that displays a form for
445 * 1 : csp = Current client state (buffers, headers, etc...)
446 * 2 : rsp = http_response data structure for output
447 * 3 : parameters = map of cgi parameters
450 * i : (action index) Identifies the file to edit
451 * v : (version) File's last-modified time
452 * p : (pattern) Line number of pattern to edit
454 * Returns : JB_ERR_OK on success
455 * JB_ERR_MEMORY on out-of-memory
456 * JB_ERR_CGI_PARAMS if the CGI parameters are not
457 * specified or not valid.
459 *********************************************************************/
460 jb_err cgi_edit_actions_url_form(struct client_state *csp,
461 struct http_response *rsp,
462 const struct map *parameters)
464 struct map * exports;
466 struct editable_file * file;
467 struct file_line * cur_line;
468 unsigned line_number;
469 unsigned section_start_line_number = 0;
476 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
478 return cgi_error_disabled(csp, rsp);
481 err = get_number_param(csp, parameters, "p", &patternid);
487 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
490 /* No filename specified, can't read file, modified, or out of memory. */
491 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
494 cur_line = file->lines;
496 for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
498 if (cur_line->type == FILE_LINE_ACTION)
500 section_start_line_number = line_number;
502 cur_line = cur_line->next;
505 if ( (cur_line == NULL)
506 || (line_number != patternid)
508 || (cur_line->type != FILE_LINE_URL))
510 /* Invalid "patternid" parameter */
511 edit_free_file(file);
512 return JB_ERR_CGI_PARAMS;
515 if (NULL == (exports = default_exports(csp, NULL)))
517 edit_free_file(file);
518 return JB_ERR_MEMORY;
521 err = map(exports, "f", 1, stringify(file->identifier), 0);
522 if (!err) err = map(exports, "v", 1, file->version_str, 1);
523 if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
524 if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
525 if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0);
527 edit_free_file(file);
535 return template_fill_for_cgi(csp, "edit-actions-url-form", exports, rsp);
539 /*********************************************************************
541 * Function : cgi_edit_actions_add_url_form
543 * Description : CGI function that displays a form for
547 * 1 : csp = Current client state (buffers, headers, etc...)
548 * 2 : rsp = http_response data structure for output
549 * 3 : parameters = map of cgi parameters
552 * f : (filename) Identifies the file to edit
553 * v : (version) File's last-modified time
554 * s : (section) Line number of section to edit
556 * Returns : JB_ERR_OK on success
557 * JB_ERR_MEMORY on out-of-memory
558 * JB_ERR_CGI_PARAMS if the CGI parameters are not
559 * specified or not valid.
561 *********************************************************************/
562 jb_err cgi_edit_actions_add_url_form(struct client_state *csp,
563 struct http_response *rsp,
564 const struct map *parameters)
573 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
575 return cgi_error_disabled(csp, rsp);
578 if (NULL == (exports = default_exports(csp, NULL)))
580 return JB_ERR_MEMORY;
583 err = map_copy_parameter_html(exports, parameters, "f");
584 if (!err) err = map_copy_parameter_html(exports, parameters, "v");
585 if (!err) err = map_copy_parameter_html(exports, parameters, "s");
593 return template_fill_for_cgi(csp, "edit-actions-add-url-form", exports, rsp);
597 /*********************************************************************
599 * Function : cgi_edit_actions_remove_url_form
601 * Description : CGI function that displays a form for
605 * 1 : csp = Current client state (buffers, headers, etc...)
606 * 2 : rsp = http_response data structure for output
607 * 3 : parameters = map of cgi parameters
610 * f : (number) The action file identifier.
611 * v : (version) File's last-modified time
612 * p : (pattern) Line number of pattern to edit
614 * Returns : JB_ERR_OK on success
615 * JB_ERR_MEMORY on out-of-memory
616 * JB_ERR_CGI_PARAMS if the CGI parameters are not
617 * specified or not valid.
619 *********************************************************************/
620 jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
621 struct http_response *rsp,
622 const struct map *parameters)
624 struct map * exports;
626 struct editable_file * file;
627 struct file_line * cur_line;
628 unsigned line_number;
629 unsigned section_start_line_number = 0;
636 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
638 return cgi_error_disabled(csp, rsp);
641 err = get_number_param(csp, parameters, "p", &patternid);
647 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
650 /* No filename specified, can't read file, modified, or out of memory. */
651 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
654 cur_line = file->lines;
656 for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
658 if (cur_line->type == FILE_LINE_ACTION)
660 section_start_line_number = line_number;
662 cur_line = cur_line->next;
665 if ( (cur_line == NULL)
666 || (line_number != patternid)
668 || (cur_line->type != FILE_LINE_URL))
670 /* Invalid "patternid" parameter */
671 edit_free_file(file);
672 return JB_ERR_CGI_PARAMS;
675 if (NULL == (exports = default_exports(csp, NULL)))
677 edit_free_file(file);
678 return JB_ERR_MEMORY;
681 err = map(exports, "f", 1, stringify(file->identifier), 0);
682 if (!err) err = map(exports, "v", 1, file->version_str, 1);
683 if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
684 if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
685 if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0);
686 if (!err) err = map(exports, "actions-file", 1, html_encode(file->filename), 0);
688 edit_free_file(file);
696 return template_fill_for_cgi(csp, "edit-actions-remove-url-form", exports, rsp);
700 /*********************************************************************
702 * Function : edit_write_file
704 * Description : Write a complete file to disk.
707 * 1 : file = File to write.
709 * Returns : JB_ERR_OK on success
710 * JB_ERR_FILE on error writing to file.
711 * JB_ERR_MEMORY on out of memory
713 *********************************************************************/
714 jb_err edit_write_file(struct editable_file * file)
717 struct file_line * cur_line;
718 struct stat statbuf[1];
719 char version_buf[22]; /* 22 = ceil(log10(2^64)) + 2 = max number of
720 digits in time_t, assuming this is a 64-bit
721 machine, plus null terminator, plus one
725 assert(file->filename);
727 if (NULL == (fp = fopen(file->filename, "wb")))
732 cur_line = file->lines;
733 while (cur_line != NULL)
737 if (fputs(cur_line->raw, fp) < 0)
745 if (cur_line->prefix)
747 if (fputs(cur_line->prefix, fp) < 0)
753 if (cur_line->unprocessed)
756 if (NULL != strchr(cur_line->unprocessed, '#'))
758 /* Must quote '#' characters */
765 /* Count number of # characters, so we know length of output string */
766 src = cur_line->unprocessed;
767 while (NULL != (src = strchr(src, '#')))
774 /* Allocate new memory for string */
775 len = strlen(cur_line->unprocessed) + (size_t)numhash;
776 str = malloc_or_die(len + 1);
778 /* Copy string but quote hashes */
779 src = cur_line->unprocessed;
787 assert(numhash >= 0);
793 assert(numhash == 0);
794 assert(strlen(str) == len);
795 assert(str == dest - len);
796 assert(src - len <= cur_line->unprocessed);
798 if ((strlen(str) != len) || (numhash != 0))
801 * Escaping didn't work as expected, go spread the news.
802 * Only reached in non-debugging builds.
804 log_error(LOG_LEVEL_ERROR,
805 "Looks like hash escaping failed. %s might be corrupted now.",
809 if (fputs(str, fp) < 0)
820 /* Can write without quoting '#' characters. */
821 if (fputs(cur_line->unprocessed, fp) < 0)
827 if (fputs(NEWLINE(file->newline), fp) < 0)
835 /* FIXME: Write data from file->data->whatever */
839 cur_line = cur_line->next;
845 /* Update the version stamp in the file structure, since we just
846 * wrote to the file & changed it's date.
848 if (stat(file->filename, statbuf) < 0)
850 /* Error, probably file not found. */
853 file->version = (unsigned)statbuf->st_mtime;
855 /* Correct file->version_str */
856 freez(file->version_str);
857 snprintf(version_buf, sizeof(version_buf), "%u", file->version);
858 version_buf[sizeof(version_buf)-1] = '\0';
859 file->version_str = strdup_or_die(version_buf);
865 /*********************************************************************
867 * Function : edit_free_file
869 * Description : Free a complete file in memory.
872 * 1 : file = Data structure to free.
876 *********************************************************************/
877 void edit_free_file(struct editable_file * file)
881 /* Silently ignore NULL pointer */
885 edit_free_file_lines(file->lines);
886 freez(file->version_str);
888 file->parse_error_text = NULL; /* Statically allocated */
889 file->parse_error = NULL;
895 /*********************************************************************
897 * Function : edit_free_file_lines
899 * Description : Free an entire linked list of file lines.
902 * 1 : first_line = Data structure to free.
906 *********************************************************************/
907 static void edit_free_file_lines(struct file_line * first_line)
909 struct file_line * next_line;
911 while (first_line != NULL)
913 next_line = first_line->next;
914 first_line->next = NULL;
915 freez(first_line->raw);
916 freez(first_line->prefix);
917 freez(first_line->unprocessed);
918 switch(first_line->type)
920 case 0: /* special case if memory zeroed */
921 case FILE_LINE_UNPROCESSED:
922 case FILE_LINE_BLANK:
923 case FILE_LINE_ALIAS_HEADER:
924 case FILE_LINE_SETTINGS_HEADER:
925 case FILE_LINE_DESCRIPTION_HEADER:
926 case FILE_LINE_DESCRIPTION_ENTRY:
927 case FILE_LINE_ALIAS_ENTRY:
929 /* No data is stored for these */
932 case FILE_LINE_ACTION:
933 free_action(first_line->data.action);
936 case FILE_LINE_SETTINGS_ENTRY:
937 freez(first_line->data.setting.name);
938 freez(first_line->data.setting.svalue);
941 /* Should never happen */
945 first_line->type = 0; /* paranoia */
947 first_line = next_line;
952 /*********************************************************************
954 * Function : match_actions_file_header_line
956 * Description : Match an actions file {{header}} line
959 * 1 : line = String from file
960 * 2 : name = Header to match against
962 * Returns : 0 iff they match.
964 *********************************************************************/
965 static int match_actions_file_header_line(const char * line, const char * name)
973 if ((line[0] != '{') || (line[1] != '{'))
979 /* Look for optional whitespace */
980 while ((*line == ' ') || (*line == '\t'))
985 /* Look for the specified name (case-insensitive) */
987 if (0 != strncmpic(line, name, len))
993 /* Look for optional whitespace */
994 while ((*line == ' ') || (*line == '\t'))
999 /* Look for "}}" and end of string*/
1000 if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0'))
1010 /*********************************************************************
1012 * Function : match_actions_file_header_line
1014 * Description : Match an actions file {{header}} line
1017 * 1 : line = String from file. Must not start with
1018 * whitespace (else infinite loop!)
1019 * 2 : pname = Destination for name
1020 * 2 : pvalue = Destination for value
1022 * Returns : JB_ERR_OK on success
1023 * JB_ERR_MEMORY on out-of-memory
1024 * JB_ERR_PARSE if there's no "=" sign, or if there's
1025 * nothing before the "=" sign (but empty
1026 * values *after* the "=" sign are legal).
1028 *********************************************************************/
1029 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue)
1031 const char * name_end;
1032 const char * value_start;
1038 assert(*line != ' ');
1039 assert(*line != '\t');
1044 value_start = strchr(line, '=');
1045 if ((value_start == NULL) || (value_start == line))
1047 return JB_ERR_PARSE;
1050 name_end = value_start - 1;
1052 /* Eat any whitespace before the '=' */
1053 while ((*name_end == ' ') || (*name_end == '\t'))
1056 * we already know we must have at least 1 non-ws char
1057 * at start of buf - no need to check
1062 name_len = (size_t)(name_end - line) + 1; /* Length excluding \0 */
1063 *pname = malloc_or_die(name_len + 1);
1064 strncpy(*pname, line, name_len);
1065 (*pname)[name_len] = '\0';
1067 /* Eat any the whitespace after the '=' */
1069 while ((*value_start == ' ') || (*value_start == '\t'))
1074 if (NULL == (*pvalue = strdup(value_start)))
1078 return JB_ERR_MEMORY;
1085 /*********************************************************************
1087 * Function : edit_parse_actions_file
1089 * Description : Parse an actions file in memory.
1091 * Passed linked list must have the "data" member
1092 * zeroed, and must contain valid "next" and
1093 * "unprocessed" fields. The "raw" and "prefix"
1094 * fields are ignored, and "type" is just overwritten.
1096 * Note that on error the file may have been
1100 * 1 : file = Actions file to be parsed in-place.
1102 * Returns : JB_ERR_OK on success
1103 * JB_ERR_MEMORY on out-of-memory
1104 * JB_ERR_PARSE on error
1106 *********************************************************************/
1107 jb_err edit_parse_actions_file(struct editable_file * file)
1109 struct file_line * cur_line;
1111 const char * text; /* Text from a line */
1112 char * name; /* For lines of the form name=value */
1113 char * value; /* For lines of the form name=value */
1114 struct action_alias * alias_list = NULL;
1115 jb_err err = JB_ERR_OK;
1117 /* alias_list contains the aliases defined in this file.
1118 * It might be better to use the "file_line.data" fields
1119 * in the relevant places instead.
1122 cur_line = file->lines;
1124 /* A note about blank line support: Blank lines should only
1125 * ever occur as the last line in the file. This function
1126 * is more forgiving than that - FILE_LINE_BLANK can occur
1130 /* Skip leading blanks. Should only happen if file is
1131 * empty (which is valid, but pointless).
1133 while ((cur_line != NULL)
1134 && (cur_line->unprocessed[0] == '\0'))
1137 cur_line->type = FILE_LINE_BLANK;
1138 cur_line = cur_line->next;
1141 if ((cur_line != NULL)
1142 && (cur_line->unprocessed[0] != '{'))
1144 /* File doesn't start with a header */
1145 file->parse_error = cur_line;
1146 file->parse_error_text = "First (non-comment) line of the file must contain a header.";
1147 return JB_ERR_PARSE;
1150 if ((cur_line != NULL) && (0 ==
1151 match_actions_file_header_line(cur_line->unprocessed, "settings")))
1153 cur_line->type = FILE_LINE_SETTINGS_HEADER;
1155 cur_line = cur_line->next;
1156 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1158 if (cur_line->unprocessed[0])
1160 cur_line->type = FILE_LINE_SETTINGS_ENTRY;
1162 err = split_line_on_equals(cur_line->unprocessed,
1163 &cur_line->data.setting.name,
1164 &cur_line->data.setting.svalue);
1165 if (err == JB_ERR_MEMORY)
1169 else if (err != JB_ERR_OK)
1171 /* Line does not contain a name=value pair */
1172 file->parse_error = cur_line;
1173 file->parse_error_text = "Expected a name=value pair on this {{description}} line, but couldn't find one.";
1174 return JB_ERR_PARSE;
1179 cur_line->type = FILE_LINE_BLANK;
1181 cur_line = cur_line->next;
1185 if ((cur_line != NULL) && (0 ==
1186 match_actions_file_header_line(cur_line->unprocessed, "description")))
1188 cur_line->type = FILE_LINE_DESCRIPTION_HEADER;
1190 cur_line = cur_line->next;
1191 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1193 if (cur_line->unprocessed[0])
1195 cur_line->type = FILE_LINE_DESCRIPTION_ENTRY;
1199 cur_line->type = FILE_LINE_BLANK;
1201 cur_line = cur_line->next;
1205 if ((cur_line != NULL) && (0 ==
1206 match_actions_file_header_line(cur_line->unprocessed, "alias")))
1208 cur_line->type = FILE_LINE_ALIAS_HEADER;
1210 cur_line = cur_line->next;
1211 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1213 if (cur_line->unprocessed[0])
1215 /* define an alias */
1216 struct action_alias * new_alias;
1218 cur_line->type = FILE_LINE_ALIAS_ENTRY;
1220 err = split_line_on_equals(cur_line->unprocessed, &name, &value);
1221 if (err == JB_ERR_MEMORY)
1223 free_alias_list(alias_list);
1226 else if (err != JB_ERR_OK)
1228 /* Line does not contain a name=value pair */
1229 file->parse_error = cur_line;
1230 file->parse_error_text = "Expected a name=value pair on this {{alias}} line, but couldn't find one.";
1231 free_alias_list(alias_list);
1232 return JB_ERR_PARSE;
1235 new_alias = zalloc_or_die(sizeof(*new_alias));
1237 err = get_actions(value, alias_list, new_alias->action);
1240 /* Invalid action or out of memory */
1244 free_alias_list(alias_list);
1245 if (err == JB_ERR_MEMORY)
1251 /* Line does not contain a name=value pair */
1252 file->parse_error = cur_line;
1253 file->parse_error_text = "This alias does not specify a valid set of actions.";
1254 return JB_ERR_PARSE;
1260 new_alias->name = name;
1263 new_alias->next = alias_list;
1264 alias_list = new_alias;
1268 cur_line->type = FILE_LINE_BLANK;
1270 cur_line = cur_line->next;
1274 /* Header done, process the main part of the file */
1275 while (cur_line != NULL)
1277 /* At this point, (cur_line->unprocessed[0] == '{') */
1278 assert(cur_line->unprocessed[0] == '{');
1279 text = cur_line->unprocessed + 1;
1280 len = strlen(text) - 1;
1281 if (text[len] != '}')
1283 /* No closing } on header */
1284 free_alias_list(alias_list);
1285 file->parse_error = cur_line;
1286 file->parse_error_text = "Headers starting with '{' must have a "
1287 "closing bracket ('}'). Headers starting with two brackets ('{{') "
1288 "must close with two brackets ('}}').";
1289 return JB_ERR_PARSE;
1294 /* An invalid {{ header. */
1295 free_alias_list(alias_list);
1296 file->parse_error = cur_line;
1297 file->parse_error_text = "Unknown or unexpected two-bracket header. "
1298 "Please remember that the system (two-bracket) headers must "
1299 "appear in the order {{settings}}, {{description}}, {{alias}}, "
1300 "and must appear before any actions (one-bracket) headers. "
1301 "Also note that system headers may not be repeated.";
1302 return JB_ERR_PARSE;
1305 while ((*text == ' ') || (*text == '\t'))
1310 while ((len > (size_t)0)
1311 && ((text[len - 1] == ' ')
1312 || (text[len - 1] == '\t')))
1317 cur_line->type = FILE_LINE_ACTION;
1319 /* Remove {} and make copy */
1320 value = malloc_or_die(len + 1);
1321 strncpy(value, text, len);
1325 err = get_actions(value, alias_list, cur_line->data.action);
1328 /* Invalid action or out of memory */
1330 free_alias_list(alias_list);
1331 if (err == JB_ERR_MEMORY)
1337 /* Line does not contain a name=value pair */
1338 file->parse_error = cur_line;
1339 file->parse_error_text = "This header does not specify a valid set of actions.";
1340 return JB_ERR_PARSE;
1344 /* Done with string - it was clobbered anyway */
1347 /* Process next line */
1348 cur_line = cur_line->next;
1350 /* Loop processing URL patterns */
1351 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1353 if (cur_line->unprocessed[0])
1355 /* Could parse URL here, but this isn't currently needed */
1357 cur_line->type = FILE_LINE_URL;
1361 cur_line->type = FILE_LINE_BLANK;
1363 cur_line = cur_line->next;
1365 } /* End main while(cur_line != NULL) loop */
1367 free_alias_list(alias_list);
1373 /*********************************************************************
1375 * Function : edit_read_file_lines
1377 * Description : Read all the lines of a file into memory.
1378 * Handles whitespace, comments and line continuation.
1381 * 1 : fp = File to read from. On return, this will be
1382 * at EOF but it will not have been closed.
1383 * 2 : pfile = Destination for a linked list of file_lines.
1384 * Will be set to NULL on error.
1385 * 3 : newline = How to handle newlines.
1387 * Returns : JB_ERR_OK on success
1388 * JB_ERR_MEMORY on out-of-memory
1390 *********************************************************************/
1391 jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline)
1393 struct file_line * first_line; /* Keep for return value or to free */
1394 struct file_line * cur_line; /* Current line */
1395 struct file_line * prev_line; /* Entry with prev_line->next = cur_line */
1403 cur_line = first_line = zalloc_or_die(sizeof(struct file_line));
1405 cur_line->type = FILE_LINE_UNPROCESSED;
1407 rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1410 /* Out of memory or empty file. */
1411 /* Note that empty file is not an error we propagate up */
1413 return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval);
1418 prev_line = cur_line;
1419 cur_line = prev_line->next = zalloc_or_die(sizeof(struct file_line));
1421 cur_line->type = FILE_LINE_UNPROCESSED;
1423 rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1424 if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
1427 edit_free_file_lines(first_line);
1428 return JB_ERR_MEMORY;
1432 while (rval != JB_ERR_FILE);
1436 /* We allocated one too many - free it */
1437 prev_line->next = NULL;
1440 *pfile = first_line;
1445 /*********************************************************************
1447 * Function : edit_read_file
1449 * Description : Read a complete file into memory.
1450 * Handles CGI parameter parsing. If requested, also
1451 * checks the file's modification timestamp.
1454 * 1 : csp = Current client state (buffers, headers, etc...)
1455 * 2 : parameters = map of cgi parameters.
1456 * 3 : require_version = true to check "ver" parameter.
1457 * 4 : pfile = Destination for the file. Will be set
1461 * f : The action file identifier.
1462 * ver : (Only if require_version is nonzero)
1463 * Timestamp of the actions file. If wrong, this
1464 * function fails with JB_ERR_MODIFIED.
1466 * Returns : JB_ERR_OK on success
1467 * JB_ERR_MEMORY on out-of-memory
1468 * JB_ERR_CGI_PARAMS if "filename" was not specified
1470 * JB_ERR_FILE if the file cannot be opened or
1472 * JB_ERR_MODIFIED if version checking was requested and
1473 * failed - the file was modified outside
1474 * of this CGI editor instance.
1476 *********************************************************************/
1477 jb_err edit_read_file(struct client_state *csp,
1478 const struct map *parameters,
1479 int require_version,
1480 struct editable_file **pfile)
1482 struct file_line * lines;
1485 const char *filename = NULL;
1486 struct editable_file * file;
1487 unsigned version = 0;
1488 struct stat statbuf[1];
1489 char version_buf[22];
1490 int newline = NEWLINE_UNKNOWN;
1499 err = get_number_param(csp, parameters, "f", &i);
1500 if ((JB_ERR_OK == err) && (i < MAX_AF_FILES) && (NULL != csp->config->actions_file[i]))
1502 filename = csp->config->actions_file[i];
1504 else if (JB_ERR_CGI_PARAMS == err)
1507 * Probably an old-school URL like
1508 * http://config.privoxy.org/edit-actions-list?f=default
1510 get_file_name_param(csp, parameters, "f", &filename);
1513 if (NULL == filename || stat(filename, statbuf) < 0)
1515 /* Error, probably file not found. */
1518 version = (unsigned) statbuf->st_mtime;
1520 if (require_version)
1522 unsigned specified_version;
1523 err = get_number_param(csp, parameters, "v", &specified_version);
1529 if (version != specified_version)
1531 return JB_ERR_MODIFIED;
1535 if (NULL == (fp = fopen(filename,"rb")))
1540 err = edit_read_file_lines(fp, &lines, &newline);
1549 file = zalloc_or_die(sizeof(*file));
1551 file->lines = lines;
1552 file->newline = newline;
1553 file->filename = filename;
1554 file->version = version;
1555 file->identifier = i;
1557 /* Correct file->version_str */
1558 freez(file->version_str);
1559 snprintf(version_buf, sizeof(version_buf), "%u", file->version);
1560 version_buf[sizeof(version_buf)-1] = '\0';
1561 file->version_str = strdup_or_die(version_buf);
1568 /*********************************************************************
1570 * Function : edit_read_actions_file
1572 * Description : Read a complete actions file into memory.
1573 * Handles CGI parameter parsing. If requested, also
1574 * checks the file's modification timestamp.
1576 * If this function detects an error in the categories
1577 * JB_ERR_FILE, JB_ERR_MODIFIED, or JB_ERR_PARSE,
1578 * then it handles it by filling in the specified
1579 * response structure and returning JB_ERR_FILE.
1582 * 1 : csp = Current client state (buffers, headers, etc...)
1583 * 2 : rsp = HTTP response. Only filled in on error.
1584 * 2 : parameters = map of cgi parameters.
1585 * 3 : require_version = true to check "ver" parameter.
1586 * 4 : pfile = Destination for the file. Will be set
1590 * f : The actions file identifier.
1591 * ver : (Only if require_version is nonzero)
1592 * Timestamp of the actions file. If wrong, this
1593 * function fails with JB_ERR_MODIFIED.
1595 * Returns : JB_ERR_OK on success
1596 * JB_ERR_MEMORY on out-of-memory
1597 * JB_ERR_CGI_PARAMS if "filename" was not specified
1599 * JB_ERR_FILE if the file does not contain valid data,
1600 * or if file cannot be opened or
1601 * contains no data, or if version
1602 * checking was requested and failed.
1604 *********************************************************************/
1605 jb_err edit_read_actions_file(struct client_state *csp,
1606 struct http_response *rsp,
1607 const struct map *parameters,
1608 int require_version,
1609 struct editable_file **pfile)
1612 struct editable_file *file;
1613 static int acceptable_failures = ACCEPTABLE_TIMESTAMP_MISMATCHES - 1;
1621 err = edit_read_file(csp, parameters, require_version, &file);
1624 /* Try to handle if possible */
1625 if (err == JB_ERR_FILE)
1627 err = cgi_error_file(csp, rsp, lookup(parameters, "f"));
1629 else if (err == JB_ERR_MODIFIED)
1631 assert(require_version);
1632 err = cgi_error_modified(csp, rsp, lookup(parameters, "f"));
1633 log_error(LOG_LEVEL_ERROR,
1634 "Blocking CGI edit request due to modification time mismatch.");
1635 if (acceptable_failures > 0)
1637 log_error(LOG_LEVEL_INFO,
1638 "The CGI editor will be turned off after another %d mismatche(s).",
1639 acceptable_failures);
1640 acceptable_failures--;
1644 log_error(LOG_LEVEL_INFO,
1645 "Timestamp mismatch limit reached, turning CGI editor off. "
1646 "Reload the configuration file to re-enable it.");
1647 csp->config->feature_flags &= ~RUNTIME_FEATURE_CGI_EDIT_ACTIONS;
1650 if (err == JB_ERR_OK)
1653 * Signal to higher-level CGI code that there was a problem but we
1654 * handled it, they should just return JB_ERR_OK.
1661 err = edit_parse_actions_file(file);
1664 if (err == JB_ERR_PARSE)
1666 err = cgi_error_parse(csp, rsp, file);
1667 if (err == JB_ERR_OK)
1670 * Signal to higher-level CGI code that there was a problem but we
1671 * handled it, they should just return JB_ERR_OK.
1676 edit_free_file(file);
1685 /*********************************************************************
1687 * Function : get_file_name_param
1689 * Description : Get the name of the file to edit from the parameters
1690 * passed to a CGI function using the old syntax.
1691 * This function handles security checks and only
1692 * accepts files that Privoxy already knows.
1695 * 1 : csp = Current client state (buffers, headers, etc...)
1696 * 2 : parameters = map of cgi parameters
1697 * 3 : param_name = The name of the parameter to read
1698 * 4 : pfilename = pointer to the filename in
1699 * csp->config->actions_file[] if found. Set to NULL on error.
1701 * Returns : JB_ERR_OK on success
1702 * JB_ERR_MEMORY on out-of-memory
1703 * JB_ERR_CGI_PARAMS if "filename" was not specified
1706 *********************************************************************/
1707 static jb_err get_file_name_param(struct client_state *csp,
1708 const struct map *parameters,
1709 const char *param_name,
1710 const char **pfilename)
1713 const char suffix[] = ".action";
1728 param = lookup(parameters, param_name);
1731 return JB_ERR_CGI_PARAMS;
1734 len = strlen(param);
1735 if (len >= FILENAME_MAX)
1738 return JB_ERR_CGI_PARAMS;
1742 * Check every character to see if it's legal.
1743 * Totally unnecessary but we do it anyway.
1746 while ((ch = *s++) != '\0')
1748 if ( ((ch < 'A') || (ch > 'Z'))
1749 && ((ch < 'a') || (ch > 'z'))
1750 && ((ch < '0') || (ch > '9'))
1754 /* Probable hack attempt. */
1755 return JB_ERR_CGI_PARAMS;
1759 /* Append extension */
1760 name_size = len + strlen(suffix) + 1;
1761 name = malloc_or_die(name_size);
1762 strlcpy(name, param, name_size);
1763 strlcat(name, suffix, name_size);
1766 fullpath = make_path(csp->config->confdir, name);
1769 if (fullpath == NULL)
1771 return JB_ERR_MEMORY;
1774 /* Check if the file is known */
1775 for (i = 0; i < MAX_AF_FILES; i++)
1777 if (NULL != csp->config->actions_file[i] &&
1778 !strcmp(fullpath, csp->config->actions_file[i]))
1781 *pfilename = csp->config->actions_file[i];
1789 return JB_ERR_CGI_PARAMS;
1793 /*********************************************************************
1795 * Function : get_url_spec_param
1797 * Description : Get a URL pattern from the parameters
1798 * passed to a CGI function. Removes leading/trailing
1799 * spaces and validates it.
1802 * 1 : csp = Current client state (buffers, headers, etc...)
1803 * 2 : parameters = map of cgi parameters
1804 * 3 : name = Name of CGI parameter to read
1805 * 4 : pvalue = destination for value. Will be malloc()'d.
1806 * Set to NULL on error.
1808 * Returns : JB_ERR_OK on success
1809 * JB_ERR_MEMORY on out-of-memory
1810 * JB_ERR_CGI_PARAMS if the parameter was not specified
1813 *********************************************************************/
1814 static jb_err get_url_spec_param(struct client_state *csp,
1815 const struct map *parameters,
1819 const char *orig_param;
1822 struct pattern_spec compiled[1];
1832 orig_param = lookup(parameters, name);
1835 return JB_ERR_CGI_PARAMS;
1838 /* Copy and trim whitespace */
1839 param = strdup(orig_param);
1842 return JB_ERR_MEMORY;
1846 /* Must be non-empty, and can't allow 1st character to be '{' */
1847 if (param[0] == '\0' || param[0] == '{')
1850 return JB_ERR_CGI_PARAMS;
1853 /* Check for embedded newlines */
1854 for (s = param; *s != '\0'; s++)
1856 if ((*s == '\r') || (*s == '\n'))
1859 return JB_ERR_CGI_PARAMS;
1863 /* Check that regex is valid */
1868 return JB_ERR_MEMORY;
1870 err = create_pattern_spec(compiled, s);
1875 return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
1877 free_pattern_spec(compiled);
1879 if (param[strlen(param) - 1] == '\\')
1882 * Must protect trailing '\\' from becoming line continuation character.
1883 * Two methods: 1) If it's a domain only, add a trailing '/'.
1884 * 2) For path, add the do-nothing PCRE expression (?:) to the end
1886 if (strchr(param, '/') == NULL)
1888 err = string_append(¶m, "/");
1892 err = string_append(¶m, "(?:)");
1899 /* Check that the modified regex is valid */
1904 return JB_ERR_MEMORY;
1906 err = create_pattern_spec(compiled, s);
1911 return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
1913 free_pattern_spec(compiled);
1920 /*********************************************************************
1922 * Function : map_radio
1924 * Description : Map a set of radio button values. E.g. if you have
1925 * 3 radio buttons, declare them as:
1926 * <option type="radio" name="xyz" @xyz-a@>
1927 * <option type="radio" name="xyz" @xyz-b@>
1928 * <option type="radio" name="xyz" @xyz-c@>
1929 * Then map one of the @xyz-?@ variables to "checked"
1930 * and all the others to empty by calling:
1931 * map_radio(exports, "xyz", "abc", sel)
1932 * Where 'sel' is 'a', 'b', or 'c'.
1935 * 1 : exports = Exports map to modify.
1936 * 2 : optionname = name for map
1937 * 3 : values = null-terminated list of values;
1938 * 4 : value = Selected value.
1940 * CGI Parameters : None
1942 * Returns : JB_ERR_OK on success
1943 * JB_ERR_MEMORY on out-of-memory
1945 *********************************************************************/
1946 static jb_err map_radio(struct map * exports,
1947 const char * optionname,
1948 const char * values,
1954 const size_t len = strlen(optionname);
1955 const size_t buf_size = len + 3;
1961 buf = malloc_or_die(buf_size);
1963 strlcpy(buf, optionname, buf_size);
1965 /* XXX: this looks ... interesting */
1970 while ((c = *values++) != '\0')
1975 if (map(exports, buf, 1, "", 1))
1977 return JB_ERR_MEMORY;
1983 return map(exports, buf, 0, "checked", 1);
1987 /*********************************************************************
1989 * Function : cgi_error_modified
1991 * Description : CGI function that is called when a file is modified
1992 * outside the CGI editor.
1995 * 1 : csp = Current client state (buffers, headers, etc...)
1996 * 2 : rsp = http_response data structure for output
1997 * 3 : filename = The file that was modified.
1999 * CGI Parameters : none
2001 * Returns : JB_ERR_OK on success
2002 * JB_ERR_MEMORY on out-of-memory error.
2004 *********************************************************************/
2005 jb_err cgi_error_modified(struct client_state *csp,
2006 struct http_response *rsp,
2007 const char *filename)
2009 struct map *exports;
2016 if (NULL == (exports = default_exports(csp, NULL)))
2018 return JB_ERR_MEMORY;
2021 err = map(exports, "f", 1, html_encode(filename), 0);
2028 return template_fill_for_cgi(csp, "cgi-error-modified", exports, rsp);
2032 /*********************************************************************
2034 * Function : cgi_error_parse
2036 * Description : CGI function that is called when a file cannot
2037 * be parsed by the CGI editor.
2040 * 1 : csp = Current client state (buffers, headers, etc...)
2041 * 2 : rsp = http_response data structure for output
2042 * 3 : file = The file that was modified.
2044 * CGI Parameters : none
2046 * Returns : JB_ERR_OK on success
2047 * JB_ERR_MEMORY on out-of-memory error.
2049 *********************************************************************/
2050 jb_err cgi_error_parse(struct client_state *csp,
2051 struct http_response *rsp,
2052 struct editable_file *file)
2054 struct map *exports;
2056 struct file_line *cur_line;
2062 if (NULL == (exports = default_exports(csp, NULL)))
2064 return JB_ERR_MEMORY;
2067 err = map(exports, "f", 1, stringify(file->identifier), 0);
2068 if (!err) err = map(exports, "parse-error", 1, html_encode(file->parse_error_text), 0);
2070 cur_line = file->parse_error;
2073 if (!err) err = map(exports, "line-raw", 1, html_encode(cur_line->raw), 0);
2074 if (!err) err = map(exports, "line-data", 1, html_encode(cur_line->unprocessed), 0);
2082 return template_fill_for_cgi(csp, "cgi-error-parse", exports, rsp);
2086 /*********************************************************************
2088 * Function : cgi_error_file
2090 * Description : CGI function that is called when a file cannot be
2091 * opened by the CGI editor.
2094 * 1 : csp = Current client state (buffers, headers, etc...)
2095 * 2 : rsp = http_response data structure for output
2096 * 3 : filename = The file that was modified.
2098 * CGI Parameters : none
2100 * Returns : JB_ERR_OK on success
2101 * JB_ERR_MEMORY on out-of-memory error.
2103 *********************************************************************/
2104 jb_err cgi_error_file(struct client_state *csp,
2105 struct http_response *rsp,
2106 const char *filename)
2108 struct map *exports;
2115 if (NULL == (exports = default_exports(csp, NULL)))
2117 return JB_ERR_MEMORY;
2120 err = map(exports, "f", 1, html_encode(filename), 0);
2127 return template_fill_for_cgi(csp, "cgi-error-file", exports, rsp);
2131 /*********************************************************************
2133 * Function : cgi_error_file_read_only
2135 * Description : CGI function that is called when a file cannot be
2136 * opened for writing by the CGI editor.
2139 * 1 : csp = Current client state (buffers, headers, etc...)
2140 * 2 : rsp = http_response data structure for output
2141 * 3 : filename = The file that we can't write to
2143 * CGI Parameters : none
2145 * Returns : JB_ERR_OK on success
2146 * JB_ERR_MEMORY on out-of-memory error.
2148 *********************************************************************/
2149 jb_err cgi_error_file_read_only(struct client_state *csp,
2150 struct http_response *rsp,
2151 const char *filename)
2153 struct map *exports;
2160 if (NULL == (exports = default_exports(csp, NULL)))
2162 return JB_ERR_MEMORY;
2165 err = map(exports, "f", 1, html_encode(filename), 0);
2172 return template_fill_for_cgi(csp, "cgi-error-file-read-only", exports, rsp);
2176 /*********************************************************************
2178 * Function : cgi_edit_actions
2180 * Description : CGI function that allows the user to choose which
2181 * actions file to edit.
2184 * 1 : csp = Current client state (buffers, headers, etc...)
2185 * 2 : rsp = http_response data structure for output
2186 * 3 : parameters = map of cgi parameters
2188 * CGI Parameters : None
2190 * Returns : JB_ERR_OK on success
2191 * JB_ERR_MEMORY on out-of-memory error
2193 *********************************************************************/
2194 jb_err cgi_edit_actions(struct client_state *csp,
2195 struct http_response *rsp,
2196 const struct map *parameters)
2200 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2202 return cgi_error_disabled(csp, rsp);
2205 /* FIXME: Incomplete */
2207 return cgi_redirect(rsp, CGI_PREFIX "edit-actions-list?f=default");
2212 /*********************************************************************
2214 * Function : cgi_edit_actions_list
2216 * Description : CGI function that edits the actions list.
2217 * FIXME: This function shouldn't FATAL ever.
2218 * FIXME: This function doesn't check the retval of map()
2220 * 1 : csp = Current client state (buffers, headers, etc...)
2221 * 2 : rsp = http_response data structure for output
2222 * 3 : parameters = map of cgi parameters
2224 * CGI Parameters : filename
2226 * Returns : JB_ERR_OK on success
2227 * JB_ERR_MEMORY on out-of-memory
2228 * JB_ERR_FILE if the file cannot be opened or
2230 * JB_ERR_CGI_PARAMS if "filename" was not specified
2233 *********************************************************************/
2234 jb_err cgi_edit_actions_list(struct client_state *csp,
2235 struct http_response *rsp,
2236 const struct map *parameters)
2238 char * section_template;
2239 char * url_template;
2244 struct map * exports;
2245 struct map * section_exports;
2246 struct map * url_exports;
2247 struct editable_file * file;
2248 struct file_line * cur_line;
2249 unsigned line_number = 0;
2250 unsigned prev_section_line_number = ((unsigned) (-1));
2252 struct file_list * fl;
2253 struct url_actions * b;
2254 char * buttons = NULL;
2257 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2259 return cgi_error_disabled(csp, rsp);
2262 if (NULL == (exports = default_exports(csp, NULL)))
2264 return JB_ERR_MEMORY;
2267 /* Load actions file */
2268 err = edit_read_actions_file(csp, rsp, parameters, 0, &file);
2271 /* No filename specified, can't read file, or out of memory. */
2273 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2276 /* Find start of actions in file */
2277 cur_line = file->lines;
2279 while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
2281 cur_line = cur_line->next;
2286 * Conventional actions files should have a match all block
2288 * cur_line = {...global actions...}
2289 * cur_line->next = /
2290 * cur_line->next->next = {...actions...} or EOF
2292 if ( (cur_line != NULL)
2293 && (cur_line->type == FILE_LINE_ACTION)
2294 && (cur_line->next != NULL)
2295 && (cur_line->next->type == FILE_LINE_URL)
2296 && (0 == strcmp(cur_line->next->unprocessed, "/"))
2297 && ( (cur_line->next->next == NULL)
2298 || (cur_line->next->next->type != FILE_LINE_URL)
2302 * Generate string with buttons to set actions for "/" to
2303 * any predefined set of actions (named standard.*, probably
2304 * residing in standard.action).
2307 err = template_load(csp, §ion_template, "edit-actions-list-button", 0);
2310 edit_free_file(file);
2312 if (err == JB_ERR_FILE)
2314 return cgi_error_no_template(csp, rsp, "edit-actions-list-button");
2319 err = template_fill(§ion_template, exports);
2322 edit_free_file(file);
2327 buttons = strdup("");
2328 for (i = 0; i < MAX_AF_FILES; i++)
2330 if (((fl = csp->actions_list[i]) != NULL) && ((b = fl->f) != NULL))
2332 for (b = b->next; NULL != b; b = b->next)
2334 if (!strncmp(b->url->spec, "standard.", 9) && *(b->url->spec + 9) != '\0')
2339 free(section_template);
2340 edit_free_file(file);
2342 return JB_ERR_MEMORY;
2345 section_exports = new_map();
2346 err = map(section_exports, "button-name", 1, b->url->spec + 9, 1);
2348 if (err || (NULL == (s = strdup(section_template))))
2350 free_map(section_exports);
2352 free(section_template);
2353 edit_free_file(file);
2355 return JB_ERR_MEMORY;
2358 if (!err) err = template_fill(&s, section_exports);
2359 free_map(section_exports);
2360 if (!err) err = string_join(&buttons, s);
2365 freez(section_template);
2366 if (!err) err = map(exports, "all-urls-buttons", 1, buttons, 0);
2369 * Conventional actions file, supply extra editing help.
2370 * (e.g. don't allow them to make it an unconventional one).
2372 if (!err) err = map_conditional(exports, "all-urls-present", 1);
2374 snprintf(buf, sizeof(buf), "%u", line_number);
2375 if (!err) err = map(exports, "all-urls-s", 1, buf, 1);
2376 snprintf(buf, sizeof(buf), "%u", line_number + 2);
2377 if (!err) err = map(exports, "all-urls-s-next", 1, buf, 1);
2378 if (!err) err = map(exports, "all-urls-actions", 1,
2379 actions_to_html(csp, cur_line->data.action), 0);
2381 /* Skip the 2 lines */
2382 cur_line = cur_line->next->next;
2386 * Note that prev_section_line_number is NOT set here.
2387 * This is deliberate and not a bug. It stops a "Move up"
2388 * option appearing on the next section. Clicking "Move
2389 * up" would make the actions file unconventional, which
2390 * we don't want, so we hide this option.
2396 * Non-standard actions file - does not begin with
2397 * the "All URLs" section.
2399 if (!err) err = map_conditional(exports, "all-urls-present", 0);
2402 /* Set up global exports */
2404 if (!err) err = map(exports, "actions-file", 1, html_encode(file->filename), 0);
2405 if (!err) err = map(exports, "f", 1, stringify(file->identifier), 0);
2406 if (!err) err = map(exports, "v", 1, file->version_str, 1);
2408 /* Discourage private additions to default.action */
2410 if (!err) err = map_conditional(exports, "default-action",
2411 (strstr("default.action", file->filename) != NULL));
2414 edit_free_file(file);
2419 /* Should do all global exports above this point */
2421 /* Load templates */
2423 err = template_load(csp, §ion_template, "edit-actions-list-section", 0);
2426 edit_free_file(file);
2428 if (err == JB_ERR_FILE)
2430 return cgi_error_no_template(csp, rsp, "edit-actions-list-section");
2435 err = template_load(csp, &url_template, "edit-actions-list-url", 0);
2438 free(section_template);
2439 edit_free_file(file);
2441 if (err == JB_ERR_FILE)
2443 return cgi_error_no_template(csp, rsp, "edit-actions-list-url");
2448 err = template_fill(§ion_template, exports);
2452 edit_free_file(file);
2457 err = template_fill(&url_template, exports);
2460 free(section_template);
2461 edit_free_file(file);
2466 if (NULL == (sections = strdup("")))
2468 free(section_template);
2470 edit_free_file(file);
2472 return JB_ERR_MEMORY;
2475 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_ACTION))
2477 section_exports = new_map();
2479 snprintf(buf, sizeof(buf), "%u", line_number);
2480 err = map(section_exports, "s", 1, buf, 1);
2481 if (!err) err = map(section_exports, "actions", 1,
2482 actions_to_html(csp, cur_line->data.action), 0);
2485 && (cur_line->next != NULL)
2486 && (cur_line->next->type == FILE_LINE_URL))
2488 /* This section contains at least one URL, don't allow delete */
2489 err = map_block_killer(section_exports, "empty-section");
2493 if (!err) err = map_block_keep(section_exports, "empty-section");
2496 if (prev_section_line_number != ((unsigned)(-1)))
2498 /* Not last section */
2499 snprintf(buf, sizeof(buf), "%u", prev_section_line_number);
2500 if (!err) err = map(section_exports, "s-prev", 1, buf, 1);
2501 if (!err) err = map_block_keep(section_exports, "s-prev-exists");
2506 if (!err) err = map_block_killer(section_exports, "s-prev-exists");
2508 prev_section_line_number = line_number;
2513 free(section_template);
2515 edit_free_file(file);
2517 free_map(section_exports);
2521 /* Should do all section-specific exports above this point */
2523 if (NULL == (urls = strdup("")))
2526 free(section_template);
2528 edit_free_file(file);
2530 free_map(section_exports);
2531 return JB_ERR_MEMORY;
2536 cur_line = cur_line->next;
2539 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL))
2541 url_exports = new_map();
2543 snprintf(buf, sizeof(buf), "%u", line_number);
2544 err = map(url_exports, "p", 1, buf, 1);
2546 snprintf(buf, sizeof(buf), "%d", url_1_2);
2547 if (!err) err = map(url_exports, "url-1-2", 1, buf, 1);
2549 if (!err) err = map(url_exports, "url-html", 1,
2550 html_encode(cur_line->unprocessed), 0);
2551 if (!err) err = map(url_exports, "url", 1,
2552 url_encode(cur_line->unprocessed), 0);
2558 free(section_template);
2560 edit_free_file(file);
2562 free_map(section_exports);
2563 free_map(url_exports);
2567 if (NULL == (s = strdup(url_template)))
2571 free(section_template);
2573 edit_free_file(file);
2575 free_map(section_exports);
2576 free_map(url_exports);
2577 return JB_ERR_MEMORY;
2580 err = template_fill(&s, section_exports);
2581 if (!err) err = template_fill(&s, url_exports);
2582 if (!err) err = string_append(&urls, s);
2584 free_map(url_exports);
2591 free(section_template);
2593 edit_free_file(file);
2595 free_map(section_exports);
2599 url_1_2 = 3 - url_1_2;
2601 cur_line = cur_line->next;
2605 err = map(section_exports, "urls", 1, urls, 0);
2607 /* Could also do section-specific exports here, but it wouldn't be as fast */
2609 snprintf(buf, sizeof(buf), "%u", line_number);
2610 if (!err) err = map(section_exports, "s-next", 1, buf, 1);
2612 if ((cur_line != NULL)
2613 && (cur_line->type == FILE_LINE_ACTION))
2615 /* Not last section */
2616 if (!err) err = map_block_keep(section_exports, "s-next-exists");
2621 if (!err) err = map_block_killer(section_exports, "s-next-exists");
2627 free(section_template);
2629 edit_free_file(file);
2631 free_map(section_exports);
2635 if (NULL == (s = strdup(section_template)))
2638 free(section_template);
2640 edit_free_file(file);
2642 free_map(section_exports);
2643 return JB_ERR_MEMORY;
2646 err = template_fill(&s, section_exports);
2647 if (!err) err = string_append(§ions, s);
2650 free_map(section_exports);
2655 free(section_template);
2657 edit_free_file(file);
2663 edit_free_file(file);
2664 free(section_template);
2667 err = map(exports, "sections", 1, sections, 0);
2674 /* Could also do global exports here, but it wouldn't be as fast */
2676 return template_fill_for_cgi(csp, "edit-actions-list", exports, rsp);
2680 /*********************************************************************
2682 * Function : cgi_edit_actions_for_url
2684 * Description : CGI function that edits the Actions list.
2687 * 1 : csp = Current client state (buffers, headers, etc...)
2688 * 2 : rsp = http_response data structure for output
2689 * 3 : parameters = map of cgi parameters
2691 * CGI Parameters : None
2693 * Returns : JB_ERR_OK on success
2694 * JB_ERR_MEMORY on out-of-memory
2695 * JB_ERR_CGI_PARAMS if the CGI parameters are not
2696 * specified or not valid.
2698 *********************************************************************/
2699 jb_err cgi_edit_actions_for_url(struct client_state *csp,
2700 struct http_response *rsp,
2701 const struct map *parameters)
2703 struct map * exports;
2704 char *filter_template;
2706 struct editable_file * file;
2707 struct file_line * cur_line;
2708 unsigned line_number;
2710 struct re_filterfile_spec *filter_group;
2711 int i, have_filters = 0;
2713 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2715 return cgi_error_disabled(csp, rsp);
2718 err = get_number_param(csp, parameters, "s", §ionid);
2724 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2727 /* No filename specified, can't read file, modified, or out of memory. */
2728 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2731 cur_line = file->lines;
2733 for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2735 cur_line = cur_line->next;
2738 if ( (cur_line == NULL)
2739 || (line_number != sectionid)
2741 || (cur_line->type != FILE_LINE_ACTION))
2743 /* Invalid "sectionid" parameter */
2744 edit_free_file(file);
2745 return JB_ERR_CGI_PARAMS;
2748 if (NULL == (exports = default_exports(csp, NULL)))
2750 edit_free_file(file);
2751 return JB_ERR_MEMORY;
2754 err = template_load(csp, &filter_template, "edit-actions-for-url-string-filter", 0);
2757 edit_free_file(file);
2759 return cgi_error_no_template(csp, rsp, "edit-actions-for-url-string-filter");
2762 err = map(exports, "f", 1, stringify(file->identifier), 0);
2763 if (!err) err = map(exports, "v", 1, file->version_str, 1);
2764 if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0);
2766 if (!err) err = actions_to_radio(exports, cur_line->data.action);
2768 if (!err) err = action_render_string_filters_template(exports, cur_line->data.action, filter_template,
2769 &filter_type_info[FT_SUPPRESS_TAG]);
2770 freez(filter_template);
2773 * XXX: Some browsers (at least IE6 and IE7) have an artificial URL
2774 * length limitation and ignore clicks on the Submit buttons if
2775 * the resulting GET URL would be longer than their limit.
2777 * In Privoxy 3.0.5 beta the standard edit-actions-for-url template
2778 * reached this limit and action editing stopped working in these
2779 * browsers (BR #1570678).
2781 * The config option split-large-forms works around this browser
2782 * bug (HTTP has no URL length limitation) by dividing the action
2783 * list form into multiple smaller ones. It means the URLs are shorter
2784 * and work in broken browsers as well, but the user can no longer change
2785 * all actions with one submit.
2787 * A better solution would be to switch to POST requests,
2788 * but this will do for now.
2790 if (!err && (csp->config->feature_flags & RUNTIME_FEATURE_SPLIT_LARGE_FORMS))
2792 /* Generate multiple smaller form by killing the big one. */
2793 err = map_block_killer(exports, "one-form-only");
2797 /* Default: Generate one large form by killing the smaller ones. */
2798 err = map_block_killer(exports, "multiple-forms");
2801 for (i = 0; i < MAX_AF_FILES; i++)
2803 if ((csp->rlist[i] != NULL) && (csp->rlist[i]->f != NULL))
2805 if (!err) err = map_conditional(exports, "any-filters-defined", 1);
2811 #ifndef FEATURE_EXTERNAL_FILTERS
2812 if (!err) err = map_block_killer(exports, "external-content-filters");
2814 #ifndef FEATURE_HTTPS_INSPECTION
2815 if (!err) err = map_block_killer(exports, "https-inspection");
2820 edit_free_file(file);
2825 if (0 == have_filters)
2827 err = map(exports, "filter-params", 1, "", 1);
2832 * List available filters and their settings.
2834 int filter_identifier = 0;
2835 char *prepared_templates[MAX_FILTER_TYPES];
2837 for (i = 0; i < MAX_FILTER_TYPES; i++)
2839 prepared_templates[i] = strdup("");
2842 err = template_load(csp, &filter_template, "edit-actions-for-url-filter", 0);
2845 edit_free_file(file);
2847 if (err == JB_ERR_FILE)
2849 return cgi_error_no_template(csp, rsp, "edit-actions-for-url-filter");
2854 err = template_fill(&filter_template, exports);
2856 for (i = 0; i < MAX_AF_FILES; i++)
2858 if ((csp->rlist[i] != NULL) && (csp->rlist[i]->f != NULL))
2860 filter_group = csp->rlist[i]->f;
2861 for (;(!err) && (filter_group != NULL); filter_group = filter_group->next)
2863 char current_mode = 'x';
2865 struct list_entry *filter_name;
2866 struct map *line_exports;
2867 const enum filter_type type = filter_group->type;
2868 const int multi_action_index = filter_type_info[type].multi_action_index;
2870 assert(type < MAX_FILTER_TYPES);
2872 filter_name = cur_line->data.action->multi_add[multi_action_index]->first;
2873 while ((filter_name != NULL)
2874 && (0 != strcmp(filter_group->name, filter_name->str)))
2876 filter_name = filter_name->next;
2879 if (filter_name != NULL)
2885 filter_name = cur_line->data.action->multi_remove[multi_action_index]->first;
2886 while ((filter_name != NULL)
2887 && (0 != strcmp(filter_group->name, filter_name->str)))
2889 filter_name = filter_name->next;
2891 if (filter_name != NULL)
2897 /* Generate a unique serial number */
2898 snprintf(number, sizeof(number), "%x", filter_identifier++);
2899 number[sizeof(number) - 1] = '\0';
2901 line_exports = new_map();
2902 if (line_exports == NULL)
2904 err = JB_ERR_MEMORY;
2910 if (!err) err = map(line_exports, "index", 1, number, 1);
2911 if (!err) err = map(line_exports, "name", 1, filter_group->name, 1);
2912 if (!err) err = map(line_exports, "description", 1, filter_group->description, 1);
2913 if (!err) err = map_radio(line_exports, "this-filter", "ynx", current_mode);
2914 if (!err) err = map(line_exports, "filter-type", 1, filter_type_info[type].type, 1);
2915 if (!err) err = map(line_exports, "abbr-filter-type", 1, filter_type_info[type].abbr_type, 1);
2916 if (!err) err = map(line_exports, "anchor", 1, filter_type_info[type].anchor, 1);
2920 filter_line = strdup(filter_template);
2921 if (filter_line == NULL) err = JB_ERR_MEMORY;
2923 if (!err) err = template_fill(&filter_line, line_exports);
2924 if (!err) err = string_join(&prepared_templates[type], filter_line);
2926 free_map(line_exports);
2931 freez(filter_template);
2933 /* Replace all filter macros with the aggregated templates */
2934 for (i = 0; i < MAX_FILTER_TYPES; i++)
2937 err = map(exports, filter_type_info[i].macro_name, 1, prepared_templates[i], 0);
2942 /* Free aggregated templates */
2943 for (i = 0; i < MAX_FILTER_TYPES; i++)
2945 freez(prepared_templates[i]);
2950 /* Check or uncheck the "disable all of this type" radio buttons. */
2951 for (i = 0; i < MAX_FILTER_TYPES; i++)
2953 const int a = filter_type_info[i].multi_action_index;
2954 const int disable_all = cur_line->data.action->multi_remove_all[a];
2956 err = map_radio(exports, filter_type_info[i].disable_all_option, "nx", (disable_all ? 'n' : 'x'));
2959 edit_free_file(file);
2967 return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
2971 /*********************************************************************
2973 * Function : get_number_of_filters
2975 * Description : Counts the number of filter available.
2978 * 1 : csp = Current client state (buffers, headers, etc...)
2980 * Returns : Number of filters available.
2982 *********************************************************************/
2983 static int get_number_of_filters(const struct client_state *csp)
2986 struct re_filterfile_spec *b;
2987 struct file_list *fl;
2988 int number_of_filters = 0;
2990 for (i = 0; i < MAX_AF_FILES; i++)
2993 if ((NULL == fl) || (NULL == fl->f))
2996 * Either there are no filter files left or this
2997 * filter file just contains no valid filters.
2999 * Continue to be sure we don't miss valid filter
3000 * files that are chained after empty or invalid ones.
3005 for (b = fl->f; b != NULL; b = b->next)
3007 number_of_filters++;
3011 return number_of_filters;
3016 /*********************************************************************
3018 * Function : cgi_edit_actions_submit
3020 * Description : CGI function that actually edits the Actions list.
3023 * 1 : csp = Current client state (buffers, headers, etc...)
3024 * 2 : rsp = http_response data structure for output
3025 * 3 : parameters = map of cgi parameters
3027 * CGI Parameters : None
3029 * Returns : JB_ERR_OK on success
3030 * JB_ERR_MEMORY on out-of-memory
3031 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3032 * specified or not valid.
3034 *********************************************************************/
3035 jb_err cgi_edit_actions_submit(struct client_state *csp,
3036 struct http_response *rsp,
3037 const struct map *parameters)
3042 size_t newtext_size;
3044 struct editable_file * file;
3045 struct file_line * cur_line;
3046 unsigned line_number;
3049 int filter_identifier;
3051 const char * action_set_name;
3052 struct file_list * fl;
3053 struct url_actions * b;
3054 int number_of_filters;
3056 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3058 return cgi_error_disabled(csp, rsp);
3061 err = get_number_param(csp, parameters, "s", §ionid);
3067 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3070 /* No filename specified, can't read file, modified, or out of memory. */
3071 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3074 cur_line = file->lines;
3076 for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
3078 cur_line = cur_line->next;
3081 if ( (cur_line == NULL)
3082 || (line_number != sectionid)
3084 || (cur_line->type != FILE_LINE_ACTION))
3086 /* Invalid "sectionid" parameter */
3087 edit_free_file(file);
3088 return JB_ERR_CGI_PARAMS;
3091 get_string_param(parameters, "p", &action_set_name);
3092 if (action_set_name != NULL)
3094 for (filter_identifier = 0; filter_identifier < MAX_AF_FILES; filter_identifier++)
3096 if (((fl = csp->actions_list[filter_identifier]) != NULL) && ((b = fl->f) != NULL))
3098 for (b = b->next; NULL != b; b = b->next)
3100 if (!strncmp(b->url->spec, "standard.", 9) && !strcmp(b->url->spec + 9, action_set_name))
3102 copy_action(cur_line->data.action, b->action);
3108 edit_free_file(file);
3109 return JB_ERR_CGI_PARAMS;
3115 err = actions_from_radio(parameters, cur_line->data.action);
3121 edit_free_file(file);
3125 /* Check the "disable all of this type" parameters. */
3126 for (i = 0; i < MAX_FILTER_TYPES; i++)
3128 const int multi_action_index = filter_type_info[i].multi_action_index;
3129 const char ch = get_char_param(parameters, filter_type_info[i].disable_all_param);
3133 list_remove_all(cur_line->data.action->multi_add[multi_action_index]);
3134 list_remove_all(cur_line->data.action->multi_remove[multi_action_index]);
3135 cur_line->data.action->multi_remove_all[multi_action_index] = 1;
3139 cur_line->data.action->multi_remove_all[multi_action_index] = 0;
3143 number_of_filters = get_number_of_filters(csp);
3145 for (filter_identifier = 0; filter_identifier < number_of_filters && !err; filter_identifier++)
3152 * Filter state. Valid states are: 'Y' (active),
3153 * 'N' (inactive) and 'X' (no change).
3157 * Abbreviated filter type. Valid types are: 'F' (content filter),
3158 * 'S' (server-header filter) and 'C' (client-header filter).
3160 int multi_action_index = 0;
3162 /* Generate the keys */
3163 snprintf(key_value, sizeof(key_value), "filter_r%x", filter_identifier);
3164 key_value[sizeof(key_value) - 1] = '\0'; /* XXX: Why? */
3165 snprintf(key_name, sizeof(key_name), "filter_n%x", filter_identifier);
3166 key_name[sizeof(key_name) - 1] = '\0'; /* XXX: Why? */
3167 snprintf(key_type, sizeof(key_type), "filter_t%x", filter_identifier);
3169 err = get_string_param(parameters, key_name, &name);
3174 /* The filter identifier isn't present. Try the next one. */
3178 type = get_char_param(parameters, key_type);
3182 multi_action_index = ACTION_MULTI_FILTER;
3185 multi_action_index = ACTION_MULTI_SERVER_HEADER_FILTER;
3188 multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER;
3191 multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER;
3194 multi_action_index = ACTION_MULTI_SERVER_HEADER_TAGGER;
3197 multi_action_index = ACTION_MULTI_CLIENT_BODY_FILTER;
3200 log_error(LOG_LEVEL_ERROR,
3201 "Unknown filter type: %c for filter %s. Filter ignored.", type, name);
3204 assert(multi_action_index);
3206 value = get_char_param(parameters, key_value);
3209 list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3210 if (!err) err = enlist(cur_line->data.action->multi_add[multi_action_index], name);
3211 list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3213 else if (value == 'N')
3215 list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3216 if (!cur_line->data.action->multi_remove_all[multi_action_index])
3218 list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3219 if (!err) err = enlist(cur_line->data.action->multi_remove[multi_action_index], name);
3222 else if (value == 'X')
3224 list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3225 list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3229 /* process existing suppress tag */
3230 for (filter_identifier = 0; !err; filter_identifier++)
3236 const char *name, *new_name;
3238 * Filter state. Valid states are: 'Y' (active),
3239 * 'N' (inactive) and 'X' (no change).
3243 * Abbreviated filter type. Valid types are: 'U' (suppress tag).
3245 int multi_action_index = 0;
3247 /* Generate the keys */
3248 snprintf(key_value, sizeof(key_value), "string_filter_r%x", filter_identifier);
3249 snprintf(key_name, sizeof(key_name), "string_filter_n%x", filter_identifier);
3250 snprintf(old_name, sizeof(old_name), "string_filter_o%x", filter_identifier);
3251 snprintf(key_type, sizeof(key_type), "string_filter_t%x", filter_identifier);
3253 err = get_string_param(parameters, old_name, &name);
3258 /* The filter identifier isn't present: we're done! */
3262 err = get_string_param(parameters, key_name, &new_name);
3264 if (new_name == NULL) new_name = name;
3266 type = get_char_param(parameters, key_type);
3270 multi_action_index = ACTION_MULTI_SUPPRESS_TAG;
3273 log_error(LOG_LEVEL_ERROR,
3274 "Unknown filter type: %c for filter %s. Filter ignored.", type, name);
3277 assert(multi_action_index);
3279 value = get_char_param(parameters, key_value);
3280 if (value == 'X' || value == 'Y' || value == 'N')
3282 list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3283 list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3288 err = enlist(cur_line->data.action->multi_add[multi_action_index], new_name);
3290 else if (value == 'N')
3292 err = enlist(cur_line->data.action->multi_remove[multi_action_index], new_name);
3296 /* process new string filters */
3297 for (filter_identifier = 0; !err; filter_identifier++)
3304 * Filter state. Valid states are: 'Y' (active),
3305 * 'N' (inactive) and 'X' (no change).
3309 * Abbreviated filter type. Valid types are: 'U' (suppress tag).
3311 int multi_action_index = 0;
3313 /* Generate the keys */
3314 snprintf(key_value, sizeof(key_value), "new_string_filter_r%x", filter_identifier);
3315 snprintf(key_name, sizeof(key_name), "new_string_filter_n%x", filter_identifier);
3316 snprintf(key_type, sizeof(key_type), "new_string_filter_t%x", filter_identifier);
3318 err = get_string_param(parameters, key_name, &name);
3323 /* The filter identifier isn't present: we've done! */
3327 type = get_char_param(parameters, key_type);
3331 multi_action_index = ACTION_MULTI_SUPPRESS_TAG;
3334 log_error(LOG_LEVEL_ERROR,
3335 "Unknown filter type: %c for filter %s. Filter ignored.", type, name);
3338 assert(multi_action_index);
3340 value = get_char_param(parameters, key_value);
3343 list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3344 if (!err) err = enlist(cur_line->data.action->multi_add[multi_action_index], name);
3345 list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3347 else if (value == 'N')
3349 list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3350 list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3351 if (!err) err = enlist(cur_line->data.action->multi_remove[multi_action_index], name);
3353 /* nothing to do if the value is 'X' */
3359 edit_free_file(file);
3363 if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
3366 edit_free_file(file);
3367 return JB_ERR_MEMORY;
3370 len = strlen(actiontext);
3374 * Empty action - must special-case this.
3375 * Simply setting len to 1 is sufficient...
3380 newtext_size = len + 2;
3381 newtext = malloc_or_die(newtext_size);
3382 strlcpy(newtext, actiontext, newtext_size);
3386 newtext[len + 1] = '\0';
3388 freez(cur_line->raw);
3389 freez(cur_line->unprocessed);
3390 cur_line->unprocessed = newtext;
3392 err = edit_write_file(file);
3395 /* Error writing file */
3396 if (err == JB_ERR_FILE)
3398 /* Read-only file. */
3399 err = cgi_error_file_read_only(csp, rsp, file->filename);
3401 edit_free_file(file);
3405 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3406 (unsigned long) time(NULL), file->identifier, sectionid);
3408 edit_free_file(file);
3410 return cgi_redirect(rsp, target);
3414 /*********************************************************************
3416 * Function : cgi_edit_actions_url
3418 * Description : CGI function that actually edits a URL pattern in
3422 * 1 : csp = Current client state (buffers, headers, etc...)
3423 * 2 : rsp = http_response data structure for output
3424 * 3 : parameters = map of cgi parameters
3427 * filename : Identifies the file to edit
3428 * ver : File's last-modified time
3429 * section : Line number of section to edit
3430 * pattern : Line number of pattern to edit
3431 * newval : New value for pattern
3433 * Returns : JB_ERR_OK on success
3434 * JB_ERR_MEMORY on out-of-memory
3435 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3436 * specified or not valid.
3438 *********************************************************************/
3439 jb_err cgi_edit_actions_url(struct client_state *csp,
3440 struct http_response *rsp,
3441 const struct map *parameters)
3445 struct editable_file * file;
3446 struct file_line * cur_line;
3447 unsigned line_number;
3448 unsigned section_start_line_number = 0;
3456 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3458 return cgi_error_disabled(csp, rsp);
3461 err = get_number_param(csp, parameters, "p", &patternid);
3468 return JB_ERR_CGI_PARAMS;
3471 err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3477 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3480 /* No filename specified, can't read file, modified, or out of memory. */
3482 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3486 cur_line = file->lines;
3488 while ((cur_line != NULL) && (line_number < patternid))
3490 if (cur_line->type == FILE_LINE_ACTION)
3492 section_start_line_number = line_number;
3494 cur_line = cur_line->next;
3498 if ((cur_line == NULL)
3499 || (cur_line->type != FILE_LINE_URL))
3501 /* Invalid "patternid" parameter */
3503 edit_free_file(file);
3504 return JB_ERR_CGI_PARAMS;
3507 /* At this point, the line to edit is in cur_line */
3509 freez(cur_line->raw);
3510 freez(cur_line->unprocessed);
3511 cur_line->unprocessed = new_pattern;
3513 err = edit_write_file(file);
3516 /* Error writing file */
3517 if (err == JB_ERR_FILE)
3519 /* Read-only file. */
3520 err = cgi_error_file_read_only(csp, rsp, file->filename);
3522 edit_free_file(file);
3526 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3527 (unsigned long) time(NULL), file->identifier, section_start_line_number);
3529 edit_free_file(file);
3531 return cgi_redirect(rsp, target);
3535 /*********************************************************************
3537 * Function : cgi_edit_actions_add_url
3539 * Description : CGI function that actually adds a URL pattern to
3543 * 1 : csp = Current client state (buffers, headers, etc...)
3544 * 2 : rsp = http_response data structure for output
3545 * 3 : parameters = map of cgi parameters
3548 * filename : Identifies the file to edit
3549 * ver : File's last-modified time
3550 * section : Line number of section to edit
3551 * newval : New pattern
3553 * Returns : JB_ERR_OK on success
3554 * JB_ERR_MEMORY on out-of-memory
3555 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3556 * specified or not valid.
3558 *********************************************************************/
3559 jb_err cgi_edit_actions_add_url(struct client_state *csp,
3560 struct http_response *rsp,
3561 const struct map *parameters)
3565 struct file_line * new_line;
3566 struct editable_file * file;
3567 struct file_line * cur_line;
3568 unsigned line_number;
3572 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3574 return cgi_error_disabled(csp, rsp);
3577 err = get_number_param(csp, parameters, "s", §ionid);
3584 return JB_ERR_CGI_PARAMS;
3587 err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3593 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3596 /* No filename specified, can't read file, modified, or out of memory. */
3598 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3602 cur_line = file->lines;
3604 while ((cur_line != NULL) && (line_number < sectionid))
3606 cur_line = cur_line->next;
3610 if ((cur_line == NULL)
3611 || (cur_line->type != FILE_LINE_ACTION))
3613 /* Invalid "sectionid" parameter */
3615 edit_free_file(file);
3616 return JB_ERR_CGI_PARAMS;
3619 /* At this point, the section header is in cur_line - add after this. */
3621 /* Allocate the new line */
3622 new_line = zalloc_or_die(sizeof(*new_line));
3624 /* Fill in the data members of the new line */
3625 new_line->raw = NULL;
3626 new_line->prefix = NULL;
3627 new_line->unprocessed = new_pattern;
3628 new_line->type = FILE_LINE_URL;
3630 /* Link new_line into the list, after cur_line */
3631 new_line->next = cur_line->next;
3632 cur_line->next = new_line;
3634 /* Done making changes, now commit */
3636 err = edit_write_file(file);
3639 /* Error writing file */
3640 if (err == JB_ERR_FILE)
3642 /* Read-only file. */
3643 err = cgi_error_file_read_only(csp, rsp, file->filename);
3645 edit_free_file(file);
3649 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3650 (unsigned long) time(NULL), file->identifier, sectionid);
3652 edit_free_file(file);
3654 return cgi_redirect(rsp, target);
3658 /*********************************************************************
3660 * Function : cgi_edit_actions_remove_url
3662 * Description : CGI function that actually removes a URL pattern from
3666 * 1 : csp = Current client state (buffers, headers, etc...)
3667 * 2 : rsp = http_response data structure for output
3668 * 3 : parameters = map of cgi parameters
3671 * f : (filename) Identifies the file to edit
3672 * v : (version) File's last-modified time
3673 * p : (pattern) Line number of pattern to remove
3675 * Returns : JB_ERR_OK on success
3676 * JB_ERR_MEMORY on out-of-memory
3677 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3678 * specified or not valid.
3680 *********************************************************************/
3681 jb_err cgi_edit_actions_remove_url(struct client_state *csp,
3682 struct http_response *rsp,
3683 const struct map *parameters)
3686 struct editable_file * file;
3687 struct file_line * cur_line;
3688 struct file_line * prev_line;
3689 unsigned line_number;
3690 unsigned section_start_line_number = 0;
3694 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3696 return cgi_error_disabled(csp, rsp);
3699 err = get_number_param(csp, parameters, "p", &patternid);
3705 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3708 /* No filename specified, can't read file, modified, or out of memory. */
3709 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3714 cur_line = file->lines;
3716 while ((cur_line != NULL) && (line_number < patternid))
3718 if (cur_line->type == FILE_LINE_ACTION)
3720 section_start_line_number = line_number;
3722 prev_line = cur_line;
3723 cur_line = cur_line->next;
3727 if ( (cur_line == NULL)
3728 || (prev_line == NULL)
3729 || (cur_line->type != FILE_LINE_URL))
3731 /* Invalid "patternid" parameter */
3732 edit_free_file(file);
3733 return JB_ERR_CGI_PARAMS;
3736 /* At this point, the line to remove is in cur_line, and the previous
3737 * one is in prev_line
3740 /* Unlink cur_line */
3741 prev_line->next = cur_line->next;
3742 cur_line->next = NULL;
3745 edit_free_file_lines(cur_line);
3747 err = edit_write_file(file);
3750 /* Error writing file */
3751 if (err == JB_ERR_FILE)
3753 /* Read-only file. */
3754 err = cgi_error_file_read_only(csp, rsp, file->filename);
3756 edit_free_file(file);
3760 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3761 (unsigned long) time(NULL), file->identifier, section_start_line_number);
3763 edit_free_file(file);
3765 return cgi_redirect(rsp, target);
3769 /*********************************************************************
3771 * Function : cgi_edit_actions_section_remove
3773 * Description : CGI function that actually removes a whole section from
3774 * the actions file. The section must be empty first
3775 * (else JB_ERR_CGI_PARAMS).
3778 * 1 : csp = Current client state (buffers, headers, etc...)
3779 * 2 : rsp = http_response data structure for output
3780 * 3 : parameters = map of cgi parameters
3783 * f : (filename) Identifies the file to edit
3784 * v : (version) File's last-modified time
3785 * s : (section) Line number of section to edit
3787 * Returns : JB_ERR_OK on success
3788 * JB_ERR_MEMORY on out-of-memory
3789 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3790 * specified or not valid.
3792 *********************************************************************/
3793 jb_err cgi_edit_actions_section_remove(struct client_state *csp,
3794 struct http_response *rsp,
3795 const struct map *parameters)
3798 struct editable_file * file;
3799 struct file_line * cur_line;
3800 struct file_line * prev_line;
3801 unsigned line_number;
3805 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3807 return cgi_error_disabled(csp, rsp);
3810 err = get_number_param(csp, parameters, "s", §ionid);
3816 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3819 /* No filename specified, can't read file, modified, or out of memory. */
3820 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3824 cur_line = file->lines;
3827 while ((cur_line != NULL) && (line_number < sectionid))
3829 prev_line = cur_line;
3830 cur_line = cur_line->next;
3834 if ((cur_line == NULL)
3835 || (cur_line->type != FILE_LINE_ACTION))
3837 /* Invalid "sectionid" parameter */
3838 edit_free_file(file);
3839 return JB_ERR_CGI_PARAMS;
3842 if ((cur_line->next != NULL)
3843 && (cur_line->next->type == FILE_LINE_URL))
3845 /* Section not empty. */
3846 edit_free_file(file);
3847 return JB_ERR_CGI_PARAMS;
3850 /* At this point, the line to remove is in cur_line, and the previous
3851 * one is in prev_line
3854 /* Unlink cur_line */
3855 if (prev_line == NULL)
3857 /* Removing the first line from the file */
3858 file->lines = cur_line->next;
3862 prev_line->next = cur_line->next;
3864 cur_line->next = NULL;
3867 edit_free_file_lines(cur_line);
3869 err = edit_write_file(file);
3872 /* Error writing file */
3873 if (err == JB_ERR_FILE)
3875 /* Read-only file. */
3876 err = cgi_error_file_read_only(csp, rsp, file->filename);
3878 edit_free_file(file);
3882 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
3883 (unsigned long) time(NULL), file->identifier);
3885 edit_free_file(file);
3887 return cgi_redirect(rsp, target);
3891 /*********************************************************************
3893 * Function : cgi_edit_actions_section_add
3895 * Description : CGI function that adds a new empty section to
3899 * 1 : csp = Current client state (buffers, headers, etc...)
3900 * 2 : rsp = http_response data structure for output
3901 * 3 : parameters = map of cgi parameters
3904 * f : (filename) Identifies the file to edit
3905 * v : (version) File's last-modified time
3906 * s : (section) Line number of section to add after, 0 for
3909 * Returns : JB_ERR_OK on success
3910 * JB_ERR_MEMORY on out-of-memory
3911 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3912 * specified or not valid.
3914 *********************************************************************/
3915 jb_err cgi_edit_actions_section_add(struct client_state *csp,
3916 struct http_response *rsp,
3917 const struct map *parameters)
3920 struct file_line * new_line;
3922 struct editable_file * file;
3923 struct file_line * cur_line;
3924 unsigned line_number;
3928 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3930 return cgi_error_disabled(csp, rsp);
3933 err = get_number_param(csp, parameters, "s", §ionid);
3939 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3942 /* No filename specified, can't read file, modified, or out of memory. */
3943 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3947 cur_line = file->lines;
3949 if (sectionid <= 1U)
3951 /* Add to start of file */
3952 if (cur_line != NULL && cur_line->type != FILE_LINE_ACTION)
3954 /* There's something in the file, find the line before the first
3957 while ((cur_line->next != NULL)
3958 && (cur_line->next->type != FILE_LINE_ACTION))
3960 cur_line = cur_line->next;
3966 /* File starts with action line, so insert at top */
3972 /* Add after stated section. */
3973 while ((cur_line != NULL) && (line_number < sectionid))
3975 cur_line = cur_line->next;
3979 if ((cur_line == NULL)
3980 || (cur_line->type != FILE_LINE_ACTION))
3982 /* Invalid "sectionid" parameter */
3983 edit_free_file(file);
3984 return JB_ERR_CGI_PARAMS;
3987 /* Skip through the section to find the last line in it. */
3988 while ((cur_line->next != NULL)
3989 && (cur_line->next->type != FILE_LINE_ACTION))
3991 cur_line = cur_line->next;
3996 /* At this point, the last line in the previous section is in cur_line
3997 * - add after this. (Or if we need to add as the first line, cur_line
4001 new_text = strdup("{}");
4002 if (NULL == new_text)
4004 edit_free_file(file);
4005 return JB_ERR_MEMORY;
4008 /* Allocate the new line */
4009 new_line = zalloc_or_die(sizeof(*new_line));
4011 /* Fill in the data members of the new line */
4012 new_line->raw = NULL;
4013 new_line->prefix = NULL;
4014 new_line->unprocessed = new_text;
4015 new_line->type = FILE_LINE_ACTION;
4017 if (cur_line != NULL)
4019 /* Link new_line into the list, after cur_line */
4020 new_line->next = cur_line->next;
4021 cur_line->next = new_line;
4025 /* Link new_line into the list, as first line */
4026 new_line->next = file->lines;
4027 file->lines = new_line;
4030 /* Done making changes, now commit */
4032 err = edit_write_file(file);
4035 /* Error writing file */
4036 if (err == JB_ERR_FILE)
4038 /* Read-only file. */
4039 err = cgi_error_file_read_only(csp, rsp, file->filename);
4041 edit_free_file(file);
4045 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
4046 (unsigned long) time(NULL), file->identifier);
4048 edit_free_file(file);
4050 return cgi_redirect(rsp, target);
4054 /*********************************************************************
4056 * Function : cgi_edit_actions_section_swap
4058 * Description : CGI function that swaps the order of two sections
4059 * in the actions file. Note that this CGI can actually
4060 * swap any two arbitrary sections, but the GUI interface
4061 * currently only allows consecutive sections to be
4065 * 1 : csp = Current client state (buffers, headers, etc...)
4066 * 2 : rsp = http_response data structure for output
4067 * 3 : parameters = map of cgi parameters
4070 * f : (filename) Identifies the file to edit
4071 * v : (version) File's last-modified time
4072 * s1 : (section1) Line number of first section to swap
4073 * s2 : (section2) Line number of second section to swap
4075 * Returns : JB_ERR_OK on success
4076 * JB_ERR_MEMORY on out-of-memory
4077 * JB_ERR_CGI_PARAMS if the CGI parameters are not
4078 * specified or not valid.
4080 *********************************************************************/
4081 jb_err cgi_edit_actions_section_swap(struct client_state *csp,
4082 struct http_response *rsp,
4083 const struct map *parameters)
4087 struct editable_file * file;
4088 struct file_line * cur_line;
4089 struct file_line * prev_line;
4090 struct file_line * line_before_section1;
4091 struct file_line * line_start_section1;
4092 struct file_line * line_end_section1;
4093 struct file_line * line_after_section1;
4094 struct file_line * line_before_section2;
4095 struct file_line * line_start_section2;
4096 struct file_line * line_end_section2;
4097 struct file_line * line_after_section2;
4098 unsigned line_number;
4102 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
4104 return cgi_error_disabled(csp, rsp);
4107 err = get_number_param(csp, parameters, "s1", §ion1);
4108 if (!err) err = get_number_param(csp, parameters, "s2", §ion2);
4114 if (section1 > section2)
4116 unsigned temp = section2;
4117 section2 = section1;
4121 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
4124 /* No filename specified, can't read file, modified, or out of memory. */
4125 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
4128 /* Start at the beginning... */
4130 cur_line = file->lines;
4133 /* ... find section1 ... */
4134 while ((cur_line != NULL) && (line_number < section1))
4136 prev_line = cur_line;
4137 cur_line = cur_line->next;
4141 if ((cur_line == NULL)
4142 || (cur_line->type != FILE_LINE_ACTION))
4144 /* Invalid "section1" parameter */
4145 edit_free_file(file);
4146 return JB_ERR_CGI_PARAMS;
4149 /* If no-op, we've validated params and can skip the rest. */
4150 if (section1 != section2)
4152 /* ... find the end of section1 ... */
4153 line_before_section1 = prev_line;
4154 line_start_section1 = cur_line;
4157 prev_line = cur_line;
4158 cur_line = cur_line->next;
4161 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
4162 line_end_section1 = prev_line;
4163 line_after_section1 = cur_line;
4165 /* ... find section2 ... */
4166 while ((cur_line != NULL) && (line_number < section2))
4168 prev_line = cur_line;
4169 cur_line = cur_line->next;
4173 if ((cur_line == NULL)
4174 || (cur_line->type != FILE_LINE_ACTION))
4176 /* Invalid "section2" parameter */
4177 edit_free_file(file);
4178 return JB_ERR_CGI_PARAMS;
4181 /* ... find the end of section2 ... */
4182 line_before_section2 = prev_line;
4183 line_start_section2 = cur_line;
4186 prev_line = cur_line;
4187 cur_line = cur_line->next;
4190 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
4191 line_end_section2 = prev_line;
4192 line_after_section2 = cur_line;
4194 /* Now have all the pointers we need. Do the swap. */
4196 /* Change the pointer to section1 to point to section2 instead */
4197 if (line_before_section1 == NULL)
4199 file->lines = line_start_section2;
4203 line_before_section1->next = line_start_section2;
4206 if (line_before_section2 == line_end_section1)
4208 /* Consecutive sections */
4209 line_end_section2->next = line_start_section1;
4213 line_end_section2->next = line_after_section1;
4214 line_before_section2->next = line_start_section1;
4217 /* Set the pointer from the end of section1 to the rest of the file */
4218 line_end_section1->next = line_after_section2;
4220 err = edit_write_file(file);
4223 /* Error writing file */
4224 if (err == JB_ERR_FILE)
4226 /* Read-only file. */
4227 err = cgi_error_file_read_only(csp, rsp, file->filename);
4229 edit_free_file(file);
4232 } /* END if (section1 != section2) */
4234 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
4235 (unsigned long) time(NULL), file->identifier);
4237 edit_free_file(file);
4239 return cgi_redirect(rsp, target);
4243 /*********************************************************************
4245 * Function : javascriptify
4247 * Description : Converts a string into a form JavaScript will like.
4249 * Netscape 4's JavaScript sucks - it doesn't use
4250 * "id" parameters, so you have to set the "name"
4251 * used to submit a form element to something JavaScript
4252 * will like. (Or access the elements by index in an
4253 * array. That array contains >60 elements and will
4254 * be changed whenever we add a new action to the
4255 * editor, so I'm NOT going to use indexes that have
4256 * to be figured out by hand.)
4258 * Currently the only thing we have to worry about
4259 * is "-" ==> "_" conversion.
4261 * This is a length-preserving operation so it is
4262 * carried out in-place, no memory is allocated
4266 * 1 : identifier = String to make JavaScript-friendly.
4270 *********************************************************************/
4271 static void javascriptify(char * identifier)
4273 char * p = identifier;
4274 while (NULL != (p = strchr(p, '-')))
4281 /*********************************************************************
4283 * Function : actions_to_radio
4285 * Description : Converts a actionsfile entry into settings for
4286 * radio buttons and edit boxes on a HTML form.
4289 * 1 : exports = List of substitutions to add to.
4290 * 2 : action = Action to read
4292 * Returns : JB_ERR_OK on success
4293 * JB_ERR_MEMORY on out-of-memory
4295 *********************************************************************/
4296 static jb_err actions_to_radio(struct map * exports,
4297 const struct action_spec *action)
4308 mask = action->mask;
4311 /* sanity - prevents "-feature +feature" */
4315 #define DEFINE_ACTION_BOOL(name, bit) \
4316 if (!(mask & bit)) \
4318 current_mode = 'n'; \
4320 else if (add & bit) \
4322 current_mode = 'y'; \
4326 current_mode = 'x'; \
4328 if (map_radio(exports, name, "ynx", current_mode)) \
4330 return JB_ERR_MEMORY; \
4333 #define DEFINE_ACTION_STRING(name, bit, index) \
4334 DEFINE_ACTION_BOOL(name, bit); \
4337 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default) \
4340 checked = !strcmp(action->string[index], value); \
4344 checked = is_default; \
4346 mapped_param |= checked; \
4347 if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
4349 return JB_ERR_MEMORY; \
4352 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val) \
4353 if (map(exports, name "-param-custom", 1, \
4354 ((!mapped_param) ? "checked" : ""), 1)) \
4356 return JB_ERR_MEMORY; \
4358 if (map(exports, name "-param", 1, \
4359 (((add & bit) && !mapped_param) ? \
4360 action->string[index] : default_val), 1)) \
4362 return JB_ERR_MEMORY; \
4365 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val) \
4366 if (map(exports, name "-param", 1, \
4367 ((add & bit) ? action->string[index] : default_val), 1)) \
4369 return JB_ERR_MEMORY; \
4372 #define DEFINE_ACTION_MULTI(name, index) \
4373 if (action->multi_add[index]->first) \
4375 current_mode = 'y'; \
4377 else if (action->multi_remove_all[index]) \
4379 current_mode = 'n'; \
4381 else if (action->multi_remove[index]->first) \
4383 current_mode = 'y'; \
4387 current_mode = 'x'; \
4389 if (map_radio(exports, name, "ynx", current_mode)) \
4391 return JB_ERR_MEMORY; \
4394 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
4396 #include "actionlist.h"
4398 #undef DEFINE_ACTION_MULTI
4399 #undef DEFINE_ACTION_STRING
4400 #undef DEFINE_ACTION_BOOL
4401 #undef DEFINE_ACTION_ALIAS
4402 #undef DEFINE_CGI_PARAM_CUSTOM
4403 #undef DEFINE_CGI_PARAM_RADIO
4404 #undef DEFINE_CGI_PARAM_NO_RADIO
4409 /*********************************************************************
4411 * Function : action_render_string_filters_template
4413 * Description : Converts a actionsfile entry into HTML template for actions with string
4414 * filters (currently SUPPRESS-TAG actions only)
4417 * 1 : exports = List of substitutions to add to.
4418 * 2 : action = Action to read
4419 * 3 : filter_template = template to fill
4420 * 4 : type = filter type info for rendered values/macro name
4422 * Returns : JB_ERR_OK on success
4423 * JB_ERR_MEMORY on out-of-memory
4425 *********************************************************************/
4426 static jb_err action_render_string_filters_template(struct map * exports,
4427 const struct action_spec *action,
4428 const char* filter_template,
4429 const struct filter_type_info *type)
4431 jb_err err = JB_ERR_OK;
4432 int filter_identifier = 0;
4434 char *prepared_template = strdup("");
4436 struct action_multi {
4438 struct list_entry *list;
4441 struct action_multi desc[] = {
4442 { 'y', action->multi_add[type->multi_action_index][0].first },
4443 { 'n', action->multi_remove[type->multi_action_index][0].first }
4446 for (i = 0; i < SZ(desc); ++i)
4448 const char radio = desc[i].radio;
4449 struct list_entry *entry = desc[i].list;
4450 for (;(!err) && (entry != NULL); entry = entry->next)
4453 struct map *line_exports;
4455 /* Generate a unique serial number */
4456 snprintf(number, sizeof(number), "%x", filter_identifier++);
4458 line_exports = new_map();
4459 if (line_exports == NULL)
4461 err = JB_ERR_MEMORY;
4466 if (!err) err = map(line_exports, "index", 1, number, 1);
4467 if (!err) err = map(line_exports, "name", 1, entry->str, 1);
4468 if (!err) err = map_radio(line_exports, "this-filter", "ynx", radio);
4469 if (!err) err = map(line_exports, "filter-type", 1, type->type, 1);
4470 if (!err) err = map(line_exports, "abbr-filter-type", 1, type->abbr_type, 1);
4471 if (!err) err = map(line_exports, "anchor", 1, type->anchor, 1);
4474 filter_line = strdup(filter_template);
4475 if (filter_line == NULL) err = JB_ERR_MEMORY;
4477 if (!err) err = template_fill(&filter_line, line_exports);
4478 if (!err) err = string_join(&prepared_template, filter_line);
4480 free_map(line_exports);
4484 if (!err) map(exports, type->macro_name, 1, prepared_template, 1);
4485 freez(prepared_template);
4489 /*********************************************************************
4491 * Function : actions_from_radio
4493 * Description : Converts a map of parameters passed to a CGI function
4494 * into an actionsfile entry.
4497 * 1 : parameters = parameters to the CGI call
4498 * 2 : action = Action to change. Must be valid before
4499 * the call, actions not specified will be
4502 * Returns : JB_ERR_OK on success
4503 * JB_ERR_MEMORY on out-of-memory
4505 *********************************************************************/
4506 static jb_err actions_from_radio(const struct map * parameters,
4507 struct action_spec *action)
4512 const char * js_name;
4513 jb_err err = JB_ERR_OK;
4518 /* Statics are generally a potential race condition,
4519 * but in this case we're safe and don't need semaphores.
4520 * Be careful if you modify this function.
4522 * The js_name_arr's are never free()d, but this is no
4523 * problem, since they will only be created once and
4524 * used by all threads thereafter. -oes
4527 #define JAVASCRIPTIFY(dest_var, string) \
4529 static int first_time = 1; \
4530 static char *js_name_arr; \
4533 js_name_arr = strdup(string); \
4534 javascriptify(js_name_arr); \
4536 dest_var = js_name_arr; \
4540 #define DEFINE_ACTION_BOOL(name, bit) \
4541 JAVASCRIPTIFY(js_name, name); \
4542 ch = get_char_param(parameters, js_name); \
4545 action->add |= bit; \
4546 action->mask |= bit; \
4548 else if (ch == 'N') \
4550 action->add &= ~bit; \
4551 action->mask &= ~bit; \
4553 else if (ch == 'X') \
4555 action->add &= ~bit; \
4556 action->mask |= bit; \
4559 #define DEFINE_ACTION_STRING(name, bit, index) \
4560 JAVASCRIPTIFY(js_name, name); \
4561 ch = get_char_param(parameters, js_name); \
4565 JAVASCRIPTIFY(js_name, name "-mode"); \
4566 if (!err) err = get_string_param(parameters, js_name, ¶m); \
4567 if ((param == NULL) || (0 == strcmp(param, "CUSTOM"))) \
4569 JAVASCRIPTIFY(js_name, name "-param"); \
4570 if (!err) err = get_string_param(parameters, js_name, ¶m); \
4572 if (param != NULL) \
4574 if (NULL == (param_dup = strdup(param))) \
4576 return JB_ERR_MEMORY; \
4578 freez(action->string[index]); \
4579 action->add |= bit; \
4580 action->mask |= bit; \
4581 action->string[index] = param_dup; \
4584 else if (ch == 'N') \
4586 if (action->add & bit) \
4588 freez(action->string[index]); \
4590 action->add &= ~bit; \
4591 action->mask &= ~bit; \
4593 else if (ch == 'X') \
4595 if (action->add & bit) \
4597 freez(action->string[index]); \
4599 action->add &= ~bit; \
4600 action->mask |= bit; \
4603 #define DEFINE_ACTION_MULTI(name, index) \
4604 JAVASCRIPTIFY(js_name, name); \
4605 ch = get_char_param(parameters, js_name); \
4610 else if (ch == 'N') \
4612 list_remove_all(action->multi_add[index]); \
4613 list_remove_all(action->multi_remove[index]); \
4614 action->multi_remove_all[index] = 1; \
4616 else if (ch == 'X') \
4618 list_remove_all(action->multi_add[index]); \
4619 list_remove_all(action->multi_remove[index]); \
4620 action->multi_remove_all[index] = 0; \
4623 #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
4625 #include "actionlist.h"
4627 #undef DEFINE_ACTION_MULTI
4628 #undef DEFINE_ACTION_STRING
4629 #undef DEFINE_ACTION_BOOL
4630 #undef DEFINE_ACTION_ALIAS
4631 #undef JAVASCRIPTIFY
4635 #endif /* def FEATURE_CGI_EDIT_ACTIONS */
4638 #ifdef FEATURE_TOGGLE
4639 /*********************************************************************
4641 * Function : cgi_toggle
4643 * Description : CGI function that adds a new empty section to
4647 * 1 : csp = Current client state (buffers, headers, etc...)
4648 * 2 : rsp = http_response data structure for output
4649 * 3 : parameters = map of cgi parameters
4652 * set : If present, how to change toggle setting:
4653 * "enable", "disable", "toggle", or none (default).
4654 * mini : If present, use mini reply template.
4656 * Returns : JB_ERR_OK on success
4657 * JB_ERR_MEMORY on out-of-memory
4659 *********************************************************************/
4660 jb_err cgi_toggle(struct client_state *csp,
4661 struct http_response *rsp,
4662 const struct map *parameters)
4664 struct map *exports;
4666 const char *template_name;
4672 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE))
4674 return cgi_error_disabled(csp, rsp);
4677 mode = get_char_param(parameters, "set");
4682 global_toggle_state = 1;
4684 else if (mode == 'D')
4687 global_toggle_state = 0;
4689 else if (mode == 'T')
4692 global_toggle_state = !global_toggle_state;
4695 log_error(LOG_LEVEL_INFO, "Now toggled %s.", global_toggle_state ? "ON" : "OFF");
4697 if (NULL == (exports = default_exports(csp, "toggle")))
4699 return JB_ERR_MEMORY;
4702 template_name = (get_char_param(parameters, "mini")
4706 return template_fill_for_cgi(csp, template_name, exports, rsp);
4708 #endif /* def FEATURE_TOGGLE */