RELEASE SHOWSTOPPERS:
- * Copy the backport branch of all of the mod_proxy_http.c's request body
- handling security, protocol and bug fixes; by svn copy'ing the file
- httpd/httpd/branches/proxy-reqbody-2.0.x/modules/proxy/proxy_http.c back to
- httpd/branches/2.0.x/... preserving the detail of all of the individually
- backported changes.
-
- +1: wrowe, jim, minfrin
- -1:
-
- For a complete history of individual unit changes, see r230703 - r230744 in
- http://svn.apache.org/viewcvs.cgi/httpd/httpd/branches/proxy-reqbody-2.0.x/
- [...] modules/proxy/proxy_http.c?&view=log
- Cite the specific patch with justification for each specific objection.
-
- Suggested; revert r219061 to thoroughly test this patch, as r219061 masks
- some underlying bugs (although it is a -good- patch in and of itself and
- provides additional protection to other content-handling modules).
-
- * TRACE must not have a request body per RFC2616; see the -trace.patch
- below for one of two alternatives. The other alternative; simply
- hack mod_proxy.c to reject TRACE when a body is seen, again see that
- -trace.patch for an illustration.
-
PATCHES ACCEPTED TO BACKPORT FROM TRUNK:
[ start all new proposals below, under PATCHES PROPOSED. ]
- *) mod_cgi: Added API call and overload of detached field in
- cgi_exec_info_t structure to support loading in current or new address
- space for CGIs. The patch change how NetWare use cmdtype for
- processes. It was made necessary by changes done to log.c r1.145.
- The HTTP and the APR patches are available at:
- <http://www.apache.org/~clar/detach-addrspace_HTTP_2_0.patch>
- <http://www.apache.org/~clar/detach-addrspace_APR_0_9.patch>
- +1: jjclar, bnicholes, trawick
- jerenkrantz: I'm confused as to the status of this backport.
- trawick: Somebody commits the APR 0.9 patch, then:
- do we have to wait for later APR 0.x release before putting
- calls to apr_procattr_addrspace_set() into httpd-2.0.x, or
- do we go ahead and introduce the prerequisite?
- clar replies: I am ready to commit the apr 0.9.x patch, but then will need
- the changes in the httpd-2.0.x to be done in order for NetWare to work
- as expected when calling apr_proc_create. Should I do both, APR and Http,
- at the same time?
- wrowe: commit to APR. Use an APR version test *in httpd* to determine
- if the old or new behavior should be used in httpd. In future versions
- you could remove the test altogether.
-
*) mod_actions: Regression from 1.3: the file referred to must exist.
Solve this by introducing the "virtual" modifier to the Action
directive. PR 28553.
+1: pquerna, nd, wrowe
Votes from before the integration branch: +1: jerenkrantz
- *) Fix CAN-2005-2491, integer overflow in pcre.
- http://svn.apache.org/viewcvs?rev=233493&view=rev
- rediff for 2.0: http://people.apache.org/~jorton/CAN-2005-2491.patch
- test case: perl-framework/t/security/CAN-2005-2491.t
- +1: jorton, nd, wrowe
-
PATCHES PROPOSED TO BACKPORT FROM TRUNK:
[ please place SVN revisions from trunk here, so it is easy to
identify exactly what the proposed changes are! Add all new
+1: jorton, wrowe
wrowe cautions to backport to 2.2.x branch as well.
- *) Correct RFC 2616 non-compliance by refusing to proxy a request body
- in a TRACE request, unless TraceEnable extended is configured.
- Introduces TraceEnable [on|off|extended] to give the administrator
- full control of TRACE request handling. RFC 2616 does NOT require
- TRACE (although to disable remains silly). Current patch at;
- http://people.apache.org/~wrowe/httpd-2.0-trace.patch
- +1 wrowe, jimjag, colm
- colm notes: There are some \n's in apr_table_setn calls that are
- not consistent with other calls to apr_table_setn.
- There is no documentation for TraceEnable in trunk to
- backport, shouldn't release while still undocumented.
-
*) mod_headers: Support {...}s tag for SSL variable lookup.
http://www.apache.org/~jorton/mod_headers-2.0-ssl.diff
+1: jorton, trawick
*) Provide TLS/SSL upgrade functionality in mod_ssl allowing an unsecure
connection to be upgraded to a secure connection upon request by the
- client. The full patch file is available at http://www.apache.org/~bnicholes/
- as well as a test client tlsupgrade.c. This functionality is mainly used by
- IPP clients today.
+ client. The full patch is available at http://www.apache.org/~bnicholes/
+ as well as a test client tlsupgrade.c. This functionality is mainly used
+ by IPP clients today.
modules/ssl/mod_ssl.c: r1.75, r1.97, r1.100
modules/ssl/mod_ssl.h: r1.123
modules/ssl/ssl_engine_config.c: r1.71, r1.90
algorithims can be pretty "interesting", probably more
2.2.
- *) mod_ldap: Fix PR 36563. Keep track of the number of attributes
- retrieved from LDAP so that all of the values can be properly
- cached even if the value is NULL.
- http://issues.apache.org/bugzilla/attachment.cgi?id=16429
- or
- http://svn.apache.org/viewcvs.cgi?rev=156587&view=rev
- +1: bnicholes
-
PATCHES TO BACKPORT THAT ARE ON HOLD OR NOT GOING ANYWHERE SOON:
- *) Remove LDAP toolkit specific code from util_ldap and mod_auth_ldap.
- modules/experimental/mod_auth_ldap.c: 1.28
- modules/experimental/util_ldap.c: 1.36
- +0: minfrin (this requires the apr-util LDAP overhaul to be ported to
- apr-util v0.9 first)
- -0: jerenkrantz
- jerenkrantz: I don't think we can change the APR 0.9 interfaces.
- They are supposed to be set in stone.
- -1: wrowe: agrees with jerenkrantz, further realized that this major
- change in APR 1.0 caused -every- apr-util linked app to have
- the ldap sdk (openldap etc) linked in, and our --static-support
- stuff is horribly broken by this change. Not that it's wrong,
- we need to look at making it slightly more dynamic for those
- apps that don't touch ldap.
-
- *) Add load balancer support to the scoreboard in preparation for
- load balancing support in mod_proxy.
- include/scoreboard.h: 1.52
- server/scoreboard.c: 1.75
- +0: minfrin: it makes sense for v2.1 or v2.2
- -0: nd, jerenkrantz
- nd: -0 as in "it should be considered as a 2.1 feature".
- If the modified structures are public (are they?), I'm just -1.
- jerenkrantz: Sounds like a good 2.1 feature...
- -1: wrowe (make this a private score to the module and you would be fine;
- we don't need to keep overloading a single scoreboard.)
-
- *) mod_ssl: Remove some unused functions (after CAN-2004-0488 fix is applied)
- http://cvs.apache.org/viewcvs.cgi/httpd-2.0/modules/ssl/ssl_util.c?r1=1.46&r2=1.47
- +1: jorton
- trawick: need changes to mod_ssl.h to remove prototypes for those removed functions
- 0: nd: IMHO that's a public API change then and not applicable for
- 2.0, just let 'em in
- -1: wrowe (as nd suggests, leave the dead horse in peace.)
-
*) Replace some of the mutex locking in the worker MPM with
atomic operations for higher concurrency.
server/mpm/worker/fdqueue.c 1.24, 1.25
http://svn.apache.org/viewcvs?view=rev&rev=158798
http://svn.apache.org/viewcvs?view=rev&rev=159410
http://svn.apache.org/viewcvs?view=rev&rev=160573
- +1: gregames
+ +1: gregames, wrowe (provided this is applied to ALL subreq types!)
-1: jerenkrantz (read_length isn't a sufficient check to see if a body
is present in the request; presence of T-E and C-L in
the headers is the correct flag.)
- gregames: done in rev 160573
- ±0: wrowe (this has a negative impact on modules who wish to 'inspect'
+ gregames: addressed jerenkrantz' objection in rev 160573
+ wrowe: this has a negative impact on modules who wish to 'inspect'
the headers, e.g. an xml transformation affected by the query
string or request POST args. The right solution is adopt apreq,
- providing an API for filters to participate in POST bodies.)
+ providing an API for filters to participate in POST bodies.
gregames: this does not affect POSTs. the affected function helps
create a GET subrequest with no body and is unprepared to deal with
subrequest bodies. any modules or applications wishing to
inspect headers will in fact work better because the headers will
reflect reality.
-
+ wrowe: I've reconsidered - the simple fact is that subrequests
+ don't have a good mechanism to 'share' the input body with the
+ main request, and it's gotta be up to the main request to handle
+ the input body. If the module wants to use apreq-provided data,
+ then it's going to have to ask apreq for the data instead of
+ looking at the headers. For that matter, why are subreq's even
+ propogating POST or other non-GET types? It seems that almost
+ any subreq should be handled as a GET in 2.0.
CURRENT VOTES:
- *) Promote mod_ldap and mod_auth_ldap from experimental to
- non experimental status.
- +1: bnicholes, wrowe
- +0: minfrin (wait till the last cache bugs are ironed out)
- -1: jerenkrantz
-
*) httpd-std.conf and friends;
a) httpd-std.conf should be tailored by install (from src or
(.default.conf rather than .conf.default so that win32
can recognize .conf files as text configuration files.)
- b) tailored httpd-std.conf should be copied by install to
- sysconfdir/examples
- -0: striker
-
c) tailored httpd-std.conf should be installed to
sysconfdir/examples or manualdir/exampleconf/
+1: slive, trawick, Ken, nd (prefer the latter), erikabele
- d) tailored httpd-std.conf should be installed as httpd-std-<version>.conf.
- +1: striker
-
- e) Installing a set of default config files when upgrading a server
- doesn't make ANY sense at all.
- +1: ianh - medium/big sites don't use 'standard config' anyway, as it
- usually needs major customizations
- -1: Ken, wrowe, jwoolley, jim, nd, erikabele
- wrowe - diff is wonderful when comparing old/new default configs,
- even for customized sites that ianh mentions
- jim - it makes sense assuming that the default configs
- include the updated directives and inline comments
- that explain the changes and make the 'diff' more useful.
-
*) If the parent process dies, should the remaining child processes
"gracefully" self-terminate. Or maybe we should make it a runtime
option, or have a concept of 2 parent processes (one being a
static apr_status_t pass_brigade(apr_bucket_alloc_t *bucket_alloc,
request_rec *r, proxy_http_conn_t *p_conn,
- conn_rec *origin, apr_bucket_brigade *b,
+ conn_rec *origin, apr_bucket_brigade *bb,
int flush)
{
apr_status_t status;
if (flush) {
apr_bucket *e = apr_bucket_flush_create(bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(b, e);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
}
- status = ap_pass_brigade(origin->output_filters, b);
+ status = ap_pass_brigade(origin->output_filters, bb);
if (status != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
- "proxy: pass request data failed to %pI (%s)",
+ "proxy: pass request body failed to %pI (%s)",
p_conn->addr, p_conn->name);
return status;
}
- apr_brigade_cleanup(b);
+ apr_brigade_cleanup(bb);
return APR_SUCCESS;
}
request_rec *r,
proxy_http_conn_t *p_conn,
conn_rec *origin,
- apr_bucket_brigade *header_brigade)
+ apr_bucket_brigade *header_brigade,
+ apr_bucket_brigade *input_brigade)
{
int seen_eos = 0;
apr_size_t hdr_len;
apr_off_t bytes;
apr_status_t status;
apr_bucket_alloc_t *bucket_alloc = r->connection->bucket_alloc;
- apr_bucket_brigade *b, *input_brigade;
+ apr_bucket_brigade *bb;
apr_bucket *e;
- input_brigade = apr_brigade_create(p, bucket_alloc);
+ add_te_chunked(p, bucket_alloc, header_brigade);
+ terminate_headers(bucket_alloc, header_brigade);
- do {
+ while (!APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade)))
+ {
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 contains EOS, either stop or remove it. */
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(input_brigade))) {
- break;
- }
-
/* We can't pass this EOS to the output_filters. */
e = APR_BRIGADE_LAST(input_brigade);
apr_bucket_delete(e);
/* we never sent the header brigade, so go ahead and
* take care of that now
*/
- add_te_chunked(p, bucket_alloc, header_brigade);
- terminate_headers(bucket_alloc, header_brigade);
- b = header_brigade;
- APR_BRIGADE_CONCAT(b, input_brigade);
+ bb = header_brigade;
+ APR_BRIGADE_CONCAT(bb, input_brigade);
header_brigade = NULL;
}
else {
- b = input_brigade;
+ bb = input_brigade;
}
- status = pass_brigade(bucket_alloc, r, p_conn, origin, b, 0);
+ /* The request is flushed below this loop with chunk EOS header */
+ status = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 0);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ if (seen_eos) {
+ break;
+ }
+
+ status = ap_get_brigade(r->input_filters, input_brigade,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ HUGE_STRING_LEN);
+
if (status != APR_SUCCESS) {
return status;
}
- } while (!seen_eos);
+ }
if (header_brigade) {
/* we never sent the header brigade because there was no request body;
- * send it now without T-E
+ * send it now
*/
- terminate_headers(bucket_alloc, header_brigade);
- b = header_brigade;
+ bb = header_brigade;
}
else {
if (!APR_BRIGADE_EMPTY(input_brigade)) {
AP_DEBUG_ASSERT(APR_BUCKET_IS_EOS(e));
apr_bucket_delete(e);
}
- e = apr_bucket_immortal_create(ASCII_ZERO ASCII_CRLF
- /* <trailers> */
- ASCII_CRLF,
- 5, bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(input_brigade, e);
- b = input_brigade;
+ bb = input_brigade;
}
-
- status = pass_brigade(bucket_alloc, r, p_conn, origin, b, 1);
+
+ e = apr_bucket_immortal_create(ASCII_ZERO ASCII_CRLF
+ /* <trailers> */
+ ASCII_CRLF,
+ 5, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+
+ /* Now we have headers-only, or the chunk EOS mark; flush it */
+ status = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1);
return status;
}
proxy_http_conn_t *p_conn,
conn_rec *origin,
apr_bucket_brigade *header_brigade,
+ apr_bucket_brigade *input_brigade,
const char *old_cl_val)
{
int seen_eos = 0;
- apr_status_t status;
+ apr_status_t status = APR_SUCCESS;
apr_bucket_alloc_t *bucket_alloc = r->connection->bucket_alloc;
- apr_bucket_brigade *b, *input_brigade;
+ apr_bucket_brigade *bb;
apr_bucket *e;
+ apr_off_t cl_val = 0;
+ apr_off_t bytes;
+ apr_off_t bytes_streamed = 0;
- input_brigade = apr_brigade_create(p, bucket_alloc);
-
- do {
- status = ap_get_brigade(r->input_filters, input_brigade,
- AP_MODE_READBYTES, APR_BLOCK_READ,
- HUGE_STRING_LEN);
+ if (old_cl_val) {
+ add_cl(p, bucket_alloc, header_brigade, old_cl_val);
+ cl_val = atol(old_cl_val);
+ }
+ terminate_headers(bucket_alloc, header_brigade);
- if (status != APR_SUCCESS) {
- return status;
- }
+ while (!APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade)))
+ {
+ apr_brigade_length(input_brigade, 1, &bytes);
+ bytes_streamed += bytes;
/* If this brigade contains EOS, either stop or remove it. */
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(input_brigade))) {
- break;
- }
-
/* We can't pass this EOS to the output_filters. */
e = APR_BRIGADE_LAST(input_brigade);
apr_bucket_delete(e);
}
+ /* C-L < bytes streamed?!?
+ * We will error out after the body is completely
+ * consumed, but we can't stream more bytes at the
+ * back end since they would in part be interpreted
+ * as another request! If nothing is sent, then
+ * just send nothing.
+ *
+ * Prevents HTTP Response Splitting.
+ */
+ if (bytes_streamed > cl_val)
+ continue;
+
if (header_brigade) {
/* we never sent the header brigade, so go ahead and
* take care of that now
*/
- add_cl(p, bucket_alloc, header_brigade, old_cl_val);
- terminate_headers(bucket_alloc, header_brigade);
- b = header_brigade;
- APR_BRIGADE_CONCAT(b, input_brigade);
+ bb = header_brigade;
+ APR_BRIGADE_CONCAT(bb, input_brigade);
header_brigade = NULL;
}
else {
- b = input_brigade;
+ bb = input_brigade;
}
- status = pass_brigade(bucket_alloc, r, p_conn, origin, b, 0);
+ /* Once we hit EOS, we are ready to flush. */
+ status = pass_brigade(bucket_alloc, r, p_conn, origin, bb, seen_eos);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ if (seen_eos) {
+ break;
+ }
+
+ status = ap_get_brigade(r->input_filters, input_brigade,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ HUGE_STRING_LEN);
+
if (status != APR_SUCCESS) {
return status;
}
- } while (!seen_eos);
+ }
+
+ if (bytes_streamed != cl_val) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "proxy: client %s given Content-Length did not match"
+ " number of body bytes read", r->connection->remote_ip);
+ return APR_EOF;
+ }
if (header_brigade) {
/* we never sent the header brigade since there was no request
- * body; send it now, and only specify C-L if client specified
- * C-L: 0
+ * body; send it now with the flush flag
*/
- if (!strcmp(old_cl_val, "0")) {
- add_cl(p, bucket_alloc, header_brigade, old_cl_val);
- }
- terminate_headers(bucket_alloc, header_brigade);
- b = header_brigade;
+ bb = header_brigade;
+ status = pass_brigade(bucket_alloc, r, p_conn, origin, bb, 1);
}
- else {
- /* need to flush any pending data */
- b = input_brigade; /* empty now; pass_brigade() will add flush */
- }
- status = pass_brigade(bucket_alloc, r, p_conn, origin, b, 1);
return status;
}
request_rec *r,
proxy_http_conn_t *p_conn,
conn_rec *origin,
- apr_bucket_brigade *header_brigade)
+ apr_bucket_brigade *header_brigade,
+ apr_bucket_brigade *input_brigade,
+ int force_cl)
{
int seen_eos = 0;
apr_status_t status;
apr_bucket_alloc_t *bucket_alloc = r->connection->bucket_alloc;
- apr_bucket_brigade *body_brigade, *input_brigade;
+ apr_bucket_brigade *body_brigade;
apr_bucket *e;
apr_off_t bytes, bytes_spooled = 0, fsize = 0;
apr_file_t *tmpfile = NULL;
body_brigade = apr_brigade_create(p, bucket_alloc);
- input_brigade = apr_brigade_create(p, bucket_alloc);
-
- do {
- status = ap_get_brigade(r->input_filters, input_brigade,
- AP_MODE_READBYTES, APR_BLOCK_READ,
- HUGE_STRING_LEN);
-
- if (status != APR_SUCCESS) {
- return status;
- }
+ while (!APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(input_brigade)))
+ {
/* If this brigade contains EOS, either stop or remove it. */
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(input_brigade))) {
- break;
- }
-
/* We can't pass this EOS to the output_filters. */
e = APR_BRIGADE_LAST(input_brigade);
apr_bucket_delete(e);
bytes_spooled += bytes;
- } while (!seen_eos);
+ if (seen_eos) {
+ break;
+ }
- if (bytes_spooled) {
+ 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 (bytes_spooled || force_cl) {
add_cl(p, bucket_alloc, header_brigade, apr_off_t_toa(p, bytes_spooled));
}
terminate_headers(bucket_alloc, header_brigade);
}
APR_BRIGADE_INSERT_TAIL(header_brigade, e);
}
+ /* This is all a single brigade, pass with flush flagged */
status = pass_brigade(bucket_alloc, r, p_conn, origin, header_brigade, 1);
return status;
}
-static apr_status_t send_request_body(apr_pool_t *p,
- request_rec *r,
- proxy_http_conn_t *p_conn,
- conn_rec *origin,
- apr_bucket_brigade *header_brigade,
- int force10)
-{
- enum {RB_INIT, RB_STREAM_CL, RB_STREAM_CHUNKED, RB_SPOOL_CL} rb_method = RB_INIT;
- const char *old_cl_val, *te_val;
- int cl_zero; /* client sent "Content-Length: 0", which we forward on to server */
- apr_status_t status;
-
- /* send CL or use chunked encoding?
- *
- * . CL is the most friendly to the origin server since it is the
- * most widely supported
- * . CL stinks if we don't know the length since we have to buffer
- * the data in memory or on disk until we get the entire data
- *
- * special cases to check for:
- * . if we're using HTTP/1.0 to origin server, then we must send CL
- * . if client sent C-L and there are no input resource filters, the
- * the body size can't change so we send the same CL and stream the
- * body
- * . if client used chunked or proxy-sendchunks is set, we'll also
- * use chunked
- *
- * normal case:
- * we have to compute content length by reading the entire request
- * body; if request body is not small, we'll spool the remaining input
- * to a temporary file
- *
- * special envvars to override the normal decision:
- * . proxy-sendchunks
- * use chunked encoding; not compatible with force-proxy-request-1.0
- * . proxy-sendcl
- * spool the request body to compute C-L
- * . proxy-sendunchangedcl
- * use C-L from client and spool the request body
- */
- old_cl_val = apr_table_get(r->headers_in, "Content-Length");
- cl_zero = old_cl_val && !strcmp(old_cl_val, "0");
-
- if (!force10
- && !cl_zero
- && apr_table_get(r->subprocess_env, "proxy-sendchunks")) {
- rb_method = RB_STREAM_CHUNKED;
- }
- else if (!cl_zero
- && apr_table_get(r->subprocess_env, "proxy-sendcl")) {
- rb_method = RB_SPOOL_CL;
- }
- else {
- if (old_cl_val &&
- (r->input_filters == r->proto_input_filters
- || cl_zero
- || apr_table_get(r->subprocess_env, "proxy-sendunchangedcl"))) {
- rb_method = RB_STREAM_CL;
- }
- else if (force10) {
- rb_method = RB_SPOOL_CL;
- }
- else if ((te_val = apr_table_get(r->headers_in, "Transfer-Encoding"))
- && !strcasecmp(te_val, "chunked")) {
- rb_method = RB_STREAM_CHUNKED;
- }
- else {
- rb_method = RB_SPOOL_CL;
- }
- }
-
- switch(rb_method) {
- case RB_STREAM_CHUNKED:
- status = stream_reqbody_chunked(p, r, p_conn, origin, header_brigade);
- break;
- case RB_STREAM_CL:
- status = stream_reqbody_cl(p, r, p_conn, origin, header_brigade, old_cl_val);
- break;
- case RB_SPOOL_CL:
- status = spool_reqbody_cl(p, r, p_conn, origin, header_brigade);
- break;
- default:
- ap_assert(1 != 1);
- }
-
- return status;
-}
-
static
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,
+ apr_bucket_brigade *header_brigade,
+ char *server_portstr)
+{
conn_rec *c = r->connection;
- char *buf;
+ apr_bucket_alloc_t *bucket_alloc = c->bucket_alloc;
+ apr_bucket_brigade *input_brigade;
+ apr_bucket_brigade *temp_brigade;
apr_bucket *e;
+ char *buf;
const apr_array_header_t *headers_in_array;
const apr_table_entry_t *headers_in;
int counter;
apr_status_t status;
+ enum rb_methods {RB_INIT, RB_STREAM_CL, RB_STREAM_CHUNKED, RB_SPOOL_CL};
+ enum rb_methods rb_method = RB_INIT;
+ const char *old_cl_val = NULL;
+ const char *old_te_val = NULL;
+ apr_off_t bytes_read = 0;
+ apr_off_t bytes;
int force10;
/*
/* strip connection listed hop-by-hop headers from the request */
/* even though in theory a connection: close coming from the client
* should not affect the connection to the server, it's unlikely
- * that subsequent client requests will hit this thread/process, so
- * we cancel server keepalive if the client does.
+ * that subsequent client requests will hit this thread/process,
+ * so we cancel server keepalive if the client does.
*/
- p_conn->close += ap_proxy_liststr(apr_table_get(r->headers_in,
- "Connection"), "close");
- /* sub-requests never use keepalives */
- if (r->main) {
+ if (ap_proxy_liststr(apr_table_get(r->headers_in,
+ "Connection"), "close")) {
p_conn->close++;
+ /* XXX: we are abusing r->headers_in rather than a copy,
+ * give the core output handler a clue the client would
+ * rather just close.
+ */
+ c->keepalive = AP_CONN_CLOSE;
}
-
ap_proxy_clear_connection(p, r->headers_in);
- if (p_conn->close) {
- apr_table_setn(r->headers_in, "Connection", "close");
- origin->keepalive = AP_CONN_CLOSE;
- }
- if ( apr_table_get(r->subprocess_env,"force-proxy-request-1.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);
force10 = 1;
+ p_conn->close++;
} else {
buf = apr_pstrcat(p, r->method, " ", url, " HTTP/1.1" CRLF, NULL);
force10 = 0;
}
- if ( apr_table_get(r->subprocess_env,"proxy-nokeepalive")) {
- apr_table_unset(r->headers_in, "Connection");
+ if (apr_table_get(r->subprocess_env, "proxy-nokeepalive")) {
origin->keepalive = AP_CONN_CLOSE;
+ p_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);
}
}
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) {
* determine, where the original request came from.
*/
apr_table_mergen(r->headers_in, "X-Forwarded-For",
- r->connection->remote_ip);
+ r->connection->remote_ip);
/* Add X-Forwarded-Host: so that upstream knows what the
* original request hostname was.
* XXX: This duplicates Via: - do we strictly need it?
*/
apr_table_mergen(r->headers_in, "X-Forwarded-Server",
- r->server->server_hostname);
+ r->server->server_hostname);
}
/* send request headers */
headers_in_array = apr_table_elts(r->headers_in);
headers_in = (const apr_table_entry_t *) headers_in_array->elts;
for (counter = 0; counter < headers_in_array->nelts; counter++) {
- if (headers_in[counter].key == NULL || headers_in[counter].val == NULL
+ if (headers_in[counter].key == NULL
+ || headers_in[counter].val == NULL
- /* Clear out hop-by-hop request headers not to send
- * RFC2616 13.5.1 says we should strip these headers
- */
- /* Already sent */
- || !apr_strnatcasecmp(headers_in[counter].key, "Host")
-
- || !apr_strnatcasecmp(headers_in[counter].key, "Keep-Alive")
- || !apr_strnatcasecmp(headers_in[counter].key, "TE")
- || !apr_strnatcasecmp(headers_in[counter].key, "Trailer")
- || !apr_strnatcasecmp(headers_in[counter].key, "Transfer-Encoding")
- || !apr_strnatcasecmp(headers_in[counter].key, "Upgrade")
+ /* Already sent */
+ || !strcasecmp(headers_in[counter].key, "Host")
- /* We'll add appropriate Content-Length later, if appropriate.
+ /* Clear out hop-by-hop request headers not to send
+ * RFC2616 13.5.1 says we should strip these headers
*/
- || !apr_strnatcasecmp(headers_in[counter].key, "Content-Length")
+ || !strcasecmp(headers_in[counter].key, "Keep-Alive")
+ || !strcasecmp(headers_in[counter].key, "TE")
+ || !strcasecmp(headers_in[counter].key, "Trailer")
+ || !strcasecmp(headers_in[counter].key, "Upgrade")
+
+ /* XXX: @@@ FIXME: "Proxy-Authorization" should *only* be
+ * suppressed if THIS server requested the authentication,
+ * not when a frontend proxy requested it!
+ *
+ * The solution to this problem is probably to strip out
+ * the Proxy-Authorisation header in the authorisation
+ * code itself, not here. This saves us having to signal
+ * somehow whether this request was authenticated or not.
+ */
+ || !strcasecmp(headers_in[counter].key,"Proxy-Authorization")
+ || !strcasecmp(headers_in[counter].key,"Proxy-Authenticate")) {
+ continue;
+ }
- /* XXX: @@@ FIXME: "Proxy-Authorization" should *only* be
- * suppressed if THIS server requested the authentication,
- * not when a frontend proxy requested it!
- *
- * The solution to this problem is probably to strip out
- * the Proxy-Authorisation header in the authorisation
- * code itself, not here. This saves us having to signal
- * somehow whether this request was authenticated or not.
+ /* Skip Transfer-Encoding and Content-Length for now.
*/
- || !apr_strnatcasecmp(headers_in[counter].key,"Proxy-Authorization")
- || !apr_strnatcasecmp(headers_in[counter].key,"Proxy-Authenticate")) {
+ if (!strcasecmp(headers_in[counter].key, "Transfer-Encoding")) {
+ old_te_val = headers_in[counter].val;
+ continue;
+ }
+ if (!strcasecmp(headers_in[counter].key, "Content-Length")) {
+ old_cl_val = headers_in[counter].val;
continue;
}
+
/* for sub-requests, ignore freshness/expiry headers */
if (r->main) {
- if (headers_in[counter].key == NULL || headers_in[counter].val == NULL
- || !apr_strnatcasecmp(headers_in[counter].key, "If-Match")
- || !apr_strnatcasecmp(headers_in[counter].key, "If-Modified-Since")
- || !apr_strnatcasecmp(headers_in[counter].key, "If-Range")
- || !apr_strnatcasecmp(headers_in[counter].key, "If-Unmodified-Since")
- || !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;
- }
+ if ( !strcasecmp(headers_in[counter].key, "If-Match")
+ || !strcasecmp(headers_in[counter].key, "If-Modified-Since")
+ || !strcasecmp(headers_in[counter].key, "If-Range")
+ || !strcasecmp(headers_in[counter].key, "If-Unmodified-Since")
+ || !strcasecmp(headers_in[counter].key, "If-None-Match")) {
+ continue;
+ }
}
-
buf = apr_pstrcat(p, headers_in[counter].key, ": ",
headers_in[counter].val, CRLF,
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);
+ }
+
+ /* We have headers, let's figure out our request body... */
+ input_brigade = apr_brigade_create(p, bucket_alloc);
+
+ /* sub-requests never use keepalives, and mustn't pass request bodies.
+ * Because the new logic looks at input_brigade, we will self-terminate
+ * input_brigade and jump past all of the request body logic...
+ * Reading anything with ap_get_brigade is likely to consume the
+ * main request's body or read beyond EOS - which would be unplesant.
+ */
+ if (r->main) {
+ p_conn->close++;
+ if (old_cl_val) {
+ old_cl_val = NULL;
+ apr_table_unset(r->headers_in, "Content-Length");
+ }
+ if (old_te_val) {
+ old_te_val = NULL;
+ apr_table_unset(r->headers_in, "Transfer-Encoding");
+ }
+ rb_method = RB_STREAM_CL;
+ e = apr_bucket_eos_create(input_brigade->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(input_brigade, e);
+ goto skip_body;
+ }
+
+ /* WE only understand chunked. Other modules might inject
+ * (and therefore, decode) other flavors but we don't know
+ * that the can and have done so unless they they remove
+ * their decoding from the headers_in T-E list.
+ * XXX: Make this extensible, but in doing so, presume the
+ * encoding has been done by the extensions' handler, and
+ * do not modify add_te_chunked's logic
+ */
+ if (old_te_val && strcmp(old_te_val, "chunked") != 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+ "proxy: %s Transfer-Encoding is not supported",
+ old_te_val);
+ return APR_EINVAL;
+ }
+
+ if (old_cl_val && old_te_val) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_ENOTIMPL, r->server,
+ "proxy: client %s (%s) requested Transfer-Encoding body"
+ " with Content-Length (C-L ignored)",
+ c->remote_ip, c->remote_host ? c->remote_host: "");
+ apr_table_unset(r->headers_in, "Content-Length");
+ old_cl_val = NULL;
+ origin->keepalive = AP_CONN_CLOSE;
+ p_conn->close++;
+ }
+
+ /* Prefetch MAX_MEM_SPOOL bytes
+ *
+ * This helps us avoid any election of C-L v.s. T-E
+ * request bodies, since we are willing to keep in
+ * memory this much data, in any case. This gives
+ * us an instant C-L election if the body is of some
+ * reasonable size.
+ */
+ temp_brigade = apr_brigade_create(p, bucket_alloc);
+ do {
+ status = ap_get_brigade(r->input_filters, temp_brigade,
+ AP_MODE_READBYTES, APR_BLOCK_READ,
+ MAX_MEM_SPOOL - bytes_read);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "proxy: prefetch request body failed to %s"
+ " from %s (%s)",
+ p_conn->name ? p_conn->name: "",
+ c->remote_ip, c->remote_host ? c->remote_host: "");
+ return status;
+ }
+
+ apr_brigade_length(temp_brigade, 1, &bytes);
+ APR_BRIGADE_CONCAT(input_brigade, temp_brigade);
+ bytes_read += bytes;
+
+ /* Ensure we don't hit a wall where we have a buffer too small
+ * for ap_get_brigade's filters to fetch us another bucket,
+ * surrender once we hit 80 bytes less than MAX_MEM_SPOOL
+ * (an arbitrary value.)
+ */
+ } while ((bytes_read < MAX_MEM_SPOOL - 80)
+ && !APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade)));
+
+ /* Use chunked request body encoding or send a content-length body?
+ *
+ * Prefer C-L when:
+ *
+ * We have no request body (handled by RB_STREAM_CL)
+ *
+ * We have a request body length <= MAX_MEM_SPOOL
+ *
+ * The administrator has setenv force-proxy-request-1.0
+ *
+ * The client sent a C-L body, and the administrator has
+ * not setenv proxy-sendchunked or has set setenv proxy-sendcl
+ *
+ * The client sent a T-E body, and the administrator has
+ * setenv proxy-sendcl, and not setenv proxy-sendchunked
+ *
+ * If both proxy-sendcl and proxy-sendchunked are set, the
+ * behavior is the same as if neither were set, large bodies
+ * that can't be read will be forwarded in their original
+ * form of C-L, or T-E.
+ *
+ * To ensure maximum compatibility, setenv proxy-sendcl
+ * To reduce server resource use, setenv proxy-sendchunked
+ *
+ * Then address specific servers with conditional setenv
+ * options to restore the default behavior where desireable.
+ *
+ * We have to compute content length by reading the entire request
+ * body; if request body is not small, we'll spool the remaining
+ * input to a temporary file. Chunked is always preferable.
+ *
+ * We can only trust the client-provided C-L if the T-E header
+ * is absent, and the filters are unchanged (the body won't
+ * be resized by another content filter).
+ */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) {
+ /* The whole thing fit, so our decision is trivial, use
+ * the filtered bytes read from the client for the request
+ * body Content-Length.
+ *
+ * If we expected no body, and read no body, do not set
+ * the Content-Length.
+ */
+ if (old_cl_val || old_te_val || bytes_read) {
+ old_cl_val = apr_off_t_toa(r->pool, bytes_read);
+ }
+ rb_method = RB_STREAM_CL;
+ }
+ else if (old_te_val) {
+ if (force10
+ || (apr_table_get(r->subprocess_env, "proxy-sendcl")
+ && !apr_table_get(r->subprocess_env, "proxy-sendchunks"))) {
+ rb_method = RB_SPOOL_CL;
+ }
+ else {
+ rb_method = RB_STREAM_CHUNKED;
+ }
+ }
+ else if (old_cl_val) {
+ if (r->input_filters == r->proto_input_filters) {
+ rb_method = RB_STREAM_CL;
+ }
+ else if (!force10
+ && apr_table_get(r->subprocess_env, "proxy-sendchunks")
+ && !apr_table_get(r->subprocess_env, "proxy-sendcl")) {
+ rb_method = RB_STREAM_CHUNKED;
+ }
+ else {
+ rb_method = RB_SPOOL_CL;
+ }
+ }
+ else {
+ /* This is an appropriate default; very efficient for no-body
+ * requests, and has the behavior that it will not add any C-L
+ * when the old_cl_val is NULL.
+ */
+ rb_method = RB_SPOOL_CL;
+ }
+
+/* Yes I hate gotos. This is the subrequest shortcut */
+skip_body:
+ /* Handle Connection: header */
+ if (!force10 && p_conn->close) {
+ buf = apr_pstrdup(p, "Connection: close" CRLF);
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(header_brigade, e);
+ }
+
+ /* send the request body, if any. */
+ switch(rb_method) {
+ case RB_STREAM_CHUNKED:
+ status = stream_reqbody_chunked(p, r, p_conn, origin, header_brigade,
+ input_brigade);
+ break;
+ case RB_STREAM_CL:
+ status = stream_reqbody_cl(p, r, p_conn, origin, header_brigade,
+ input_brigade, old_cl_val);
+ break;
+ case RB_SPOOL_CL:
+ status = spool_reqbody_cl(p, r, p_conn, origin, header_brigade,
+ input_brigade, (old_cl_val != NULL)
+ || (old_te_val != NULL)
+ || (bytes_read > 0));
+ break;
+ default:
+ ap_assert(1 != 1);
+ break;
}
- /* send the request data, if any. */
- status = send_request_body(p, r, p_conn, origin, bb, force10);
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);
+ "proxy: pass request body failed to %pI (%s)"
+ " from %s (%s)",
+ p_conn->addr, p_conn->name ? p_conn->name: "",
+ c->remote_ip, c->remote_host ? c->remote_host: "");
return status;
}
char *server_portstr) {
conn_rec *c = r->connection;
char buffer[HUGE_STRING_LEN];
+ const char *buf;
char keepchar;
request_rec *rp;
apr_bucket *e;
"Error reading from remote server");
}
- /* Is it an HTTP/1 response?
- * This is buggy if we ever see an HTTP/1.10
- */
+ /* Is it an HTTP/1 response?
+ * This is buggy if we ever see an HTTP/1.10
+ */
if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
int major, minor;
}
r->status_line = apr_pstrdup(p, &buffer[9]);
-
/* read the headers. */
/* N.B. for HTTP/1.0 clients, we have to fold line-wrapped headers*/
/* Also, take care with headers with multiple occurences. */
r->status = HTTP_BAD_GATEWAY;
r->status_line = "bad gateway";
return r->status;
+ }
- } else {
- const char *buf;
-
- /* can't have both Content-Length and Transfer-Encoding */
- if (apr_table_get(r->headers_out, "Transfer-Encoding")
- && apr_table_get(r->headers_out, "Content-Length")) {
- /* 2616 section 4.4, point 3: "if both Transfer-Encoding
- * and Content-Length are received, the latter MUST be
- * ignored"; so unset it here to prevent any confusion
- * later. */
- apr_table_unset(r->headers_out, "Content-Length");
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
- r->server,
- "proxy: server %s returned Transfer-Encoding and Content-Length",
- p_conn->name);
- p_conn->close += 1;
- }
-
- /* strip connection listed hop-by-hop headers from response */
- p_conn->close += ap_proxy_liststr(apr_table_get(r->headers_out,
- "Connection"),
- "close");
- ap_proxy_clear_connection(p, r->headers_out);
- if ((buf = apr_table_get(r->headers_out, "Content-Type"))) {
- ap_set_content_type(r, apr_pstrdup(p, buf));
- }
- ap_proxy_pre_http_request(origin,rp);
+ /* can't have both Content-Length and Transfer-Encoding */
+ if (apr_table_get(r->headers_out, "Transfer-Encoding")
+ && apr_table_get(r->headers_out, "Content-Length")) {
+ /* 2616 section 4.4, point 3: "if both Transfer-Encoding
+ * and Content-Length are received, the latter MUST be
+ * ignored"; so unset it here to prevent any confusion
+ * later. */
+ apr_table_unset(r->headers_out, "Content-Length");
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ r->server,
+ "proxy: server %s returned Transfer-Encoding and Content-Length",
+ p_conn->name);
+ p_conn->close += 1;
}
+
+ /* strip connection listed hop-by-hop headers from response */
+ p_conn->close += ap_proxy_liststr(apr_table_get(r->headers_out,
+ "Connection"),
+ "close");
+ ap_proxy_clear_connection(p, r->headers_out);
+ if ((buf = apr_table_get(r->headers_out, "Content-Type"))) {
+ ap_set_content_type(r, apr_pstrdup(p, buf));
+ }
+ ap_proxy_pre_http_request(origin,rp);
/* handle Via header in response */
if (conf->viaopt != via_off && conf->viaopt != via_block) {
}
/* we must accept 3 kinds of date, but generate only 1 kind of date */
- {
- const char *buf;
- if ((buf = apr_table_get(r->headers_out, "Date")) != NULL) {
- apr_table_set(r->headers_out, "Date",
- ap_proxy_date_canon(p, buf));
- }
- if ((buf = apr_table_get(r->headers_out, "Expires")) != NULL) {
- apr_table_set(r->headers_out, "Expires",
- ap_proxy_date_canon(p, buf));
- }
- if ((buf = apr_table_get(r->headers_out, "Last-Modified")) != NULL) {
- apr_table_set(r->headers_out, "Last-Modified",
- ap_proxy_date_canon(p, buf));
- }
+ if ((buf = apr_table_get(r->headers_out, "Date")) != NULL) {
+ apr_table_set(r->headers_out, "Date",
+ ap_proxy_date_canon(p, buf));
+ }
+ if ((buf = apr_table_get(r->headers_out, "Expires")) != NULL) {
+ apr_table_set(r->headers_out, "Expires",
+ ap_proxy_date_canon(p, buf));
+ }
+ if ((buf = apr_table_get(r->headers_out, "Last-Modified")) != NULL) {
+ apr_table_set(r->headers_out, "Last-Modified",
+ ap_proxy_date_canon(p, buf));
}
/* munge the Location and URI response headers according to
* ProxyPassReverse
*/
- {
- const char *buf;
- if ((buf = apr_table_get(r->headers_out, "Location")) != NULL) {
- apr_table_set(r->headers_out, "Location",
- ap_proxy_location_reverse_map(r, conf, buf));
- }
- if ((buf = apr_table_get(r->headers_out, "Content-Location")) != NULL) {
- apr_table_set(r->headers_out, "Content-Location",
- ap_proxy_location_reverse_map(r, conf, buf));
- }
- if ((buf = apr_table_get(r->headers_out, "URI")) != NULL) {
- apr_table_set(r->headers_out, "URI",
- ap_proxy_location_reverse_map(r, conf, buf));
- }
+ if ((buf = apr_table_get(r->headers_out, "Location")) != NULL) {
+ apr_table_set(r->headers_out, "Location",
+ ap_proxy_location_reverse_map(r, conf, buf));
+ }
+ if ((buf = apr_table_get(r->headers_out, "Content-Location")) != NULL) {
+ apr_table_set(r->headers_out, "Content-Location",
+ ap_proxy_location_reverse_map(r, conf, buf));
+ }
+ if ((buf = apr_table_get(r->headers_out, "URI")) != NULL) {
+ apr_table_set(r->headers_out, "URI",
+ ap_proxy_location_reverse_map(r, conf, buf));
}
if ((r->status == 401) && (conf->error_override != 0)) {
- const char *buf;
const char *wa = "WWW-Authenticate";
if ((buf = apr_table_get(r->headers_out, wa))) {
apr_table_set(r->err_headers_out, wa, buf);