From: Justin Erenkrantz Date: Mon, 9 Dec 2002 05:37:26 +0000 (+0000) Subject: Rewrite how proxy sends its request to allow input bodies to morph the request X-Git-Tag: pre_ajp_proxy~2483 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=392fee3abcb315b2b2feb12fdda9c7278e9245ea;p=apache Rewrite how proxy sends its request to allow input bodies to morph the request bodies. Previously, if an input filter changed the request body, the original C-L would be sent which would be incorrect. Due to HTTP compliance, we must either send the body T-E: chunked or include a C-L for the request body. Connection: Close is not an option. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@97812 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index d222e04c51..8a8a10fc28 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,8 @@ Changes with Apache 2.1.0-dev [Remove entries to the current 2.0 section below, when backported] + *) Fix mod_proxy handling of filtered input bodies. [Justin Erenkrantz] + *) Don't remove the Content-Length from responses in mod_proxy [Brian Pane] diff --git a/modules/proxy/proxy_http.c b/modules/proxy/proxy_http.c index b0aa9aa098..e9e50cee1e 100644 --- a/modules/proxy/proxy_http.c +++ b/modules/proxy/proxy_http.c @@ -416,15 +416,20 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, proxy_http_conn_t *p_conn, conn_rec *origin, proxy_server_conf *conf, apr_uri_t *uri, - char *url, apr_bucket_brigade *bb, - char *server_portstr) { + char *url, char *server_portstr) +{ conn_rec *c = r->connection; char *buf; - apr_bucket *e; + apr_bucket *e, *last_header_bucket; const apr_array_header_t *headers_in_array; const apr_table_entry_t *headers_in; - int counter, seen_eos; + int counter, seen_eos, send_chunks; apr_status_t status; + apr_bucket_brigade *header_brigade, *body_brigade, *input_brigade; + + header_brigade = apr_brigade_create(p, origin->bucket_alloc); + body_brigade = apr_brigade_create(p, origin->bucket_alloc); + input_brigade = apr_brigade_create(p, origin->bucket_alloc); /* * Send the HTTP/1.1 request to the remote server @@ -438,6 +443,7 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, */ p_conn->close += ap_proxy_liststr(apr_table_get(r->headers_in, "Connection"), "close"); + /* sub-requests never use keepalives */ if (r->main) { p_conn->close++; @@ -449,22 +455,31 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, origin->keepalive = AP_CONN_CLOSE; } - if ( apr_table_get(r->subprocess_env,"force-proxy-request-1.0")) { + /* By default, we can not send chunks. That means we must buffer + * the entire request before sending it along to ensure we have + * the correct Content-Length attached. + */ + send_chunks = 0; + + if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) { buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.0" CRLF, NULL); } else { buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.1" CRLF, NULL); + if (apr_table_get(r->subprocess_env, "proxy-sendchunks")) { + send_chunks = 1; + } } - if ( apr_table_get(r->subprocess_env,"proxy-nokeepalive")) { + if (apr_table_get(r->subprocess_env, "proxy-nokeepalive")) { apr_table_unset(r->headers_in, "Connection"); origin->keepalive = AP_CONN_CLOSE; } ap_xlate_proto_to_ascii(buf, strlen(buf)); e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, e); - if ( conf->preserve_host == 0 ) { + APR_BRIGADE_INSERT_TAIL(header_brigade, e); + if (conf->preserve_host == 0) { if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) { - buf = apr_pstrcat(p, "Host: ", uri->hostname, ":", uri->port_str, CRLF, - NULL); + buf = apr_pstrcat(p, "Host: ", uri->hostname, ":", uri->port_str, + CRLF, NULL); } else { buf = apr_pstrcat(p, "Host: ", uri->hostname, CRLF, NULL); } @@ -487,7 +502,7 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, } ap_xlate_proto_to_ascii(buf, strlen(buf)); e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, e); + APR_BRIGADE_INSERT_TAIL(header_brigade, e); /* handle Via */ if (conf->viaopt == via_block) { @@ -574,6 +589,10 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, || !apr_strnatcasecmp(headers_in[counter].key, "Transfer-Encoding") || !apr_strnatcasecmp(headers_in[counter].key, "Upgrade") + /* We have no way of knowing whether this Content-Length will + * be accurate, so we must not include it. + */ + || !apr_strnatcasecmp(headers_in[counter].key, "Content-Length") /* XXX: @@@ FIXME: "Proxy-Authorization" should *only* be * suppressed if THIS server requested the authentication, * not when a frontend proxy requested it! @@ -597,24 +616,6 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, || !apr_strnatcasecmp(headers_in[counter].key, "If-None-Match")) { continue; } - - /* If you POST to a page that gets server-side parsed - * by mod_include, and the parsing results in a reverse - * proxy call, the proxied request will be a GET, but - * its request_rec will have inherited the Content-Length - * of the original request (the POST for the enclosing - * page). We can't send the original POST's request body - * as part of the proxied subrequest, so we need to avoid - * sending the corresponding content length. Otherwise, - * the server to which we're proxying will sit there - * forever, waiting for a request body that will never - * arrive. - */ - if ((r->method_number == M_GET) && headers_in[counter].key && - !apr_strnatcasecmp(headers_in[counter].key, - "Content-Length")) { - continue; - } } @@ -623,7 +624,21 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, NULL); ap_xlate_proto_to_ascii(buf, strlen(buf)); e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, e); + APR_BRIGADE_INSERT_TAIL(header_brigade, e); + } + + /* If we can send chunks, do so! */ + if (send_chunks) { + const char *te_hdr = "Transfer-Encoding: chunked" CRLF; + + buf = apr_pmemdup(p, te_hdr, sizeof(te_hdr)-1); + ap_xlate_proto_to_ascii(buf, sizeof(te_hdr)-1); + + e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(header_brigade, e); + } + else { + last_header_bucket = APR_BRIGADE_LAST(header_brigade); } /* add empty line at the end of the headers */ @@ -632,55 +647,133 @@ apr_status_t ap_proxy_http_request(apr_pool_t *p, request_rec *r, #else e = apr_bucket_immortal_create(CRLF, sizeof(CRLF)-1, c->bucket_alloc); #endif - APR_BRIGADE_INSERT_TAIL(bb, e); + APR_BRIGADE_INSERT_TAIL(header_brigade, e); e = apr_bucket_flush_create(c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, e); + APR_BRIGADE_INSERT_TAIL(header_brigade, e); - status = ap_pass_brigade(origin->output_filters, bb); + if (send_chunks) { + status = ap_pass_brigade(origin->output_filters, header_brigade); - if (status != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server, - "proxy: request failed to %pI (%s)", - p_conn->addr, p_conn->name); - return status; + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server, + "proxy: request failed to %pI (%s)", + p_conn->addr, p_conn->name); + return status; + } } /* send the request data, if any. */ seen_eos = 0; do { - status = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, - APR_BLOCK_READ, HUGE_STRING_LEN); + char chunk_hdr[20]; /* must be here due to transient bucket. */ + + status = ap_get_brigade(r->input_filters, input_brigade, + AP_MODE_READBYTES, APR_BLOCK_READ, + HUGE_STRING_LEN); if (status != APR_SUCCESS) { return status; } /* If this brigade contain EOS, either stop or remove it. */ - if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) { + if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) { + seen_eos = 1; + /* As a shortcut, if this brigade is simply an EOS bucket, * don't send anything down the filter chain. */ - if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(bb))) { + if (APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade))) { break; } /* We can't pass this EOS to the output_filters. */ - APR_BUCKET_REMOVE(APR_BRIGADE_LAST(bb)); - seen_eos = 1; + APR_BUCKET_REMOVE(APR_BRIGADE_LAST(input_brigade)); + } + + if (send_chunks) { +#define ASCII_CRLF "\015\012" +#define ASCII_ZERO "\060" + apr_size_t hdr_len; + apr_off_t bytes; + + apr_brigade_length(input_brigade, 1, &bytes); + + hdr_len = apr_snprintf(chunk_hdr, sizeof(chunk_hdr), + "%qx" CRLF, (apr_uint64_t)bytes); + + ap_xlate_proto_to_ascii(chunk_hdr, hdr_len); + e = apr_bucket_transient_create(chunk_hdr, hdr_len, + body_brigade->bucket_alloc); + APR_BRIGADE_INSERT_HEAD(input_brigade, e); + + /* + * Append the end-of-chunk CRLF + */ + e = apr_bucket_immortal_create(ASCII_CRLF, 2, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(input_brigade, e); } e = apr_bucket_flush_create(c->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, e); + APR_BRIGADE_INSERT_TAIL(input_brigade, e); + + APR_BRIGADE_CONCAT(body_brigade, input_brigade); + + if (send_chunks) { + status = ap_pass_brigade(origin->output_filters, body_brigade); + + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server, + "proxy: pass request data failed to %pI (%s)", + p_conn->addr, p_conn->name); + return status; + } + + apr_brigade_cleanup(body_brigade); + } + + } while (!seen_eos); + + if (send_chunks) { + e = apr_bucket_immortal_create(ASCII_ZERO ASCII_CRLF + /* */ + ASCII_CRLF, 5, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(body_brigade, e); + } - status = ap_pass_brigade(origin->output_filters, bb); + if (!send_chunks) { + apr_off_t bytes; + + apr_brigade_length(body_brigade, 1, &bytes); + + if (bytes) { + const char *cl_hdr = "Content-Length", *cl_val; + cl_val = apr_off_t_toa(c->pool, bytes); + buf = apr_pstrcat(p, cl_hdr, ": ", cl_val, CRLF, NULL); + ap_xlate_proto_to_ascii(buf, strlen(buf)); + e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); + APR_BUCKET_INSERT_AFTER(last_header_bucket, e); + } + status = ap_pass_brigade(origin->output_filters, header_brigade); if (status != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server, "proxy: pass request data failed to %pI (%s)", p_conn->addr, p_conn->name); return status; } - apr_brigade_cleanup(bb); - } while (!seen_eos); + + apr_brigade_cleanup(header_brigade); + } + + status = ap_pass_brigade(origin->output_filters, body_brigade); + + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server, + "proxy: pass request data failed to %pI (%s)", + p_conn->addr, p_conn->name); + return status; + } + + apr_brigade_cleanup(body_brigade); return APR_SUCCESS; } @@ -691,13 +784,13 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, conn_rec *origin, proxy_conn_rec *backend, proxy_server_conf *conf, - apr_bucket_brigade *bb, char *server_portstr) { conn_rec *c = r->connection; char buffer[HUGE_STRING_LEN]; char keepchar; request_rec *rp; apr_bucket *e; + apr_bucket_brigade *bb; int len, backasswards; int received_continue = 1; /* flag to indicate if we should * loop over response parsing logic @@ -705,6 +798,8 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, * to HTTP_CONTINUE */ + bb = apr_brigade_create(p, c->bucket_alloc); + /* Get response from the remote server, and pass it up the * filter chain */ @@ -1053,7 +1148,6 @@ int ap_proxy_http_handler(request_rec *r, proxy_server_conf *conf, */ apr_pool_t *p = r->connection->pool; conn_rec *c = r->connection; - apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc); apr_uri_t *uri = apr_palloc(r->connection->pool, sizeof(*uri)); proxy_http_conn_t *p_conn = apr_pcalloc(r->connection->pool, sizeof(*p_conn)); @@ -1114,7 +1208,7 @@ int ap_proxy_http_handler(request_rec *r, proxy_server_conf *conf, } /* Step Three: Send the Request */ - status = ap_proxy_http_request(p, r, p_conn, origin, conf, uri, url, bb, + status = ap_proxy_http_request(p, r, p_conn, origin, conf, uri, url, server_portstr); if ( status != OK ) { return status; @@ -1122,7 +1216,7 @@ int ap_proxy_http_handler(request_rec *r, proxy_server_conf *conf, /* Step Four: Receive the Response */ status = ap_proxy_http_process_response(p, r, p_conn, origin, backend, conf, - bb, server_portstr); + server_portstr); if ( status != OK ) { /* clean up even if there is an error */ ap_proxy_http_cleanup(r, p_conn, backend);