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-2023 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 action_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 action_type_info action_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"
256 ACTION_MULTI_CLIENT_BODY_TAGGER,
257 "client-body-tagger-params", "client-body-tagger",
258 "client-body-tagger-all", "client_body_tagger_all",
259 "Q", "CLIENT-BODY-TAGGER"
262 ACTION_MULTI_ADD_HEADER,
263 "add-header-params", "add-header",
264 "add-header-all", "add_header_all",
267 #ifdef FEATURE_EXTERNAL_FILTERS
269 ACTION_MULTI_EXTERNAL_FILTER,
270 "external-content-filter-params", "external-filter",
271 "external-content-filter-all", "external_content_filter_all",
272 "E", "EXTERNAL-CONTENT-FILTER"
278 * Information about the string filter actions.
279 * Used for macro replacement in action_render_string_actions_template
281 struct string_action_type_info
283 const enum filter_type action_type; /**< Action type */
284 const char *description; /**< Action description */
285 const char *input_description; /**< Input field description */
288 /* String action types: special CGI handling */
289 static const struct string_action_type_info string_action_type_info[] =
293 "Suppress tag", "Tag to suppress"
297 "Add HTTP client header", "HTTP client header to add"
301 /* FIXME: Following non-static functions should be prototyped in .h or made static */
303 /* Functions to read and write arbitrary config files */
304 jb_err edit_read_file(struct client_state *csp,
305 const struct map *parameters,
307 struct editable_file **pfile);
308 jb_err edit_write_file(struct editable_file * file);
309 void edit_free_file(struct editable_file * file);
311 /* Functions to read and write actions files */
312 jb_err edit_parse_actions_file(struct editable_file * file);
313 jb_err edit_read_actions_file(struct client_state *csp,
314 struct http_response *rsp,
315 const struct map *parameters,
317 struct editable_file **pfile);
320 jb_err cgi_error_modified(struct client_state *csp,
321 struct http_response *rsp,
322 const char *filename);
323 jb_err cgi_error_parse(struct client_state *csp,
324 struct http_response *rsp,
325 struct editable_file *file);
326 jb_err cgi_error_file(struct client_state *csp,
327 struct http_response *rsp,
328 const char *filename);
329 jb_err cgi_error_file_read_only(struct client_state *csp,
330 struct http_response *rsp,
331 const char *filename);
333 /* Internal arbitrary config file support functions */
334 static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline);
335 static void edit_free_file_lines(struct file_line * first_line);
337 /* Internal actions file support functions */
338 static int match_actions_file_header_line(const char * line, const char * name);
339 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue);
341 /* Internal parameter parsing functions */
342 static jb_err get_url_spec_param(struct client_state *csp,
343 const struct map *parameters,
348 /* Internal actionsfile <==> HTML conversion functions */
349 static jb_err map_radio(struct map * exports,
350 const char * optionname,
353 static jb_err actions_to_radio(struct map * exports,
354 const struct action_spec *action);
355 static jb_err actions_from_radio(const struct map * parameters,
356 struct action_spec *action);
357 static jb_err action_render_string_actions_template(struct map * exports,
358 const struct action_spec *action,
359 const char* action_template,
360 const struct string_action_type_info *string_action_type);
363 static jb_err map_copy_parameter_html(struct map *out,
364 const struct map *in,
367 static jb_err get_file_name_param(struct client_state *csp,
368 const struct map *parameters,
369 const char *param_name,
370 const char **pfilename);
372 /*********************************************************************
374 * Function : stringify
376 * Description : Convert a number into a dynamically allocated string.
379 * 1 : number = The number to convert.
381 * Returns : String with link target, or NULL if out of memory
383 *********************************************************************/
384 static char *stringify(const unsigned number)
388 snprintf(buf, sizeof(buf), "%u", number);
393 /*********************************************************************
395 * Function : map_copy_parameter_html
397 * Description : Copy a CGI parameter from one map to another, HTML
401 * 1 : out = target map
402 * 2 : in = source map
403 * 3 : name = name of cgi parameter to copy
405 * Returns : JB_ERR_OK on success
406 * JB_ERR_MEMORY on out-of-memory
407 * JB_ERR_CGI_PARAMS if the parameter doesn't exist
410 *********************************************************************/
411 static jb_err map_copy_parameter_html(struct map *out,
412 const struct map *in,
422 value = lookup(in, name);
423 err = map(out, name, 1, html_encode(value), 0);
430 else if (*value == '\0')
432 return JB_ERR_CGI_PARAMS;
441 /*********************************************************************
443 * Function : cgi_edit_actions_url_form
445 * Description : CGI function that displays a form for
449 * 1 : csp = Current client state (buffers, headers, etc...)
450 * 2 : rsp = http_response data structure for output
451 * 3 : parameters = map of cgi parameters
454 * i : (action index) Identifies the file to edit
455 * v : (version) File's last-modified time
456 * p : (pattern) Line number of pattern to edit
458 * Returns : JB_ERR_OK on success
459 * JB_ERR_MEMORY on out-of-memory
460 * JB_ERR_CGI_PARAMS if the CGI parameters are not
461 * specified or not valid.
463 *********************************************************************/
464 jb_err cgi_edit_actions_url_form(struct client_state *csp,
465 struct http_response *rsp,
466 const struct map *parameters)
468 struct map * exports;
470 struct editable_file * file;
471 struct file_line * cur_line;
472 unsigned line_number;
473 unsigned section_start_line_number = 0;
480 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
482 return cgi_error_disabled(csp, rsp);
485 err = get_number_param(csp, parameters, "p", &patternid);
491 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
494 /* No filename specified, can't read file, modified, or out of memory. */
495 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
498 cur_line = file->lines;
500 for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
502 if (cur_line->type == FILE_LINE_ACTION)
504 section_start_line_number = line_number;
506 cur_line = cur_line->next;
509 if ( (cur_line == NULL)
510 || (line_number != patternid)
512 || (cur_line->type != FILE_LINE_URL))
514 /* Invalid "patternid" parameter */
515 edit_free_file(file);
516 return JB_ERR_CGI_PARAMS;
519 if (NULL == (exports = default_exports(csp, NULL)))
521 edit_free_file(file);
522 return JB_ERR_MEMORY;
525 err = map(exports, "f", 1, stringify(file->identifier), 0);
526 if (!err) err = map(exports, "v", 1, file->version_str, 1);
527 if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
528 if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
529 if (!err) err = map(exports, "jumptarget", 1, stringify(section_start_line_number), 0);
531 edit_free_file(file);
539 return template_fill_for_cgi(csp, "edit-actions-url-form", exports, rsp);
543 /*********************************************************************
545 * Function : cgi_edit_actions_add_url_form
547 * Description : CGI function that displays a form for
551 * 1 : csp = Current client state (buffers, headers, etc...)
552 * 2 : rsp = http_response data structure for output
553 * 3 : parameters = map of cgi parameters
556 * f : (filename) Identifies the file to edit
557 * v : (version) File's last-modified time
558 * s : (section) Line number of section to edit
560 * Returns : JB_ERR_OK on success
561 * JB_ERR_MEMORY on out-of-memory
562 * JB_ERR_CGI_PARAMS if the CGI parameters are not
563 * specified or not valid.
565 *********************************************************************/
566 jb_err cgi_edit_actions_add_url_form(struct client_state *csp,
567 struct http_response *rsp,
568 const struct map *parameters)
577 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
579 return cgi_error_disabled(csp, rsp);
582 if (NULL == (exports = default_exports(csp, NULL)))
584 return JB_ERR_MEMORY;
587 err = map_copy_parameter_html(exports, parameters, "f");
588 if (!err) err = map_copy_parameter_html(exports, parameters, "v");
589 if (!err) err = map_copy_parameter_html(exports, parameters, "s");
597 return template_fill_for_cgi(csp, "edit-actions-add-url-form", exports, rsp);
601 /*********************************************************************
603 * Function : cgi_edit_actions_remove_url_form
605 * Description : CGI function that displays a form for
609 * 1 : csp = Current client state (buffers, headers, etc...)
610 * 2 : rsp = http_response data structure for output
611 * 3 : parameters = map of cgi parameters
614 * f : (number) The action file identifier.
615 * v : (version) File's last-modified time
616 * p : (pattern) Line number of pattern to edit
618 * Returns : JB_ERR_OK on success
619 * JB_ERR_MEMORY on out-of-memory
620 * JB_ERR_CGI_PARAMS if the CGI parameters are not
621 * specified or not valid.
623 *********************************************************************/
624 jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
625 struct http_response *rsp,
626 const struct map *parameters)
628 struct map * exports;
630 struct editable_file * file;
631 struct file_line * cur_line;
632 unsigned line_number;
633 unsigned section_start_line_number = 0;
640 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
642 return cgi_error_disabled(csp, rsp);
645 err = get_number_param(csp, parameters, "p", &patternid);
651 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
654 /* No filename specified, can't read file, modified, or out of memory. */
655 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
658 cur_line = file->lines;
660 for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
662 if (cur_line->type == FILE_LINE_ACTION)
664 section_start_line_number = line_number;
666 cur_line = cur_line->next;
669 if ( (cur_line == NULL)
670 || (line_number != patternid)
672 || (cur_line->type != FILE_LINE_URL))
674 /* Invalid "patternid" parameter */
675 edit_free_file(file);
676 return JB_ERR_CGI_PARAMS;
679 if (NULL == (exports = default_exports(csp, NULL)))
681 edit_free_file(file);
682 return JB_ERR_MEMORY;
685 err = map(exports, "f", 1, stringify(file->identifier), 0);
686 if (!err) err = map(exports, "v", 1, file->version_str, 1);
687 if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
688 if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
689 if (!err) err = map(exports, "jumptarget", 1, stringify(section_start_line_number), 0);
690 if (!err) err = map(exports, "actions-file", 1, html_encode(file->filename), 0);
692 edit_free_file(file);
700 return template_fill_for_cgi(csp, "edit-actions-remove-url-form", exports, rsp);
704 /*********************************************************************
706 * Function : edit_write_file
708 * Description : Write a complete file to disk.
711 * 1 : file = File to write.
713 * Returns : JB_ERR_OK on success
714 * JB_ERR_FILE on error writing to file.
715 * JB_ERR_MEMORY on out of memory
717 *********************************************************************/
718 jb_err edit_write_file(struct editable_file * file)
721 struct file_line * cur_line;
722 struct stat statbuf[1];
723 char version_buf[22]; /* 22 = ceil(log10(2^64)) + 2 = max number of
724 digits in time_t, assuming this is a 64-bit
725 machine, plus null terminator, plus one
729 assert(file->filename);
731 if (NULL == (fp = fopen(file->filename, "wb")))
736 cur_line = file->lines;
737 while (cur_line != NULL)
741 if (fputs(cur_line->raw, fp) < 0)
749 if (cur_line->prefix)
751 if (fputs(cur_line->prefix, fp) < 0)
757 if (cur_line->unprocessed)
760 if (NULL != strchr(cur_line->unprocessed, '#'))
762 /* Must quote '#' characters */
769 /* Count number of # characters, so we know length of output string */
770 src = cur_line->unprocessed;
771 while (NULL != (src = strchr(src, '#')))
778 /* Allocate new memory for string */
779 len = strlen(cur_line->unprocessed) + (size_t)numhash;
780 str = malloc_or_die(len + 1);
782 /* Copy string but quote hashes */
783 src = cur_line->unprocessed;
791 assert(numhash >= 0);
797 assert(numhash == 0);
798 assert(strlen(str) == len);
799 assert(str == dest - len);
800 assert(src - len <= cur_line->unprocessed);
802 if ((strlen(str) != len) || (numhash != 0))
805 * Escaping didn't work as expected, go spread the news.
806 * Only reached in non-debugging builds.
808 log_error(LOG_LEVEL_ERROR,
809 "Looks like hash escaping failed. %s might be corrupted now.",
813 if (fputs(str, fp) < 0)
824 /* Can write without quoting '#' characters. */
825 if (fputs(cur_line->unprocessed, fp) < 0)
831 if (fputs(NEWLINE(file->newline), fp) < 0)
839 /* FIXME: Write data from file->data->whatever */
843 cur_line = cur_line->next;
849 /* Update the version stamp in the file structure, since we just
850 * wrote to the file & changed it's date.
852 if (stat(file->filename, statbuf) < 0)
854 /* Error, probably file not found. */
857 file->version = (unsigned)statbuf->st_mtime;
859 /* Correct file->version_str */
860 freez(file->version_str);
861 snprintf(version_buf, sizeof(version_buf), "%u", file->version);
862 version_buf[sizeof(version_buf)-1] = '\0';
863 file->version_str = strdup_or_die(version_buf);
869 /*********************************************************************
871 * Function : edit_free_file
873 * Description : Free a complete file in memory.
876 * 1 : file = Data structure to free.
880 *********************************************************************/
881 void edit_free_file(struct editable_file * file)
885 /* Silently ignore NULL pointer */
889 edit_free_file_lines(file->lines);
890 freez(file->version_str);
892 file->parse_error_text = NULL; /* Statically allocated */
893 file->parse_error = NULL;
899 /*********************************************************************
901 * Function : edit_free_file_lines
903 * Description : Free an entire linked list of file lines.
906 * 1 : first_line = Data structure to free.
910 *********************************************************************/
911 static void edit_free_file_lines(struct file_line * first_line)
913 struct file_line * next_line;
915 while (first_line != NULL)
917 next_line = first_line->next;
918 first_line->next = NULL;
919 freez(first_line->raw);
920 freez(first_line->prefix);
921 freez(first_line->unprocessed);
922 switch(first_line->type)
924 case 0: /* special case if memory zeroed */
925 case FILE_LINE_UNPROCESSED:
926 case FILE_LINE_BLANK:
927 case FILE_LINE_ALIAS_HEADER:
928 case FILE_LINE_SETTINGS_HEADER:
929 case FILE_LINE_DESCRIPTION_HEADER:
930 case FILE_LINE_DESCRIPTION_ENTRY:
931 case FILE_LINE_ALIAS_ENTRY:
933 /* No data is stored for these */
936 case FILE_LINE_ACTION:
937 free_action(first_line->data.action);
940 case FILE_LINE_SETTINGS_ENTRY:
941 freez(first_line->data.setting.name);
942 freez(first_line->data.setting.svalue);
945 /* Should never happen */
949 first_line->type = 0; /* paranoia */
951 first_line = next_line;
956 /*********************************************************************
958 * Function : match_actions_file_header_line
960 * Description : Match an actions file {{header}} line
963 * 1 : line = String from file
964 * 2 : name = Header to match against
966 * Returns : 0 iff they match.
968 *********************************************************************/
969 static int match_actions_file_header_line(const char * line, const char * name)
977 if ((line[0] != '{') || (line[1] != '{'))
983 /* Look for optional whitespace */
984 while ((*line == ' ') || (*line == '\t'))
989 /* Look for the specified name (case-insensitive) */
991 if (0 != strncmpic(line, name, len))
997 /* Look for optional whitespace */
998 while ((*line == ' ') || (*line == '\t'))
1003 /* Look for "}}" and end of string*/
1004 if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0'))
1014 /*********************************************************************
1016 * Function : match_actions_file_header_line
1018 * Description : Match an actions file {{header}} line
1021 * 1 : line = String from file. Must not start with
1022 * whitespace (else infinite loop!)
1023 * 2 : pname = Destination for name
1024 * 2 : pvalue = Destination for value
1026 * Returns : JB_ERR_OK on success
1027 * JB_ERR_MEMORY on out-of-memory
1028 * JB_ERR_PARSE if there's no "=" sign, or if there's
1029 * nothing before the "=" sign (but empty
1030 * values *after* the "=" sign are legal).
1032 *********************************************************************/
1033 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue)
1035 const char * name_end;
1036 const char * value_start;
1042 assert(*line != ' ');
1043 assert(*line != '\t');
1048 value_start = strchr(line, '=');
1049 if ((value_start == NULL) || (value_start == line))
1051 return JB_ERR_PARSE;
1054 name_end = value_start - 1;
1056 /* Eat any whitespace before the '=' */
1057 while ((*name_end == ' ') || (*name_end == '\t'))
1060 * we already know we must have at least 1 non-ws char
1061 * at start of buf - no need to check
1066 name_len = (size_t)(name_end - line) + 1; /* Length excluding \0 */
1067 *pname = malloc_or_die(name_len + 1);
1068 strncpy(*pname, line, name_len);
1069 (*pname)[name_len] = '\0';
1071 /* Eat any the whitespace after the '=' */
1073 while ((*value_start == ' ') || (*value_start == '\t'))
1078 if (NULL == (*pvalue = strdup(value_start)))
1082 return JB_ERR_MEMORY;
1089 /*********************************************************************
1091 * Function : edit_parse_actions_file
1093 * Description : Parse an actions file in memory.
1095 * Passed linked list must have the "data" member
1096 * zeroed, and must contain valid "next" and
1097 * "unprocessed" fields. The "raw" and "prefix"
1098 * fields are ignored, and "type" is just overwritten.
1100 * Note that on error the file may have been
1104 * 1 : file = Actions file to be parsed in-place.
1106 * Returns : JB_ERR_OK on success
1107 * JB_ERR_MEMORY on out-of-memory
1108 * JB_ERR_PARSE on error
1110 *********************************************************************/
1111 jb_err edit_parse_actions_file(struct editable_file * file)
1113 struct file_line * cur_line;
1115 const char * text; /* Text from a line */
1116 char * name; /* For lines of the form name=value */
1117 char * value; /* For lines of the form name=value */
1118 struct action_alias * alias_list = NULL;
1119 jb_err err = JB_ERR_OK;
1121 /* alias_list contains the aliases defined in this file.
1122 * It might be better to use the "file_line.data" fields
1123 * in the relevant places instead.
1126 cur_line = file->lines;
1128 /* A note about blank line support: Blank lines should only
1129 * ever occur as the last line in the file. This function
1130 * is more forgiving than that - FILE_LINE_BLANK can occur
1134 /* Skip leading blanks. Should only happen if file is
1135 * empty (which is valid, but pointless).
1137 while ((cur_line != NULL)
1138 && (cur_line->unprocessed[0] == '\0'))
1141 cur_line->type = FILE_LINE_BLANK;
1142 cur_line = cur_line->next;
1145 if ((cur_line != NULL)
1146 && (cur_line->unprocessed[0] != '{'))
1148 /* File doesn't start with a header */
1149 file->parse_error = cur_line;
1150 file->parse_error_text = "First (non-comment) line of the file must contain a header.";
1151 return JB_ERR_PARSE;
1154 if ((cur_line != NULL) && (0 ==
1155 match_actions_file_header_line(cur_line->unprocessed, "settings")))
1157 cur_line->type = FILE_LINE_SETTINGS_HEADER;
1159 cur_line = cur_line->next;
1160 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1162 if (cur_line->unprocessed[0])
1164 cur_line->type = FILE_LINE_SETTINGS_ENTRY;
1166 err = split_line_on_equals(cur_line->unprocessed,
1167 &cur_line->data.setting.name,
1168 &cur_line->data.setting.svalue);
1169 if (err == JB_ERR_MEMORY)
1173 else if (err != JB_ERR_OK)
1175 /* Line does not contain a name=value pair */
1176 file->parse_error = cur_line;
1177 file->parse_error_text = "Expected a name=value pair on this {{description}} line, but couldn't find one.";
1178 return JB_ERR_PARSE;
1183 cur_line->type = FILE_LINE_BLANK;
1185 cur_line = cur_line->next;
1189 if ((cur_line != NULL) && (0 ==
1190 match_actions_file_header_line(cur_line->unprocessed, "description")))
1192 cur_line->type = FILE_LINE_DESCRIPTION_HEADER;
1194 cur_line = cur_line->next;
1195 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1197 if (cur_line->unprocessed[0])
1199 cur_line->type = FILE_LINE_DESCRIPTION_ENTRY;
1203 cur_line->type = FILE_LINE_BLANK;
1205 cur_line = cur_line->next;
1209 if ((cur_line != NULL) && (0 ==
1210 match_actions_file_header_line(cur_line->unprocessed, "alias")))
1212 cur_line->type = FILE_LINE_ALIAS_HEADER;
1214 cur_line = cur_line->next;
1215 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1217 if (cur_line->unprocessed[0])
1219 /* define an alias */
1220 struct action_alias * new_alias;
1222 cur_line->type = FILE_LINE_ALIAS_ENTRY;
1224 err = split_line_on_equals(cur_line->unprocessed, &name, &value);
1225 if (err == JB_ERR_MEMORY)
1227 free_alias_list(alias_list);
1230 else if (err != JB_ERR_OK)
1232 /* Line does not contain a name=value pair */
1233 file->parse_error = cur_line;
1234 file->parse_error_text = "Expected a name=value pair on this {{alias}} line, but couldn't find one.";
1235 free_alias_list(alias_list);
1236 return JB_ERR_PARSE;
1239 new_alias = zalloc_or_die(sizeof(*new_alias));
1241 err = get_actions(value, alias_list, new_alias->action);
1244 /* Invalid action or out of memory */
1248 free_alias_list(alias_list);
1249 if (err == JB_ERR_MEMORY)
1255 /* Line does not contain a name=value pair */
1256 file->parse_error = cur_line;
1257 file->parse_error_text = "This alias does not specify a valid set of actions.";
1258 return JB_ERR_PARSE;
1264 new_alias->name = name;
1267 new_alias->next = alias_list;
1268 alias_list = new_alias;
1272 cur_line->type = FILE_LINE_BLANK;
1274 cur_line = cur_line->next;
1278 /* Header done, process the main part of the file */
1279 while (cur_line != NULL)
1281 /* At this point, (cur_line->unprocessed[0] == '{') */
1282 assert(cur_line->unprocessed[0] == '{');
1283 text = cur_line->unprocessed + 1;
1284 len = strlen(text) - 1;
1285 if (text[len] != '}')
1287 /* No closing } on header */
1288 free_alias_list(alias_list);
1289 file->parse_error = cur_line;
1290 file->parse_error_text = "Headers starting with '{' must have a "
1291 "closing bracket ('}'). Headers starting with two brackets ('{{') "
1292 "must close with two brackets ('}}').";
1293 return JB_ERR_PARSE;
1298 /* An invalid {{ header. */
1299 free_alias_list(alias_list);
1300 file->parse_error = cur_line;
1301 file->parse_error_text = "Unknown or unexpected two-bracket header. "
1302 "Please remember that the system (two-bracket) headers must "
1303 "appear in the order {{settings}}, {{description}}, {{alias}}, "
1304 "and must appear before any actions (one-bracket) headers. "
1305 "Also note that system headers may not be repeated.";
1306 return JB_ERR_PARSE;
1309 while ((*text == ' ') || (*text == '\t'))
1314 while ((len > (size_t)0)
1315 && ((text[len - 1] == ' ')
1316 || (text[len - 1] == '\t')))
1321 cur_line->type = FILE_LINE_ACTION;
1323 /* Remove {} and make copy */
1324 value = malloc_or_die(len + 1);
1325 strncpy(value, text, len);
1329 err = get_actions(value, alias_list, cur_line->data.action);
1332 /* Invalid action or out of memory */
1334 free_alias_list(alias_list);
1335 if (err == JB_ERR_MEMORY)
1341 /* Line does not contain a name=value pair */
1342 file->parse_error = cur_line;
1343 file->parse_error_text = "This header does not specify a valid set of actions.";
1344 return JB_ERR_PARSE;
1348 /* Done with string - it was clobbered anyway */
1351 /* Process next line */
1352 cur_line = cur_line->next;
1354 /* Loop processing URL patterns */
1355 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1357 if (cur_line->unprocessed[0])
1359 /* Could parse URL here, but this isn't currently needed */
1361 cur_line->type = FILE_LINE_URL;
1365 cur_line->type = FILE_LINE_BLANK;
1367 cur_line = cur_line->next;
1369 } /* End main while(cur_line != NULL) loop */
1371 free_alias_list(alias_list);
1377 /*********************************************************************
1379 * Function : edit_read_file_lines
1381 * Description : Read all the lines of a file into memory.
1382 * Handles whitespace, comments and line continuation.
1385 * 1 : fp = File to read from. On return, this will be
1386 * at EOF but it will not have been closed.
1387 * 2 : pfile = Destination for a linked list of file_lines.
1388 * Will be set to NULL on error.
1389 * 3 : newline = How to handle newlines.
1391 * Returns : JB_ERR_OK on success
1392 * JB_ERR_MEMORY on out-of-memory
1394 *********************************************************************/
1395 jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline)
1397 struct file_line * first_line; /* Keep for return value or to free */
1398 struct file_line * cur_line; /* Current line */
1399 struct file_line * prev_line; /* Entry with prev_line->next = cur_line */
1407 cur_line = first_line = zalloc_or_die(sizeof(struct file_line));
1409 cur_line->type = FILE_LINE_UNPROCESSED;
1411 rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1414 /* Out of memory or empty file. */
1415 /* Note that empty file is not an error we propagate up */
1417 return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval);
1422 prev_line = cur_line;
1423 cur_line = prev_line->next = zalloc_or_die(sizeof(struct file_line));
1425 cur_line->type = FILE_LINE_UNPROCESSED;
1427 rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1428 if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
1431 edit_free_file_lines(first_line);
1432 return JB_ERR_MEMORY;
1436 while (rval != JB_ERR_FILE);
1440 /* We allocated one too many - free it */
1441 prev_line->next = NULL;
1444 *pfile = first_line;
1449 /*********************************************************************
1451 * Function : edit_read_file
1453 * Description : Read a complete file into memory.
1454 * Handles CGI parameter parsing. If requested, also
1455 * checks the file's modification timestamp.
1458 * 1 : csp = Current client state (buffers, headers, etc...)
1459 * 2 : parameters = map of cgi parameters.
1460 * 3 : require_version = true to check "ver" parameter.
1461 * 4 : pfile = Destination for the file. Will be set
1465 * f : The action file identifier.
1466 * ver : (Only if require_version is nonzero)
1467 * Timestamp of the actions file. If wrong, this
1468 * function fails with JB_ERR_MODIFIED.
1470 * Returns : JB_ERR_OK on success
1471 * JB_ERR_MEMORY on out-of-memory
1472 * JB_ERR_CGI_PARAMS if "filename" was not specified
1474 * JB_ERR_FILE if the file cannot be opened or
1476 * JB_ERR_MODIFIED if version checking was requested and
1477 * failed - the file was modified outside
1478 * of this CGI editor instance.
1480 *********************************************************************/
1481 jb_err edit_read_file(struct client_state *csp,
1482 const struct map *parameters,
1483 int require_version,
1484 struct editable_file **pfile)
1486 struct file_line * lines;
1489 const char *filename = NULL;
1490 struct editable_file * file;
1491 unsigned version = 0;
1492 struct stat statbuf[1];
1493 char version_buf[22];
1494 int newline = NEWLINE_UNKNOWN;
1503 err = get_number_param(csp, parameters, "f", &i);
1504 if ((JB_ERR_OK == err) && (i < MAX_AF_FILES) && (NULL != csp->config->actions_file[i]))
1506 filename = csp->config->actions_file[i];
1508 else if (JB_ERR_CGI_PARAMS == err)
1511 * Probably an old-school URL like
1512 * http://config.privoxy.org/edit-actions-list?f=default
1514 get_file_name_param(csp, parameters, "f", &filename);
1517 if (NULL == filename || stat(filename, statbuf) < 0)
1519 /* Error, probably file not found. */
1522 version = (unsigned) statbuf->st_mtime;
1524 if (require_version)
1526 unsigned specified_version;
1527 err = get_number_param(csp, parameters, "v", &specified_version);
1533 if (version != specified_version)
1535 return JB_ERR_MODIFIED;
1539 if (NULL == (fp = fopen(filename,"rb")))
1544 err = edit_read_file_lines(fp, &lines, &newline);
1553 file = zalloc_or_die(sizeof(*file));
1555 file->lines = lines;
1556 file->newline = newline;
1557 file->filename = filename;
1558 file->version = version;
1559 file->identifier = i;
1561 /* Correct file->version_str */
1562 freez(file->version_str);
1563 snprintf(version_buf, sizeof(version_buf), "%u", file->version);
1564 version_buf[sizeof(version_buf)-1] = '\0';
1565 file->version_str = strdup_or_die(version_buf);
1572 /*********************************************************************
1574 * Function : edit_read_actions_file
1576 * Description : Read a complete actions file into memory.
1577 * Handles CGI parameter parsing. If requested, also
1578 * checks the file's modification timestamp.
1580 * If this function detects an error in the categories
1581 * JB_ERR_FILE, JB_ERR_MODIFIED, or JB_ERR_PARSE,
1582 * then it handles it by filling in the specified
1583 * response structure and returning JB_ERR_FILE.
1586 * 1 : csp = Current client state (buffers, headers, etc...)
1587 * 2 : rsp = HTTP response. Only filled in on error.
1588 * 2 : parameters = map of cgi parameters.
1589 * 3 : require_version = true to check "ver" parameter.
1590 * 4 : pfile = Destination for the file. Will be set
1594 * f : The actions file identifier.
1595 * ver : (Only if require_version is nonzero)
1596 * Timestamp of the actions file. If wrong, this
1597 * function fails with JB_ERR_MODIFIED.
1599 * Returns : JB_ERR_OK on success
1600 * JB_ERR_MEMORY on out-of-memory
1601 * JB_ERR_CGI_PARAMS if "filename" was not specified
1603 * JB_ERR_FILE if the file does not contain valid data,
1604 * or if file cannot be opened or
1605 * contains no data, or if version
1606 * checking was requested and failed.
1608 *********************************************************************/
1609 jb_err edit_read_actions_file(struct client_state *csp,
1610 struct http_response *rsp,
1611 const struct map *parameters,
1612 int require_version,
1613 struct editable_file **pfile)
1616 struct editable_file *file;
1617 static int acceptable_failures = ACCEPTABLE_TIMESTAMP_MISMATCHES - 1;
1625 err = edit_read_file(csp, parameters, require_version, &file);
1628 /* Try to handle if possible */
1629 if (err == JB_ERR_FILE)
1631 err = cgi_error_file(csp, rsp, lookup(parameters, "f"));
1633 else if (err == JB_ERR_MODIFIED)
1635 assert(require_version);
1636 err = cgi_error_modified(csp, rsp, lookup(parameters, "f"));
1637 log_error(LOG_LEVEL_ERROR,
1638 "Blocking CGI edit request due to modification time mismatch.");
1639 if (acceptable_failures > 0)
1641 log_error(LOG_LEVEL_INFO,
1642 "The CGI editor will be turned off after another %d mismatche(s).",
1643 acceptable_failures);
1644 acceptable_failures--;
1648 log_error(LOG_LEVEL_INFO,
1649 "Timestamp mismatch limit reached, turning CGI editor off. "
1650 "Reload the configuration file to re-enable it.");
1651 csp->config->feature_flags &= ~RUNTIME_FEATURE_CGI_EDIT_ACTIONS;
1654 if (err == JB_ERR_OK)
1657 * Signal to higher-level CGI code that there was a problem but we
1658 * handled it, they should just return JB_ERR_OK.
1665 err = edit_parse_actions_file(file);
1668 if (err == JB_ERR_PARSE)
1670 err = cgi_error_parse(csp, rsp, file);
1671 if (err == JB_ERR_OK)
1674 * Signal to higher-level CGI code that there was a problem but we
1675 * handled it, they should just return JB_ERR_OK.
1680 edit_free_file(file);
1689 /*********************************************************************
1691 * Function : get_file_name_param
1693 * Description : Get the name of the file to edit from the parameters
1694 * passed to a CGI function using the old syntax.
1695 * This function handles security checks and only
1696 * accepts files that Privoxy already knows.
1699 * 1 : csp = Current client state (buffers, headers, etc...)
1700 * 2 : parameters = map of cgi parameters
1701 * 3 : param_name = The name of the parameter to read
1702 * 4 : pfilename = pointer to the filename in
1703 * csp->config->actions_file[] if found. Set to NULL on error.
1705 * Returns : JB_ERR_OK on success
1706 * JB_ERR_MEMORY on out-of-memory
1707 * JB_ERR_CGI_PARAMS if "filename" was not specified
1710 *********************************************************************/
1711 static jb_err get_file_name_param(struct client_state *csp,
1712 const struct map *parameters,
1713 const char *param_name,
1714 const char **pfilename)
1717 const char suffix[] = ".action";
1732 param = lookup(parameters, param_name);
1735 return JB_ERR_CGI_PARAMS;
1738 len = strlen(param);
1739 if (len >= FILENAME_MAX)
1742 return JB_ERR_CGI_PARAMS;
1746 * Check every character to see if it's legal.
1747 * Totally unnecessary but we do it anyway.
1750 while ((ch = *s++) != '\0')
1752 if ( ((ch < 'A') || (ch > 'Z'))
1753 && ((ch < 'a') || (ch > 'z'))
1754 && ((ch < '0') || (ch > '9'))
1758 /* Probable hack attempt. */
1759 return JB_ERR_CGI_PARAMS;
1763 /* Append extension */
1764 name_size = len + strlen(suffix) + 1;
1765 name = malloc_or_die(name_size);
1766 strlcpy(name, param, name_size);
1767 strlcat(name, suffix, name_size);
1770 fullpath = make_path(csp->config->confdir, name);
1773 if (fullpath == NULL)
1775 return JB_ERR_MEMORY;
1778 /* Check if the file is known */
1779 for (i = 0; i < MAX_AF_FILES; i++)
1781 if (NULL != csp->config->actions_file[i] &&
1782 !strcmp(fullpath, csp->config->actions_file[i]))
1785 *pfilename = csp->config->actions_file[i];
1793 return JB_ERR_CGI_PARAMS;
1797 /*********************************************************************
1799 * Function : get_url_spec_param
1801 * Description : Get a URL pattern from the parameters
1802 * passed to a CGI function. Removes leading/trailing
1803 * spaces and validates it.
1806 * 1 : csp = Current client state (buffers, headers, etc...)
1807 * 2 : parameters = map of cgi parameters
1808 * 3 : name = Name of CGI parameter to read
1809 * 4 : pvalue = destination for value. Will be malloc()'d.
1810 * Set to NULL on error.
1812 * Returns : JB_ERR_OK on success
1813 * JB_ERR_MEMORY on out-of-memory
1814 * JB_ERR_CGI_PARAMS if the parameter was not specified
1817 *********************************************************************/
1818 static jb_err get_url_spec_param(struct client_state *csp,
1819 const struct map *parameters,
1823 const char *orig_param;
1826 struct pattern_spec compiled[1];
1836 orig_param = lookup(parameters, name);
1839 return JB_ERR_CGI_PARAMS;
1842 /* Copy and trim whitespace */
1843 param = strdup(orig_param);
1846 return JB_ERR_MEMORY;
1850 /* Must be non-empty, and can't allow 1st character to be '{' */
1851 if (param[0] == '\0' || param[0] == '{')
1854 return JB_ERR_CGI_PARAMS;
1857 /* Check for embedded newlines */
1858 for (s = param; *s != '\0'; s++)
1860 if ((*s == '\r') || (*s == '\n'))
1863 return JB_ERR_CGI_PARAMS;
1867 /* Check that regex is valid */
1872 return JB_ERR_MEMORY;
1874 err = create_pattern_spec(compiled, s);
1876 free_pattern_spec(compiled);
1880 return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
1883 if (param[strlen(param) - 1] == '\\')
1886 * Must protect trailing '\\' from becoming line continuation character.
1887 * Two methods: 1) If it's a domain only, add a trailing '/'.
1888 * 2) For path, add the do-nothing PCRE expression (?:) to the end
1890 if (strchr(param, '/') == NULL)
1892 err = string_append(¶m, "/");
1896 err = string_append(¶m, "(?:)");
1903 /* Check that the modified regex is valid */
1908 return JB_ERR_MEMORY;
1910 err = create_pattern_spec(compiled, s);
1912 free_pattern_spec(compiled);
1916 return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
1924 /*********************************************************************
1926 * Function : map_radio
1928 * Description : Map a set of radio button values. E.g. if you have
1929 * 3 radio buttons, declare them as:
1930 * <option type="radio" name="xyz" @xyz-a@>
1931 * <option type="radio" name="xyz" @xyz-b@>
1932 * <option type="radio" name="xyz" @xyz-c@>
1933 * Then map one of the @xyz-?@ variables to "checked"
1934 * and all the others to empty by calling:
1935 * map_radio(exports, "xyz", "abc", sel)
1936 * Where 'sel' is 'a', 'b', or 'c'.
1939 * 1 : exports = Exports map to modify.
1940 * 2 : optionname = name for map
1941 * 3 : values = null-terminated list of values;
1942 * 4 : value = Selected value.
1944 * CGI Parameters : None
1946 * Returns : JB_ERR_OK on success
1947 * JB_ERR_MEMORY on out-of-memory
1949 *********************************************************************/
1950 static jb_err map_radio(struct map * exports,
1951 const char * optionname,
1952 const char * values,
1958 const size_t len = strlen(optionname);
1959 const size_t buf_size = len + 3;
1965 buf = malloc_or_die(buf_size);
1967 strlcpy(buf, optionname, buf_size);
1969 /* XXX: this looks ... interesting */
1974 while ((c = *values++) != '\0')
1979 if (map(exports, buf, 1, "", 1))
1981 return JB_ERR_MEMORY;
1987 return map(exports, buf, 0, "checked", 1);
1991 /*********************************************************************
1993 * Function : cgi_error_modified
1995 * Description : CGI function that is called when a file is modified
1996 * outside the CGI editor.
1999 * 1 : csp = Current client state (buffers, headers, etc...)
2000 * 2 : rsp = http_response data structure for output
2001 * 3 : filename = The file that was modified.
2003 * CGI Parameters : none
2005 * Returns : JB_ERR_OK on success
2006 * JB_ERR_MEMORY on out-of-memory error.
2008 *********************************************************************/
2009 jb_err cgi_error_modified(struct client_state *csp,
2010 struct http_response *rsp,
2011 const char *filename)
2013 struct map *exports;
2020 if (NULL == (exports = default_exports(csp, NULL)))
2022 return JB_ERR_MEMORY;
2025 err = map(exports, "f", 1, html_encode(filename), 0);
2032 return template_fill_for_cgi(csp, "cgi-error-modified", exports, rsp);
2036 /*********************************************************************
2038 * Function : cgi_error_parse
2040 * Description : CGI function that is called when a file cannot
2041 * be parsed by the CGI editor.
2044 * 1 : csp = Current client state (buffers, headers, etc...)
2045 * 2 : rsp = http_response data structure for output
2046 * 3 : file = The file that was modified.
2048 * CGI Parameters : none
2050 * Returns : JB_ERR_OK on success
2051 * JB_ERR_MEMORY on out-of-memory error.
2053 *********************************************************************/
2054 jb_err cgi_error_parse(struct client_state *csp,
2055 struct http_response *rsp,
2056 struct editable_file *file)
2058 struct map *exports;
2060 struct file_line *cur_line;
2066 if (NULL == (exports = default_exports(csp, NULL)))
2068 return JB_ERR_MEMORY;
2071 err = map(exports, "f", 1, stringify(file->identifier), 0);
2072 if (!err) err = map(exports, "parse-error", 1, html_encode(file->parse_error_text), 0);
2074 cur_line = file->parse_error;
2077 if (!err) err = map(exports, "line-raw", 1, html_encode(cur_line->raw), 0);
2078 if (!err) err = map(exports, "line-data", 1, html_encode(cur_line->unprocessed), 0);
2086 return template_fill_for_cgi(csp, "cgi-error-parse", exports, rsp);
2090 /*********************************************************************
2092 * Function : cgi_error_file
2094 * Description : CGI function that is called when a file cannot be
2095 * opened by the CGI editor.
2098 * 1 : csp = Current client state (buffers, headers, etc...)
2099 * 2 : rsp = http_response data structure for output
2100 * 3 : filename = The file that was modified.
2102 * CGI Parameters : none
2104 * Returns : JB_ERR_OK on success
2105 * JB_ERR_MEMORY on out-of-memory error.
2107 *********************************************************************/
2108 jb_err cgi_error_file(struct client_state *csp,
2109 struct http_response *rsp,
2110 const char *filename)
2112 struct map *exports;
2119 if (NULL == (exports = default_exports(csp, NULL)))
2121 return JB_ERR_MEMORY;
2124 err = map(exports, "f", 1, html_encode(filename), 0);
2131 return template_fill_for_cgi(csp, "cgi-error-file", exports, rsp);
2135 /*********************************************************************
2137 * Function : cgi_error_file_read_only
2139 * Description : CGI function that is called when a file cannot be
2140 * opened for writing by the CGI editor.
2143 * 1 : csp = Current client state (buffers, headers, etc...)
2144 * 2 : rsp = http_response data structure for output
2145 * 3 : filename = The file that we can't write to
2147 * CGI Parameters : none
2149 * Returns : JB_ERR_OK on success
2150 * JB_ERR_MEMORY on out-of-memory error.
2152 *********************************************************************/
2153 jb_err cgi_error_file_read_only(struct client_state *csp,
2154 struct http_response *rsp,
2155 const char *filename)
2157 struct map *exports;
2164 if (NULL == (exports = default_exports(csp, NULL)))
2166 return JB_ERR_MEMORY;
2169 err = map(exports, "f", 1, html_encode(filename), 0);
2176 return template_fill_for_cgi(csp, "cgi-error-file-read-only", exports, rsp);
2180 /*********************************************************************
2182 * Function : cgi_edit_actions
2184 * Description : CGI function that allows the user to choose which
2185 * actions file to edit.
2188 * 1 : csp = Current client state (buffers, headers, etc...)
2189 * 2 : rsp = http_response data structure for output
2190 * 3 : parameters = map of cgi parameters
2192 * CGI Parameters : None
2194 * Returns : JB_ERR_OK on success
2195 * JB_ERR_MEMORY on out-of-memory error
2197 *********************************************************************/
2198 jb_err cgi_edit_actions(struct client_state *csp,
2199 struct http_response *rsp,
2200 const struct map *parameters)
2204 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2206 return cgi_error_disabled(csp, rsp);
2209 /* FIXME: Incomplete */
2211 return cgi_redirect(rsp, CGI_PREFIX "edit-actions-list?f=default");
2216 /*********************************************************************
2218 * Function : cgi_edit_actions_list
2220 * Description : CGI function that edits the actions list.
2221 * FIXME: This function shouldn't FATAL ever.
2222 * FIXME: This function doesn't check the retval of map()
2224 * 1 : csp = Current client state (buffers, headers, etc...)
2225 * 2 : rsp = http_response data structure for output
2226 * 3 : parameters = map of cgi parameters
2228 * CGI Parameters : filename
2230 * Returns : JB_ERR_OK on success
2231 * JB_ERR_MEMORY on out-of-memory
2232 * JB_ERR_FILE if the file cannot be opened or
2234 * JB_ERR_CGI_PARAMS if "filename" was not specified
2237 *********************************************************************/
2238 jb_err cgi_edit_actions_list(struct client_state *csp,
2239 struct http_response *rsp,
2240 const struct map *parameters)
2242 char * section_template;
2243 char * url_template;
2248 struct map * exports;
2249 struct map * section_exports;
2250 struct map * url_exports;
2251 struct editable_file * file;
2252 struct file_line * cur_line;
2253 unsigned line_number = 0;
2254 unsigned prev_section_line_number = ((unsigned) (-1));
2256 struct file_list * fl;
2257 struct url_actions * b;
2258 char * buttons = NULL;
2261 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2263 return cgi_error_disabled(csp, rsp);
2266 if (NULL == (exports = default_exports(csp, NULL)))
2268 return JB_ERR_MEMORY;
2271 /* Load actions file */
2272 err = edit_read_actions_file(csp, rsp, parameters, 0, &file);
2275 /* No filename specified, can't read file, or out of memory. */
2277 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2280 /* Find start of actions in file */
2281 cur_line = file->lines;
2283 while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
2285 cur_line = cur_line->next;
2290 * Conventional actions files should have a match all block
2292 * cur_line = {...global actions...}
2293 * cur_line->next = /
2294 * cur_line->next->next = {...actions...} or EOF
2296 if ( (cur_line != NULL)
2297 && (cur_line->type == FILE_LINE_ACTION)
2298 && (cur_line->next != NULL)
2299 && (cur_line->next->type == FILE_LINE_URL)
2300 && (0 == strcmp(cur_line->next->unprocessed, "/"))
2301 && ( (cur_line->next->next == NULL)
2302 || (cur_line->next->next->type != FILE_LINE_URL)
2306 * Generate string with buttons to set actions for "/" to
2307 * any predefined set of actions (named standard.*, probably
2308 * residing in standard.action).
2311 err = template_load(csp, §ion_template, "edit-actions-list-button", 0);
2314 edit_free_file(file);
2316 if (err == JB_ERR_FILE)
2318 return cgi_error_no_template(csp, rsp, "edit-actions-list-button");
2323 err = template_fill(§ion_template, exports);
2326 edit_free_file(file);
2331 buttons = strdup("");
2332 for (i = 0; i < MAX_AF_FILES; i++)
2334 if (((fl = csp->actions_list[i]) != NULL) && ((b = fl->f) != NULL))
2336 for (b = b->next; NULL != b; b = b->next)
2338 if (!strncmp(b->url->spec, "standard.", 9) && *(b->url->spec + 9) != '\0')
2343 free(section_template);
2344 edit_free_file(file);
2346 return JB_ERR_MEMORY;
2349 section_exports = new_map();
2350 err = map(section_exports, "button-name", 1, b->url->spec + 9, 1);
2352 if (err || (NULL == (s = strdup(section_template))))
2354 free_map(section_exports);
2356 free(section_template);
2357 edit_free_file(file);
2359 return JB_ERR_MEMORY;
2362 if (!err) err = template_fill(&s, section_exports);
2363 free_map(section_exports);
2364 if (!err) err = string_join(&buttons, s);
2369 freez(section_template);
2370 if (!err) err = map(exports, "all-urls-buttons", 1, buttons, 0);
2373 * Conventional actions file, supply extra editing help.
2374 * (e.g. don't allow them to make it an unconventional one).
2376 if (!err) err = map_conditional(exports, "all-urls-present", 1);
2378 snprintf(buf, sizeof(buf), "%u", line_number);
2379 if (!err) err = map(exports, "all-urls-s", 1, buf, 1);
2380 snprintf(buf, sizeof(buf), "%u", line_number + 2);
2381 if (!err) err = map(exports, "all-urls-s-next", 1, buf, 1);
2382 if (!err) err = map(exports, "all-urls-actions", 1,
2383 actions_to_html(csp, cur_line->data.action), 0);
2385 /* Skip the 2 lines */
2386 cur_line = cur_line->next->next;
2390 * Note that prev_section_line_number is NOT set here.
2391 * This is deliberate and not a bug. It stops a "Move up"
2392 * option appearing on the next section. Clicking "Move
2393 * up" would make the actions file unconventional, which
2394 * we don't want, so we hide this option.
2400 * Non-standard actions file - does not begin with
2401 * the "All URLs" section.
2403 if (!err) err = map_conditional(exports, "all-urls-present", 0);
2406 /* Set up global exports */
2408 if (!err) err = map(exports, "actions-file", 1, html_encode(file->filename), 0);
2409 if (!err) err = map(exports, "f", 1, stringify(file->identifier), 0);
2410 if (!err) err = map(exports, "v", 1, file->version_str, 1);
2412 /* Discourage private additions to default.action */
2414 if (!err) err = map_conditional(exports, "default-action",
2415 (strstr("default.action", file->filename) != NULL));
2418 edit_free_file(file);
2423 /* Should do all global exports above this point */
2425 /* Load templates */
2427 err = template_load(csp, §ion_template, "edit-actions-list-section", 0);
2430 edit_free_file(file);
2432 if (err == JB_ERR_FILE)
2434 return cgi_error_no_template(csp, rsp, "edit-actions-list-section");
2439 err = template_load(csp, &url_template, "edit-actions-list-url", 0);
2442 free(section_template);
2443 edit_free_file(file);
2445 if (err == JB_ERR_FILE)
2447 return cgi_error_no_template(csp, rsp, "edit-actions-list-url");
2452 err = template_fill(§ion_template, exports);
2456 edit_free_file(file);
2461 err = template_fill(&url_template, exports);
2464 free(section_template);
2465 edit_free_file(file);
2470 if (NULL == (sections = strdup("")))
2472 free(section_template);
2474 edit_free_file(file);
2476 return JB_ERR_MEMORY;
2479 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_ACTION))
2481 section_exports = new_map();
2483 snprintf(buf, sizeof(buf), "%u", line_number);
2484 err = map(section_exports, "s", 1, buf, 1);
2485 if (!err) err = map(section_exports, "actions", 1,
2486 actions_to_html(csp, cur_line->data.action), 0);
2489 && (cur_line->next != NULL)
2490 && (cur_line->next->type == FILE_LINE_URL))
2492 /* This section contains at least one URL, don't allow delete */
2493 err = map_block_killer(section_exports, "empty-section");
2497 if (!err) err = map_block_keep(section_exports, "empty-section");
2500 if (prev_section_line_number != ((unsigned)(-1)))
2502 /* Not last section */
2503 snprintf(buf, sizeof(buf), "%u", prev_section_line_number);
2504 if (!err) err = map(section_exports, "s-prev", 1, buf, 1);
2505 if (!err) err = map_block_keep(section_exports, "s-prev-exists");
2510 if (!err) err = map_block_killer(section_exports, "s-prev-exists");
2512 prev_section_line_number = line_number;
2517 free(section_template);
2519 edit_free_file(file);
2521 free_map(section_exports);
2525 /* Should do all section-specific exports above this point */
2527 if (NULL == (urls = strdup("")))
2530 free(section_template);
2532 edit_free_file(file);
2534 free_map(section_exports);
2535 return JB_ERR_MEMORY;
2540 cur_line = cur_line->next;
2543 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL))
2545 url_exports = new_map();
2547 snprintf(buf, sizeof(buf), "%u", line_number);
2548 err = map(url_exports, "p", 1, buf, 1);
2550 snprintf(buf, sizeof(buf), "%d", url_1_2);
2551 if (!err) err = map(url_exports, "url-1-2", 1, buf, 1);
2553 if (!err) err = map(url_exports, "url-html", 1,
2554 html_encode(cur_line->unprocessed), 0);
2555 if (!err) err = map(url_exports, "url", 1,
2556 url_encode(cur_line->unprocessed), 0);
2562 free(section_template);
2564 edit_free_file(file);
2566 free_map(section_exports);
2567 free_map(url_exports);
2571 if (NULL == (s = strdup(url_template)))
2575 free(section_template);
2577 edit_free_file(file);
2579 free_map(section_exports);
2580 free_map(url_exports);
2581 return JB_ERR_MEMORY;
2584 err = template_fill(&s, section_exports);
2585 if (!err) err = template_fill(&s, url_exports);
2586 if (!err) err = string_append(&urls, s);
2588 free_map(url_exports);
2595 free(section_template);
2597 edit_free_file(file);
2599 free_map(section_exports);
2603 url_1_2 = 3 - url_1_2;
2605 cur_line = cur_line->next;
2609 err = map(section_exports, "urls", 1, urls, 0);
2611 /* Could also do section-specific exports here, but it wouldn't be as fast */
2613 snprintf(buf, sizeof(buf), "%u", line_number);
2614 if (!err) err = map(section_exports, "s-next", 1, buf, 1);
2616 if ((cur_line != NULL)
2617 && (cur_line->type == FILE_LINE_ACTION))
2619 /* Not last section */
2620 if (!err) err = map_block_keep(section_exports, "s-next-exists");
2625 if (!err) err = map_block_killer(section_exports, "s-next-exists");
2631 free(section_template);
2633 edit_free_file(file);
2635 free_map(section_exports);
2639 if (NULL == (s = strdup(section_template)))
2642 free(section_template);
2644 edit_free_file(file);
2646 free_map(section_exports);
2647 return JB_ERR_MEMORY;
2650 err = template_fill(&s, section_exports);
2651 if (!err) err = string_append(§ions, s);
2654 free_map(section_exports);
2659 free(section_template);
2661 edit_free_file(file);
2667 edit_free_file(file);
2668 free(section_template);
2671 err = map(exports, "sections", 1, sections, 0);
2678 /* Could also do global exports here, but it wouldn't be as fast */
2680 return template_fill_for_cgi(csp, "edit-actions-list", exports, rsp);
2684 /*********************************************************************
2686 * Function : cgi_edit_actions_for_url
2688 * Description : CGI function that edits the Actions list.
2691 * 1 : csp = Current client state (buffers, headers, etc...)
2692 * 2 : rsp = http_response data structure for output
2693 * 3 : parameters = map of cgi parameters
2695 * CGI Parameters : None
2697 * Returns : JB_ERR_OK on success
2698 * JB_ERR_MEMORY on out-of-memory
2699 * JB_ERR_CGI_PARAMS if the CGI parameters are not
2700 * specified or not valid.
2702 *********************************************************************/
2703 jb_err cgi_edit_actions_for_url(struct client_state *csp,
2704 struct http_response *rsp,
2705 const struct map *parameters)
2707 struct map * exports;
2708 char *filter_template;
2710 struct editable_file * file;
2711 struct file_line * cur_line;
2712 unsigned line_number;
2714 struct re_filterfile_spec *filter_group;
2715 int i, have_filters = 0;
2717 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2719 return cgi_error_disabled(csp, rsp);
2722 err = get_number_param(csp, parameters, "s", §ionid);
2728 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2731 /* No filename specified, can't read file, modified, or out of memory. */
2732 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2735 cur_line = file->lines;
2737 for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2739 cur_line = cur_line->next;
2742 if ( (cur_line == NULL)
2743 || (line_number != sectionid)
2745 || (cur_line->type != FILE_LINE_ACTION))
2747 /* Invalid "sectionid" parameter */
2748 edit_free_file(file);
2749 return JB_ERR_CGI_PARAMS;
2752 if (NULL == (exports = default_exports(csp, NULL)))
2754 edit_free_file(file);
2755 return JB_ERR_MEMORY;
2758 err = template_load(csp, &filter_template, "edit-actions-for-url-string-action", 0);
2761 edit_free_file(file);
2763 return cgi_error_no_template(csp, rsp, "edit-actions-for-url-string-action");
2766 err = map(exports, "f", 1, stringify(file->identifier), 0);
2767 if (!err) err = map(exports, "v", 1, file->version_str, 1);
2768 if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0);
2770 if (!err) err = actions_to_radio(exports, cur_line->data.action);
2772 for (i = 0; !err && i < SZ(string_action_type_info); i++)
2774 err = action_render_string_actions_template(exports,
2775 cur_line->data.action, filter_template, &string_action_type_info[i]);
2777 freez(filter_template);
2780 * XXX: Some browsers (at least IE6 and IE7) have an artificial URL
2781 * length limitation and ignore clicks on the Submit buttons if
2782 * the resulting GET URL would be longer than their limit.
2784 * In Privoxy 3.0.5 beta the standard edit-actions-for-url template
2785 * reached this limit and action editing stopped working in these
2786 * browsers (BR #1570678).
2788 * The config option split-large-forms works around this browser
2789 * bug (HTTP has no URL length limitation) by dividing the action
2790 * list form into multiple smaller ones. It means the URLs are shorter
2791 * and work in broken browsers as well, but the user can no longer change
2792 * all actions with one submit.
2794 * A better solution would be to switch to POST requests,
2795 * but this will do for now.
2797 if (!err && (csp->config->feature_flags & RUNTIME_FEATURE_SPLIT_LARGE_FORMS))
2799 /* Generate multiple smaller form by killing the big one. */
2800 err = map_block_killer(exports, "one-form-only");
2804 /* Default: Generate one large form by killing the smaller ones. */
2805 err = map_block_killer(exports, "multiple-forms");
2808 for (i = 0; i < MAX_AF_FILES; i++)
2810 if ((csp->rlist[i] != NULL) && (csp->rlist[i]->f != NULL))
2812 if (!err) err = map_conditional(exports, "any-filters-defined", 1);
2818 #ifndef FEATURE_EXTERNAL_FILTERS
2819 if (!err) err = map_block_killer(exports, "external-content-filters");
2821 #ifndef FEATURE_HTTPS_INSPECTION
2822 if (!err) err = map_block_killer(exports, "https-inspection");
2827 edit_free_file(file);
2832 if (0 == have_filters)
2834 err = map(exports, "filter-params", 1, "", 1);
2839 * List available filters and their settings.
2841 int filter_identifier = 0;
2842 char *prepared_templates[MAX_FILTER_TYPES];
2844 for (i = 0; i < MAX_FILTER_TYPES; i++)
2846 prepared_templates[i] = strdup("");
2849 err = template_load(csp, &filter_template, "edit-actions-for-url-filter", 0);
2852 edit_free_file(file);
2854 if (err == JB_ERR_FILE)
2856 return cgi_error_no_template(csp, rsp, "edit-actions-for-url-filter");
2861 err = template_fill(&filter_template, exports);
2863 for (i = 0; i < MAX_AF_FILES; i++)
2865 if ((csp->rlist[i] != NULL) && (csp->rlist[i]->f != NULL))
2867 filter_group = csp->rlist[i]->f;
2868 for (; (!err) && (filter_group != NULL); filter_group = filter_group->next)
2870 char current_mode = 'x';
2872 struct list_entry *filter_name;
2873 struct map *line_exports;
2874 const enum filter_type type = filter_group->type;
2875 const int multi_action_index = action_type_info[type].multi_action_index;
2877 assert(type < MAX_FILTER_TYPES);
2878 assert(multi_action_index < ACTION_MULTI_COUNT);
2880 filter_name = cur_line->data.action->multi_add[multi_action_index]->first;
2881 while ((filter_name != NULL)
2882 && (0 != strcmp(filter_group->name, filter_name->str)))
2884 filter_name = filter_name->next;
2887 if (filter_name != NULL)
2893 filter_name = cur_line->data.action->multi_remove[multi_action_index]->first;
2894 while ((filter_name != NULL)
2895 && (0 != strcmp(filter_group->name, filter_name->str)))
2897 filter_name = filter_name->next;
2899 if (filter_name != NULL)
2905 /* Generate a unique serial number */
2906 snprintf(number, sizeof(number), "%x", filter_identifier++);
2907 number[sizeof(number) - 1] = '\0';
2909 line_exports = new_map();
2910 if (line_exports == NULL)
2912 err = JB_ERR_MEMORY;
2918 if (!err) err = map(line_exports, "index", 1, number, 1);
2919 if (!err) err = map(line_exports, "name", 1, filter_group->name, 1);
2920 if (!err) err = map(line_exports, "description", 1, filter_group->description, 1);
2921 if (!err) err = map_radio(line_exports, "this-filter", "ynx", current_mode);
2922 if (!err) err = map(line_exports, "filter-type", 1, action_type_info[type].type, 1);
2923 if (!err) err = map(line_exports, "abbr-action-type", 1, action_type_info[type].abbr_type, 1);
2924 if (!err) err = map(line_exports, "anchor", 1, action_type_info[type].anchor, 1);
2928 filter_line = strdup(filter_template);
2929 if (filter_line == NULL) err = JB_ERR_MEMORY;
2931 if (!err) err = template_fill(&filter_line, line_exports);
2932 if (!err) err = string_join(&prepared_templates[type], filter_line);
2934 free_map(line_exports);
2939 freez(filter_template);
2941 /* Replace all filter macros with the aggregated templates */
2942 for (i = 0; i < MAX_FILTER_TYPES; i++)
2945 err = map(exports, action_type_info[i].macro_name, 1, prepared_templates[i], 0);
2950 /* Free aggregated templates */
2951 for (i = 0; i < MAX_FILTER_TYPES; i++)
2953 freez(prepared_templates[i]);
2958 /* Check or uncheck the "disable all of this type" radio buttons. */
2959 for (i = 0; i < MAX_FILTER_TYPES; i++)
2961 const int a = action_type_info[i].multi_action_index;
2962 const int disable_all = cur_line->data.action->multi_remove_all[a];
2964 err = map_radio(exports, action_type_info[i].disable_all_option, "nx", (disable_all ? 'n' : 'x'));
2967 edit_free_file(file);
2975 return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
2979 /*********************************************************************
2981 * Function : get_number_of_filters
2983 * Description : Counts the number of filter available.
2986 * 1 : csp = Current client state (buffers, headers, etc...)
2988 * Returns : Number of filters available.
2990 *********************************************************************/
2991 static int get_number_of_filters(const struct client_state *csp)
2994 struct re_filterfile_spec *b;
2995 struct file_list *fl;
2996 int number_of_filters = 0;
2998 for (i = 0; i < MAX_AF_FILES; i++)
3001 if ((NULL == fl) || (NULL == fl->f))
3004 * Either there are no filter files left or this
3005 * filter file just contains no valid filters.
3007 * Continue to be sure we don't miss valid filter
3008 * files that are chained after empty or invalid ones.
3013 for (b = fl->f; b != NULL; b = b->next)
3015 number_of_filters++;
3019 return number_of_filters;
3024 /*********************************************************************
3026 * Function : cgi_edit_process_string_action
3028 * Description : Helper CGI function that actually edits the Actions list for
3029 * the action string parameters.
3032 * 1 : csp = Current client state (buffers, headers, etc...)
3033 * 2 : rsp = http_response data structure for output
3034 * 3 : parameters = map of cgi parameters
3035 * 4 : cur_line = current config file line
3036 * 5 : action_type = string filter type to process
3038 * Returns : JB_ERR_OK on success
3039 * JB_ERR_MEMORY on out-of-memory
3040 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3041 * specified or not valid.
3043 *********************************************************************/
3044 static jb_err cgi_edit_process_string_action(struct client_state *csp,
3045 struct http_response *rsp,
3046 const struct map *parameters,
3047 struct file_line *cur_line,
3048 enum filter_type action_type)
3050 jb_err err = JB_ERR_OK;
3051 const char *abbr_type = action_type_info[action_type].abbr_type;
3052 int action_identifier = 0;
3054 /* process existing string filter actions */
3055 for (action_identifier = 0; !err; action_identifier++)
3061 const char *name, *new_name;
3063 * Action state. Valid states are: 'Y' (active),
3064 * 'N' (inactive) and 'X' (no change).
3068 * Abbreviated filter type. Valid types are: 'U' (suppress tag), 'H' (add header)
3070 int multi_action_index = 0;
3072 /* Generate the keys */
3073 snprintf(key_value, sizeof(key_value), "string_action_%s_r%x", abbr_type, action_identifier);
3074 snprintf(key_name, sizeof(key_name), "string_action_%s_n%x", abbr_type, action_identifier);
3075 snprintf(old_name, sizeof(old_name), "string_action_%s_o%x", abbr_type, action_identifier);
3076 snprintf(key_type, sizeof(key_type), "string_action_%s_t%x", abbr_type, action_identifier);
3078 err = get_string_param(parameters, old_name, &name);
3083 /* The action identifier isn't present: we're done! */
3087 err = get_string_param(parameters, key_name, &new_name);
3089 if (new_name == NULL) new_name = name;
3091 type = get_char_param(parameters, key_type);
3095 multi_action_index = ACTION_MULTI_SUPPRESS_TAG;
3098 multi_action_index = ACTION_MULTI_ADD_HEADER;
3101 log_error(LOG_LEVEL_ERROR,
3102 "Unknown action type: %c for action %s. Action ignored.", type, name);
3106 value = get_char_param(parameters, key_value);
3107 if (value == 'X' || value == 'Y' || value == 'N')
3109 list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3110 list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3115 err = enlist(cur_line->data.action->multi_add[multi_action_index], new_name);
3117 else if (value == 'N')
3119 err = enlist(cur_line->data.action->multi_remove[multi_action_index], new_name);
3123 /* process new string filter actions */
3124 for (action_identifier = 0; !err; action_identifier++)
3131 * Action state. Valid states are: 'Y' (active),
3132 * 'N' (inactive) and 'X' (no change).
3136 * Abbreviated filter type. Valid types are: 'U' (suppress tag), 'H' (add header)
3138 int multi_action_index = 0;
3140 /* Generate the keys */
3141 snprintf(key_value, sizeof(key_value), "new_string_action_%s_r%x", abbr_type, action_identifier);
3142 snprintf(key_name, sizeof(key_name), "new_string_action_%s_n%x", abbr_type, action_identifier);
3143 snprintf(key_type, sizeof(key_type), "new_string_action_%s_t%x", abbr_type, action_identifier);
3145 err = get_string_param(parameters, key_name, &name);
3150 /* The action identifier isn't present: we've done! */
3154 type = get_char_param(parameters, key_type);
3158 multi_action_index = ACTION_MULTI_SUPPRESS_TAG;
3161 multi_action_index = ACTION_MULTI_ADD_HEADER;
3164 log_error(LOG_LEVEL_ERROR,
3165 "Unknown filter type: %c for filter %s. Filter ignored.", type, name);
3169 value = get_char_param(parameters, key_value);
3172 list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3173 if (!err) err = enlist(cur_line->data.action->multi_add[multi_action_index], name);
3174 list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3176 else if (value == 'N')
3178 list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3179 list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3180 if (!err) err = enlist(cur_line->data.action->multi_remove[multi_action_index], name);
3182 /* nothing to do if the value is 'X' */
3188 /*********************************************************************
3190 * Function : cgi_edit_actions_submit
3192 * Description : CGI function that actually edits the Actions list.
3195 * 1 : csp = Current client state (buffers, headers, etc...)
3196 * 2 : rsp = http_response data structure for output
3197 * 3 : parameters = map of cgi parameters
3199 * CGI Parameters : None
3201 * Returns : JB_ERR_OK on success
3202 * JB_ERR_MEMORY on out-of-memory
3203 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3204 * specified or not valid.
3206 *********************************************************************/
3207 jb_err cgi_edit_actions_submit(struct client_state *csp,
3208 struct http_response *rsp,
3209 const struct map *parameters)
3214 size_t newtext_size;
3216 struct editable_file * file;
3217 struct file_line * cur_line;
3218 unsigned line_number;
3221 int filter_identifier;
3223 const char * action_set_name;
3224 struct file_list * fl;
3225 struct url_actions * b;
3226 int number_of_filters;
3228 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3230 return cgi_error_disabled(csp, rsp);
3233 err = get_number_param(csp, parameters, "s", §ionid);
3239 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3242 /* No filename specified, can't read file, modified, or out of memory. */
3243 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3246 cur_line = file->lines;
3248 for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
3250 cur_line = cur_line->next;
3253 if ( (cur_line == NULL)
3254 || (line_number != sectionid)
3256 || (cur_line->type != FILE_LINE_ACTION))
3258 /* Invalid "sectionid" parameter */
3259 edit_free_file(file);
3260 return JB_ERR_CGI_PARAMS;
3263 get_string_param(parameters, "p", &action_set_name);
3264 if (action_set_name != NULL)
3266 for (filter_identifier = 0; filter_identifier < MAX_AF_FILES; filter_identifier++)
3268 if (((fl = csp->actions_list[filter_identifier]) != NULL) && ((b = fl->f) != NULL))
3270 for (b = b->next; NULL != b; b = b->next)
3272 if (!strncmp(b->url->spec, "standard.", 9) && !strcmp(b->url->spec + 9, action_set_name))
3274 copy_action(cur_line->data.action, b->action);
3280 edit_free_file(file);
3281 return JB_ERR_CGI_PARAMS;
3287 err = actions_from_radio(parameters, cur_line->data.action);
3293 edit_free_file(file);
3297 /* Check the "disable all of this type" parameters. */
3298 for (i = 0; i < MAX_FILTER_TYPES; i++)
3300 const int multi_action_index = action_type_info[i].multi_action_index;
3301 const char ch = get_char_param(parameters, action_type_info[i].disable_all_param);
3305 list_remove_all(cur_line->data.action->multi_add[multi_action_index]);
3306 list_remove_all(cur_line->data.action->multi_remove[multi_action_index]);
3307 cur_line->data.action->multi_remove_all[multi_action_index] = 1;
3311 cur_line->data.action->multi_remove_all[multi_action_index] = 0;
3315 number_of_filters = get_number_of_filters(csp);
3317 for (filter_identifier = 0; filter_identifier < number_of_filters && !err; filter_identifier++)
3324 * Filter state. Valid states are: 'Y' (active),
3325 * 'N' (inactive) and 'X' (no change).
3329 * Abbreviated filter type. Valid types are: 'F' (content filter),
3330 * 'S' (server-header filter) and 'C' (client-header filter).
3332 int multi_action_index = 0;
3334 /* Generate the keys */
3335 snprintf(key_value, sizeof(key_value), "filter_r%x", filter_identifier);
3336 key_value[sizeof(key_value) - 1] = '\0'; /* XXX: Why? */
3337 snprintf(key_name, sizeof(key_name), "filter_n%x", filter_identifier);
3338 key_name[sizeof(key_name) - 1] = '\0'; /* XXX: Why? */
3339 snprintf(key_type, sizeof(key_type), "filter_t%x", filter_identifier);
3341 err = get_string_param(parameters, key_name, &name);
3346 /* The filter identifier isn't present. Try the next one. */
3350 type = get_char_param(parameters, key_type);
3354 multi_action_index = ACTION_MULTI_FILTER;
3357 multi_action_index = ACTION_MULTI_SERVER_HEADER_FILTER;
3360 multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER;
3363 multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER;
3366 multi_action_index = ACTION_MULTI_SERVER_HEADER_TAGGER;
3369 multi_action_index = ACTION_MULTI_CLIENT_BODY_FILTER;
3372 log_error(LOG_LEVEL_ERROR,
3373 "Unknown filter type: %c for filter %s. Filter ignored.", type, name);
3376 assert(multi_action_index);
3378 value = get_char_param(parameters, key_value);
3381 list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3382 if (!err) err = enlist(cur_line->data.action->multi_add[multi_action_index], name);
3383 list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3385 else if (value == 'N')
3387 list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3388 if (!cur_line->data.action->multi_remove_all[multi_action_index])
3390 list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3391 if (!err) err = enlist(cur_line->data.action->multi_remove[multi_action_index], name);
3394 else if (value == 'X')
3396 list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3397 list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3401 /* process new string filters */
3402 for (i = 0; !err && i < SZ(string_action_type_info); i++)
3404 err = cgi_edit_process_string_action(csp, rsp, parameters, cur_line,
3405 string_action_type_info[i].action_type);
3411 edit_free_file(file);
3415 if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
3418 edit_free_file(file);
3419 return JB_ERR_MEMORY;
3422 len = strlen(actiontext);
3426 * Empty action - must special-case this.
3427 * Simply setting len to 1 is sufficient...
3432 newtext_size = len + 2;
3433 newtext = malloc_or_die(newtext_size);
3434 strlcpy(newtext, actiontext, newtext_size);
3438 newtext[len + 1] = '\0';
3440 freez(cur_line->raw);
3441 freez(cur_line->unprocessed);
3442 cur_line->unprocessed = newtext;
3444 err = edit_write_file(file);
3447 /* Error writing file */
3448 if (err == JB_ERR_FILE)
3450 /* Read-only file. */
3451 err = cgi_error_file_read_only(csp, rsp, file->filename);
3453 edit_free_file(file);
3457 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3458 (unsigned long) time(NULL), file->identifier, sectionid);
3460 edit_free_file(file);
3462 return cgi_redirect(rsp, target);
3466 /*********************************************************************
3468 * Function : cgi_edit_actions_url
3470 * Description : CGI function that actually edits a URL pattern in
3474 * 1 : csp = Current client state (buffers, headers, etc...)
3475 * 2 : rsp = http_response data structure for output
3476 * 3 : parameters = map of cgi parameters
3479 * filename : Identifies the file to edit
3480 * ver : File's last-modified time
3481 * section : Line number of section to edit
3482 * pattern : Line number of pattern to edit
3483 * newval : New value for pattern
3485 * Returns : JB_ERR_OK on success
3486 * JB_ERR_MEMORY on out-of-memory
3487 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3488 * specified or not valid.
3490 *********************************************************************/
3491 jb_err cgi_edit_actions_url(struct client_state *csp,
3492 struct http_response *rsp,
3493 const struct map *parameters)
3497 struct editable_file * file;
3498 struct file_line * cur_line;
3499 unsigned line_number;
3500 unsigned section_start_line_number = 0;
3508 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3510 return cgi_error_disabled(csp, rsp);
3513 err = get_number_param(csp, parameters, "p", &patternid);
3520 return JB_ERR_CGI_PARAMS;
3523 err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3529 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3532 /* No filename specified, can't read file, modified, or out of memory. */
3534 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3538 cur_line = file->lines;
3540 while ((cur_line != NULL) && (line_number < patternid))
3542 if (cur_line->type == FILE_LINE_ACTION)
3544 section_start_line_number = line_number;
3546 cur_line = cur_line->next;
3550 if ((cur_line == NULL)
3551 || (cur_line->type != FILE_LINE_URL))
3553 /* Invalid "patternid" parameter */
3555 edit_free_file(file);
3556 return JB_ERR_CGI_PARAMS;
3559 /* At this point, the line to edit is in cur_line */
3561 freez(cur_line->raw);
3562 freez(cur_line->unprocessed);
3563 cur_line->unprocessed = new_pattern;
3565 err = edit_write_file(file);
3568 /* Error writing file */
3569 if (err == JB_ERR_FILE)
3571 /* Read-only file. */
3572 err = cgi_error_file_read_only(csp, rsp, file->filename);
3574 edit_free_file(file);
3578 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3579 (unsigned long) time(NULL), file->identifier, section_start_line_number);
3581 edit_free_file(file);
3583 return cgi_redirect(rsp, target);
3587 /*********************************************************************
3589 * Function : cgi_edit_actions_add_url
3591 * Description : CGI function that actually adds a URL pattern to
3595 * 1 : csp = Current client state (buffers, headers, etc...)
3596 * 2 : rsp = http_response data structure for output
3597 * 3 : parameters = map of cgi parameters
3600 * filename : Identifies the file to edit
3601 * ver : File's last-modified time
3602 * section : Line number of section to edit
3603 * newval : New pattern
3605 * Returns : JB_ERR_OK on success
3606 * JB_ERR_MEMORY on out-of-memory
3607 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3608 * specified or not valid.
3610 *********************************************************************/
3611 jb_err cgi_edit_actions_add_url(struct client_state *csp,
3612 struct http_response *rsp,
3613 const struct map *parameters)
3617 struct file_line * new_line;
3618 struct editable_file * file;
3619 struct file_line * cur_line;
3620 unsigned line_number;
3624 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3626 return cgi_error_disabled(csp, rsp);
3629 err = get_number_param(csp, parameters, "s", §ionid);
3636 return JB_ERR_CGI_PARAMS;
3639 err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3645 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3648 /* No filename specified, can't read file, modified, or out of memory. */
3650 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3654 cur_line = file->lines;
3656 while ((cur_line != NULL) && (line_number < sectionid))
3658 cur_line = cur_line->next;
3662 if ((cur_line == NULL)
3663 || (cur_line->type != FILE_LINE_ACTION))
3665 /* Invalid "sectionid" parameter */
3667 edit_free_file(file);
3668 return JB_ERR_CGI_PARAMS;
3671 /* At this point, the section header is in cur_line - add after this. */
3673 /* Allocate the new line */
3674 new_line = zalloc_or_die(sizeof(*new_line));
3676 /* Fill in the data members of the new line */
3677 new_line->raw = NULL;
3678 new_line->prefix = NULL;
3679 new_line->unprocessed = new_pattern;
3680 new_line->type = FILE_LINE_URL;
3682 /* Link new_line into the list, after cur_line */
3683 new_line->next = cur_line->next;
3684 cur_line->next = new_line;
3686 /* Done making changes, now commit */
3688 err = edit_write_file(file);
3691 /* Error writing file */
3692 if (err == JB_ERR_FILE)
3694 /* Read-only file. */
3695 err = cgi_error_file_read_only(csp, rsp, file->filename);
3697 edit_free_file(file);
3701 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3702 (unsigned long) time(NULL), file->identifier, sectionid);
3704 edit_free_file(file);
3706 return cgi_redirect(rsp, target);
3710 /*********************************************************************
3712 * Function : cgi_edit_actions_remove_url
3714 * Description : CGI function that actually removes a URL pattern from
3718 * 1 : csp = Current client state (buffers, headers, etc...)
3719 * 2 : rsp = http_response data structure for output
3720 * 3 : parameters = map of cgi parameters
3723 * f : (filename) Identifies the file to edit
3724 * v : (version) File's last-modified time
3725 * p : (pattern) Line number of pattern to remove
3727 * Returns : JB_ERR_OK on success
3728 * JB_ERR_MEMORY on out-of-memory
3729 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3730 * specified or not valid.
3732 *********************************************************************/
3733 jb_err cgi_edit_actions_remove_url(struct client_state *csp,
3734 struct http_response *rsp,
3735 const struct map *parameters)
3738 struct editable_file * file;
3739 struct file_line * cur_line;
3740 struct file_line * prev_line;
3741 unsigned line_number;
3742 unsigned section_start_line_number = 0;
3746 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3748 return cgi_error_disabled(csp, rsp);
3751 err = get_number_param(csp, parameters, "p", &patternid);
3757 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3760 /* No filename specified, can't read file, modified, or out of memory. */
3761 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3766 cur_line = file->lines;
3768 while ((cur_line != NULL) && (line_number < patternid))
3770 if (cur_line->type == FILE_LINE_ACTION)
3772 section_start_line_number = line_number;
3774 prev_line = cur_line;
3775 cur_line = cur_line->next;
3779 if ( (cur_line == NULL)
3780 || (prev_line == NULL)
3781 || (cur_line->type != FILE_LINE_URL))
3783 /* Invalid "patternid" parameter */
3784 edit_free_file(file);
3785 return JB_ERR_CGI_PARAMS;
3788 /* At this point, the line to remove is in cur_line, and the previous
3789 * one is in prev_line
3792 /* Unlink cur_line */
3793 prev_line->next = cur_line->next;
3794 cur_line->next = NULL;
3797 edit_free_file_lines(cur_line);
3799 err = edit_write_file(file);
3802 /* Error writing file */
3803 if (err == JB_ERR_FILE)
3805 /* Read-only file. */
3806 err = cgi_error_file_read_only(csp, rsp, file->filename);
3808 edit_free_file(file);
3812 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3813 (unsigned long) time(NULL), file->identifier, section_start_line_number);
3815 edit_free_file(file);
3817 return cgi_redirect(rsp, target);
3821 /*********************************************************************
3823 * Function : cgi_edit_actions_section_remove
3825 * Description : CGI function that actually removes a whole section from
3826 * the actions file. The section must be empty first
3827 * (else JB_ERR_CGI_PARAMS).
3830 * 1 : csp = Current client state (buffers, headers, etc...)
3831 * 2 : rsp = http_response data structure for output
3832 * 3 : parameters = map of cgi parameters
3835 * f : (filename) Identifies the file to edit
3836 * v : (version) File's last-modified time
3837 * s : (section) Line number of section to edit
3839 * Returns : JB_ERR_OK on success
3840 * JB_ERR_MEMORY on out-of-memory
3841 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3842 * specified or not valid.
3844 *********************************************************************/
3845 jb_err cgi_edit_actions_section_remove(struct client_state *csp,
3846 struct http_response *rsp,
3847 const struct map *parameters)
3850 struct editable_file * file;
3851 struct file_line * cur_line;
3852 struct file_line * prev_line;
3853 unsigned line_number;
3857 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3859 return cgi_error_disabled(csp, rsp);
3862 err = get_number_param(csp, parameters, "s", §ionid);
3868 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3871 /* No filename specified, can't read file, modified, or out of memory. */
3872 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3876 cur_line = file->lines;
3879 while ((cur_line != NULL) && (line_number < sectionid))
3881 prev_line = cur_line;
3882 cur_line = cur_line->next;
3886 if ((cur_line == NULL)
3887 || (cur_line->type != FILE_LINE_ACTION))
3889 /* Invalid "sectionid" parameter */
3890 edit_free_file(file);
3891 return JB_ERR_CGI_PARAMS;
3894 if ((cur_line->next != NULL)
3895 && (cur_line->next->type == FILE_LINE_URL))
3897 /* Section not empty. */
3898 edit_free_file(file);
3899 return JB_ERR_CGI_PARAMS;
3902 /* At this point, the line to remove is in cur_line, and the previous
3903 * one is in prev_line
3906 /* Unlink cur_line */
3907 if (prev_line == NULL)
3909 /* Removing the first line from the file */
3910 file->lines = cur_line->next;
3914 prev_line->next = cur_line->next;
3916 cur_line->next = NULL;
3919 edit_free_file_lines(cur_line);
3921 err = edit_write_file(file);
3924 /* Error writing file */
3925 if (err == JB_ERR_FILE)
3927 /* Read-only file. */
3928 err = cgi_error_file_read_only(csp, rsp, file->filename);
3930 edit_free_file(file);
3934 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
3935 (unsigned long) time(NULL), file->identifier);
3937 edit_free_file(file);
3939 return cgi_redirect(rsp, target);
3943 /*********************************************************************
3945 * Function : cgi_edit_actions_section_add
3947 * Description : CGI function that adds a new empty section to
3951 * 1 : csp = Current client state (buffers, headers, etc...)
3952 * 2 : rsp = http_response data structure for output
3953 * 3 : parameters = map of cgi parameters
3956 * f : (filename) Identifies the file to edit
3957 * v : (version) File's last-modified time
3958 * s : (section) Line number of section to add after, 0 for
3961 * Returns : JB_ERR_OK on success
3962 * JB_ERR_MEMORY on out-of-memory
3963 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3964 * specified or not valid.
3966 *********************************************************************/
3967 jb_err cgi_edit_actions_section_add(struct client_state *csp,
3968 struct http_response *rsp,
3969 const struct map *parameters)
3972 struct file_line * new_line;
3974 struct editable_file * file;
3975 struct file_line * cur_line;
3976 unsigned line_number;
3980 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3982 return cgi_error_disabled(csp, rsp);
3985 err = get_number_param(csp, parameters, "s", §ionid);
3991 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3994 /* No filename specified, can't read file, modified, or out of memory. */
3995 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3999 cur_line = file->lines;
4001 if (sectionid <= 1U)
4003 /* Add to start of file */
4004 if (cur_line != NULL && cur_line->type != FILE_LINE_ACTION)
4006 /* There's something in the file, find the line before the first
4009 while ((cur_line->next != NULL)
4010 && (cur_line->next->type != FILE_LINE_ACTION))
4012 cur_line = cur_line->next;
4018 /* File starts with action line, so insert at top */
4024 /* Add after stated section. */
4025 while ((cur_line != NULL) && (line_number < sectionid))
4027 cur_line = cur_line->next;
4031 if ((cur_line == NULL)
4032 || (cur_line->type != FILE_LINE_ACTION))
4034 /* Invalid "sectionid" parameter */
4035 edit_free_file(file);
4036 return JB_ERR_CGI_PARAMS;
4039 /* Skip through the section to find the last line in it. */
4040 while ((cur_line->next != NULL)
4041 && (cur_line->next->type != FILE_LINE_ACTION))
4043 cur_line = cur_line->next;
4048 /* At this point, the last line in the previous section is in cur_line
4049 * - add after this. (Or if we need to add as the first line, cur_line
4053 new_text = strdup("{}");
4054 if (NULL == new_text)
4056 edit_free_file(file);
4057 return JB_ERR_MEMORY;
4060 /* Allocate the new line */
4061 new_line = zalloc_or_die(sizeof(*new_line));
4063 /* Fill in the data members of the new line */
4064 new_line->raw = NULL;
4065 new_line->prefix = NULL;
4066 new_line->unprocessed = new_text;
4067 new_line->type = FILE_LINE_ACTION;
4069 if (cur_line != NULL)
4071 /* Link new_line into the list, after cur_line */
4072 new_line->next = cur_line->next;
4073 cur_line->next = new_line;
4077 /* Link new_line into the list, as first line */
4078 new_line->next = file->lines;
4079 file->lines = new_line;
4082 /* Done making changes, now commit */
4084 err = edit_write_file(file);
4087 /* Error writing file */
4088 if (err == JB_ERR_FILE)
4090 /* Read-only file. */
4091 err = cgi_error_file_read_only(csp, rsp, file->filename);
4093 edit_free_file(file);
4097 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
4098 (unsigned long) time(NULL), file->identifier);
4100 edit_free_file(file);
4102 return cgi_redirect(rsp, target);
4106 /*********************************************************************
4108 * Function : cgi_edit_actions_section_swap
4110 * Description : CGI function that swaps the order of two sections
4111 * in the actions file. Note that this CGI can actually
4112 * swap any two arbitrary sections, but the GUI interface
4113 * currently only allows consecutive sections to be
4117 * 1 : csp = Current client state (buffers, headers, etc...)
4118 * 2 : rsp = http_response data structure for output
4119 * 3 : parameters = map of cgi parameters
4122 * f : (filename) Identifies the file to edit
4123 * v : (version) File's last-modified time
4124 * s1 : (section1) Line number of first section to swap
4125 * s2 : (section2) Line number of second section to swap
4127 * Returns : JB_ERR_OK on success
4128 * JB_ERR_MEMORY on out-of-memory
4129 * JB_ERR_CGI_PARAMS if the CGI parameters are not
4130 * specified or not valid.
4132 *********************************************************************/
4133 jb_err cgi_edit_actions_section_swap(struct client_state *csp,
4134 struct http_response *rsp,
4135 const struct map *parameters)
4139 struct editable_file * file;
4140 struct file_line * cur_line;
4141 struct file_line * prev_line;
4142 struct file_line * line_before_section1;
4143 struct file_line * line_start_section1;
4144 struct file_line * line_end_section1;
4145 struct file_line * line_after_section1;
4146 struct file_line * line_before_section2;
4147 struct file_line * line_start_section2;
4148 struct file_line * line_end_section2;
4149 struct file_line * line_after_section2;
4150 unsigned line_number;
4154 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
4156 return cgi_error_disabled(csp, rsp);
4159 err = get_number_param(csp, parameters, "s1", §ion1);
4160 if (!err) err = get_number_param(csp, parameters, "s2", §ion2);
4166 if (section1 > section2)
4168 unsigned temp = section2;
4169 section2 = section1;
4173 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
4176 /* No filename specified, can't read file, modified, or out of memory. */
4177 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
4180 /* Start at the beginning... */
4182 cur_line = file->lines;
4185 /* ... find section1 ... */
4186 while ((cur_line != NULL) && (line_number < section1))
4188 prev_line = cur_line;
4189 cur_line = cur_line->next;
4193 if ((cur_line == NULL)
4194 || (cur_line->type != FILE_LINE_ACTION))
4196 /* Invalid "section1" parameter */
4197 edit_free_file(file);
4198 return JB_ERR_CGI_PARAMS;
4201 /* If no-op, we've validated params and can skip the rest. */
4202 if (section1 != section2)
4204 /* ... find the end of section1 ... */
4205 line_before_section1 = prev_line;
4206 line_start_section1 = cur_line;
4209 prev_line = cur_line;
4210 cur_line = cur_line->next;
4213 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
4214 line_end_section1 = prev_line;
4215 line_after_section1 = cur_line;
4217 /* ... find section2 ... */
4218 while ((cur_line != NULL) && (line_number < section2))
4220 prev_line = cur_line;
4221 cur_line = cur_line->next;
4225 if ((cur_line == NULL)
4226 || (cur_line->type != FILE_LINE_ACTION))
4228 /* Invalid "section2" parameter */
4229 edit_free_file(file);
4230 return JB_ERR_CGI_PARAMS;
4233 /* ... find the end of section2 ... */
4234 line_before_section2 = prev_line;
4235 line_start_section2 = cur_line;
4238 prev_line = cur_line;
4239 cur_line = cur_line->next;
4242 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
4243 line_end_section2 = prev_line;
4244 line_after_section2 = cur_line;
4246 /* Now have all the pointers we need. Do the swap. */
4248 /* Change the pointer to section1 to point to section2 instead */
4249 if (line_before_section1 == NULL)
4251 file->lines = line_start_section2;
4255 line_before_section1->next = line_start_section2;
4258 if (line_before_section2 == line_end_section1)
4260 /* Consecutive sections */
4261 line_end_section2->next = line_start_section1;
4265 line_end_section2->next = line_after_section1;
4266 line_before_section2->next = line_start_section1;
4269 /* Set the pointer from the end of section1 to the rest of the file */
4270 line_end_section1->next = line_after_section2;
4272 err = edit_write_file(file);
4275 /* Error writing file */
4276 if (err == JB_ERR_FILE)
4278 /* Read-only file. */
4279 err = cgi_error_file_read_only(csp, rsp, file->filename);
4281 edit_free_file(file);
4284 } /* END if (section1 != section2) */
4286 snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
4287 (unsigned long) time(NULL), file->identifier);
4289 edit_free_file(file);
4291 return cgi_redirect(rsp, target);
4295 /*********************************************************************
4297 * Function : javascriptify
4299 * Description : Converts a string into a form JavaScript will like.
4301 * Netscape 4's JavaScript sucks - it doesn't use
4302 * "id" parameters, so you have to set the "name"
4303 * used to submit a form element to something JavaScript
4304 * will like. (Or access the elements by index in an
4305 * array. That array contains >60 elements and will
4306 * be changed whenever we add a new action to the
4307 * editor, so I'm NOT going to use indexes that have
4308 * to be figured out by hand.)
4310 * Currently the only thing we have to worry about
4311 * is "-" ==> "_" conversion.
4313 * This is a length-preserving operation so it is
4314 * carried out in-place, no memory is allocated
4318 * 1 : identifier = String to make JavaScript-friendly.
4322 *********************************************************************/
4323 static void javascriptify(char * identifier)
4325 char * p = identifier;
4326 while (NULL != (p = strchr(p, '-')))
4333 /*********************************************************************
4335 * Function : actions_to_radio
4337 * Description : Converts a actionsfile entry into settings for
4338 * radio buttons and edit boxes on a HTML form.
4341 * 1 : exports = List of substitutions to add to.
4342 * 2 : action = Action to read
4344 * Returns : JB_ERR_OK on success
4345 * JB_ERR_MEMORY on out-of-memory
4347 *********************************************************************/
4348 static jb_err actions_to_radio(struct map * exports,
4349 const struct action_spec *action)
4360 mask = action->mask;
4363 /* sanity - prevents "-feature +feature" */
4367 #define DEFINE_ACTION_BOOL(name, bit) \
4368 if (!(mask & bit)) \
4370 current_mode = 'n'; \
4372 else if (add & bit) \
4374 current_mode = 'y'; \
4378 current_mode = 'x'; \
4380 if (map_radio(exports, name, "ynx", current_mode)) \
4382 return JB_ERR_MEMORY; \
4385 #define DEFINE_ACTION_STRING(name, bit, index) \
4386 DEFINE_ACTION_BOOL(name, bit); \
4389 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default) \
4392 checked = !strcmp(action->string[index], value); \
4396 checked = is_default; \
4398 mapped_param |= checked; \
4399 if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
4401 return JB_ERR_MEMORY; \
4404 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val) \
4405 if (map(exports, name "-param-custom", 1, \
4406 ((!mapped_param) ? "checked" : ""), 1)) \
4408 return JB_ERR_MEMORY; \
4410 if (map(exports, name "-param", 1, \
4411 (((add & bit) && !mapped_param) ? \
4412 action->string[index] : default_val), 1)) \
4414 return JB_ERR_MEMORY; \
4417 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val) \
4418 if (map(exports, name "-param", 1, \
4419 ((add & bit) ? action->string[index] : default_val), 1)) \
4421 return JB_ERR_MEMORY; \
4424 #define DEFINE_ACTION_MULTI(name, index) \
4425 if (action->multi_add[index]->first) \
4427 current_mode = 'y'; \
4429 else if (action->multi_remove_all[index]) \
4431 current_mode = 'n'; \
4433 else if (action->multi_remove[index]->first) \
4435 current_mode = 'y'; \
4439 current_mode = 'x'; \
4441 if (map_radio(exports, name, "ynx", current_mode)) \
4443 return JB_ERR_MEMORY; \
4446 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
4448 #include "actionlist.h"
4450 #undef DEFINE_ACTION_MULTI
4451 #undef DEFINE_ACTION_STRING
4452 #undef DEFINE_ACTION_BOOL
4453 #undef DEFINE_ACTION_ALIAS
4454 #undef DEFINE_CGI_PARAM_CUSTOM
4455 #undef DEFINE_CGI_PARAM_RADIO
4456 #undef DEFINE_CGI_PARAM_NO_RADIO
4461 /*********************************************************************
4463 * Function : action_render_string_actions_template
4465 * Description : Converts an actionsfile entry into HTML template for
4466 * actions with string filters (currently SUPPRESS-TAG
4470 * 1 : exports = List of substitutions to add to.
4471 * 2 : action = Action to read
4472 * 3 : action_template = template to fill
4473 * 4 : type = filter type info for rendered values/macro name
4475 * Returns : JB_ERR_OK on success
4476 * JB_ERR_MEMORY on out-of-memory
4478 *********************************************************************/
4479 static jb_err action_render_string_actions_template(struct map *exports,
4480 const struct action_spec *action,
4481 const char *action_template,
4482 const struct string_action_type_info *string_action_type)
4484 jb_err err = JB_ERR_OK;
4485 int filter_identifier = 0;
4487 char *prepared_template = strdup("");
4488 const struct action_type_info *type = &action_type_info[string_action_type->action_type];
4490 struct action_multi {
4492 struct list_entry *list;
4495 assert(type->multi_action_index < ACTION_MULTI_COUNT);
4497 struct action_multi desc[] = {
4498 { 'y', action->multi_add[type->multi_action_index][0].first },
4499 { 'n', action->multi_remove[type->multi_action_index][0].first }
4502 for (i = 0; i < SZ(desc); ++i)
4504 const char radio = desc[i].radio;
4505 struct list_entry *entry = desc[i].list;
4506 for (;(!err) && (entry != NULL); entry = entry->next)
4509 struct map *line_exports;
4511 /* Generate a unique serial number */
4512 snprintf(number, sizeof(number), "%x", filter_identifier++);
4514 line_exports = new_map();
4515 if (line_exports == NULL)
4517 err = JB_ERR_MEMORY;
4522 if (!err) err = map(line_exports, "index", 1, number, 1);
4523 if (!err) err = map(line_exports, "name", 1, entry->str, 1);
4524 if (!err) err = map_radio(line_exports, "this-filter", "ynx", radio);
4525 if (!err) err = map(line_exports, "filter-type", 1, type->type, 1);
4526 if (!err) err = map(line_exports, "abbr-action-type", 1, type->abbr_type, 1);
4527 if (!err) err = map(line_exports, "anchor", 1, type->anchor, 1);
4528 if (!err) err = map(line_exports, "desc", 1, string_action_type->description, 1);
4529 if (!err) err = map(line_exports, "input_desc", 1, string_action_type->input_description, 1);
4532 action_line = strdup(action_template);
4533 if (action_line == NULL) err = JB_ERR_MEMORY;
4535 if (!err) err = template_fill(&action_line, line_exports);
4536 if (!err) err = string_join(&prepared_template, action_line);
4538 free_map(line_exports);
4542 if (!err) map(exports, type->macro_name, 1, prepared_template, 1);
4543 freez(prepared_template);
4547 /*********************************************************************
4549 * Function : actions_from_radio
4551 * Description : Converts a map of parameters passed to a CGI function
4552 * into an actionsfile entry.
4555 * 1 : parameters = parameters to the CGI call
4556 * 2 : action = Action to change. Must be valid before
4557 * the call, actions not specified will be
4560 * Returns : JB_ERR_OK on success
4561 * JB_ERR_MEMORY on out-of-memory
4563 *********************************************************************/
4564 static jb_err actions_from_radio(const struct map * parameters,
4565 struct action_spec *action)
4570 const char * js_name;
4571 jb_err err = JB_ERR_OK;
4576 /* Statics are generally a potential race condition,
4577 * but in this case we're safe and don't need semaphores.
4578 * Be careful if you modify this function.
4580 * The js_name_arr's are never free()d, but this is no
4581 * problem, since they will only be created once and
4582 * used by all threads thereafter. -oes
4585 #define JAVASCRIPTIFY(dest_var, string) \
4587 static int first_time = 1; \
4588 static char *js_name_arr; \
4591 js_name_arr = strdup(string); \
4592 javascriptify(js_name_arr); \
4594 dest_var = js_name_arr; \
4598 #define DEFINE_ACTION_BOOL(name, bit) \
4599 JAVASCRIPTIFY(js_name, name); \
4600 ch = get_char_param(parameters, js_name); \
4603 action->add |= bit; \
4604 action->mask |= bit; \
4606 else if (ch == 'N') \
4608 action->add &= ~bit; \
4609 action->mask &= ~bit; \
4611 else if (ch == 'X') \
4613 action->add &= ~bit; \
4614 action->mask |= bit; \
4617 #define DEFINE_ACTION_STRING(name, bit, index) \
4618 JAVASCRIPTIFY(js_name, name); \
4619 ch = get_char_param(parameters, js_name); \
4623 JAVASCRIPTIFY(js_name, name "-mode"); \
4624 if (!err) err = get_string_param(parameters, js_name, ¶m); \
4625 if ((param == NULL) || (0 == strcmp(param, "CUSTOM"))) \
4627 JAVASCRIPTIFY(js_name, name "-param"); \
4628 if (!err) err = get_string_param(parameters, js_name, ¶m); \
4630 if (param != NULL) \
4632 if (NULL == (param_dup = strdup(param))) \
4634 return JB_ERR_MEMORY; \
4636 freez(action->string[index]); \
4637 action->add |= bit; \
4638 action->mask |= bit; \
4639 action->string[index] = param_dup; \
4642 else if (ch == 'N') \
4644 if (action->add & bit) \
4646 freez(action->string[index]); \
4648 action->add &= ~bit; \
4649 action->mask &= ~bit; \
4651 else if (ch == 'X') \
4653 if (action->add & bit) \
4655 freez(action->string[index]); \
4657 action->add &= ~bit; \
4658 action->mask |= bit; \
4661 #define DEFINE_ACTION_MULTI(name, index) \
4662 JAVASCRIPTIFY(js_name, name); \
4663 ch = get_char_param(parameters, js_name); \
4668 else if (ch == 'N') \
4670 list_remove_all(action->multi_add[index]); \
4671 list_remove_all(action->multi_remove[index]); \
4672 action->multi_remove_all[index] = 1; \
4674 else if (ch == 'X') \
4676 list_remove_all(action->multi_add[index]); \
4677 list_remove_all(action->multi_remove[index]); \
4678 action->multi_remove_all[index] = 0; \
4681 #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
4683 #include "actionlist.h"
4685 #undef DEFINE_ACTION_MULTI
4686 #undef DEFINE_ACTION_STRING
4687 #undef DEFINE_ACTION_BOOL
4688 #undef DEFINE_ACTION_ALIAS
4689 #undef JAVASCRIPTIFY
4693 #endif /* def FEATURE_CGI_EDIT_ACTIONS */
4696 #ifdef FEATURE_TOGGLE
4697 /*********************************************************************
4699 * Function : cgi_toggle
4701 * Description : CGI function that adds a new empty section to
4705 * 1 : csp = Current client state (buffers, headers, etc...)
4706 * 2 : rsp = http_response data structure for output
4707 * 3 : parameters = map of cgi parameters
4710 * set : If present, how to change toggle setting:
4711 * "enable", "disable", "toggle", or none (default).
4712 * mini : If present, use mini reply template.
4714 * Returns : JB_ERR_OK on success
4715 * JB_ERR_MEMORY on out-of-memory
4717 *********************************************************************/
4718 jb_err cgi_toggle(struct client_state *csp,
4719 struct http_response *rsp,
4720 const struct map *parameters)
4722 struct map *exports;
4724 const char *template_name;
4730 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE))
4732 return cgi_error_disabled(csp, rsp);
4735 mode = get_char_param(parameters, "set");
4740 global_toggle_state = 1;
4742 else if (mode == 'D')
4745 global_toggle_state = 0;
4747 else if (mode == 'T')
4750 global_toggle_state = !global_toggle_state;
4753 log_error(LOG_LEVEL_INFO, "Now toggled %s.", global_toggle_state ? "ON" : "OFF");
4755 if (NULL == (exports = default_exports(csp, "toggle")))
4757 return JB_ERR_MEMORY;
4760 template_name = (get_char_param(parameters, "mini")
4764 return template_fill_for_cgi(csp, template_name, exports, rsp);
4766 #endif /* def FEATURE_TOGGLE */