You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by jo...@apache.org on 2017/01/31 09:52:09 UTC
svn commit: r1781045 [50/50] - in
/httpd/httpd/branches/2.4.x-openssl-1.1.0-compat: ./ build/ docs/man/
docs/manual/ docs/manual/developer/ docs/manual/faq/ docs/manual/howto/
docs/manual/misc/ docs/manual/mod/ docs/manual/platform/
docs/manual/program...
Modified: httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/protocol.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/protocol.c?rev=1781045&r1=1781044&r2=1781045&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/protocol.c (original)
+++ httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/protocol.c Tue Jan 31 09:52:02 2017
@@ -191,6 +191,10 @@ AP_DECLARE(apr_time_t) ap_rationalize_mt
/* Get a line of protocol input, including any continuation lines
* caused by MIME folding (or broken clients) if fold != 0, and place it
* in the buffer s, of size n bytes, without the ending newline.
+ *
+ * Pulls from r->proto_input_filters instead of r->input_filters for
+ * stricter protocol adherence and better input filter behavior during
+ * chunked trailer processing (for http).
*
* If s is NULL, ap_rgetline_core will allocate necessary memory from r->pool.
*
@@ -200,7 +204,7 @@ AP_DECLARE(apr_time_t) ap_rationalize_mt
* APR_ENOSPC is returned if there is not enough buffer space.
* Other errors may be returned on other errors.
*
- * The LF is *not* returned in the buffer. Therefore, a *read of 0
+ * The [CR]LF are *not* returned in the buffer. Therefore, a *read of 0
* indicates that an empty line was read.
*
* Notes: Because the buffer uses 1 char for NUL, the most we can return is
@@ -211,13 +215,15 @@ AP_DECLARE(apr_time_t) ap_rationalize_mt
*/
AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n,
apr_size_t *read, request_rec *r,
- int fold, apr_bucket_brigade *bb)
+ int flags, apr_bucket_brigade *bb)
{
apr_status_t rv;
apr_bucket *e;
apr_size_t bytes_handled = 0, current_alloc = 0;
char *pos, *last_char = *s;
int do_alloc = (*s == NULL), saw_eos = 0;
+ int fold = flags & AP_GETLINE_FOLD;
+ int crlf = flags & AP_GETLINE_CRLF;
/*
* Initialize last_char as otherwise a random value will be compared
@@ -229,13 +235,15 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
for (;;) {
apr_brigade_cleanup(bb);
- rv = ap_get_brigade(r->input_filters, bb, AP_MODE_GETLINE,
+ rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_GETLINE,
APR_BLOCK_READ, 0);
if (rv != APR_SUCCESS) {
return rv;
}
- /* Something horribly wrong happened. Someone didn't block! */
+ /* Something horribly wrong happened. Someone didn't block!
+ * (this also happens at the end of each keepalive connection)
+ */
if (APR_BRIGADE_EMPTY(bb)) {
return APR_EGENERAL;
}
@@ -321,6 +329,13 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
}
}
+ if (crlf && (last_char <= *s || last_char[-1] != APR_ASCII_CR)) {
+ *last_char = '\0';
+ bytes_handled = last_char - *s;
+ *read = bytes_handled;
+ return APR_EINVAL;
+ }
+
/* Now NUL-terminate the string at the end of the line;
* if the last-but-one character is a CR, terminate there */
if (last_char > *s && last_char[-1] == APR_ASCII_CR) {
@@ -343,7 +358,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
apr_brigade_cleanup(bb);
/* We only care about the first byte. */
- rv = ap_get_brigade(r->input_filters, bb, AP_MODE_SPECULATIVE,
+ rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_SPECULATIVE,
APR_BLOCK_READ, 1);
if (rv != APR_SUCCESS) {
return rv;
@@ -394,7 +409,8 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
*/
if (do_alloc) {
tmp = NULL;
- } else {
+ }
+ else {
/* We're null terminated. */
tmp = last_char;
}
@@ -464,7 +480,7 @@ AP_DECLARE(apr_status_t) ap_rgetline(cha
}
#endif
-AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold)
+AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags)
{
char *tmp_s = s;
apr_status_t rv;
@@ -472,7 +488,7 @@ AP_DECLARE(int) ap_getline(char *s, int
apr_bucket_brigade *tmp_bb;
tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
- rv = ap_rgetline(&tmp_s, n, &len, r, fold, tmp_bb);
+ rv = ap_rgetline(&tmp_s, n, &len, r, flags, tmp_bb);
apr_brigade_destroy(tmp_bb);
/* Map the out-of-space condition to the old API. */
@@ -552,16 +568,29 @@ AP_CORE_DECLARE(void) ap_parse_uri(reque
}
}
-static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
+/* get the length of the field name for logging, but no more than 80 bytes */
+#define LOG_NAME_MAX_LEN 80
+static int field_name_len(const char *field)
{
- const char *ll;
- const char *uri;
- const char *pro;
+ const char *end = ap_strchr_c(field, ':');
+ if (end == NULL || end - field > LOG_NAME_MAX_LEN)
+ return LOG_NAME_MAX_LEN;
+ return end - field;
+}
- unsigned int major = 1, minor = 0; /* Assume HTTP/1.0 if non-"HTTP" protocol */
- char http[5];
+static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
+{
+ enum {
+ rrl_none, rrl_badmethod, rrl_badwhitespace, rrl_excesswhitespace,
+ rrl_missinguri, rrl_baduri, rrl_badprotocol, rrl_trailingtext,
+ rrl_badmethod09, rrl_reject09
+ } deferred_error = rrl_none;
+ char *ll;
+ char *uri;
apr_size_t len;
int num_blank_lines = DEFAULT_LIMIT_BLANK_LINES;
+ core_server_config *conf = ap_get_core_module_config(r->server->module_config);
+ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
/* Read past empty lines until we get a real request line,
* a read error, the connection closes (EOF), or we timeout.
@@ -586,7 +615,7 @@ static int read_request_line(request_rec
*/
r->the_request = NULL;
rv = ap_rgetline(&(r->the_request), (apr_size_t)(r->server->limit_req_line + 2),
- &len, r, 0, bb);
+ &len, r, strict ? AP_GETLINE_CRLF : 0, bb);
if (rv != APR_SUCCESS) {
r->request_time = apr_time_now();
@@ -596,7 +625,7 @@ static int read_request_line(request_rec
* happen if it exceeds the configured limit for a request-line.
*/
if (APR_STATUS_IS_ENOSPC(rv)) {
- r->status = HTTP_REQUEST_URI_TOO_LARGE;
+ r->status = HTTP_REQUEST_URI_TOO_LARGE;
}
else if (APR_STATUS_IS_TIMEUP(rv)) {
r->status = HTTP_REQUEST_TIME_OUT;
@@ -617,58 +646,266 @@ static int read_request_line(request_rec
}
r->request_time = apr_time_now();
- ll = r->the_request;
- r->method = ap_getword_white(r->pool, &ll);
- uri = ap_getword_white(r->pool, &ll);
+ r->method = r->the_request;
- if (!*r->method || !*uri) {
- r->status = HTTP_BAD_REQUEST;
- r->proto_num = HTTP_VERSION(1,0);
- r->protocol = apr_pstrdup(r->pool, "HTTP/1.0");
- return 0;
+ /* If there is whitespace before a method, skip it and mark in error */
+ if (apr_isspace(*r->method)) {
+ deferred_error = rrl_badwhitespace;
+ for ( ; apr_isspace(*r->method); ++r->method)
+ ;
}
- /* Provide quick information about the request method as soon as known */
+ /* Scan the method up to the next whitespace, ensure it contains only
+ * valid http-token characters, otherwise mark in error
+ */
+ if (strict) {
+ ll = (char*) ap_scan_http_token(r->method);
+ }
+ else {
+ ll = (char*) ap_scan_vchar_obstext(r->method);
+ }
+
+ if (((ll == r->method) || (*ll && !apr_isspace(*ll)))
+ && deferred_error == rrl_none) {
+ deferred_error = rrl_badmethod;
+ ll = strpbrk(ll, "\t\n\v\f\r ");
+ }
+
+ /* Verify method terminated with a single SP, or mark as specific error */
+ if (!ll) {
+ if (deferred_error == rrl_none)
+ deferred_error = rrl_missinguri;
+ r->protocol = uri = "";
+ len = 0;
+ goto rrl_done;
+ }
+ else if (strict && ll[0] && apr_isspace(ll[1])
+ && deferred_error == rrl_none) {
+ deferred_error = rrl_excesswhitespace;
+ }
+
+ /* Advance uri pointer over leading whitespace, NUL terminate the method
+ * If non-SP whitespace is encountered, mark as specific error
+ */
+ for (uri = ll; apr_isspace(*uri); ++uri)
+ if (*uri != ' ' && deferred_error == rrl_none)
+ deferred_error = rrl_badwhitespace;
+ *ll = '\0';
+ if (!*uri && deferred_error == rrl_none)
+ deferred_error = rrl_missinguri;
+
+ /* Scan the URI up to the next whitespace, ensure it contains no raw
+ * control characters, otherwise mark in error
+ */
+ ll = (char*) ap_scan_vchar_obstext(uri);
+ if (ll == uri || (*ll && !apr_isspace(*ll))) {
+ deferred_error = rrl_baduri;
+ ll = strpbrk(ll, "\t\n\v\f\r ");
+ }
+
+ /* Verify URI terminated with a single SP, or mark as specific error */
+ if (!ll) {
+ r->protocol = "";
+ len = 0;
+ goto rrl_done;
+ }
+ else if (strict && ll[0] && apr_isspace(ll[1])
+ && deferred_error == rrl_none) {
+ deferred_error = rrl_excesswhitespace;
+ }
+
+ /* Advance protocol pointer over leading whitespace, NUL terminate the uri
+ * If non-SP whitespace is encountered, mark as specific error
+ */
+ for (r->protocol = ll; apr_isspace(*r->protocol); ++r->protocol)
+ if (*r->protocol != ' ' && deferred_error == rrl_none)
+ deferred_error = rrl_badwhitespace;
+ *ll = '\0';
+
+ /* Scan the protocol up to the next whitespace, validation comes later */
+ if (!(ll = (char*) ap_scan_vchar_obstext(r->protocol))) {
+ len = strlen(r->protocol);
+ goto rrl_done;
+ }
+ len = ll - r->protocol;
+
+ /* Advance over trailing whitespace, if found mark in error,
+ * determine if trailing text is found, unconditionally mark in error,
+ * finally NUL terminate the protocol string
+ */
+ if (*ll && !apr_isspace(*ll)) {
+ deferred_error = rrl_badprotocol;
+ }
+ else if (strict && *ll) {
+ deferred_error = rrl_excesswhitespace;
+ }
+ else {
+ for ( ; apr_isspace(*ll); ++ll)
+ if (*ll != ' ' && deferred_error == rrl_none)
+ deferred_error = rrl_badwhitespace;
+ if (*ll && deferred_error == rrl_none)
+ deferred_error = rrl_trailingtext;
+ }
+ *((char *)r->protocol + len) = '\0';
+
+rrl_done:
+ /* For internal integrety and palloc efficiency, reconstruct the_request
+ * in one palloc, using only single SP characters, per spec.
+ */
+ r->the_request = apr_pstrcat(r->pool, r->method, *uri ? " " : NULL, uri,
+ *r->protocol ? " " : NULL, r->protocol, NULL);
+
+ if (len == 8
+ && r->protocol[0] == 'H' && r->protocol[1] == 'T'
+ && r->protocol[2] == 'T' && r->protocol[3] == 'P'
+ && r->protocol[4] == '/' && apr_isdigit(r->protocol[5])
+ && r->protocol[6] == '.' && apr_isdigit(r->protocol[7])
+ && r->protocol[5] != '0') {
+ r->assbackwards = 0;
+ r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0');
+ }
+ else if (len == 8
+ && (r->protocol[0] == 'H' || r->protocol[0] == 'h')
+ && (r->protocol[1] == 'T' || r->protocol[1] == 't')
+ && (r->protocol[2] == 'T' || r->protocol[2] == 't')
+ && (r->protocol[3] == 'P' || r->protocol[3] == 'p')
+ && r->protocol[4] == '/' && apr_isdigit(r->protocol[5])
+ && r->protocol[6] == '.' && apr_isdigit(r->protocol[7])
+ && r->protocol[5] != '0') {
+ r->assbackwards = 0;
+ r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0');
+ if (strict && deferred_error == rrl_none)
+ deferred_error = rrl_badprotocol;
+ else
+ memcpy((char*)r->protocol, "HTTP", 4);
+ }
+ else if (r->protocol[0]) {
+ r->proto_num = HTTP_VERSION(0, 9);
+ /* Defer setting the r->protocol string till error msg is composed */
+ if (deferred_error == rrl_none)
+ deferred_error = rrl_badprotocol;
+ }
+ else {
+ r->assbackwards = 1;
+ r->protocol = apr_pstrdup(r->pool, "HTTP/0.9");
+ r->proto_num = HTTP_VERSION(0, 9);
+ }
+
+ /* Determine the method_number and parse the uri prior to invoking error
+ * handling, such that these fields are available for subsitution
+ */
r->method_number = ap_method_number_of(r->method);
- if (r->method_number == M_GET && r->method[0] == 'H') {
+ if (r->method_number == M_GET && r->method[0] == 'H')
r->header_only = 1;
- }
ap_parse_uri(r, uri);
- if (r->status != HTTP_OK) {
- r->proto_num = HTTP_VERSION(1,0);
- r->protocol = apr_pstrdup(r->pool, "HTTP/1.0");
+
+ /* With the request understood, we can consider HTTP/0.9 specific errors */
+ if (r->proto_num == HTTP_VERSION(0, 9) && deferred_error == rrl_none) {
+ if (conf->http09_enable == AP_HTTP09_DISABLE)
+ deferred_error = rrl_reject09;
+ else if (strict && (r->method_number != M_GET || r->header_only))
+ deferred_error = rrl_badmethod09;
+ }
+
+ /* Now that the method, uri and protocol are all processed,
+ * we can safely resume any deferred error reporting
+ */
+ if (deferred_error != rrl_none) {
+ if (deferred_error == rrl_badmethod)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03445)
+ "HTTP Request Line; Invalid method token: '%.*s'",
+ field_name_len(r->method), r->method);
+ else if (deferred_error == rrl_badmethod09)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03444)
+ "HTTP Request Line; Invalid method token: '%.*s'"
+ " (only GET is allowed for HTTP/0.9 requests)",
+ field_name_len(r->method), r->method);
+ else if (deferred_error == rrl_missinguri)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03446)
+ "HTTP Request Line; Missing URI");
+ else if (deferred_error == rrl_baduri)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03454)
+ "HTTP Request Line; URI incorrectly encoded: '%.*s'",
+ field_name_len(r->uri), r->uri);
+ else if (deferred_error == rrl_badwhitespace)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03447)
+ "HTTP Request Line; Invalid whitespace");
+ else if (deferred_error == rrl_excesswhitespace)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03448)
+ "HTTP Request Line; Excess whitespace "
+ "(disallowed by HttpProtocolOptions Strict");
+ else if (deferred_error == rrl_trailingtext)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03449)
+ "HTTP Request Line; Extraneous text found '%.*s' "
+ "(perhaps whitespace was injected?)",
+ field_name_len(ll), ll);
+ else if (deferred_error == rrl_reject09)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02401)
+ "HTTP Request Line; Rejected HTTP/0.9 request");
+ else if (deferred_error == rrl_badprotocol)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02418)
+ "HTTP Request Line; Unrecognized protocol '%.*s' "
+ "(perhaps whitespace was injected?)",
+ field_name_len(r->protocol), r->protocol);
+ r->status = HTTP_BAD_REQUEST;
+ goto rrl_failed;
+ }
+
+ if (conf->http_methods == AP_HTTP_METHODS_REGISTERED
+ && r->method_number == M_INVALID) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02423)
+ "HTTP Request Line; Unrecognized HTTP method: '%.*s' "
+ "(disallowed by RegisteredMethods)",
+ field_name_len(r->method), r->method);
+ r->status = HTTP_NOT_IMPLEMENTED;
+ /* This can't happen in an HTTP/0.9 request, we verified GET above */
return 0;
}
- if (ll[0]) {
- r->assbackwards = 0;
- pro = ll;
- len = strlen(ll);
- } else {
- r->assbackwards = 1;
- pro = "HTTP/0.9";
- len = 8;
+ if (r->status != HTTP_OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03450)
+ "HTTP Request Line; Unable to parse URI: '%.*s'",
+ field_name_len(r->uri), r->uri);
+ goto rrl_failed;
}
- r->protocol = apr_pstrmemdup(r->pool, pro, len);
- /* Avoid sscanf in the common case */
- if (len == 8
- && pro[0] == 'H' && pro[1] == 'T' && pro[2] == 'T' && pro[3] == 'P'
- && pro[4] == '/' && apr_isdigit(pro[5]) && pro[6] == '.'
- && apr_isdigit(pro[7])) {
- r->proto_num = HTTP_VERSION(pro[5] - '0', pro[7] - '0');
- }
- else if (3 == sscanf(r->protocol, "%4s/%u.%u", http, &major, &minor)
- && (strcasecmp("http", http) == 0)
- && (minor < HTTP_VERSION(1, 0)) ) /* don't allow HTTP/0.1000 */
- r->proto_num = HTTP_VERSION(major, minor);
- else
- r->proto_num = HTTP_VERSION(1, 0);
+ if (strict) {
+ if (r->parsed_uri.fragment) {
+ /* RFC3986 3.5: no fragment */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02421)
+ "HTTP Request Line; URI must not contain a fragment");
+ r->status = HTTP_BAD_REQUEST;
+ goto rrl_failed;
+ }
+ if (r->parsed_uri.user || r->parsed_uri.password) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02422)
+ "HTTP Request Line; URI must not contain a "
+ "username/password");
+ r->status = HTTP_BAD_REQUEST;
+ goto rrl_failed;
+ }
+ }
return 1;
+
+rrl_failed:
+ if (r->proto_num == HTTP_VERSION(0, 9)) {
+ /* Send all parsing and protocol error response with 1.x behavior,
+ * and reserve 505 errors for actual HTTP protocols presented.
+ * As called out in RFC7230 3.5, any errors parsing the protocol
+ * from the request line are nearly always misencoded HTTP/1.x
+ * requests. Only a valid 0.9 request with no parsing errors
+ * at all may be treated as a simple request, if allowed.
+ */
+ r->assbackwards = 0;
+ r->connection->keepalive = AP_CONN_CLOSE;
+ r->proto_num = HTTP_VERSION(1, 0);
+ r->protocol = apr_pstrdup(r->pool, "HTTP/1.0");
+ }
+ return 0;
}
static int table_do_fn_check_lengths(void *r_, const char *key,
@@ -680,26 +917,13 @@ static int table_do_fn_check_lengths(voi
r->status = HTTP_BAD_REQUEST;
apr_table_setn(r->notes, "error-notes",
- apr_pstrcat(r->pool, "Size of a request header field "
- "after merging exceeds server limit.<br />"
- "\n<pre>\n",
- ap_escape_html(r->pool, key),
- "</pre>\n", NULL));
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00560) "Request header "
- "exceeds LimitRequestFieldSize after merging: %s", key);
+ "Size of a request header field exceeds server limit.");
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00560) "Request "
+ "header exceeds LimitRequestFieldSize after merging: %.*s",
+ field_name_len(key), key);
return 0;
}
-/* get the length of the field name for logging, but no more than 80 bytes */
-#define LOG_NAME_MAX_LEN 80
-static int field_name_len(const char *field)
-{
- const char *end = ap_strchr_c(field, ':');
- if (end == NULL || end - field > LOG_NAME_MAX_LEN)
- return LOG_NAME_MAX_LEN;
- return end - field;
-}
-
AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb)
{
char *last_field = NULL;
@@ -710,6 +934,8 @@ AP_DECLARE(void) ap_get_mime_headers_cor
apr_size_t len;
int fields_read = 0;
char *tmp_field;
+ core_server_config *conf = ap_get_core_module_config(r->server->module_config);
+ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
/*
* Read header lines until we get the empty separator line, a read error,
@@ -717,11 +943,10 @@ AP_DECLARE(void) ap_get_mime_headers_cor
*/
while(1) {
apr_status_t rv;
- int folded = 0;
field = NULL;
rv = ap_rgetline(&field, r->server->limit_req_fieldsize + 2,
- &len, r, 0, bb);
+ &len, r, strict ? AP_GETLINE_CRLF : 0, bb);
if (rv != APR_SUCCESS) {
if (APR_STATUS_IS_TIMEUP(rv)) {
@@ -738,156 +963,218 @@ AP_DECLARE(void) ap_get_mime_headers_cor
* exceeds the configured limit for a field size.
*/
if (rv == APR_ENOSPC) {
- const char *field_escaped;
- if (field && len) {
- /* ensure ap_escape_html will terminate correctly */
- field[len - 1] = '\0';
- field_escaped = ap_escape_html(r->pool, field);
- }
- else {
- field_escaped = field = "";
- }
-
apr_table_setn(r->notes, "error-notes",
- apr_psprintf(r->pool,
- "Size of a request header field "
- "exceeds server limit.<br />\n"
- "<pre>\n%.*s\n</pre>\n",
- field_name_len(field_escaped),
- field_escaped));
+ "Size of a request header field "
+ "exceeds server limit.");
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00561)
"Request header exceeds LimitRequestFieldSize%s"
"%.*s",
- *field ? ": " : "",
- field_name_len(field), field);
+ (field && *field) ? ": " : "",
+ (field) ? field_name_len(field) : 0,
+ (field) ? field : "");
}
return;
}
- if (last_field != NULL) {
- if ((len > 0) && ((*field == '\t') || *field == ' ')) {
- /* This line is a continuation of the preceding line(s),
- * so append it to the line that we've set aside.
- * Note: this uses a power-of-two allocator to avoid
- * doing O(n) allocs and using O(n^2) space for
- * continuations that span many many lines.
- */
- apr_size_t fold_len = last_len + len + 1; /* trailing null */
+ /* For all header values, and all obs-fold lines, the presence of
+ * additional whitespace is a no-op, so collapse trailing whitespace
+ * to save buffer allocation and optimize copy operations.
+ * Do not remove the last single whitespace under any condition.
+ */
+ while (len > 1 && (field[len-1] == '\t' || field[len-1] == ' ')) {
+ field[--len] = '\0';
+ }
- if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) {
- const char *field_escaped;
+ if (*field == '\t' || *field == ' ') {
- r->status = HTTP_BAD_REQUEST;
- /* report what we have accumulated so far before the
- * overflow (last_field) as the field with the problem
- */
- field_escaped = ap_escape_html(r->pool, last_field);
- apr_table_setn(r->notes, "error-notes",
- apr_psprintf(r->pool,
- "Size of a request header field "
- "after folding "
- "exceeds server limit.<br />\n"
- "<pre>\n%.*s\n</pre>\n",
- field_name_len(field_escaped),
- field_escaped));
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00562)
- "Request header exceeds LimitRequestFieldSize "
- "after folding: %.*s",
- field_name_len(last_field), last_field);
- return;
- }
+ /* Append any newly-read obs-fold line onto the preceding
+ * last_field line we are processing
+ */
+ apr_size_t fold_len;
+ if (last_field == NULL) {
+ r->status = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03442)
+ "Line folding encountered before first"
+ " header line");
+ return;
+ }
+
+ if (field[1] == '\0') {
+ r->status = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03443)
+ "Empty folded line encountered");
+ return;
+ }
+
+ /* Leading whitespace on an obs-fold line can be
+ * similarly discarded */
+ while (field[1] == '\t' || field[1] == ' ') {
+ ++field; --len;
+ }
+
+ /* This line is a continuation of the preceding line(s),
+ * so append it to the line that we've set aside.
+ * Note: this uses a power-of-two allocator to avoid
+ * doing O(n) allocs and using O(n^2) space for
+ * continuations that span many many lines.
+ */
+ fold_len = last_len + len + 1; /* trailing null */
+
+ if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) {
+ r->status = HTTP_BAD_REQUEST;
+ /* report what we have accumulated so far before the
+ * overflow (last_field) as the field with the problem
+ */
+ apr_table_setn(r->notes, "error-notes",
+ "Size of a request header field "
+ "exceeds server limit.");
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00562)
+ "Request header exceeds LimitRequestFieldSize "
+ "after folding: %.*s",
+ field_name_len(last_field), last_field);
+ return;
+ }
+
+ if (fold_len > alloc_len) {
+ char *fold_buf;
+ alloc_len += alloc_len;
if (fold_len > alloc_len) {
- char *fold_buf;
- alloc_len += alloc_len;
- if (fold_len > alloc_len) {
- alloc_len = fold_len;
- }
- fold_buf = (char *)apr_palloc(r->pool, alloc_len);
- memcpy(fold_buf, last_field, last_len);
- last_field = fold_buf;
+ alloc_len = fold_len;
}
- memcpy(last_field + last_len, field, len +1); /* +1 for nul */
- last_len += len;
- folded = 1;
- }
- else /* not a continuation line */ {
+ fold_buf = (char *)apr_palloc(r->pool, alloc_len);
+ memcpy(fold_buf, last_field, last_len);
+ last_field = fold_buf;
+ }
+ memcpy(last_field + last_len, field, len +1); /* +1 for nul */
+ /* Replace obs-fold w/ SP per RFC 7230 3.2.4 */
+ last_field[last_len] = ' ';
+ last_len += len;
+
+ /* We've appended this obs-fold line to last_len, proceed to
+ * read the next input line
+ */
+ continue;
+ }
+ else if (last_field != NULL) {
+
+ /* Process the previous last_field header line with all obs-folded
+ * segments already concatinated (this is not operating on the
+ * most recently read input line).
+ */
- if (r->server->limit_req_fields
+ if (r->server->limit_req_fields
&& (++fields_read > r->server->limit_req_fields)) {
- r->status = HTTP_BAD_REQUEST;
- apr_table_setn(r->notes, "error-notes",
- "The number of request header fields "
- "exceeds this server's limit.");
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00563)
- "Number of request headers exceeds "
- "LimitRequestFields");
- return;
- }
+ r->status = HTTP_BAD_REQUEST;
+ apr_table_setn(r->notes, "error-notes",
+ "The number of request header fields "
+ "exceeds this server's limit.");
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00563)
+ "Number of request headers exceeds "
+ "LimitRequestFields");
+ return;
+ }
- if (!(value = strchr(last_field, ':'))) { /* Find ':' or */
- r->status = HTTP_BAD_REQUEST; /* abort bad request */
- apr_table_setn(r->notes, "error-notes",
- apr_psprintf(r->pool,
- "Request header field is "
- "missing ':' separator.<br />\n"
- "<pre>\n%.*s</pre>\n",
- (int)LOG_NAME_MAX_LEN,
- ap_escape_html(r->pool,
- last_field)));
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00564)
+ if (!strict)
+ {
+ /* Not Strict ('Unsafe' mode), using the legacy parser */
+
+ if (!(value = strchr(last_field, ':'))) { /* Find ':' or */
+ r->status = HTTP_BAD_REQUEST; /* abort bad request */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00564)
"Request header field is missing ':' "
"separator: %.*s", (int)LOG_NAME_MAX_LEN,
last_field);
return;
}
- tmp_field = value - 1; /* last character of field-name */
+ /* last character of field-name */
+ tmp_field = value - (value > last_field ? 1 : 0);
*value++ = '\0'; /* NUL-terminate at colon */
+ if (strpbrk(last_field, "\t\n\v\f\r ")) {
+ r->status = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03452)
+ "Request header field name presented"
+ " invalid whitespace");
+ return;
+ }
+
while (*value == ' ' || *value == '\t') {
- ++value; /* Skip to start of value */
+ ++value; /* Skip to start of value */
+ }
+
+ if (strpbrk(value, "\n\v\f\r")) {
+ r->status = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03451)
+ "Request header field value presented"
+ " bad whitespace");
+ return;
}
- /* Strip LWS after field-name: */
- while (tmp_field > last_field
- && (*tmp_field == ' ' || *tmp_field == '\t')) {
- *tmp_field-- = '\0';
+ if (tmp_field == last_field) {
+ r->status = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03453)
+ "Request header field name was empty");
+ return;
+ }
+ }
+ else /* Using strict RFC7230 parsing */
+ {
+ /* Ensure valid token chars before ':' per RFC 7230 3.2.4 */
+ value = (char *)ap_scan_http_token(last_field);
+ if ((value == last_field) || *value != ':') {
+ r->status = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02426)
+ "Request header field name is malformed: "
+ "%.*s", (int)LOG_NAME_MAX_LEN, last_field);
+ return;
}
- /* Strip LWS after field-value: */
- tmp_field = last_field + last_len - 1;
- while (tmp_field > value
- && (*tmp_field == ' ' || *tmp_field == '\t')) {
- *tmp_field-- = '\0';
+ *value++ = '\0'; /* NUL-terminate last_field name at ':' */
+
+ while (*value == ' ' || *value == '\t') {
+ ++value; /* Skip LWS of value */
}
- apr_table_addn(r->headers_in, last_field, value);
+ /* Find invalid, non-HT ctrl char, or the trailing NULL */
+ tmp_field = (char *)ap_scan_http_field_content(value);
- /* reset the alloc_len so that we'll allocate a new
- * buffer if we have to do any more folding: we can't
- * use the previous buffer because its contents are
- * now part of r->headers_in
+ /* Reject value for all garbage input (CTRLs excluding HT)
+ * e.g. only VCHAR / SP / HT / obs-text are allowed per
+ * RFC7230 3.2.6 - leave all more explicit rule enforcement
+ * for specific header handler logic later in the cycle
*/
- alloc_len = 0;
+ if (*tmp_field != '\0') {
+ r->status = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02427)
+ "Request header value is malformed: "
+ "%.*s", (int)LOG_NAME_MAX_LEN, value);
+ return;
+ }
+ }
+
+ apr_table_addn(r->headers_in, last_field, value);
- } /* end if current line is not a continuation starting with tab */
+ /* This last_field header is now stored in headers_in,
+ * resume processing of the current input line.
+ */
}
- /* Found a blank line, stop. */
+ /* Found the terminating empty end-of-headers line, stop. */
if (len == 0) {
break;
}
- /* Keep track of this line so that we can parse it on
- * the next loop iteration. (In the folded case, last_field
- * has been updated already.)
+ /* Keep track of this new header line so that we can extend it across
+ * any obs-fold or parse it on the next loop iteration. We referenced
+ * our previously allocated buffer in r->headers_in,
+ * so allocate a fresh buffer if required.
*/
- if (!folded) {
- last_field = field;
- last_len = len;
- }
+ alloc_len = 0;
+ last_field = field;
+ last_len = len;
}
/* Combine multiple message-header fields with the same
@@ -912,7 +1199,7 @@ request_rec *ap_read_request(conn_rec *c
request_rec *r;
apr_pool_t *p;
const char *expect;
- int access_status = HTTP_OK;
+ int access_status;
apr_bucket_brigade *tmp_bb;
apr_socket_t *csd;
apr_interval_time_t cur_timeout;
@@ -971,16 +1258,19 @@ request_rec *ap_read_request(conn_rec *c
/* Get the request... */
if (!read_request_line(r, tmp_bb)) {
- if (r->status == HTTP_REQUEST_URI_TOO_LARGE
- || r->status == HTTP_BAD_REQUEST) {
+ switch (r->status) {
+ case HTTP_REQUEST_URI_TOO_LARGE:
+ case HTTP_BAD_REQUEST:
+ case HTTP_VERSION_NOT_SUPPORTED:
+ case HTTP_NOT_IMPLEMENTED:
if (r->status == HTTP_REQUEST_URI_TOO_LARGE) {
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00565)
"request failed: client's request-line exceeds LimitRequestLine (longer than %d)",
r->server->limit_req_line);
}
else if (r->method == NULL) {
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00566)
- "request failed: invalid characters in URI");
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00566)
+ "request failed: malformed request line");
}
access_status = r->status;
r->status = HTTP_OK;
@@ -990,19 +1280,17 @@ request_rec *ap_read_request(conn_rec *c
r = NULL;
apr_brigade_destroy(tmp_bb);
goto traceout;
- }
- else if (r->status == HTTP_REQUEST_TIME_OUT) {
+ case HTTP_REQUEST_TIME_OUT:
ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, NULL);
- if (!r->connection->keepalives) {
+ if (!r->connection->keepalives)
ap_run_log_transaction(r);
- }
apr_brigade_destroy(tmp_bb);
goto traceout;
+ default:
+ apr_brigade_destroy(tmp_bb);
+ r = NULL;
+ goto traceout;
}
-
- apr_brigade_destroy(tmp_bb);
- r = NULL;
- goto traceout;
}
/* We may have been in keep_alive_timeout mode, so toggle back
@@ -1021,7 +1309,7 @@ request_rec *ap_read_request(conn_rec *c
ap_get_mime_headers_core(r, tmp_bb);
if (r->status != HTTP_OK) {
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00567)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00567)
"request failed: error reading the headers");
ap_send_error_response(r, 0);
ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
@@ -1040,7 +1328,7 @@ request_rec *ap_read_request(conn_rec *c
*/
if (!(strcasecmp(tenc, "chunked") == 0 /* fast path */
|| ap_find_last_token(r->pool, tenc, "chunked"))) {
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02539)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02539)
"client sent unknown Transfer-Encoding "
"(%s): %s", tenc, r->uri);
r->status = HTTP_BAD_REQUEST;
@@ -1061,25 +1349,6 @@ request_rec *ap_read_request(conn_rec *c
apr_table_unset(r->headers_in, "Content-Length");
}
}
- else {
- if (r->header_only) {
- /*
- * Client asked for headers only with HTTP/0.9, which doesn't send
- * headers! Have to dink things just to make sure the error message
- * comes through...
- */
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00568)
- "client sent invalid HTTP/0.9 request: HEAD %s",
- r->uri);
- r->header_only = 0;
- r->status = HTTP_BAD_REQUEST;
- ap_send_error_response(r, 0);
- ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
- ap_run_log_transaction(r);
- apr_brigade_destroy(tmp_bb);
- goto traceout;
- }
- }
apr_brigade_destroy(tmp_bb);
@@ -1087,6 +1356,7 @@ request_rec *ap_read_request(conn_rec *c
* now read. may update status.
*/
ap_update_vhost_from_headers(r);
+ access_status = r->status;
/* Toggle to the Host:-based vhost's timeout mode to fetch the
* request body and send the response body, if needed.
@@ -1110,7 +1380,7 @@ request_rec *ap_read_request(conn_rec *c
* a Host: header, and the server MUST respond with 400 if it doesn't.
*/
access_status = HTTP_BAD_REQUEST;
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00569)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00569)
"client sent HTTP/1.1 request without hostname "
"(see RFC2616 section 14.23): %s", r->uri);
}
Modified: httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/scoreboard.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/scoreboard.c?rev=1781045&r1=1781044&r2=1781045&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/scoreboard.c (original)
+++ httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/scoreboard.c Tue Jan 31 09:52:02 2017
@@ -399,7 +399,7 @@ AP_DECLARE(int) ap_find_child_by_pid(apr
int i;
int max_daemons_limit = 0;
- ap_mpm_query(AP_MPMQ_MAX_DAEMONS, &max_daemons_limit);
+ ap_mpm_query(AP_MPMQ_MAX_DAEMON_USED, &max_daemons_limit);
for (i = 0; i < max_daemons_limit; ++i) {
if (ap_scoreboard_image->parent[i].pid == pid->pid) {
Modified: httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/util.c?rev=1781045&r1=1781044&r2=1781045&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/util.c (original)
+++ httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/util.c Tue Jan 31 09:52:02 2017
@@ -79,7 +79,7 @@
* char in here and get it to work, because if char is signed then it
* will first be sign extended.
*/
-#define TEST_CHAR(c, f) (test_char_table[(unsigned)(c)] & (f))
+#define TEST_CHAR(c, f) (test_char_table[(unsigned char)(c)] & (f))
/* Win32/NetWare/OS2 need to check for both forward and back slashes
* in ap_getparents() and ap_escape_url.
@@ -1525,7 +1525,7 @@ AP_DECLARE(const char *) ap_parse_token_
while (!string_end) {
const unsigned char c = (unsigned char)*cur;
- if (!TEST_CHAR(c, T_HTTP_TOKEN_STOP) && c != '\0') {
+ if (!TEST_CHAR(c, T_HTTP_TOKEN_STOP)) {
/* Non-separator character; we are finished with leading
* whitespace. We must never have encountered any trailing
* whitespace before the delimiter (comma) */
@@ -1593,6 +1593,37 @@ AP_DECLARE(const char *) ap_parse_token_
return NULL;
}
+/* Scan a string for HTTP VCHAR/obs-text characters including HT and SP
+ * (as used in header values, for example, in RFC 7230 section 3.2)
+ * returning the pointer to the first non-HT ASCII ctrl character.
+ */
+AP_DECLARE(const char *) ap_scan_http_field_content(const char *ptr)
+{
+ for ( ; !TEST_CHAR(*ptr, T_HTTP_CTRLS); ++ptr) ;
+
+ return ptr;
+}
+
+/* Scan a string for HTTP token characters, returning the pointer to
+ * the first non-token character.
+ */
+AP_DECLARE(const char *) ap_scan_http_token(const char *ptr)
+{
+ for ( ; !TEST_CHAR(*ptr, T_HTTP_TOKEN_STOP); ++ptr) ;
+
+ return ptr;
+}
+
+/* Scan a string for visible ASCII (0x21-0x7E) or obstext (0x80+)
+ * and return a pointer to the first ctrl/space character encountered.
+ */
+AP_DECLARE(const char *) ap_scan_vchar_obstext(const char *ptr)
+{
+ for ( ; TEST_CHAR(*ptr, T_VCHAR_OBSTEXT); ++ptr) ;
+
+ return ptr;
+}
+
/* Retrieve a token, spacing over it and returning a pointer to
* the first non-white byte afterwards. Note that these tokens
* are delimited by semis and commas; and can also be delimited
Modified: httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/util_fcgi.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/util_fcgi.c?rev=1781045&r1=1781044&r2=1781045&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/util_fcgi.c (original)
+++ httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/util_fcgi.c Tue Jan 31 09:52:02 2017
@@ -153,7 +153,7 @@ AP_DECLARE(apr_size_t) ap_fcgi_encoded_e
envlen += keylen;
- vallen = strlen(elts[i].val);
+ vallen = elts[i].val ? strlen(elts[i].val) : 0;
if (vallen >> 7 == 0) {
envlen += 1;
@@ -226,7 +226,7 @@ AP_DECLARE(apr_status_t) ap_fcgi_encode_
buflen -= 4;
}
- vallen = strlen(elts[i].val);
+ vallen = elts[i].val ? strlen(elts[i].val) : 0;
if (vallen >> 7 == 0) {
if (buflen < 1) {
@@ -262,8 +262,11 @@ AP_DECLARE(apr_status_t) ap_fcgi_encode_
rv = APR_ENOSPC; /* overflow */
break;
}
- memcpy(itr, elts[i].val, vallen);
- itr += vallen;
+
+ if (elts[i].val) {
+ memcpy(itr, elts[i].val, vallen);
+ itr += vallen;
+ }
if (buflen == vallen) {
(*starting_elem)++;
Modified: httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/vhost.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/vhost.c?rev=1781045&r1=1781044&r2=1781045&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/vhost.c (original)
+++ httpd/httpd/branches/2.4.x-openssl-1.1.0-compat/server/vhost.c Tue Jan 31 09:52:02 2017
@@ -687,6 +687,113 @@ static int vhost_check_config(apr_pool_t
* run-time vhost matching functions
*/
+static apr_status_t fix_hostname_v6_literal(request_rec *r, char *host)
+{
+ char *dst;
+ int double_colon = 0;
+
+ for (dst = host; *dst; dst++) {
+ if (apr_isxdigit(*dst)) {
+ if (apr_isupper(*dst)) {
+ *dst = apr_tolower(*dst);
+ }
+ }
+ else if (*dst == ':') {
+ if (*(dst + 1) == ':') {
+ if (double_colon)
+ return APR_EINVAL;
+ double_colon = 1;
+ }
+ else if (*(dst + 1) == '.') {
+ return APR_EINVAL;
+ }
+ }
+ else if (*dst == '.') {
+ /* For IPv4-mapped IPv6 addresses like ::FFFF:129.144.52.38 */
+ if (*(dst + 1) == ':' || *(dst + 1) == '.')
+ return APR_EINVAL;
+ }
+ else {
+ return APR_EINVAL;
+ }
+ }
+ return APR_SUCCESS;
+}
+
+static apr_status_t fix_hostname_non_v6(request_rec *r, char *host)
+{
+ char *dst;
+
+ for (dst = host; *dst; dst++) {
+ if (apr_islower(*dst)) {
+ /* leave char unchanged */
+ }
+ else if (*dst == '.') {
+ if (*(dst + 1) == '.') {
+ return APR_EINVAL;
+ }
+ }
+ else if (apr_isupper(*dst)) {
+ *dst = apr_tolower(*dst);
+ }
+ else if (*dst == '/' || *dst == '\\') {
+ return APR_EINVAL;
+ }
+ }
+ /* strip trailing gubbins */
+ if (dst > host && dst[-1] == '.') {
+ dst[-1] = '\0';
+ }
+ return APR_SUCCESS;
+}
+
+/*
+ * If strict mode ever becomes the default, this should be folded into
+ * fix_hostname_non_v6()
+ */
+static apr_status_t strict_hostname_check(request_rec *r, char *host)
+{
+ char *ch;
+ int is_dotted_decimal = 1, leading_zeroes = 0, dots = 0;
+
+ for (ch = host; *ch; ch++) {
+ if (apr_isalpha(*ch) || *ch == '-') {
+ is_dotted_decimal = 0;
+ }
+ else if (ch[0] == '.') {
+ dots++;
+ if (ch[1] == '0' && apr_isdigit(ch[2]))
+ leading_zeroes = 1;
+ }
+ else if (!apr_isdigit(*ch)) {
+ /* also takes care of multiple Host headers by denying commas */
+ goto bad;
+ }
+ }
+ if (is_dotted_decimal) {
+ if (host[0] == '.' || (host[0] == '0' && apr_isdigit(host[1])))
+ leading_zeroes = 1;
+ if (leading_zeroes || dots != 3) {
+ /* RFC 3986 7.4 */
+ goto bad;
+ }
+ }
+ else {
+ /* The top-level domain must start with a letter (RFC 1123 2.1) */
+ while (ch > host && *ch != '.')
+ ch--;
+ if (ch[0] == '.' && ch[1] != '\0' && !apr_isalpha(ch[1]))
+ goto bad;
+ }
+ return APR_SUCCESS;
+
+bad:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02415)
+ "[strict] Invalid host name '%s'%s%.6s",
+ host, *ch ? ", problem near: " : "", ch);
+ return APR_EINVAL;
+}
+
/* Lowercase and remove any trailing dot and/or :port from the hostname,
* and check that it is sane.
*
@@ -700,79 +807,90 @@ static int vhost_check_config(apr_pool_t
* Instead we just check for filesystem metacharacters: directory
* separators / and \ and sequences of more than one dot.
*/
-static void fix_hostname(request_rec *r)
+static int fix_hostname(request_rec *r, const char *host_header,
+ unsigned http_conformance)
{
+ const char *src;
char *host, *scope_id;
- char *dst;
apr_port_t port;
apr_status_t rv;
const char *c;
+ int is_v6literal = 0;
+ int strict = (http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
- /* According to RFC 2616, Host header field CAN be blank. */
- if (!*r->hostname) {
- return;
+ src = host_header ? host_header : r->hostname;
+
+ /* According to RFC 2616, Host header field CAN be blank */
+ if (!*src) {
+ return is_v6literal;
}
/* apr_parse_addr_port will interpret a bare integer as a port
* which is incorrect in this context. So treat it separately.
*/
- for (c = r->hostname; apr_isdigit(*c); ++c);
- if (!*c) { /* pure integer */
- return;
+ for (c = src; apr_isdigit(*c); ++c);
+ if (!*c) {
+ /* pure integer */
+ if (strict) {
+ /* RFC 3986 7.4 */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02416)
+ "[strict] purely numeric host names not allowed: %s",
+ src);
+ goto bad_nolog;
+ }
+ r->hostname = src;
+ return is_v6literal;
+ }
+
+ if (host_header) {
+ rv = apr_parse_addr_port(&host, &scope_id, &port, src, r->pool);
+ if (rv != APR_SUCCESS || scope_id)
+ goto bad;
+ if (port) {
+ /* Don't throw the Host: header's port number away:
+ save it in parsed_uri -- ap_get_server_port() needs it! */
+ /* @@@ XXX there should be a better way to pass the port.
+ * Like r->hostname, there should be a r->portno
+ */
+ r->parsed_uri.port = port;
+ r->parsed_uri.port_str = apr_itoa(r->pool, (int)port);
+ }
+ if (host_header[0] == '[')
+ is_v6literal = 1;
+ }
+ else {
+ /*
+ * Already parsed, surrounding [ ] (if IPv6 literal) and :port have
+ * already been removed.
+ */
+ host = apr_pstrdup(r->pool, r->hostname);
+ if (ap_strchr(host, ':') != NULL)
+ is_v6literal = 1;
}
- rv = apr_parse_addr_port(&host, &scope_id, &port, r->hostname, r->pool);
- if (rv != APR_SUCCESS || scope_id) {
- goto bad;
+ if (is_v6literal) {
+ rv = fix_hostname_v6_literal(r, host);
}
-
- if (port) {
- /* Don't throw the Host: header's port number away:
- save it in parsed_uri -- ap_get_server_port() needs it! */
- /* @@@ XXX there should be a better way to pass the port.
- * Like r->hostname, there should be a r->portno
- */
- r->parsed_uri.port = port;
- r->parsed_uri.port_str = apr_itoa(r->pool, (int)port);
+ else {
+ rv = fix_hostname_non_v6(r, host);
+ if (strict && rv == APR_SUCCESS)
+ rv = strict_hostname_check(r, host);
}
+ if (rv != APR_SUCCESS)
+ goto bad;
- /* if the hostname is an IPv6 numeric address string, it was validated
- * already; otherwise, further validation is needed
- */
- if (r->hostname[0] != '[') {
- for (dst = host; *dst; dst++) {
- if (apr_islower(*dst)) {
- /* leave char unchanged */
- }
- else if (*dst == '.') {
- if (*(dst + 1) == '.') {
- goto bad;
- }
- }
- else if (apr_isupper(*dst)) {
- *dst = apr_tolower(*dst);
- }
- else if (*dst == '/' || *dst == '\\') {
- goto bad;
- }
- }
- /* strip trailing gubbins */
- if (dst > host && dst[-1] == '.') {
- dst[-1] = '\0';
- }
- }
r->hostname = host;
- return;
+ return is_v6literal;
bad:
- r->status = HTTP_BAD_REQUEST;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00550)
"Client sent malformed Host header: %s",
- r->hostname);
- return;
+ src);
+bad_nolog:
+ r->status = HTTP_BAD_REQUEST;
+ return is_v6literal;
}
-
/* return 1 if host matches ServerName or ServerAliases */
static int matches_aliases(server_rec *s, const char *host)
{
@@ -982,15 +1100,76 @@ static void check_serverpath(request_rec
}
}
+static APR_INLINE const char *construct_host_header(request_rec *r,
+ int is_v6literal)
+{
+ struct iovec iov[5];
+ apr_size_t nvec = 0;
+ /*
+ * We cannot use ap_get_server_name/port here, because we must
+ * ignore UseCanonicalName/Port.
+ */
+ if (is_v6literal) {
+ iov[nvec].iov_base = "[";
+ iov[nvec].iov_len = 1;
+ nvec++;
+ }
+ iov[nvec].iov_base = (void *)r->hostname;
+ iov[nvec].iov_len = strlen(r->hostname);
+ nvec++;
+ if (is_v6literal) {
+ iov[nvec].iov_base = "]";
+ iov[nvec].iov_len = 1;
+ nvec++;
+ }
+ if (r->parsed_uri.port_str) {
+ iov[nvec].iov_base = ":";
+ iov[nvec].iov_len = 1;
+ nvec++;
+ iov[nvec].iov_base = r->parsed_uri.port_str;
+ iov[nvec].iov_len = strlen(r->parsed_uri.port_str);
+ nvec++;
+ }
+ return apr_pstrcatv(r->pool, iov, nvec, NULL);
+}
AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r)
{
- /* must set this for HTTP/1.1 support */
- if (r->hostname || (r->hostname = apr_table_get(r->headers_in, "Host"))) {
- fix_hostname(r);
- if (r->status != HTTP_OK)
- return;
+ core_server_config *conf = ap_get_core_module_config(r->server->module_config);
+ const char *host_header = apr_table_get(r->headers_in, "Host");
+ int is_v6literal = 0;
+ int have_hostname_from_url = 0;
+
+ if (r->hostname) {
+ /*
+ * If there was a host part in the Request-URI, ignore the 'Host'
+ * header.
+ */
+ have_hostname_from_url = 1;
+ is_v6literal = fix_hostname(r, NULL, conf->http_conformance);
+ }
+ else if (host_header != NULL) {
+ is_v6literal = fix_hostname(r, host_header, conf->http_conformance);
+ }
+ if (r->status != HTTP_OK)
+ return;
+
+ if (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE) {
+ /*
+ * If we have both hostname from an absoluteURI and a Host header,
+ * we must ignore the Host header (RFC 2616 5.2).
+ * To enforce this, we reset the Host header to the value from the
+ * request line.
+ */
+ if (have_hostname_from_url && host_header != NULL) {
+ const char *repl = construct_host_header(r, is_v6literal);
+ apr_table_set(r->headers_in, "Host", repl);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02417)
+ "Replacing host header '%s' with host '%s' given "
+ "in the request uri", host_header, repl);
+ }
}
+
/* check if we tucked away a name_chain */
if (r->connection->vhost_lookup_data) {
if (r->hostname)