X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=blobdiff_plain;f=cgiedit.c;h=3ab55b587663802ea9dc7cfc7e870512acf76a7e;hp=c447baa3dca52796a077741947eecd6b21e06f27;hb=5af5acaa9aa8c28ebcb6eb7be8a5d61b1a4fbf34;hpb=02165000f2941460fac686a76ec31f555285a653 diff --git a/cgiedit.c b/cgiedit.c index c447baa3..3ab55b58 100644 --- a/cgiedit.c +++ b/cgiedit.c @@ -1,4 +1,4 @@ -const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.39 2002/05/12 21:39:15 jongfoster Exp $"; +const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.66 2009/05/16 13:27:20 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/cgiedit.c,v $ @@ -15,7 +15,7 @@ const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.39 2002/05/12 21:39:15 jongfoster * * Stick to the short names in this file for consistency. * - * Copyright : Written by and Copyright (C) 2001 the SourceForge + * Copyright : Written by and Copyright (C) 2001-2008 the SourceForge * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written @@ -40,209 +40,8 @@ const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.39 2002/05/12 21:39:15 jongfoster * or write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - * Revisions : - * $Log: cgiedit.c,v $ - * Revision 1.39 2002/05/12 21:39:15 jongfoster - * - Adding Doxygen-style comments to structures and #defines. - * - Correcting function comments - * - * Revision 1.38 2002/05/03 23:00:38 jongfoster - * Support for templates for "standard actions" buttons. - * See bug #549871 - * - * Revision 1.37 2002/04/30 11:14:52 oes - * Made csp the first parameter in *action_to_html - * - * Revision 1.36 2002/04/26 21:53:30 jongfoster - * Fixing a memory leak. (Near, but not caused by, my earlier commit). - * - * Revision 1.35 2002/04/26 21:50:02 jongfoster - * Honouring default exports in edit-actions-for-url-filter template. - * - * Revision 1.34 2002/04/26 12:54:17 oes - * Adaptions to changes in actions.c - * - * Revision 1.33 2002/04/24 02:17:47 oes - * - Moved get_char_param, get_string_param and get_number_param to cgi.c - * - Comments - * - Activated Jon's code for editing multiple AFs - * - cgi_edit_list_actions now provides context-sensitive - * help, looks up all action sets from standard.action and - * makes buttons for them in the catchall section - * - cgi_edit_action_submit now honors a p parameter, looks up - * the corresponding action set, and sets the catchall pattern's - * actions accordingly. - * - * Revision 1.32 2002/04/19 16:55:31 jongfoster - * Fixing newline problems. If we do our own text file newline - * mangling, we don't want the library to do any, so we need to - * open the files in *binary* mode. - * - * Revision 1.31 2002/04/18 19:21:08 jongfoster - * Added code to detect "conventional" action files, that start - * with a set of actions for all URLs (the pattern "/"). - * These are special-cased in the "edit-actions-list" CGI, so - * that a special UI can be written for them. - * - * Revision 1.30 2002/04/10 13:38:35 oes - * load_template signature changed - * - * Revision 1.29 2002/04/08 16:59:08 oes - * Fixed comment - * - * Revision 1.28 2002/03/27 12:30:29 oes - * Deleted unsused variable - * - * Revision 1.27 2002/03/26 23:06:04 jongfoster - * Removing duplicate @ifs on the toggle page - * - * Revision 1.26 2002/03/26 22:59:17 jongfoster - * Fixing /toggle to display status consistently. - * - * Revision 1.25 2002/03/26 22:29:54 swa - * we have a new homepage! - * - * Revision 1.24 2002/03/24 15:23:33 jongfoster - * Name changes - * - * Revision 1.23 2002/03/24 13:32:41 swa - * name change related issues - * - * Revision 1.22 2002/03/24 13:25:43 swa - * name change related issues - * - * Revision 1.21 2002/03/22 18:02:48 jongfoster - * Fixing remote toggle - * - * Revision 1.20 2002/03/16 20:28:34 oes - * Added descriptions to the filters so users will know what they select in the cgi editor - * - * Revision 1.19 2002/03/16 18:38:14 jongfoster - * Stopping stupid or malicious users from breaking the actions - * file using the web-based editor. - * - * Revision 1.18 2002/03/16 14:57:44 jongfoster - * Full support for enabling/disabling modular filters. - * - * Revision 1.17 2002/03/16 14:26:42 jongfoster - * First version of modular filters support - READ ONLY! - * Fixing a double-free bug in the out-of-memory handling in map_radio(). - * - * Revision 1.16 2002/03/07 03:46:17 oes - * Fixed compiler warnings - * - * Revision 1.15 2002/03/06 22:54:35 jongfoster - * Automated function-comment nitpicking. - * - * Revision 1.14 2002/03/05 00:24:51 jongfoster - * Patch to always edit the current actions file. - * - * Revision 1.13 2002/03/04 02:07:59 david__schmidt - * Enable web editing of actions file on OS/2 (it had been broken all this time!) - * - * Revision 1.12 2002/03/03 09:18:03 joergs - * Made jumbjuster work on AmigaOS again. - * - * Revision 1.11 2002/01/23 01:03:31 jongfoster - * Fixing gcc [CygWin] compiler warnings - * - * Revision 1.10 2002/01/23 00:22:59 jongfoster - * Adding new function cgi_edit_actions_section_swap(), to reorder - * the actions file. - * - * Adding get_url_spec_param() to get a validated URL pattern. - * - * Moving edit_read_line() out of this file and into loaders.c. - * - * Adding missing html_encode() to many CGI functions. - * - * Moving the functions that #include actionlist.h to the end of the file, - * because the Visual C++ 97 debugger gets extremely confused if you try - * to debug any code that comes after them in the file. - * - * Major optimizations in cgi_edit_actions_list() to reduce the size of - * the generated HTML (down 40% from 550k to 304k), with major side-effects - * throughout the editor and templates. In particular, the length of the - * URLs throughout the editor has been drastically reduced, by cutting - * paramater names down to 1 character and CGI names down to 3-4 - * characters, by removing all non-essential CGI paramaters even at the - * expense of having to re-read the actions file for the most trivial - * page, and by using relative rather than absolute URLs. This means - * that this (typical example): - * - * - * - * is now this: - * - * - * - * Revision 1.9 2002/01/17 20:56:22 jongfoster - * Replacing hard references to the URL of the config interface - * with #defines from project.h - * - * Revision 1.8 2001/11/30 23:35:51 jongfoster - * Renaming actionsfile to ijb.action - * - * Revision 1.7 2001/11/13 00:28:24 jongfoster - * - Renaming parameters from edit-actions-for-url so that they only - * contain legal JavaScript characters. If we wanted to write - * JavaScript that worked with Netscape 4, this is nessacery. - * (Note that at the moment the JavaScript doesn't actually work - * with Netscape 4, but now this is purely a template issue, not - * one affecting code). - * - Adding new CGIs for use by non-JavaScript browsers: - * edit-actions-url-form - * edit-actions-add-url-form - * edit-actions-remove-url-form - * - Fixing || bug. - * - * Revision 1.6 2001/10/29 03:48:09 david__schmidt - * OS/2 native needed a snprintf() routine. Added one to miscutil, brackedted - * by and __OS2__ ifdef. - * - * Revision 1.5 2001/10/25 03:40:48 david__schmidt - * Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple - * threads to call select() simultaneously. So, it's time to do a real, live, - * native OS/2 port. See defines for __EMX__ (the porting layer) vs. __OS2__ - * (native). Both versions will work, but using __OS2__ offers multi-threading. - * - * Revision 1.4 2001/10/23 21:48:19 jongfoster - * Cleaning up error handling in CGI functions - they now send back - * a HTML error page and should never cause a FATAL error. (Fixes one - * potential source of "denial of service" attacks). - * - * CGI actions file editor that works and is actually useful. - * - * Ability to toggle JunkBuster remotely using a CGI call. - * - * You can turn off both the above features in the main configuration - * file, e.g. if you are running a multi-user proxy. - * - * Revision 1.3 2001/10/14 22:12:49 jongfoster - * New version of CGI-based actionsfile editor. - * Major changes, including: - * - Completely new file parser and file output routines - * - edit-actions CGI renamed edit-actions-for-url - * - All CGIs now need a filename parameter, except for... - * - New CGI edit-actions which doesn't need a filename, - * to allow you to start the editor up. - * - edit-actions-submit now works, and now automatically - * redirects you back to the main edit-actions-list handler. - * - * Revision 1.2 2001/09/16 17:05:14 jongfoster - * Removing unused #include showarg.h - * - * Revision 1.1 2001/09/16 15:47:37 jongfoster - * First version of CGI-based edit interface. This is very much a - * work-in-progress, and you can't actually use it to edit anything - * yet. You must #define FEATURE_CGI_EDIT_ACTIONS for these changes - * to have any effect. - * - * **********************************************************************/ - + #include "config.h" @@ -258,10 +57,6 @@ const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.39 2002/05/12 21:39:15 jongfoster #include #include -#ifdef _WIN32 -#define snprintf _snprintf -#endif /* def _WIN32 */ - #include "project.h" #include "cgi.h" #include "cgiedit.h" @@ -272,8 +67,10 @@ const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.39 2002/05/12 21:39:15 jongfoster #include "miscutil.h" #include "errlog.h" #include "loaders.h" +#ifdef FEATURE_TOGGLE +/* loadcfg.h is for global_toggle_state only */ #include "loadcfg.h" -/* loadcfg.h is for g_bToggleIJB only */ +#endif /* def FEATURE_TOGGLE */ #include "urlmatch.h" const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION; @@ -374,6 +171,11 @@ struct file_line /** This file_line is in a {{description}} block. */ #define FILE_LINE_DESCRIPTION_ENTRY 10 +/* + * Number of file modification time mismatches + * before the CGI editor gets turned off. + */ +#define ACCEPTABLE_TIMESTAMP_MISMATCHES 3 /** * A configuration file, in a format that can be edited and written back to @@ -383,8 +185,7 @@ struct editable_file { struct file_line * lines; /**< The contents of the file. A linked list of lines. */ const char * filename; /**< Full pathname - e.g. "/etc/privoxy/wibble.action". */ - const char * identifier; /**< Filename stub - e.g. "wibble". Use for CGI param. */ - /**< Pre-encoded with url_encode() for ease of use. */ + unsigned identifier; /**< The file name's position in csp->config->actions_file[]. */ const char * version_str; /**< Last modification time, as a string. For CGI param. */ /**< Can be used in URL without using url_param(). */ unsigned version; /**< Last modification time - prevents chaos with @@ -401,13 +202,70 @@ struct editable_file (Statically allocated) */ }; +/** + * Information about the filter types. + * Used for macro replacement in cgi_edit_actions_for_url. + */ +struct filter_type_info +{ + const int multi_action_index; /**< The multi action index as defined in project.h */ + const char *macro_name; /**< Name of the macro that has to be replaced + with the prepared templates. + For example "content-filter-params" */ + const char *type; /**< Name of the filter type, + for example "server-header-filter". */ + /* XXX: check if these two can be combined. */ + const char *disable_all_option; /**< Name of the catch-all radio option that has + to be checked or unchecked for this filter type. */ + const char *disable_all_param; /**< Name of the parameter that causes all filters of + this type to be disabled. */ + const char *abbr_type; /**< Abbreviation of the filter type, usually the + first or second character capitalized */ + const char *anchor; /**< Anchor for the User Manual link, + for example "SERVER-HEADER-FILTER" */ +}; + +/* Accessed by index, keep the order in the way the FT_ macros are defined. */ +static const struct filter_type_info filter_type_info[] = +{ + { + ACTION_MULTI_FILTER, + "content-filter-params", "filter", + "filter-all", "filter_all", + "F", "FILTER" + }, + { + ACTION_MULTI_CLIENT_HEADER_FILTER, + "client-header-filter-params", "client-header-filter", + "client-header-filter-all", "client_header_filter_all", + "C", "CLIENT-HEADER-FILTER" + }, + { + ACTION_MULTI_SERVER_HEADER_FILTER, + "server-header-filter-params", "server-header-filter", + "server-header-filter-all", "server_header_filter_all", + "S", "SERVER-HEADER-FILTER" + }, + { + ACTION_MULTI_CLIENT_HEADER_TAGGER, + "client-header-tagger-params", "client-header-tagger", + "client-header-tagger-all", "client_header_tagger_all", + "L", "CLIENT-HEADER-TAGGER" + }, + { + ACTION_MULTI_SERVER_HEADER_TAGGER, + "server-header-tagger-params", "server-header-tagger", + "server-header-tagger-all", "server_header_tagger_all", + "E", "SERVER-HEADER-TAGGER" + }, +}; + /* FIXME: Following non-static functions should be prototyped in .h or made static */ /* Functions to read and write arbitrary config files */ jb_err edit_read_file(struct client_state *csp, const struct map *parameters, int require_version, - const char *suffix, struct editable_file **pfile); jb_err edit_write_file(struct editable_file * file); void edit_free_file(struct editable_file * file); @@ -433,8 +291,6 @@ jb_err cgi_error_file(struct client_state *csp, jb_err cgi_error_file_read_only(struct client_state *csp, struct http_response *rsp, const char *filename); -jb_err cgi_error_disabled(struct client_state *csp, - struct http_response *rsp); /* Internal arbitrary config file support functions */ static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline); @@ -445,13 +301,6 @@ static int match_actions_file_header_line(const char * line, const char * name); static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue); /* Internal parameter parsing functions */ -static jb_err get_file_name_param(struct client_state *csp, - const struct map *parameters, - const char *param_name, - const char *suffix, - char **pfilename, - const char **pparam); - static jb_err get_url_spec_param(struct client_state *csp, const struct map *parameters, const char *name, @@ -478,6 +327,64 @@ static jb_err map_copy_parameter_url(struct map *out, const char *name); #endif /* unused function */ +static jb_err get_file_name_param(struct client_state *csp, + const struct map *parameters, + const char *param_name, + const char **pfilename); + +/* Internal convenience functions */ +static char *section_target(const unsigned sectionid); + +/********************************************************************* + * + * Function : section_target + * + * Description : Given an unsigned (section id) n, produce a dynamically + * allocated string of the form #l, for use in link + * targets. + * + * XXX: The hash should be moved into the templates + * to make this function more generic and render + * stringify() obsolete. + * + * Parameters : + * 1 : sectionid = start line number of section + * + * Returns : String with link target, or NULL if out of + * memory + * + *********************************************************************/ +static char *section_target(const unsigned sectionid) +{ + char buf[30]; + + snprintf(buf, sizeof(buf), "#l%d", sectionid); + return(strdup(buf)); + +} + + +/********************************************************************* + * + * Function : stringify + * + * Description : Convert a number into a dynamically allocated string. + * + * Parameters : + * 1 : number = The number to convert. + * + * Returns : String with link target, or NULL if out of memory + * + *********************************************************************/ +static char *stringify(const unsigned number) +{ + char buf[6]; + + snprintf(buf, sizeof(buf), "%i", number); + return strdup(buf); +} + + /********************************************************************* * * Function : map_copy_parameter_html @@ -575,6 +482,7 @@ static jb_err map_copy_parameter_url(struct map *out, } #endif /* 0 - unused function */ + /********************************************************************* * * Function : cgi_edit_actions_url_form @@ -588,7 +496,7 @@ static jb_err map_copy_parameter_url(struct map *out, * 3 : parameters = map of cgi parameters * * CGI Parameters - * f : (filename) Identifies the file to edit + * i : (action index) Identifies the file to edit * v : (version) File's last-modified time * p : (pattern) Line number of pattern to edit * @@ -607,6 +515,7 @@ jb_err cgi_edit_actions_url_form(struct client_state *csp, struct editable_file * file; struct file_line * cur_line; unsigned line_number; + unsigned section_start_line_number = 0; jb_err err; assert(csp); @@ -635,12 +544,16 @@ jb_err cgi_edit_actions_url_form(struct client_state *csp, for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++) { + if (cur_line->type == FILE_LINE_ACTION) + { + section_start_line_number = line_number; + } cur_line = cur_line->next; } if ( (cur_line == NULL) || (line_number != patternid) - || (patternid < 1) + || (patternid < 1U) || (cur_line->type != FILE_LINE_URL)) { /* Invalid "patternid" parameter */ @@ -654,10 +567,11 @@ jb_err cgi_edit_actions_url_form(struct client_state *csp, return JB_ERR_MEMORY; } - err = map(exports, "f", 1, file->identifier, 1); + err = map(exports, "f", 1, stringify(file->identifier), 0); if (!err) err = map(exports, "v", 1, file->version_str, 1); if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0); if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0); + if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0); edit_free_file(file); @@ -742,7 +656,7 @@ jb_err cgi_edit_actions_add_url_form(struct client_state *csp, * 3 : parameters = map of cgi parameters * * CGI Parameters : - * f : (filename) Identifies the file to edit + * f : (number) The action file identifier. * v : (version) File's last-modified time * p : (pattern) Line number of pattern to edit * @@ -761,6 +675,7 @@ jb_err cgi_edit_actions_remove_url_form(struct client_state *csp, struct editable_file * file; struct file_line * cur_line; unsigned line_number; + unsigned section_start_line_number = 0; jb_err err; assert(csp); @@ -789,12 +704,16 @@ jb_err cgi_edit_actions_remove_url_form(struct client_state *csp, for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++) { + if (cur_line->type == FILE_LINE_ACTION) + { + section_start_line_number = line_number; + } cur_line = cur_line->next; } if ( (cur_line == NULL) || (line_number != patternid) - || (patternid < 1) + || (patternid < 1U) || (cur_line->type != FILE_LINE_URL)) { /* Invalid "patternid" parameter */ @@ -808,10 +727,12 @@ jb_err cgi_edit_actions_remove_url_form(struct client_state *csp, return JB_ERR_MEMORY; } - err = map(exports, "f", 1, file->identifier, 1); + err = map(exports, "f", 1, stringify(file->identifier), 0); if (!err) err = map(exports, "v", 1, file->version_str, 1); - if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0); + if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0); if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0); + if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0); + if (!err) err = map(exports, "actions-file", 1, html_encode(file->filename), 0); edit_free_file(file); @@ -885,7 +806,7 @@ jb_err edit_write_file(struct editable_file * file) { /* Must quote '#' characters */ int numhash = 0; - int len; + size_t len; char * src; char * dest; char * str; @@ -900,29 +821,44 @@ jb_err edit_write_file(struct editable_file * file) assert(numhash > 0); /* Allocate new memory for string */ - len = strlen(cur_line->unprocessed); - if (NULL == (str = malloc((size_t) len + 1 + numhash))) + len = strlen(cur_line->unprocessed) + (size_t)numhash; + if (NULL == (str = malloc(len + 1))) { /* Uh oh, just trashed file! */ fclose(fp); return JB_ERR_MEMORY; } - /* Loop through string from end */ - src = cur_line->unprocessed + len; - dest = str + len + numhash; - for ( ; len >= 0; len--) + /* Copy string but quote hashes */ + src = cur_line->unprocessed; + dest = str; + while (*src) { - if ((*dest-- = *src--) == '#') + if (*src == '#') { - *dest-- = '\\'; + *dest++ = '\\'; numhash--; assert(numhash >= 0); } + *dest++ = *src++; } + *dest = '\0'; + assert(numhash == 0); - assert(src + 1 == cur_line->unprocessed); - assert(dest + 1 == str); + assert(strlen(str) == len); + assert(str == dest - len); + assert(src - len <= cur_line->unprocessed); + + if ((strlen(str) != len) || (numhash != 0)) + { + /* + * Escaping didn't work as expected, go spread the news. + * Only reached in non-debugging builds. + */ + log_error(LOG_LEVEL_ERROR, + "Looks like hash escaping failed. %s might be corrupted now.", + file->filename); + } if (fputs(str, fp) < 0) { @@ -972,8 +908,8 @@ jb_err edit_write_file(struct editable_file * file) /* Correct file->version_str */ freez(file->version_str); - snprintf(version_buf, 22, "%u", file->version); - version_buf[21] = '\0'; + snprintf(version_buf, sizeof(version_buf), "%u", file->version); + version_buf[sizeof(version_buf)-1] = '\0'; file->version_str = strdup(version_buf); if (version_buf == NULL) { @@ -1005,8 +941,6 @@ void edit_free_file(struct editable_file * file) } edit_free_file_lines(file->lines); - freez(file->filename); - freez(file->identifier); freez(file->version_str); file->version = 0; file->parse_error_text = NULL; /* Statically allocated */ @@ -1183,7 +1117,7 @@ static jb_err split_line_on_equals(const char * line, char ** pname, char ** pva name_end--; } - name_len = name_end - line + 1; /* Length excluding \0 */ + name_len = (size_t)(name_end - line) + 1; /* Length excluding \0 */ if (NULL == (*pname = (char *) malloc(name_len + 1))) { return JB_ERR_MEMORY; @@ -1439,7 +1373,7 @@ jb_err edit_parse_actions_file(struct editable_file * file) text++; len--; } - while ( (len > 0) + while ( (len > (size_t)0) && ( (text[len - 1] == ' ') || (text[len - 1] == '\t') ) ) { @@ -1601,13 +1535,11 @@ jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline) * 1 : csp = Current client state (buffers, headers, etc...) * 2 : parameters = map of cgi parameters. * 3 : require_version = true to check "ver" parameter. - * 4 : suffix = File extension, e.g. ".action". - * 5 : pfile = Destination for the file. Will be set + * 4 : pfile = Destination for the file. Will be set * to NULL on error. * * CGI Parameters : - * filename : The name of the file to read, without the - * path or ".action" extension. + * f : The action file identifier. * ver : (Only if require_version is nonzero) * Timestamp of the actions file. If wrong, this * function fails with JB_ERR_MODIFIED. @@ -1626,19 +1558,18 @@ jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline) jb_err edit_read_file(struct client_state *csp, const struct map *parameters, int require_version, - const char *suffix, struct editable_file **pfile) { struct file_line * lines; FILE * fp; jb_err err; - char * filename; - const char * identifier; + const char *filename = NULL; struct editable_file * file; unsigned version = 0; struct stat statbuf[1]; char version_buf[22]; int newline = NEWLINE_UNKNOWN; + unsigned i; assert(csp); assert(parameters); @@ -1646,17 +1577,23 @@ jb_err edit_read_file(struct client_state *csp, *pfile = NULL; - err = get_file_name_param(csp, parameters, "f", suffix, - &filename, &identifier); - if (err) + err = get_number_param(csp, parameters, "f", &i); + if ((JB_ERR_OK == err) && (i < MAX_AF_FILES) && (NULL != csp->config->actions_file[i])) { - return err; + filename = csp->config->actions_file[i]; + } + else if (JB_ERR_CGI_PARAMS == err) + { + /* + * Probably an old-school URL like + * http://config.privoxy.org/edit-actions-list?f=default + */ + get_file_name_param(csp, parameters, "f", &filename); } - if (stat(filename, statbuf) < 0) + if (NULL == filename || stat(filename, statbuf) < 0) { /* Error, probably file not found. */ - free(filename); return JB_ERR_FILE; } version = (unsigned) statbuf->st_mtime; @@ -1667,7 +1604,6 @@ jb_err edit_read_file(struct client_state *csp, err = get_number_param(csp, parameters, "v", &specified_version); if (err) { - free(filename); return err; } @@ -1679,7 +1615,6 @@ jb_err edit_read_file(struct client_state *csp, if (NULL == (fp = fopen(filename,"rb"))) { - free(filename); return JB_ERR_FILE; } @@ -1689,14 +1624,12 @@ jb_err edit_read_file(struct client_state *csp, if (err) { - free(filename); return err; } file = (struct editable_file *) zalloc(sizeof(*file)); if (err) { - free(filename); edit_free_file_lines(lines); return err; } @@ -1705,18 +1638,12 @@ jb_err edit_read_file(struct client_state *csp, file->newline = newline; file->filename = filename; file->version = version; - file->identifier = url_encode(identifier); - - if (file->identifier == NULL) - { - edit_free_file(file); - return JB_ERR_MEMORY; - } + file->identifier = i; /* Correct file->version_str */ freez(file->version_str); - snprintf(version_buf, 22, "%u", file->version); - version_buf[21] = '\0'; + snprintf(version_buf, sizeof(version_buf), "%u", file->version); + version_buf[sizeof(version_buf)-1] = '\0'; file->version_str = strdup(version_buf); if (version_buf == NULL) { @@ -1751,8 +1678,7 @@ jb_err edit_read_file(struct client_state *csp, * to NULL on error. * * CGI Parameters : - * filename : The name of the actions file to read, without the - * path or ".action" extension. + * f : The actions file identifier. * ver : (Only if require_version is nonzero) * Timestamp of the actions file. If wrong, this * function fails with JB_ERR_MODIFIED. @@ -1775,6 +1701,7 @@ jb_err edit_read_actions_file(struct client_state *csp, { jb_err err; struct editable_file *file; + static int acceptable_failures = ACCEPTABLE_TIMESTAMP_MISMATCHES - 1; assert(csp); assert(parameters); @@ -1782,7 +1709,7 @@ jb_err edit_read_actions_file(struct client_state *csp, *pfile = NULL; - err = edit_read_file(csp, parameters, require_version, ".action", &file); + err = edit_read_file(csp, parameters, require_version, &file); if (err) { /* Try to handle if possible */ @@ -1792,7 +1719,24 @@ jb_err edit_read_actions_file(struct client_state *csp, } else if (err == JB_ERR_MODIFIED) { + assert(require_version); err = cgi_error_modified(csp, rsp, lookup(parameters, "f")); + log_error(LOG_LEVEL_ERROR, + "Blocking CGI edit request due to modification time mismatch."); + if (acceptable_failures > 0) + { + log_error(LOG_LEVEL_INFO, + "The CGI editor will be turned off after another %d mismatche(s).", + acceptable_failures); + acceptable_failures--; + } + else + { + log_error(LOG_LEVEL_INFO, + "Timestamp mismatch limit reached, turning CGI editor off. " + "Reload the configuration file to reenable it."); + csp->config->feature_flags &= ~RUNTIME_FEATURE_CGI_EDIT_ACTIONS; + } } if (err == JB_ERR_OK) { @@ -1834,32 +1778,16 @@ jb_err edit_read_actions_file(struct client_state *csp, * Function : get_file_name_param * * Description : Get the name of the file to edit from the parameters - * passed to a CGI function. This function handles - * security checks such as blocking urls containing - * "/" or ".", prepending the config file directory, - * and adding the specified suffix. - * - * (This is an essential security check, otherwise - * users may be able to pass "../../../etc/passwd" - * and overwrite the password file [linux], "prn:" - * and print random data [Windows], etc...) - * - * This function only allows filenames contining the - * characters '-', '_', 'A'-'Z', 'a'-'z', and '0'-'9'. - * That's probably too restrictive but at least it's - * secure. + * passed to a CGI function using the old syntax. + * This function handles security checks and only + * accepts files that Privoxy already knows. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : parameters = map of cgi parameters * 3 : param_name = The name of the parameter to read - * 4 : suffix = File extension, e.g. ".actions" - * 5 : pfilename = destination for full filename. Caller - * free()s. Set to NULL on error. - * 6 : pparam = destination for partial filename, - * suitable for use in another URL. Allocated as part - * of the map "parameters", so don't free it. - * Set to NULL if not specified. + * 4 : pfilename = pointer to the filename in + * csp->config->actions_file[] if found. Set to NULL on error. * * Returns : JB_ERR_OK on success * JB_ERR_MEMORY on out-of-memory @@ -1870,25 +1798,23 @@ jb_err edit_read_actions_file(struct client_state *csp, static jb_err get_file_name_param(struct client_state *csp, const struct map *parameters, const char *param_name, - const char *suffix, - char **pfilename, - const char **pparam) + const char **pfilename) { const char *param; + const char suffix[] = ".action"; const char *s; char *name; char *fullpath; char ch; - int len; + size_t len; + size_t name_size; + int i; assert(csp); assert(parameters); - assert(suffix); assert(pfilename); - assert(pparam); *pfilename = NULL; - *pparam = NULL; param = lookup(parameters, param_name); if (!*param) @@ -1896,8 +1822,6 @@ static jb_err get_file_name_param(struct client_state *csp, return JB_ERR_CGI_PARAMS; } - *pparam = param; - len = strlen(param); if (len >= FILENAME_MAX) { @@ -1905,7 +1829,10 @@ static jb_err get_file_name_param(struct client_state *csp, return JB_ERR_CGI_PARAMS; } - /* Check every character to see if it's legal */ + /* + * Check every character to see if it's legal. + * Totally unnecessary but we do it anyway. + */ s = param; while ((ch = *s++) != '\0') { @@ -1921,13 +1848,14 @@ static jb_err get_file_name_param(struct client_state *csp, } /* Append extension */ - name = malloc(len + strlen(suffix) + 1); + name_size = len + strlen(suffix) + 1; + name = malloc(name_size); if (name == NULL) { return JB_ERR_MEMORY; } - strcpy(name, param); - strcpy(name + len, suffix); + strlcpy(name, param, name_size); + strlcat(name, suffix, name_size); /* Prepend path */ fullpath = make_path(csp->config->confdir, name); @@ -1938,10 +1866,22 @@ static jb_err get_file_name_param(struct client_state *csp, return JB_ERR_MEMORY; } - /* Success */ - *pfilename = fullpath; + /* Check if the file is known */ + for (i = 0; i < MAX_AF_FILES; i++) + { + if (NULL != csp->config->actions_file[i] && + !strcmp(fullpath, csp->config->actions_file[i])) + { + /* Success */ + *pfilename = csp->config->actions_file[i]; + freez(fullpath); + + return JB_ERR_OK; + } + } + freez(fullpath); - return JB_ERR_OK; + return JB_ERR_CGI_PARAMS; } @@ -2103,23 +2043,25 @@ static jb_err map_radio(struct map * exports, const char * values, int value) { - size_t len; char * buf; char * p; char c; + const size_t len = strlen(optionname); + const size_t buf_size = len + 3; assert(exports); assert(optionname); assert(values); - len = strlen(optionname); - buf = malloc(len + 3); + buf = malloc(buf_size); if (buf == NULL) { return JB_ERR_MEMORY; } - strcpy(buf, optionname); + strlcpy(buf, optionname, buf_size); + + /* XXX: this looks ... interesting */ p = buf + len; *p++ = '-'; p[1] = '\0'; @@ -2136,7 +2078,7 @@ static jb_err map_radio(struct map * exports, } } - *p = value; + *p = (char)value; return map(exports, buf, 0, "checked", 1); } @@ -2221,7 +2163,7 @@ jb_err cgi_error_parse(struct client_state *csp, return JB_ERR_MEMORY; } - err = map(exports, "f", 1, file->identifier, 1); + err = map(exports, "f", 1, stringify(file->identifier), 0); if (!err) err = map(exports, "parse-error", 1, html_encode(file->parse_error_text), 0); cur_line = file->parse_error; @@ -2287,7 +2229,7 @@ jb_err cgi_error_file(struct client_state *csp, /********************************************************************* * - * Function : cgi_error_file + * Function : cgi_error_file_read_only * * Description : CGI function that is called when a file cannot be * opened for writing by the CGI editor. @@ -2330,40 +2272,6 @@ jb_err cgi_error_file_read_only(struct client_state *csp, } -/********************************************************************* - * - * Function : cgi_error_disabled - * - * Description : CGI function that is called if the actions editor - * is called although it's disabled in config - * - * Parameters : - * 1 : csp = Current client state (buffers, headers, etc...) - * 2 : rsp = http_response data structure for output - * - * CGI Parameters : none - * - * Returns : JB_ERR_OK on success - * JB_ERR_MEMORY on out-of-memory error. - * - *********************************************************************/ -jb_err cgi_error_disabled(struct client_state *csp, - struct http_response *rsp) -{ - struct map *exports; - - assert(csp); - assert(rsp); - - if (NULL == (exports = default_exports(csp, NULL))) - { - return JB_ERR_MEMORY; - } - - return template_fill_for_cgi(csp, "cgi-error-disabled", exports, rsp); -} - - /********************************************************************* * * Function : cgi_edit_actions @@ -2386,6 +2294,7 @@ jb_err cgi_edit_actions(struct client_state *csp, struct http_response *rsp, const struct map *parameters) { + (void)parameters; if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)) { @@ -2393,20 +2302,9 @@ jb_err cgi_edit_actions(struct client_state *csp, } /* FIXME: Incomplete */ - rsp->status = strdup("302 Local Redirect from Privoxy"); - if (rsp->status == NULL) - { - return JB_ERR_MEMORY; - } - if (enlist_unique_header(rsp->headers, "Location", - CGI_PREFIX "edit-actions-list?f=default")) - { - free(rsp->status); - rsp->status = NULL; - return JB_ERR_MEMORY; - } - return JB_ERR_OK; + return cgi_redirect(rsp, CGI_PREFIX "edit-actions-list?f=default"); + } @@ -2462,7 +2360,6 @@ jb_err cgi_edit_actions_list(struct client_state *csp, if (NULL == (exports = default_exports(csp, NULL))) { - edit_free_file(file); return JB_ERR_MEMORY; } @@ -2471,6 +2368,7 @@ jb_err cgi_edit_actions_list(struct client_state *csp, if (err) { /* No filename specified, can't read file, or out of memory. */ + free_map(exports); return (err == JB_ERR_FILE ? JB_ERR_OK : err); } @@ -2526,7 +2424,7 @@ jb_err cgi_edit_actions_list(struct client_state *csp, } buttons = strdup(""); - for (i = 0; i < MAX_ACTION_FILES; i++) + for (i = 0; i < MAX_AF_FILES; i++) { if (((fl = csp->actions_list[i]) != NULL) && ((b = fl->f) != NULL)) { @@ -2571,9 +2469,9 @@ jb_err cgi_edit_actions_list(struct client_state *csp, */ if (!err) err = map_conditional(exports, "all-urls-present", 1); - snprintf(buf, 150, "%d", line_number); + snprintf(buf, sizeof(buf), "%d", line_number); if (!err) err = map(exports, "all-urls-s", 1, buf, 1); - snprintf(buf, 150, "%d", line_number + 2); + snprintf(buf, sizeof(buf), "%d", line_number + 2); if (!err) err = map(exports, "all-urls-s-next", 1, buf, 1); if (!err) err = map(exports, "all-urls-actions", 1, actions_to_html(csp, cur_line->data.action), 0); @@ -2601,13 +2499,14 @@ jb_err cgi_edit_actions_list(struct client_state *csp, /* Set up global exports */ - if (!err) err = map(exports, "f", 1, file->identifier, 1); + if (!err) err = map(exports, "actions-file", 1, html_encode(file->filename), 0); + if (!err) err = map(exports, "f", 1, stringify(file->identifier), 0); if (!err) err = map(exports, "v", 1, file->version_str, 1); /* Discourage private additions to default.action */ if (!err) err = map_conditional(exports, "default-action", - (strcmp("default", lookup(parameters, "f")) == 0)); + (strstr("default.action", file->filename) != NULL)); if (err) { edit_free_file(file); @@ -2650,7 +2549,6 @@ jb_err cgi_edit_actions_list(struct client_state *csp, free(url_template); edit_free_file(file); free_map(exports); - free(url_template); return err; } @@ -2684,7 +2582,7 @@ jb_err cgi_edit_actions_list(struct client_state *csp, return JB_ERR_MEMORY; } - snprintf(buf, 150, "%d", line_number); + snprintf(buf, sizeof(buf), "%d", line_number); err = map(section_exports, "s", 1, buf, 1); if (!err) err = map(section_exports, "actions", 1, actions_to_html(csp, cur_line->data.action), 0); @@ -2704,7 +2602,7 @@ jb_err cgi_edit_actions_list(struct client_state *csp, if (prev_section_line_number != ((unsigned)(-1))) { /* Not last section */ - snprintf(buf, 150, "%d", prev_section_line_number); + snprintf(buf, sizeof(buf), "%d", prev_section_line_number); if (!err) err = map(section_exports, "s-prev", 1, buf, 1); if (!err) err = map_block_keep(section_exports, "s-prev-exists"); } @@ -2758,10 +2656,10 @@ jb_err cgi_edit_actions_list(struct client_state *csp, return JB_ERR_MEMORY; } - snprintf(buf, 150, "%d", line_number); + snprintf(buf, sizeof(buf), "%d", line_number); err = map(url_exports, "p", 1, buf, 1); - snprintf(buf, 150, "%d", url_1_2); + snprintf(buf, sizeof(buf), "%d", url_1_2); if (!err) err = map(url_exports, "url-1-2", 1, buf, 1); if (!err) err = map(url_exports, "url-html", 1, @@ -2824,12 +2722,13 @@ jb_err cgi_edit_actions_list(struct client_state *csp, /* Could also do section-specific exports here, but it wouldn't be as fast */ + snprintf(buf, sizeof(buf), "%d", line_number); + if (!err) err = map(section_exports, "s-next", 1, buf, 1); + if ( (cur_line != NULL) && (cur_line->type == FILE_LINE_ACTION)) { /* Not last section */ - snprintf(buf, 150, "%d", line_number); - if (!err) err = map(section_exports, "s-next", 1, buf, 1); if (!err) err = map_block_keep(section_exports, "s-next-exists"); } else @@ -2923,8 +2822,8 @@ jb_err cgi_edit_actions_for_url(struct client_state *csp, struct file_line * cur_line; unsigned line_number; jb_err err; - struct file_list *filter_file; struct re_filterfile_spec *filter_group; + int i, have_filters = 0; if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)) { @@ -2967,16 +2866,50 @@ jb_err cgi_edit_actions_for_url(struct client_state *csp, return JB_ERR_MEMORY; } - err = map(exports, "f", 1, file->identifier, 1); + err = map(exports, "f", 1, stringify(file->identifier), 0); if (!err) err = map(exports, "v", 1, file->version_str, 1); if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0); if (!err) err = actions_to_radio(exports, cur_line->data.action); - filter_file = csp->rlist; - filter_group = ((filter_file != NULL) ? filter_file->f : NULL); + /* + * XXX: Some browsers (at least IE6 and IE7) have an artifical URL + * length limitation and ignore clicks on the Submit buttons if + * the resulting GET URL would be longer than their limit. + * + * In Privoxy 3.0.5 beta the standard edit-actions-for-url template + * reached this limit and action editing stopped working in these + * browsers (BR #1570678). + * + * The config option split-large-forms works around this browser + * bug (HTTP has no URL lenght limitation) by deviding the action + * list form into multiple smaller ones. It means the URLs are shorter + * and work in broken browsers as well, but the user can no longer change + * all actions with one submit. + * + * A better solution would be to switch to POST requests, + * but this will do for now. + */ + if(!err && (csp->config->feature_flags & RUNTIME_FEATURE_SPLIT_LARGE_FORMS)) + { + /* Generate multiple smaller form by killing the big one. */ + err = map_block_killer(exports, "one-form-only"); + } + else + { + /* Default: Generate one large form by killing the smaller ones. */ + err = map_block_killer(exports, "multiple-forms"); + } - if (!err) err = map_conditional(exports, "any-filters-defined", (filter_group != NULL)); + for (i = 0; i < MAX_AF_FILES; i++) + { + if ((csp->rlist[i] != NULL) && (csp->rlist[i]->f != NULL)) + { + if (!err) err = map_conditional(exports, "any-filters-defined", 1); + have_filters = 1; + break; + } + } if (err) { @@ -2985,16 +2918,23 @@ jb_err cgi_edit_actions_for_url(struct client_state *csp, return err; } - if (filter_group == NULL) + if (0 == have_filters) { err = map(exports, "filter-params", 1, "", 1); } else { - /* We have some entries in the filter list */ - char * result; - int index = 0; - char * filter_template; + /* + * List available filters and their settings. + */ + char *filter_template; + int filter_identifier = 0; + char *prepared_templates[MAX_FILTER_TYPES]; + + for (i = 0; i < MAX_FILTER_TYPES; i++) + { + prepared_templates[i] = strdup(""); + } err = template_load(csp, &filter_template, "edit-actions-for-url-filter", 0); if (err) @@ -3010,85 +2950,108 @@ jb_err cgi_edit_actions_for_url(struct client_state *csp, err = template_fill(&filter_template, exports); - result = strdup(""); - - for (;(!err) && (filter_group != NULL); filter_group = filter_group->next) + for (i = 0; i < MAX_AF_FILES; i++) { - char current_mode = 'x'; - struct list_entry *filter_name; - char * this_line; - struct map *line_exports; - char number[20]; - - filter_name = cur_line->data.action->multi_add[ACTION_MULTI_FILTER]->first; - while ((filter_name != NULL) - && (0 != strcmp(filter_group->name, filter_name->str))) - { - filter_name = filter_name->next; - } - - if (filter_name != NULL) + if ((csp->rlist[i] != NULL) && (csp->rlist[i]->f != NULL)) { - current_mode = 'y'; - } - else - { - filter_name = cur_line->data.action->multi_remove[ACTION_MULTI_FILTER]->first; - while ((filter_name != NULL) - && (0 != strcmp(filter_group->name, filter_name->str))) + filter_group = csp->rlist[i]->f; + for (;(!err) && (filter_group != NULL); filter_group = filter_group->next) { - filter_name = filter_name->next; - } - if (filter_name != NULL) - { - current_mode = 'n'; - } - } + char current_mode = 'x'; + char number[20]; + struct list_entry *filter_name; + struct map *line_exports; + const int type = filter_group->type; + const int multi_action_index = filter_type_info[type].multi_action_index; + + assert(type < MAX_FILTER_TYPES); + + filter_name = cur_line->data.action->multi_add[multi_action_index]->first; + while ((filter_name != NULL) + && (0 != strcmp(filter_group->name, filter_name->str))) + { + filter_name = filter_name->next; + } - /* Generate a unique serial number */ - snprintf(number, sizeof(number), "%x", index++); - number[sizeof(number) - 1] = '\0'; + if (filter_name != NULL) + { + current_mode = 'y'; + } + else + { + filter_name = cur_line->data.action->multi_remove[multi_action_index]->first; + while ((filter_name != NULL) + && (0 != strcmp(filter_group->name, filter_name->str))) + { + filter_name = filter_name->next; + } + if (filter_name != NULL) + { + current_mode = 'n'; + } + } - line_exports = new_map(); - if (line_exports == NULL) - { - err = JB_ERR_MEMORY; - freez(result); - } - else - { - if (!err) err = map(line_exports, "index", 1, number, 1); - if (!err) err = map(line_exports, "name", 1, filter_group->name, 1); - if (!err) err = map(line_exports, "description", 1, filter_group->description, 1); - if (!err) err = map_radio(line_exports, "this-filter", "ynx", current_mode); + /* Generate a unique serial number */ + snprintf(number, sizeof(number), "%x", filter_identifier++); + number[sizeof(number) - 1] = '\0'; - this_line = NULL; - if (!err) - { - this_line = strdup(filter_template); - if (this_line == NULL) err = JB_ERR_MEMORY; - } - if (!err) err = template_fill(&this_line, line_exports); - string_join(&result, this_line); + line_exports = new_map(); + if (line_exports == NULL) + { + err = JB_ERR_MEMORY; + } + else + { + char *filter_line; - free_map(line_exports); + if (!err) err = map(line_exports, "index", 1, number, 1); + if (!err) err = map(line_exports, "name", 1, filter_group->name, 1); + if (!err) err = map(line_exports, "description", 1, filter_group->description, 1); + if (!err) err = map_radio(line_exports, "this-filter", "ynx", current_mode); + if (!err) err = map(line_exports, "filter-type", 1, filter_type_info[type].type, 1); + if (!err) err = map(line_exports, "abbr-filter-type", 1, filter_type_info[type].abbr_type, 1); + if (!err) err = map(line_exports, "anchor", 1, filter_type_info[type].anchor, 1); + + if (!err) + { + filter_line = strdup(filter_template); + if (filter_line == NULL) err = JB_ERR_MEMORY; + } + if (!err) err = template_fill(&filter_line, line_exports); + string_join(&prepared_templates[type], filter_line); + + free_map(line_exports); + } + } } } - freez(filter_template); - if (!err) + /* Replace all filter macros with the aggregated templates */ + for (i = 0; i < MAX_FILTER_TYPES; i++) { - err = map(exports, "filter-params", 1, result, 0); + if (err) break; + err = map(exports, filter_type_info[i].macro_name, 1, prepared_templates[i], 0); } - else + + if (err) { - freez(result); + /* Free aggregated templates */ + for (i = 0; i < MAX_FILTER_TYPES; i++) + { + freez(prepared_templates[i]); + } } } - if (!err) err = map_radio(exports, "filter-all", "nx", - (cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] ? 'n' : 'x')); + /* Check or uncheck the "disable all of this type" radio buttons. */ + for (i = 0; i < MAX_FILTER_TYPES; i++) + { + const int a = filter_type_info[i].multi_action_index; + const int disable_all = cur_line->data.action->multi_remove_all[a]; + if (err) break; + err = map_radio(exports, filter_type_info[i].disable_all_option, "nx", (disable_all ? 'n' : 'x')); + } edit_free_file(file); @@ -3128,15 +3091,16 @@ jb_err cgi_edit_actions_submit(struct client_state *csp, unsigned sectionid; char * actiontext; char * newtext; + size_t newtext_size; size_t len; struct editable_file * file; struct file_line * cur_line; unsigned line_number; - char * target; + char target[1024]; jb_err err; - int index; + int filter_identifier; + int i; const char * action_set_name; - char ch; struct file_list * fl; struct url_actions * b; @@ -3178,15 +3142,15 @@ jb_err cgi_edit_actions_submit(struct client_state *csp, get_string_param(parameters, "p", &action_set_name); if (action_set_name != NULL) { - for (index = 0; index < MAX_ACTION_FILES; index++) + for (filter_identifier = 0; filter_identifier < MAX_AF_FILES; filter_identifier++) { - if (((fl = csp->actions_list[index]) != NULL) && ((b = fl->f) != NULL)) + if (((fl = csp->actions_list[filter_identifier]) != NULL) && ((b = fl->f) != NULL)) { for (b = b->next; NULL != b; b = b->next) { if (!strncmp(b->url->spec, "standard.", 9) && !strcmp(b->url->spec + 9, action_set_name)) { - copy_action(cur_line->data.action, b->action); + copy_action(cur_line->data.action, b->action); goto found; } } @@ -3202,37 +3166,54 @@ jb_err cgi_edit_actions_submit(struct client_state *csp, err = actions_from_radio(parameters, cur_line->data.action); } - if(err) + if (err) { /* Out of memory */ edit_free_file(file); return err; } - ch = get_char_param(parameters, "filter_all"); - if (ch == 'N') - { - list_remove_all(cur_line->data.action->multi_add[ACTION_MULTI_FILTER]); - list_remove_all(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER]); - cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] = 1; - } - else if (ch == 'X') + /* Check the "disable all of this type" parameters. */ + for (i = 0; i < MAX_FILTER_TYPES; i++) { - cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] = 0; + const int multi_action_index = filter_type_info[i].multi_action_index; + const char ch = get_char_param(parameters, filter_type_info[i].disable_all_param); + + if (ch == 'N') + { + list_remove_all(cur_line->data.action->multi_add[multi_action_index]); + list_remove_all(cur_line->data.action->multi_remove[multi_action_index]); + cur_line->data.action->multi_remove_all[multi_action_index] = 1; + } + else if (ch == 'X') + { + cur_line->data.action->multi_remove_all[multi_action_index] = 0; + } } - for (index = 0; !err; index++) + for (filter_identifier = 0; !err; filter_identifier++) { char key_value[30]; char key_name[30]; + char key_type[30]; const char *name; - char value; + char value; /* + * Filter state. Valid states are: 'Y' (active), + * 'N' (inactive) and 'X' (no change). + * XXX: bad name. + */ + char type; /* + * Abbreviated filter type. Valid types are: 'F' (content filter), + * 'S' (server-header filter) and 'C' (client-header filter). + */ + int multi_action_index = 0; /* Generate the keys */ - snprintf(key_value, sizeof(key_value), "filter_r%x", index); - key_value[sizeof(key_value) - 1] = '\0'; - snprintf(key_name, sizeof(key_name), "filter_n%x", index); - key_name[sizeof(key_name) - 1] = '\0'; + snprintf(key_value, sizeof(key_value), "filter_r%x", filter_identifier); + key_value[sizeof(key_value) - 1] = '\0'; /* XXX: Why? */ + snprintf(key_name, sizeof(key_name), "filter_n%x", filter_identifier); + key_name[sizeof(key_name) - 1] = '\0'; /* XXX: Why? */ + snprintf(key_type, sizeof(key_type), "filter_t%x", filter_identifier); err = get_string_param(parameters, key_name, &name); if (err) break; @@ -3243,26 +3224,51 @@ jb_err cgi_edit_actions_submit(struct client_state *csp, break; } + type = get_char_param(parameters, key_type); + switch (type) + { + case 'F': + multi_action_index = ACTION_MULTI_FILTER; + break; + case 'S': + multi_action_index = ACTION_MULTI_SERVER_HEADER_FILTER; + break; + case 'C': + multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER; + break; + case 'L': + multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER; + break; + case 'E': + multi_action_index = ACTION_MULTI_SERVER_HEADER_TAGGER; + break; + default: + log_error(LOG_LEVEL_ERROR, + "Unknown filter type: %c for filter %s. Filter ignored.", type, name); + continue; + } + assert(multi_action_index); + value = get_char_param(parameters, key_value); if (value == 'Y') { - list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name); - if (!err) err = enlist(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name); - list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name); + list_remove_item(cur_line->data.action->multi_add[multi_action_index], name); + if (!err) err = enlist(cur_line->data.action->multi_add[multi_action_index], name); + list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name); } else if (value == 'N') { - list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name); - if (!cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER]) + list_remove_item(cur_line->data.action->multi_add[multi_action_index], name); + if (!cur_line->data.action->multi_remove_all[multi_action_index]) { - list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name); - if (!err) err = enlist(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name); + list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name); + if (!err) err = enlist(cur_line->data.action->multi_remove[multi_action_index], name); } } else if (value == 'X') { - list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name); - list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name); + list_remove_item(cur_line->data.action->multi_add[multi_action_index], name); + list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name); } } @@ -3290,14 +3296,15 @@ jb_err cgi_edit_actions_submit(struct client_state *csp, len = 1; } - if (NULL == (newtext = malloc(len + 2))) + newtext_size = len + 2; + if (NULL == (newtext = malloc(newtext_size))) { /* Out of memory */ free(actiontext); edit_free_file(file); return JB_ERR_MEMORY; } - strcpy(newtext, actiontext); + strlcpy(newtext, actiontext, newtext_size); free(actiontext); newtext[0] = '{'; newtext[len] = '}'; @@ -3314,33 +3321,18 @@ jb_err cgi_edit_actions_submit(struct client_state *csp, if (err == JB_ERR_FILE) { /* Read-only file. */ - err = cgi_error_file_read_only(csp, rsp, file->identifier); + err = cgi_error_file_read_only(csp, rsp, file->filename); } edit_free_file(file); return err; } - target = strdup(CGI_PREFIX "edit-actions-list?f="); - string_append(&target, file->identifier); + snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%i#l%d", + (long) time(NULL), file->identifier, sectionid); edit_free_file(file); - if (target == NULL) - { - /* Out of memory */ - return JB_ERR_MEMORY; - } - - rsp->status = strdup("302 Local Redirect from Privoxy"); - if (rsp->status == NULL) - { - free(target); - return JB_ERR_MEMORY; - } - err = enlist_unique_header(rsp->headers, "Location", target); - free(target); - - return err; + return cgi_redirect(rsp, target); } @@ -3378,9 +3370,14 @@ jb_err cgi_edit_actions_url(struct client_state *csp, struct editable_file * file; struct file_line * cur_line; unsigned line_number; - char * target; + unsigned section_start_line_number = 0; + char target[1024]; jb_err err; + assert(csp); + assert(rsp); + assert(parameters); + if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)) { return cgi_error_disabled(csp, rsp); @@ -3415,6 +3412,10 @@ jb_err cgi_edit_actions_url(struct client_state *csp, while ((cur_line != NULL) && (line_number < patternid)) { + if (cur_line->type == FILE_LINE_ACTION) + { + section_start_line_number = line_number; + } cur_line = cur_line->next; line_number++; } @@ -3441,33 +3442,18 @@ jb_err cgi_edit_actions_url(struct client_state *csp, if (err == JB_ERR_FILE) { /* Read-only file. */ - err = cgi_error_file_read_only(csp, rsp, file->identifier); + err = cgi_error_file_read_only(csp, rsp, file->filename); } edit_free_file(file); return err; } - target = strdup(CGI_PREFIX "edit-actions-list?f="); - string_append(&target, file->identifier); + snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%i#l%d", + (long) time(NULL), file->identifier, section_start_line_number); edit_free_file(file); - if (target == NULL) - { - /* Out of memory */ - return JB_ERR_MEMORY; - } - - rsp->status = strdup("302 Local Redirect from Privoxy"); - if (rsp->status == NULL) - { - free(target); - return JB_ERR_MEMORY; - } - err = enlist_unique_header(rsp->headers, "Location", target); - free(target); - - return err; + return cgi_redirect(rsp, target); } @@ -3505,7 +3491,7 @@ jb_err cgi_edit_actions_add_url(struct client_state *csp, struct editable_file * file; struct file_line * cur_line; unsigned line_number; - char * target; + char target[1024]; jb_err err; if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)) @@ -3585,33 +3571,18 @@ jb_err cgi_edit_actions_add_url(struct client_state *csp, if (err == JB_ERR_FILE) { /* Read-only file. */ - err = cgi_error_file_read_only(csp, rsp, file->identifier); + err = cgi_error_file_read_only(csp, rsp, file->filename); } edit_free_file(file); return err; } - target = strdup(CGI_PREFIX "edit-actions-list?f="); - string_append(&target, file->identifier); + snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%i#l%d", + (long) time(NULL), file->identifier, sectionid); edit_free_file(file); - if (target == NULL) - { - /* Out of memory */ - return JB_ERR_MEMORY; - } - - rsp->status = strdup("302 Local Redirect from Privoxy"); - if (rsp->status == NULL) - { - free(target); - return JB_ERR_MEMORY; - } - err = enlist_unique_header(rsp->headers, "Location", target); - free(target); - - return err; + return cgi_redirect(rsp, target); } @@ -3647,7 +3618,8 @@ jb_err cgi_edit_actions_remove_url(struct client_state *csp, struct file_line * cur_line; struct file_line * prev_line; unsigned line_number; - char * target; + unsigned section_start_line_number = 0; + char target[1024]; jb_err err; if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)) @@ -3674,6 +3646,10 @@ jb_err cgi_edit_actions_remove_url(struct client_state *csp, while ((cur_line != NULL) && (line_number < patternid)) { + if (cur_line->type == FILE_LINE_ACTION) + { + section_start_line_number = line_number; + } prev_line = cur_line; cur_line = cur_line->next; line_number++; @@ -3706,33 +3682,18 @@ jb_err cgi_edit_actions_remove_url(struct client_state *csp, if (err == JB_ERR_FILE) { /* Read-only file. */ - err = cgi_error_file_read_only(csp, rsp, file->identifier); + err = cgi_error_file_read_only(csp, rsp, file->filename); } edit_free_file(file); return err; } - target = strdup(CGI_PREFIX "edit-actions-list?f="); - string_append(&target, file->identifier); + snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%i#l%d", + (long) time(NULL), file->identifier, section_start_line_number); edit_free_file(file); - if (target == NULL) - { - /* Out of memory */ - return JB_ERR_MEMORY; - } - - rsp->status = strdup("302 Local Redirect from Privoxy"); - if (rsp->status == NULL) - { - free(target); - return JB_ERR_MEMORY; - } - err = enlist_unique_header(rsp->headers, "Location", target); - free(target); - - return err; + return cgi_redirect(rsp, target); } @@ -3769,7 +3730,7 @@ jb_err cgi_edit_actions_section_remove(struct client_state *csp, struct file_line * cur_line; struct file_line * prev_line; unsigned line_number; - char * target; + char target[1024]; jb_err err; if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)) @@ -3843,33 +3804,18 @@ jb_err cgi_edit_actions_section_remove(struct client_state *csp, if (err == JB_ERR_FILE) { /* Read-only file. */ - err = cgi_error_file_read_only(csp, rsp, file->identifier); + err = cgi_error_file_read_only(csp, rsp, file->filename); } edit_free_file(file); return err; } - target = strdup(CGI_PREFIX "edit-actions-list?f="); - string_append(&target, file->identifier); + snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%i", + (long) time(NULL), file->identifier); edit_free_file(file); - if (target == NULL) - { - /* Out of memory */ - return JB_ERR_MEMORY; - } - - rsp->status = strdup("302 Local Redirect from Privoxy"); - if (rsp->status == NULL) - { - free(target); - return JB_ERR_MEMORY; - } - err = enlist_unique_header(rsp->headers, "Location", target); - free(target); - - return err; + return cgi_redirect(rsp, target); } @@ -3907,7 +3853,7 @@ jb_err cgi_edit_actions_section_add(struct client_state *csp, struct editable_file * file; struct file_line * cur_line; unsigned line_number; - char * target; + char target[1024]; jb_err err; if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)) @@ -3931,10 +3877,10 @@ jb_err cgi_edit_actions_section_add(struct client_state *csp, line_number = 1; cur_line = file->lines; - if (sectionid < 1U) + if (sectionid <= 1U) { /* Add to start of file */ - if (cur_line != NULL) + if (cur_line != NULL && cur_line->type != FILE_LINE_ACTION) { /* There's something in the file, find the line before the first * action. @@ -3946,6 +3892,11 @@ jb_err cgi_edit_actions_section_add(struct client_state *csp, line_number++; } } + else + { + /* File starts with action line, so insert at top */ + cur_line = NULL; + } } else { @@ -4022,33 +3973,18 @@ jb_err cgi_edit_actions_section_add(struct client_state *csp, if (err == JB_ERR_FILE) { /* Read-only file. */ - err = cgi_error_file_read_only(csp, rsp, file->identifier); + err = cgi_error_file_read_only(csp, rsp, file->filename); } edit_free_file(file); return err; } - target = strdup(CGI_PREFIX "edit-actions-list?f="); - string_append(&target, file->identifier); + snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%i", + (long) time(NULL), file->identifier); edit_free_file(file); - if (target == NULL) - { - /* Out of memory */ - return JB_ERR_MEMORY; - } - - rsp->status = strdup("302 Local Redirect from Privoxy"); - if (rsp->status == NULL) - { - free(target); - return JB_ERR_MEMORY; - } - err = enlist_unique_header(rsp->headers, "Location", target); - free(target); - - return err; + return cgi_redirect(rsp, target); } @@ -4097,7 +4033,7 @@ jb_err cgi_edit_actions_section_swap(struct client_state *csp, struct file_line * line_end_section2; struct file_line * line_after_section2; unsigned line_number; - char * target; + char target[1024]; jb_err err; if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS)) @@ -4225,103 +4161,19 @@ jb_err cgi_edit_actions_section_swap(struct client_state *csp, if (err == JB_ERR_FILE) { /* Read-only file. */ - err = cgi_error_file_read_only(csp, rsp, file->identifier); + err = cgi_error_file_read_only(csp, rsp, file->filename); } edit_free_file(file); return err; } } /* END if (section1 != section2) */ - target = strdup(CGI_PREFIX "edit-actions-list?f="); - string_append(&target, file->identifier); + snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%i", + (long) time(NULL), file->identifier); edit_free_file(file); - if (target == NULL) - { - /* Out of memory */ - return JB_ERR_MEMORY; - } - - rsp->status = strdup("302 Local Redirect from Privoxy"); - if (rsp->status == NULL) - { - free(target); - return JB_ERR_MEMORY; - } - err = enlist_unique_header(rsp->headers, "Location", target); - free(target); - - return err; -} - - -/********************************************************************* - * - * Function : cgi_toggle - * - * Description : CGI function that adds a new empty section to - * an actions file. - * - * Parameters : - * 1 : csp = Current client state (buffers, headers, etc...) - * 2 : rsp = http_response data structure for output - * 3 : parameters = map of cgi parameters - * - * CGI Parameters : - * set : If present, how to change toggle setting: - * "enable", "disable", "toggle", or none (default). - * mini : If present, use mini reply template. - * - * Returns : JB_ERR_OK on success - * JB_ERR_MEMORY on out-of-memory - * - *********************************************************************/ -jb_err cgi_toggle(struct client_state *csp, - struct http_response *rsp, - const struct map *parameters) -{ - struct map *exports; - char mode; - const char *template_name; - - assert(csp); - assert(rsp); - assert(parameters); - - if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE)) - { - return cgi_error_disabled(csp, rsp); - } - - mode = get_char_param(parameters, "set"); - - if (mode == 'E') - { - /* Enable */ - g_bToggleIJB = 1; - } - else if (mode == 'D') - { - /* Disable */ - g_bToggleIJB = 0; - } - else if (mode == 'T') - { - /* Toggle */ - g_bToggleIJB = !g_bToggleIJB; - } - - if (NULL == (exports = default_exports(csp, "toggle"))) - { - return JB_ERR_MEMORY; - } - - template_name = (get_char_param(parameters, "mini") - ? "toggle-mini" - : "toggle"); - - return template_fill_for_cgi(csp, template_name, exports, rsp); + return cgi_redirect(rsp, target); } @@ -4381,8 +4233,8 @@ static void javascriptify(char * identifier) static jb_err actions_to_radio(struct map * exports, const struct action_spec *action) { - unsigned mask = action->mask; - unsigned add = action->add; + unsigned long mask; + unsigned long add; int mapped_param; int checked; char current_mode; @@ -4512,7 +4364,6 @@ static jb_err actions_to_radio(struct map * exports, static jb_err actions_from_radio(const struct map * parameters, struct action_spec *action) { - static int first_time = 1; const char * param; char * param_dup; char ch; @@ -4526,16 +4377,22 @@ static jb_err actions_from_radio(const struct map * parameters, * but in this case we're safe and don't need semaphores. * Be careful if you modify this function. * - Jon + * The js_name_arr's are never free()d, but this is no + * problem, since they will only be created once and + * used by all threads thereafter. -oes */ #define JAVASCRIPTIFY(dest_var, string) \ { \ - static char js_name_arr[] = string; \ + static int first_time = 1; \ + static char *js_name_arr; \ if (first_time) \ { \ + js_name_arr = strdup(string); \ javascriptify(js_name_arr); \ } \ dest_var = js_name_arr; \ + first_time = 0; \ } \ #define DEFINE_ACTION_BOOL(name, bit) \ @@ -4631,13 +4488,80 @@ static jb_err actions_from_radio(const struct map * parameters, #undef DEFINE_ACTION_ALIAS #undef JAVASCRIPTIFY - first_time = 0; - return err; } +#endif /* def FEATURE_CGI_EDIT_ACTIONS */ -#endif /* def FEATURE_CGI_EDIT_ACTIONS */ +#ifdef FEATURE_TOGGLE +/********************************************************************* + * + * Function : cgi_toggle + * + * Description : CGI function that adds a new empty section to + * an actions file. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : rsp = http_response data structure for output + * 3 : parameters = map of cgi parameters + * + * CGI Parameters : + * set : If present, how to change toggle setting: + * "enable", "disable", "toggle", or none (default). + * mini : If present, use mini reply template. + * + * Returns : JB_ERR_OK on success + * JB_ERR_MEMORY on out-of-memory + * + *********************************************************************/ +jb_err cgi_toggle(struct client_state *csp, + struct http_response *rsp, + const struct map *parameters) +{ + struct map *exports; + char mode; + const char *template_name; + + assert(csp); + assert(rsp); + assert(parameters); + + if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE)) + { + return cgi_error_disabled(csp, rsp); + } + + mode = get_char_param(parameters, "set"); + + if (mode == 'E') + { + /* Enable */ + global_toggle_state = 1; + } + else if (mode == 'D') + { + /* Disable */ + global_toggle_state = 0; + } + else if (mode == 'T') + { + /* Toggle */ + global_toggle_state = !global_toggle_state; + } + + if (NULL == (exports = default_exports(csp, "toggle"))) + { + return JB_ERR_MEMORY; + } + + template_name = (get_char_param(parameters, "mini") + ? "toggle-mini" + : "toggle"); + + return template_fill_for_cgi(csp, template_name, exports, rsp); +} +#endif /* def FEATURE_TOGGLE */ /*