-*- coding: utf-8 -*-
Changes with Apache 2.2.30
+ *) http: Make ap_die() robust against any HTTP error code and not modify
+ response status (finally logged) when nothing is to be done. [Yann Ylavic]
+
+ *) core, modules: Avoid error response/document handling by the core if some
+ handler or input filter already did it while reading the request (causing
+ a double response body). [Yann Ylavic]
+
*) FreeBSD: Disable IPv4-mapped listening sockets by default for versions
5+ instead of just for FreeBSD 5. PR 53824. [Jeff Trawick,
Olli Hauer <ohauer gmx de>]
(modulo CHANGES)
+1: ylavic, wrowe, covener
- *) core, modules: Avoid error response/document handling by the core if some
- handler or input filter already did it while reading the request (causing
- a double response body).
- trunk patch: http://svn.apache.org/r1482522 (partial, ap_map_http_request_error() things only!)
- http://svn.apache.org/r1529988
- http://svn.apache.org/r1529991
- http://svn.apache.org/r1643537
- http://svn.apache.org/r1643543
- http://svn.apache.org/r1657897
- http://svn.apache.org/r1665625
- http://svn.apache.org/r1665721
- http://svn.apache.org/r1674056
- 2.4.x patch: http://people.apache.org/~ylavic/httpd-2.4.x-ap_map_http_request_error-v2.patch
- 2.2.x patch: http://people.apache.org/~ylavic/httpd-2.2.x-ap_map_http_request_error-v2.patch
- +1: ylavic, wrowe, covener
- ylavic: Depends on httpd-2.2.x-ap_die.patch above.
-
PATCHES PROPOSED TO BACKPORT FROM TRUNK:
[ New proposals should be added at the end of the list ]
* 20051115.37 (2.2.30) Add ap_get_server_name_for_url()
* 20051115.38 (2.2.30) Add ap_proxy_set_scoreboard_lb() in mod_proxy.h
* 20051115.39 (2.2.30) Add ap_proxy_connection_reusable()
+ * 20051115.40 (2.2.30) Add ap_map_http_request_error()
*/
#define MODULE_MAGIC_COOKIE 0x41503232UL /* "AP22" */
#ifndef MODULE_MAGIC_NUMBER_MAJOR
#define MODULE_MAGIC_NUMBER_MAJOR 20051115
#endif
-#define MODULE_MAGIC_NUMBER_MINOR 39 /* 0...n */
+#define MODULE_MAGIC_NUMBER_MINOR 40 /* 0...n */
/**
* Determine if the server's current MODULE_MAGIC_NUMBER is at least a
*/
AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer, apr_size_t bufsiz);
+/*
+ * Map specific APR codes returned by the filter stack to HTTP error
+ * codes, or the default status code provided. Use it as follows:
+ *
+ * return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
+ *
+ * If the filter has already handled the error, AP_FILTER_ERROR will
+ * be returned, which is cleanly passed through.
+ *
+ * These mappings imply that the filter stack is reading from the
+ * downstream client, the proxy will map these codes differently.
+ * @param rv APR status code
+ * @param status Default HTTP code should the APR code not be recognised
+ * @return Mapped HTTP status code
+ */
+AP_DECLARE(int) ap_map_http_request_error(apr_status_t rv, int status);
+
/**
* In HTTP/1.1, any method can have a body. However, most GET handlers
* wouldn't know what to do with a request body if they received one.
APR_BRIGADE_INSERT_TAIL(bb, b);
if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
- return HTTP_INTERNAL_SERVER_ERROR;
+ return AP_FILTER_ERROR;
#endif
return OK;
}
APR_BRIGADE_INSERT_TAIL(bb, b);
if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
- return HTTP_INTERNAL_SERVER_ERROR;
+ return AP_FILTER_ERROR;
#endif
return OK;
}
APR_BRIGADE_INSERT_TAIL(bb, bkt);
if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
- return dav_new_error(pool, HTTP_FORBIDDEN, 0,
+ return dav_new_error(pool, AP_FILTER_ERROR, 0,
"Could not write contents to filter.");
}
/* log the errors */
dav_log_err(r, err, APLOG_ERR);
+ if (!ap_is_HTTP_VALID_RESPONSE(err->status)) {
+ /* we have responded already */
+ return AP_FILTER_ERROR;
+ }
+
if (response == NULL) {
dav_error *stackerr = err;
APR_BLOCK_READ, DAV_READ_BLOCKSIZE);
if (rc != APR_SUCCESS) {
- err = dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
- "Could not get next bucket brigade");
+ int http_err;
+ const char *msg;
+ if (APR_STATUS_IS_TIMEUP(rc)) {
+ http_err = HTTP_REQUEST_TIME_OUT;
+ msg = "Timeout reading the body";
+ }
+ else {
+ http_err = ap_map_http_request_error(rc, HTTP_BAD_REQUEST);
+ msg = "Error reading the body";
+ }
+ err = dav_new_error(r->pool, http_err, 0, msg);
break;
}
if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"mod_asis: ap_pass_brigade failed for file %s", r->filename);
- return HTTP_INTERNAL_SERVER_ERROR;
+ return AP_FILTER_ERROR;
}
}
else {
if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"Error reading request entity data");
- return HTTP_INTERNAL_SERVER_ERROR;
+ return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
}
for (bucket = APR_BRIGADE_FIRST(bb);
if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"Error reading request entity data");
- return HTTP_INTERNAL_SERVER_ERROR;
+ return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
}
for (bucket = APR_BRIGADE_FIRST(bb);
e = apr_bucket_flush_create(f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, e);
- ap_pass_brigade(f->c->output_filters, bb);
+ rv = ap_pass_brigade(f->c->output_filters, bb);
+ if (rv != APR_SUCCESS) {
+ return AP_FILTER_ERROR;
+ }
}
}
return ap_pass_brigade(f->next, b);
}
+/*
+ * Map specific APR codes returned by the filter stack to HTTP error
+ * codes, or the default status code provided. Use it as follows:
+ *
+ * return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
+ *
+ * If the filter has already handled the error, AP_FILTER_ERROR will
+ * be returned, which is cleanly passed through.
+ *
+ * These mappings imply that the filter stack is reading from the
+ * downstream client, the proxy will map these codes differently.
+ */
+AP_DECLARE(int) ap_map_http_request_error(apr_status_t rv, int status)
+{
+ switch (rv) {
+ case AP_FILTER_ERROR: {
+ return AP_FILTER_ERROR;
+ }
+ case APR_ENOSPC: {
+ return HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+ case APR_ENOTIMPL: {
+ return HTTP_NOT_IMPLEMENTED;
+ }
+ case APR_ETIMEDOUT: {
+ return HTTP_REQUEST_TIME_OUT;
+ }
+ default: {
+ return status;
+ }
+ }
+}
+
/* In HTTP/1.1, any method can have a body. However, most GET handlers
* wouldn't know what to do with a request body if they received one.
* This helper routine tests for and reads any message body in the request,
AP_DECLARE(int) ap_discard_request_body(request_rec *r)
{
apr_bucket_brigade *bb;
- int rv, seen_eos;
+ int seen_eos;
+ apr_status_t rv;
/* Sometimes we'll get in a state where the input handling has
* detected an error where we want to drop the connection, so if
APR_BLOCK_READ, HUGE_STRING_LEN);
if (rv != APR_SUCCESS) {
- /* FIXME: If we ever have a mapping from filters (apr_status_t)
- * to HTTP error codes, this would be a good place for them.
- *
- * If we received the special case AP_FILTER_ERROR, it means
- * that the filters have already handled this error.
- * Otherwise, we should assume we have a bad request.
- */
- if (rv == AP_FILTER_ERROR) {
- apr_brigade_destroy(bb);
- return rv;
- }
- else {
- apr_brigade_destroy(bb);
- return HTTP_BAD_REQUEST;
- }
+ apr_brigade_destroy(bb);
+ return ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
}
for (bucket = APR_BRIGADE_FIRST(bb);
/* We lose the failure code here. This is why ap_get_client_block should
* not be used.
*/
+ if (rv == AP_FILTER_ERROR) {
+ /* AP_FILTER_ERROR means a filter has responded already,
+ * we are DONE.
+ */
+ apr_brigade_destroy(bb);
+ return -1;
+ }
if (rv != APR_SUCCESS) {
/* if we actually fail here, we want to just return and
* stop trying to read data from the client.
}
}
-AP_DECLARE(void) ap_die(int type, request_rec *r)
+static void ap_die_r(int type, request_rec *r, int recursive_error)
{
- int error_index = ap_index_of_response(type);
- char *custom_response = ap_response_code_string(r, error_index);
- int recursive_error = 0;
+ char *custom_response;
request_rec *r_1st_err = r;
- if (type == AP_FILTER_ERROR) {
+ if (type == OK || type == DONE) {
+ ap_finalize_request_protocol(r);
+ return;
+ }
+
+ if (!ap_is_HTTP_VALID_RESPONSE(type)) {
ap_filter_t *next;
/*
* Check if we still have the ap_http_header_filter in place. If
- * this is the case we should not ignore AP_FILTER_ERROR here because
+ * this is the case we should not ignore the error here because
* it means that we have not sent any response at all and never
* will. This is bad. Sent an internal server error instead.
*/
* next->frec == ap_http_header_filter
*/
if (next) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
- "Custom error page caused AP_FILTER_ERROR");
+ if (type != AP_FILTER_ERROR) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Invalid response status %i", type);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "Response from AP_FILTER_ERROR");
+ }
type = HTTP_INTERNAL_SERVER_ERROR;
}
else {
}
}
- if (type == DONE) {
- ap_finalize_request_protocol(r);
- return;
- }
-
/*
* The following takes care of Apache redirects to custom response URLs
* Note that if we are already dealing with the response to some other
* error condition, we just report on the original error, and give up on
* any attempt to handle the other thing "intelligently"...
*/
- if (r->status != HTTP_OK) {
- recursive_error = type;
-
+ if (recursive_error != HTTP_OK) {
while (r_1st_err->prev && (r_1st_err->prev->status != HTTP_OK))
r_1st_err = r_1st_err->prev; /* Get back to original error */
custom_response = NULL; /* Do NOT retry the custom thing! */
}
+ else {
+ int error_index = ap_index_of_response(type);
+ custom_response = ap_response_code_string(r, error_index);
+ recursive_error = 0;
+ }
r->status = type;
ap_send_error_response(r_1st_err, recursive_error);
}
+AP_DECLARE(void) ap_die(int type, request_rec *r)
+{
+ ap_die_r(type, r, r->status);
+}
+
static void check_pipeline_flush(request_rec *r)
{
apr_bucket *e;
}
}
- if (access_status == DONE) {
- /* e.g., something not in storage like TRACE */
- access_status = OK;
- }
-
- if (access_status == OK) {
- ap_finalize_request_protocol(r);
- }
- else {
- r->status = HTTP_OK;
- ap_die(access_status, r);
- }
+ ap_die_r(access_status, r, HTTP_OK);
/*
* We want to flush the last packet if this isn't a pipelining connection
AP_DECLARE(void) ap_internal_redirect(const char *new_uri, request_rec *r)
{
- request_rec *new = internal_internal_redirect(new_uri, r);
int access_status;
+ request_rec *new = internal_internal_redirect(new_uri, r);
/* ap_die was already called, if an error occured */
if (!new) {
access_status = ap_invoke_handler(new);
}
}
- if (access_status == OK) {
- ap_finalize_request_protocol(new);
- }
- else {
- ap_die(access_status, new);
- }
+ ap_die(access_status, new);
}
/* This function is designed for things like actions or CGI scripts, when
ap_set_content_type(new, r->content_type);
access_status = ap_process_request_internal(new);
if (access_status == OK) {
- if ((access_status = ap_invoke_handler(new)) != 0) {
- ap_die(access_status, new);
- return;
- }
- ap_finalize_request_protocol(new);
- }
- else {
- ap_die(access_status, new);
+ access_status = ap_invoke_handler(new);
}
+ ap_die(access_status, new);
}
AP_DECLARE(void) ap_allow_methods(request_rec *r, int reset, ...)
case M_TRACE: {
int access_status;
r->proxyreq = PROXYREQ_NONE;
- if ((access_status = ap_send_http_trace(r)))
- ap_die(access_status, r);
- else
- ap_finalize_request_protocol(r);
+ access_status = ap_send_http_trace(r);
+ ap_die(access_status, r);
return OK;
}
case M_OPTIONS: {
int access_status;
r->proxyreq = PROXYREQ_NONE;
- if ((access_status = ap_send_http_options(r)))
- ap_die(access_status, r);
- else
- ap_finalize_request_protocol(r);
+ access_status = ap_send_http_options(r);
+ ap_die(access_status, r);
return OK;
}
default: {
apr_byte_t conn_reuse = 0;
const char *tenc;
int havebody = 1;
- int output_failed = 0;
+ int client_failed = 0;
int backend_failed = 0;
apr_off_t bb_len;
int data_sent = 0;
int request_ended = 0;
int headers_sent = 0;
- int rv = 0;
+ int rv = OK;
apr_int32_t conn_poll_fd;
apr_pollfd_t *conn_poll;
proxy_server_conf *psf =
if (status != APR_SUCCESS) {
/* We had a failure: Close connection to backend */
conn->close++;
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, status, r->server,
"proxy: ap_get_brigade failed");
apr_brigade_destroy(input_brigade);
- return HTTP_BAD_REQUEST;
+ return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
}
/* have something */
ap_log_error(APLOG_MARK, APLOG_DEBUG, status,
r->server,
"ap_get_brigade failed");
- output_failed = 1;
+ if (APR_STATUS_IS_TIMEUP(status)) {
+ rv = HTTP_REQUEST_TIME_OUT;
+ }
+ else if (status == AP_FILTER_ERROR) {
+ rv = AP_FILTER_ERROR;
+ }
+ client_failed = 1;
break;
}
bufsiz = maxsize;
ap_log_error(APLOG_MARK, APLOG_DEBUG, status,
r->server,
"apr_brigade_flatten failed");
- output_failed = 1;
+ rv = HTTP_INTERNAL_SERVER_ERROR;
+ client_failed = 1;
break;
}
}
"proxy: error processing body.%s",
r->connection->aborted ?
" Client aborted connection." : "");
- output_failed = 1;
+ client_failed = 1;
}
data_sent = 1;
apr_brigade_cleanup(output_brigade);
output_brigade) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"proxy: error processing end");
- output_failed = 1;
+ client_failed = 1;
}
/* XXX: what about flush here? See mod_jk */
data_sent = 1;
/*
* If connection has been aborted by client: Stop working.
- * Nevertheless, we regard our operation so far as a success:
- * So reset output_failed to 0 and set result to CMD_AJP13_END_RESPONSE
- * But: Close this connection to the backend.
+ * Pretend we are done (data_sent) to avoid further processing.
*/
if (r->connection->aborted) {
- conn->close++;
- output_failed = 0;
- result = CMD_AJP13_END_RESPONSE;
- request_ended = 1;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "client connection aborted");
+ /* no response yet (or ever), set status for access log */
+ if (!headers_sent) {
+ r->status = HTTP_BAD_REQUEST;
+ }
+ client_failed = 1;
+ /* return DONE */
+ data_sent = 1;
+ break;
}
/*
* We either have finished successfully or we failed.
* So bail out
*/
- if ((result == CMD_AJP13_END_RESPONSE) || backend_failed
- || output_failed)
+ if ((result == CMD_AJP13_END_RESPONSE)
+ || backend_failed || client_failed)
break;
/* read the response */
*/
apr_brigade_cleanup(output_brigade);
- if (backend_failed || output_failed) {
+ if (backend_failed || client_failed) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
- "proxy: Processing of request failed backend: %i, "
- "output: %i", backend_failed, output_failed);
+ "Processing of request failed backend: %i, client: %i",
+ backend_failed, client_failed);
/* We had a failure: Close connection to backend */
- conn->close++;
- /* Return DONE to avoid error messages being added to the stream */
+ conn->close = 1;
if (data_sent) {
+ /* Return DONE to avoid error messages being added to the stream */
rv = DONE;
}
}
/* We had a failure: Close connection to backend */
conn->close++;
backend_failed = 1;
- /* Return DONE to avoid error messages being added to the stream */
if (data_sent) {
+ /* Return DONE to avoid error messages being added to the stream */
rv = DONE;
}
}
rv = HTTP_INTERNAL_SERVER_ERROR;
}
}
+ else if (client_failed) {
+ int level = (r->connection->aborted) ? APLOG_DEBUG : APLOG_ERR;
+ ap_log_error(APLOG_MARK, level, status, r->server,
+ "dialog with client %pI failed",
+ r->connection->remote_addr);
+ if (rv == OK) {
+ rv = HTTP_BAD_REQUEST;
+ }
+ }
/*
* Ensure that we sent an EOS bucket thru the filter chain, if we already
* one to the brigade already (no longer making it empty). So we should
* not do this in this case.
*/
- if (data_sent && !r->eos_sent && APR_BRIGADE_EMPTY(output_brigade)) {
+ if (data_sent && !r->eos_sent && !r->connection->aborted
+ && APR_BRIGADE_EMPTY(output_brigade)) {
e = apr_bucket_eos_create(r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(output_brigade, e);
}
- /* If we have added something to the brigade above, sent it */
- if (!APR_BRIGADE_EMPTY(output_brigade))
- ap_pass_brigade(r->output_filters, output_brigade);
+ /* If we have added something to the brigade above, send it */
+ if (!APR_BRIGADE_EMPTY(output_brigade)
+ && ap_pass_brigade(r->output_filters, output_brigade) != APR_SUCCESS) {
+ rv = AP_FILTER_ERROR;
+ }
apr_brigade_destroy(output_brigade);
HUGE_STRING_LEN);
if (status != APR_SUCCESS) {
- return HTTP_BAD_REQUEST;
+ return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
}
}
HUGE_STRING_LEN);
if (status != APR_SUCCESS) {
- return HTTP_BAD_REQUEST;
+ return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
}
}
HUGE_STRING_LEN);
if (status != APR_SUCCESS) {
- return HTTP_BAD_REQUEST;
+ return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
}
}
" from %s (%s)",
p_conn->addr, p_conn->hostname ? p_conn->hostname: "",
c->remote_ip, c->remote_host ? c->remote_host: "");
- return HTTP_BAD_REQUEST;
+ return ap_map_http_request_error(status, HTTP_BAD_REQUEST);
}
apr_brigade_length(temp_brigade, 1, &bytes);
}
}
- /* XXX: What could we do with that return code? */
- (void)ap_pass_brigade(r->output_filters, bb);
+ if (ap_pass_brigade(r->output_filters, bb)) {
+ return AP_FILTER_ERROR;
+ }
return OK;
}
if (rv) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"could not read request body for SSL buffer");
- return HTTP_INTERNAL_SERVER_ERROR;
+ return ap_map_http_request_error(rv, HTTP_INTERNAL_SERVER_ERROR);
}
/* Iterate through the returned brigade: setaside each bucket
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
"default_handler: ap_pass_brigade returned %i",
status);
- return HTTP_INTERNAL_SERVER_ERROR;
+ return AP_FILTER_ERROR;
}
}
else { /* unusual method (not GET or POST) */
APR_BUCKET_INIT(b);
b->free = apr_bucket_free;
b->list = list;
+ if (!ap_is_HTTP_VALID_RESPONSE(error)) {
+ error = HTTP_INTERNAL_SERVER_ERROR;
+ }
return ap_bucket_error_make(b, error, buf, p);
}
READ_BLOCKSIZE);
if (status != APR_SUCCESS) {
+ result = ap_map_http_request_error(status, HTTP_BAD_REQUEST);
goto read_error;
}