-const char cgi_rcs[] = "$Id: cgi.c,v 1.148 2012/03/09 16:23:50 fabiankeil Exp $";
/*********************************************************************
*
* File : $Source: /cvsroot/ijbswa/current/cgi.c,v $
* This only contains the framework functions, the
* actual handler functions are declared elsewhere.
*
- * Copyright : Written by and Copyright (C) 2001-2004, 2006-2008
- * the SourceForge Privoxy team. http://www.privoxy.org/
+ * Copyright : Written by and Copyright (C) 2001-2017
+ * members of the Privoxy team. https://www.privoxy.org/
*
* Based on the Internet Junkbuster originally written
* by and Copyright (C) 1997 Anonymous Coders and
/* jcc.h is for mutex semaphore globals only */
#include "jcc.h"
-const char cgi_h_rcs[] = CGI_H_VERSION;
-
/*
* List of CGI functions: name, handler, description
* Note: Do NOT use single quotes in the description;
"View the current configuration",
#endif
TRUE },
- { "show-version",
- cgi_show_version,
- "View the source code version numbers",
- TRUE },
+#ifdef FEATURE_CLIENT_TAGS
+ /*
+ * This is marked as harmless because despite the description
+ * used in the menu the actual toggling is done through another
+ * path ("/toggle-client-tag").
+ */
+ { "client-tags",
+ cgi_show_client_tags,
+ "View or toggle the tags that can be set based on the clients address",
+ TRUE },
+#endif
{ "show-request",
cgi_show_request,
"View the request headers",
"Toggle Privoxy on or off",
FALSE },
#endif /* def FEATURE_TOGGLE */
+#ifdef FEATURE_CLIENT_TAGS
+ { "toggle-client-tag",
+ cgi_toggle_client_tag,
+ NULL,
+ FALSE },
+#endif
#ifdef FEATURE_CGI_EDIT_ACTIONS
{ "edit-actions", /* Edit the actions list */
cgi_edit_actions,
/* Note: "example.com" and "example.com." are equivalent hostnames. */
/* Either the host matches CGI_SITE_1_HOST ..*/
- if ( ( (0 == strcmpic(host, CGI_SITE_1_HOST))
+ if ( ( (0 == strcmpic(host, CGI_SITE_1_HOST))
|| (0 == strcmpic(host, CGI_SITE_1_HOST ".")))
&& (path[0] == '/'))
{
path++;
}
/* Or it's the host part CGI_SITE_2_HOST, and the path CGI_SITE_2_PATH */
- else if (( (0 == strcmpic(host, CGI_SITE_2_HOST))
+ else if (( (0 == strcmpic(host, CGI_SITE_2_HOST))
|| (0 == strcmpic(host, CGI_SITE_2_HOST ".")))
&& (0 == strncmpic(path, CGI_SITE_2_PATH, strlen(CGI_SITE_2_PATH))))
{
return NULL;
}
+ if (strcmpic(csp->http->gpc, "GET")
+ && strcmpic(csp->http->gpc, "HEAD"))
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "CGI request with unsupported method received: %s", csp->http->gpc);
+ /*
+ * The CGI pages currently only support GET and HEAD requests.
+ *
+ * If the client used a different method, ditch any data following
+ * the current headers to reduce the likelihood of parse errors
+ * with the following request.
+ */
+ csp->client_iob->eod = csp->client_iob->cur;
+ }
+
/*
* This is a CGI call.
*/
{
char *referrer;
static const char alternative_prefix[] = "http://" CGI_SITE_1_HOST "/";
+ const char *trusted_cgi_referrer = csp->config->trusted_cgi_referrer;
referrer = grep_cgi_referrer(csp);
return TRUE;
}
+ else if ((trusted_cgi_referrer != NULL) && (0 == strncmp(referrer,
+ trusted_cgi_referrer, strlen(trusted_cgi_referrer))))
+ {
+ /*
+ * After some more testing this block should be merged with
+ * the previous one or the log level should bedowngraded.
+ */
+ log_error(LOG_LEVEL_INFO, "Granting access to %s based on trusted referrer %s",
+ csp->http->url, referrer);
+
+ return TRUE;
+ }
else
{
/* Untrustworthy referrer */
if (*query_args_start == '/')
{
*query_args_start++ = '\0';
- if ((param_list = new_map()))
- {
- map(param_list, "file", 1, url_decode(query_args_start), 0);
+ param_list = new_map();
+ err = map(param_list, "file", 1, url_decode(query_args_start), 0);
+ if (JB_ERR_OK != err) {
+ free(param_list);
+ free(path_copy);
+ return cgi_error_memory();
}
}
else
static struct map *parse_cgi_parameters(char *argstring)
{
char *p;
- char *vector[BUFFER_SIZE];
+ char **vector;
int pairs, i;
struct map *cgi_params;
- if (NULL == (cgi_params = new_map()))
+ /*
+ * XXX: This estimate is guaranteed to be high enough as we
+ * let ssplit() ignore empty fields, but also a bit wasteful.
+ * The same hack is used in get_last_url() so it looks like
+ * a real solution is needed.
+ */
+ size_t max_segments = strlen(argstring) / 2;
+ if (max_segments == 0)
{
- return NULL;
+ /*
+ * XXX: If the argstring is empty, there's really
+ * no point in creating a param list, but currently
+ * other parts of Privoxy depend on the list's existence.
+ */
+ max_segments = 1;
}
+ vector = malloc_or_die(max_segments * sizeof(char *));
+
+ cgi_params = new_map();
/*
* IE 5 does, of course, violate RFC 2316 Sect 4.1 and sends
*p = '\0';
}
- pairs = ssplit(argstring, "&", vector, SZ(vector), 1, 1);
+ pairs = ssplit(argstring, "&", vector, max_segments);
+ assert(pairs != -1);
+ if (pairs == -1)
+ {
+ freez(vector);
+ free_map(cgi_params);
+ return NULL;
+ }
for (i = 0; i < pairs; i++)
{
*p = '\0';
if (map(cgi_params, url_decode(vector[i]), 0, url_decode(++p), 0))
{
+ freez(vector);
free_map(cgi_params);
return NULL;
}
}
}
+ freez(vector);
+
return cgi_params;
}
unsigned *pvalue)
{
const char *param;
- char ch;
- unsigned value;
+ char *endptr;
assert(csp);
assert(parameters);
return JB_ERR_CGI_PARAMS;
}
- /* We don't use atoi because I want to check this carefully... */
-
- value = 0;
- while ((ch = *param++) != '\0')
+ *pvalue = (unsigned int)strtol(param, &endptr, 0);
+ if (*endptr != '\0')
{
- if ((ch < '0') || (ch > '9'))
- {
- return JB_ERR_CGI_PARAMS;
- }
-
- ch = (char)(ch - '0');
-
- /* Note:
- *
- * <limits.h> defines UINT_MAX
- *
- * (UINT_MAX - ch) / 10 is the largest number that
- * can be safely multiplied by 10 then have ch added.
- */
- if (value > ((UINT_MAX - (unsigned)ch) / 10U))
- {
- return JB_ERR_CGI_PARAMS;
- }
-
- value = value * 10 + (unsigned)ch;
+ return JB_ERR_CGI_PARAMS;
}
- /* Success */
- *pvalue = value;
-
return JB_ERR_OK;
}
case SOCKS_5:
socks_type = "socks5-";
break;
+ case SOCKS_5T:
+ socks_type = "socks5t-";
+ break;
+ case FORWARD_WEBSERVER:
+ socks_type = "webserver-";
+ break;
default:
log_error(LOG_LEVEL_FATAL, "Unknown socks type: %d.", fwd->type);
}
assert(csp);
assert(rsp);
+ rsp->status = strdup_or_die("403 Request not trusted or feature disabled");
+
if (NULL == (exports = default_exports(csp, "cgi-error-disabled")))
{
return JB_ERR_MEMORY;
rsp->head_length = 0;
rsp->is_static = 0;
- rsp->body = malloc(body_size);
- if (rsp->body == NULL)
- {
- return JB_ERR_MEMORY;
- }
+ rsp->body = malloc_or_die(body_size);
strlcpy(rsp->body, body_prefix, body_size);
strlcat(rsp->body, template_name, body_size);
strlcat(rsp->body, body_suffix, body_size);
rsp->is_static = 0;
rsp->crunch_reason = INTERNAL_ERROR;
- rsp->body = malloc(body_size);
- if (rsp->body == NULL)
- {
- return JB_ERR_MEMORY;
- }
+ rsp->body = malloc_or_die(body_size);
snprintf(rsp->body, body_size, "%s%d%s", body_prefix, error_to_report, body_suffix);
/* Let zlib figure out the maximum length of the compressed data */
new_length = compressBound((uLongf)*buffer_length);
- compressed_buffer = malloc(new_length);
- if (NULL == compressed_buffer)
- {
- log_error(LOG_LEVEL_FATAL,
- "Out of memory allocation compression buffer.");
- }
+ compressed_buffer = malloc_or_die(new_length);
if (Z_OK != compress2((Bytef *)compressed_buffer, &new_length,
(Bytef *)buffer, *buffer_length, compression_level))
* On error, free()s rsp and returns cgi_error_memory()
*
*********************************************************************/
-struct http_response *finish_http_response(const struct client_state *csp, struct http_response *rsp)
+struct http_response *finish_http_response(struct client_state *csp, struct http_response *rsp)
{
char buf[BUFFER_SIZE];
jb_err err;
return rsp;
}
+ /*
+ * Add "Cross-origin resource sharing" (CORS) headers if enabled
+ */
+ if (NULL != csp->config->cors_allowed_origin)
+ {
+ enlist_unique_header(rsp->headers, "Access-Control-Allow-Origin",
+ strdup_or_die(csp->config->cors_allowed_origin));
+ enlist_unique_header(rsp->headers, "Access-Control-Allow-Methods", "GET,POST");
+ enlist_unique_header(rsp->headers, "Access-Control-Allow-Headers", "X-Requested-With");
+ enlist_unique_header(rsp->headers, "Access-Control-Max-Age", "86400");
+ }
+
/*
* Fill in the HTTP Status, using HTTP/1.1
* unless the client asked for HTTP/1.0.
if (!err)
{
snprintf(buf, sizeof(buf), "Content-Length: %d", (int)rsp->content_length);
+ /*
+ * Signal serve() that the client will be able to figure out
+ * the end of the response without having to close the connection.
+ */
+ csp->flags |= CSP_FLAG_SERVER_CONTENT_LENGTH_SET;
err = enlist(rsp->headers, buf);
}
if (!err) err = enlist_unique_header(rsp->headers, "Pragma", "no-cache");
}
- if (!err && !(csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE))
+ if (!err && (!(csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE)
+ || (csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED)))
{
err = enlist_unique_header(rsp->headers, "Connection", "close");
}
/* Validate template name. Paranoia. */
for (p = templatename; *p != 0; p++)
{
- if (((*p < 'a') || (*p > 'z'))
+ if ( ((*p < 'a') || (*p > 'z'))
&& ((*p < 'A') || (*p > 'Z'))
&& ((*p < '0') || (*p > '9'))
&& (*p != '-')
err = template_load(csp, &rsp->body, templatename, 0);
if (err == JB_ERR_FILE)
{
- free_map(exports);
- return cgi_error_no_template(csp, rsp, templatename);
+ err = cgi_error_no_template(csp, rsp, templatename);
}
- else if (err)
+ else if (err == JB_ERR_OK)
{
- free_map(exports);
- return err; /* JB_ERR_MEMORY */
+ err = template_fill(&rsp->body, exports);
}
- err = template_fill(&rsp->body, exports);
free_map(exports);
return err;
}
assert(csp);
exports = new_map();
- if (exports == NULL)
- {
- return NULL;
- }
if (csp->config->hostname)
{