mod_proxy_http.c revision bf1e7c075ccc3e6597d17de7641332ff6ff92e8a
/* Copyright 1999-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* HTTP routines for Apache proxy */
#include "mod_proxy.h"
request_rec *r,
/*
* Canonicalise http-like URLs.
* scheme is the scheme for the URL
* url is the URL starting with the first '/'
* def_port is the default port for this scheme.
*/
{
const char *err;
const char *scheme;
/* ap_port_of_scheme() */
url += 5;
scheme = "http";
}
url += 6;
scheme = "https";
}
else {
return DECLINED;
}
"proxy: HTTP: canonicalising URL %s", url);
/* do syntatic check.
* We break the URL into host, port, path, search
*/
if (err) {
"error parsing URL %s: %s",
return HTTP_BAD_REQUEST;
}
/* N.B. if this isn't a true proxy request, then the URL _path_
* has already been decoded. True proxy requests have r->uri
* == r->unparsed_uri, and no others have that property.
*/
if (r->uri == r->unparsed_uri) {
*(search++) = '\0';
}
else
/* process path */
return HTTP_BAD_REQUEST;
else
sport[0] = '\0';
}
return OK;
}
/* Clear all connection-based headers from the incoming headers table */
{
const char *name;
if (!next)
return;
while (*next) {
++next;
}
*next = '\0';
++next;
}
}
}
static void add_te_chunked(apr_pool_t *p,
{
apr_bucket *e;
char *buf;
}
static void add_cl(apr_pool_t *p,
const char *cl_val)
{
apr_bucket *e;
char *buf;
CRLF,
NULL);
}
#define ASCII_CRLF "\015\012"
#define ASCII_ZERO "\060"
{
apr_bucket *e;
/* add empty line at the end of the headers */
}
int flush)
{
if (flush) {
APR_BRIGADE_INSERT_TAIL(b, e);
}
apr_brigade_length(b, 0, &transferred);
if (transferred != -1)
if (status != APR_SUCCESS) {
"proxy: pass request data failed to %pI (%s)",
return status;
}
return APR_SUCCESS;
}
request_rec *r,
{
int seen_eos = 0;
apr_bucket *e;
do {
if (status != APR_SUCCESS) {
return status;
}
/* If this brigade contains EOS, either stop or remove it. */
seen_eos = 1;
/* As a shortcut, if this brigade is simply an EOS bucket,
* don't send anything down the filter chain.
*/
break;
}
/* We can't pass this EOS to the output_filters. */
}
/*
* Append the end-of-chunk CRLF
*/
if (header_brigade) {
/* we never sent the header brigade, so go ahead and
* take care of that now
*/
b = header_brigade;
}
else {
b = input_brigade;
}
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
*/
b = header_brigade;
}
else {
if (!APR_BRIGADE_EMPTY(input_brigade)) {
/* input brigade still has an EOS which we can't pass to the output_filters. */
}
/* <trailers> */
5, bucket_alloc);
b = input_brigade;
}
return status;
}
request_rec *r,
const char *old_cl_val)
{
int seen_eos = 0;
apr_bucket *e;
do {
if (status != APR_SUCCESS) {
return status;
}
/* If this brigade contains EOS, either stop or remove it. */
seen_eos = 1;
/* As a shortcut, if this brigade is simply an EOS bucket,
* don't send anything down the filter chain.
*/
break;
}
/* We can't pass this EOS to the output_filters. */
}
if (header_brigade) {
/* we never sent the header brigade, so go ahead and
* take care of that now
*/
b = header_brigade;
}
else {
b = input_brigade;
}
if (status != APR_SUCCESS) {
return status;
}
} while (!seen_eos);
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
*/
}
b = header_brigade;
}
else {
/* need to flush any pending data */
b = input_brigade; /* empty now; pass_brigade() will add flush */
}
return status;
}
#define MAX_MEM_SPOOL 16384
request_rec *r,
{
int seen_eos = 0;
apr_bucket *e;
do {
if (status != APR_SUCCESS) {
return status;
}
/* If this brigade contains EOS, either stop or remove it. */
seen_eos = 1;
/* As a shortcut, if this brigade is simply an EOS bucket,
* don't send anything down the filter chain.
*/
break;
}
/* We can't pass this EOS to the output_filters. */
}
/* can't spool any more in memory; write latest brigade to disk;
* what we read into memory before reaching our threshold will
* remain there; we just write this and any subsequent data to disk
*/
const char *temp_dir;
char *template;
if (status != APR_SUCCESS) {
"proxy: search for temporary directory failed");
return status;
}
"modproxy.tmp.XXXXXX",
APR_FILEPATH_NATIVE, p);
if (status != APR_SUCCESS) {
"proxy: creation of temporary file in directory %s failed",
temp_dir);
return status;
}
}
for (e = APR_BRIGADE_FIRST(input_brigade);
e = APR_BUCKET_NEXT(e)) {
const char *data;
if (status != APR_SUCCESS) {
const char *tmpfile_name;
tmpfile_name = "(unknown)";
}
"proxy: write to temporary file %s failed",
return status;
}
fsize += bytes_written;
}
}
else {
}
bytes_spooled += bytes;
} while (!seen_eos);
if (bytes_spooled) {
}
if (tmpfile) {
/* For platforms where the size of the file may be larger than
* that which can be stored in a single bucket (where the
* length field is an apr_size_t), split it into several
* buckets: */
if (sizeof(apr_off_t) > sizeof(apr_size_t)
&& fsize > AP_MAX_SENDFILE) {
while (fsize > AP_MAX_SENDFILE) {
apr_bucket *ce;
apr_bucket_copy(e, &ce);
e->start += AP_MAX_SENDFILE;
fsize -= AP_MAX_SENDFILE;
}
}
else {
}
}
return status;
}
request_rec *r,
int force10)
{
const char *old_cl_val, *te_val;
int cl_zero; /* client sent "Content-Length: 0", which we forward on to server */
/* 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 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
*/
if (!force10
&& !cl_zero
}
else if (!cl_zero
}
else {
if (old_cl_val &&
(r->input_filters == r->proto_input_filters
|| cl_zero
}
else if (force10) {
}
}
else {
}
}
switch(rb_method) {
case RB_STREAM_CHUNKED:
break;
case RB_STREAM_CL:
break;
case RB_SPOOL_CL:
break;
default:
}
return status;
}
static
char *url, char *server_portstr)
{
conn_rec *c = r->connection;
char *buf;
apr_bucket *e;
const apr_array_header_t *headers_in_array;
const apr_table_entry_t *headers_in;
int counter;
int force10;
/*
* Send the HTTP/1.1 request to the remote server
*/
/* 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
* we cancel server keepalive if the client does.
*/
"Connection"), "close");
/* sub-requests never use keepalives */
if (r->main) {
}
ap_proxy_clear_connection(p, r->headers_in);
}
/* 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. A special case is when
* the client specifies Content-Length and there are no filters
* which muck with the request body. In that situation, we don't
* have to buffer the entire request and can still send the
* Content-Length. Another special case is when the client
* specifies a C-L of 0. Pass that through as well.
*/
force10 = 1;
} else {
force10 = 0;
}
}
if (conf->preserve_host == 0) {
} else {
}
}
else {
/* don't want to use r->hostname, as the incoming header might have a
* port attached
*/
if (!hostname) {
"proxy: no HTTP 0.9 request (with no host line) "
"on incoming request and preserve host set "
"forcing hostname to be %s for uri %s",
r->uri );
}
}
/* handle Via */
/* Block all outgoing Via: headers */
const char *server_name = ap_get_server_name(r);
/* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host,
* then the server name returned by ap_get_server_name() is the
* origin server name (which does make too much sense with Via: headers)
* so we use the proxy vhost's name instead.
*/
if (server_name == r->hostname)
/* Create a "Via:" request header entry and merge it */
? apr_psprintf(p, "%d.%d %s%s (%s)",
: apr_psprintf(p, "%d.%d %s%s",
);
}
/* X-Forwarded-*: handling
*
* XXX Privacy Note:
* -----------------
*
* These request headers are only really useful when the mod_proxy
* is used in a reverse proxy configuration, so that useful info
* about the client can be passed through the reverse proxy and on
* to the backend server, which may require the information to
* function properly.
*
* In a forward proxy situation, these options are a potential
* privacy violation, as information about clients behind the proxy
* are revealed to arbitrary servers out there on the internet.
*
* The HTTP/1.1 Via: header is designed for passing client
* information through proxies to a server, and should be used in
* a forward proxy configuation instead of X-Forwarded-*. See the
* ProxyVia option for details.
*/
if (PROXYREQ_REVERSE == r->proxyreq) {
const char *buf;
/* Add X-Forwarded-For: so that the upstream has a chance to
* determine, where the original request came from.
*/
r->connection->remote_ip);
/* Add X-Forwarded-Host: so that upstream knows what the
* original request hostname was.
*/
}
/* Add X-Forwarded-Server: so that upstream knows what the
* name of this proxy server is (if there are more than one)
* XXX: This duplicates Via: - do we strictly need it?
*/
r->server->server_hostname);
}
/* send request headers */
proxy_run_fixups(r);
/* Clear out hop-by-hop request headers not to send
* RFC2616 13.5.1 says we should strip these headers
*/
/* Already sent */
/* We'll add appropriate Content-Length later, if appropriate.
*/
/* 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.
*/
continue;
}
if (r->main) {
continue;
}
}
NULL);
}
/* send the request data, if any. */
if (status != APR_SUCCESS) {
"proxy: pass request data failed to %pI (%s)",
return status;
}
return APR_SUCCESS;
}
{
static const char* date_hdrs[]
static const struct {
const char* name;
} transform_hdrs[] = {
{ "Location", ap_proxy_location_reverse_map } ,
{ "Content-Location", ap_proxy_location_reverse_map } ,
{ "URI", ap_proxy_location_reverse_map } ,
{ "Set-Cookie", ap_proxy_cookie_reverse_map } ,
} ;
int i ;
for ( i = 0 ; date_hdrs[i] ; ++i ) {
return ;
}
}
for ( i = 0 ; transform_hdrs[i].name ; ++i ) {
return ;
}
}
return ;
}
static void ap_proxy_read_headers(request_rec *r, request_rec *rr, char *buffer, int size, conn_rec *c)
{
int len;
char field[MAX_STRING_LEN];
int saw_headers = 0;
/*
* Read header lines until we get the empty separator line, a read error,
* the connection closes (EOF), or we timeout.
*/
/* We may encounter invalid headers, usually from buggy
* MS IIS servers, so we need to determine just how to handle
* them. We can either ignore them, assume that they mark the
* start-of-body (eg: a missing CRLF) or (the default) mark
* the headers as totally bogus and return a 500. The sole
* exception is an extra "HTTP/1.0 200, OK" line sprinkled
* in between the usual MIME headers, which is a favorite
* IIS bug.
*/
/* XXX: The mask check is buggy if we ever see an HTTP/1.10 */
/* Nope, it wasn't even an extra HTTP header. Give up. */
return ;
}
/* if we've already started loading headers_out, then
* return what we've accumulated so far, in the hopes
* that they are useful. Otherwise, we completely bail.
*/
/* FIXME: We've already scarfed the supposed 1st line of
* the body, so the actual content may end up being bogus
* as well. If the content is HTML, we may be lucky.
*/
if (saw_headers) {
"proxy: Starting body due to bogus non-header in headers "
return ;
} else {
"proxy: No HTTP headers "
return ;
}
}
}
/* this is the psc->badopt == bad_ignore case */
"proxy: Ignoring bogus HTTP header "
continue;
}
*value = '\0';
++value;
/* XXX: RFC2068 defines only SP and HT as whitespace, this test is
* wrong... and so are many others probably.
*/
while (apr_isspace(*value))
++value; /* Skip to start of value */
/* should strip trailing whitespace as well */
end)
*end = '\0';
/* make sure we add so as not to destroy duplicated headers
* by ProxyPassReverse and family with process_proxy_header
*/
saw_headers = 1;
/* the header was too long; at the least we should skip extra data */
>= MAX_STRING_LEN - 1) {
/* soak up the extra data */
}
if (len == 0) /* time to exit the larger loop as well */
break;
}
}
}
{
return 1;
}
static
char *server_portstr) {
conn_rec *c = r->connection;
char buffer[HUGE_STRING_LEN];
char keepchar;
apr_bucket *e;
int len, backasswards;
int interim_response; /* non-zero whilst interim 1xx responses
* are being read. */
/* Get response from the remote server, and pass it up the
* filter chain
*/
/* In case anyone needs to know, this is a fake request that is really a
* response.
*/
do {
if (len == 0) {
/* handle one potential stray CRLF */
}
if (len <= 0) {
"proxy: error reading status line from remote "
return ap_proxyerror(r, HTTP_BAD_GATEWAY,
"Error reading from remote server");
}
/* XXX: Is this a real headers length send from remote? */
/* Is it an HTTP/1 response?
* This is buggy if we ever see an HTTP/1.10
*/
major = 1;
minor = 1;
}
/* If not an HTTP/1 message or
* if the status line was > 8192 bytes
*/
return ap_proxyerror(r, HTTP_BAD_GATEWAY,
apr_pstrcat(p, "Corrupt status line returned by remote "
}
backasswards = 0;
if (keepchar != '\0') {
} else {
/* 2616 requires the space in Status-Line; the origin
* server may have sent one but ap_rgetline_core will
* have stripped it. */
}
/* 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. */
/* First, tuck away all already existing cookies */
"Set-Cookie", NULL);
/* shove the headers direct into r->headers_out */
if (r->headers_out == NULL) {
r->server, "proxy: bad HTTP/%d.%d header "
r->method);
/*
* ap_send_error relies on a headers_out to be present. we
* are in a bad position here.. so force everything we send out
* to have nothing to do with the incoming packet
*/
r->status = HTTP_BAD_GATEWAY;
r->status_line = "bad gateway";
return r->status;
} else {
const char *buf;
/* Now, add in the just read cookies */
"Set-Cookie", NULL);
/* and now load 'em all in */
if (!apr_is_empty_table(save_table)) {
r->headers_out,
}
/* strip connection listed hop-by-hop headers from response */
"Connection"),
"close");
ap_proxy_clear_connection(p, r->headers_out);
}
}
/* handle Via header in response */
const char *server_name = ap_get_server_name(r);
/* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host,
* then the server name returned by ap_get_server_name() is the
* origin server name (which does make too much sense with Via: headers)
* so we use the proxy vhost's name instead.
*/
if (server_name == r->hostname)
/* create a "Via:" response header entry and merge it */
? apr_psprintf(p, "%d.%d %s%s (%s)",
: apr_psprintf(p, "%d.%d %s%s",
);
}
/* cancel keepalive if HTTP/1.0 or less */
}
} else {
/* an http/0.9 response */
backasswards = 1;
r->status = 200;
r->status_line = "200 OK";
}
if (interim_response) {
"proxy: HTTP: received interim %d response",
r->status);
}
/* Moved the fixups of Date headers and those affected by
* ProxyPassReverse/etc from here to ap_proxy_read_headers
*/
const char *buf;
const char *wa = "WWW-Authenticate";
} else {
"proxy: origin server sent 401 without WWW-Authenticate header");
}
}
r->sent_bodyct = 1;
/* Is it an HTTP/0.9 response? If so, send the extra data */
if (backasswards) {
/*@@@FIXME:
* At this point in response processing of a 0.9 response,
* we don't know yet whether data is binary or not.
* mod_charset_lite will get control later on, so it cannot
* decide on the conversion of this buffer full of data.
* However, chances are that we are not really talking to an
* HTTP/0.9 server, but to some different protocol, therefore
* the best guess IMHO is to always treat the buffer as "text/x":
*/
}
/* send body - but only if a body is expected */
if ((!r->header_only) && /* not HEAD request */
!interim_response && /* not any 1xx response */
/* We need to copy the output headers and treat them as input
* headers as well. BUT, we need to do this before we remove
* TE, so that they are preserved accordingly for
* ap_http_filter to know where to end.
*/
"proxy: start body send");
/*
* if we are overriding the errors, we can't put the content
* of the page into the brigade
*/
/* read the body, pass it to the output filters */
do {
/* ap_get_brigade will return success with an empty brigade
* for a non-blocking read which would block: */
if (APR_STATUS_IS_EAGAIN(rv)
/* flush to the client and switch to blocking mode */
e = apr_bucket_flush_create(c->bucket_alloc);
|| c->aborted) {
break;
}
continue;
}
else if (rv != APR_SUCCESS) {
"proxy: error reading response");
break;
}
/* next time try a non-blocking read */
#if DEBUGGING
{
r->server, "proxy (PID %d): readbytes: %#x",
}
#endif
/* sanity check */
if (APR_BRIGADE_EMPTY(bb)) {
break;
}
/* found the last brigade? */
/* if this is the last brigade, cleanup the
* backend connection first to prevent the
* backend server from hanging around waiting
* for a slow client to eat these bytes
*/
/* signal that we must leave */
}
/* try send what we read */
|| c->aborted) {
/* Ack! Phbtt! Die! User aborted! */
}
/* make sure we always clean up after ourselves */
} while (!finish);
}
"proxy: end body send");
} else {
"proxy: header only");
}
} while (interim_response);
if (conf->error_override) {
/* the code above this checks for 'OK' which is what the hook expects */
if (ap_is_HTTP_SUCCESS(r->status))
return OK;
else {
/* clear r->status for override error, otherwise ErrorDocument
* thinks that this is a recursive error, and doesn't find the
* custom error page
*/
/* Discard body, if one is expected */
}
return status;
}
} else
return OK;
}
static
{
/* If there are no KeepAlives, or if the connection has been signalled
* to close, close the socket and clean up
*/
/* if the connection is < HTTP/1.1, or Connection: close,
* we close the socket, otherwise we leave it open for KeepAlive support
*/
}
return OK;
}
/*
* This handles http:// URLs, and other URLs using a remote proxy over http
* If proxyhost is NULL, then contact the server directly, otherwise
* go via the proxy.
* Note that if a proxy is used, then URLs other than http: can be accessed,
* also, if we have trouble which is clearly specific to the proxy, then
* we return DECLINED so that we can try another proxy. (Or the direct
* route.)
*/
{
int status;
char server_portstr[32];
char *scheme;
const char *proxy_function;
const char *u;
int is_ssl = 0;
/* Note: Memory pool allocation.
* A downstream keepalive connection is always connected to the existence
* (or not) of an upstream keepalive connection. If this is not done then
* load balancing against multiple backend servers breaks (one backend
* server ends up taking 100% of the load), and the risk is run of
* downstream keepalive connections being kept open unnecessarily. This
* keeps webservers busy and ties up resources.
*
* As a result, we allocate all sockets out of the upstream connection
* pool, and when we want to reuse a socket, we check first whether the
* connection ID of the current upstream connection is the same as that
* of the connection when the socket was opened.
*/
conn_rec *c = r->connection;
/* find the scheme */
return DECLINED;
if ((u - url) > 14)
return HTTP_BAD_REQUEST;
/* scheme is lowercase */
/* is it for us? */
if (!ap_proxy_ssl_enable(NULL)) {
"proxy: HTTPS: declining URL %s"
" (mod_ssl not configured?)", url);
return DECLINED;
}
is_ssl = 1;
proxy_function = "HTTPS";
}
"proxy: HTTP: declining URL %s", url);
return DECLINED; /* only interested in HTTP, or FTP via proxy */
}
else {
if (*scheme == 'h')
proxy_function = "HTTP";
else
proxy_function = "FTP";
}
"proxy: HTTP: serving URL %s", url);
/* only use stored info for top-level pages. Sub requests don't share
* in keepalives
*/
if (!r->main) {
}
/* create space for state information */
if (!backend) {
goto cleanup;
if (!r->main) {
}
}
/* Step One: Determine Who To Connect To */
sizeof(server_portstr))) != OK)
goto cleanup;
/* Step Two: Make the Connection */
if (r->proxyreq == PROXYREQ_PROXY)
else
goto cleanup;
}
/* Step Three: Create conn_rec */
if (!backend->connection) {
goto cleanup;
}
/* Step Four: Send the Request */
goto cleanup;
/* Step Five: Receive the Response */
goto cleanup;
/* Step Six: Clean Up */
if (backend) {
}
}
return status;
}
static void ap_proxy_http_register_hook(apr_pool_t *p)
{
}
NULL, /* create per-directory config structure */
NULL, /* merge per-directory config structures */
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
NULL, /* command apr_table_t */
ap_proxy_http_register_hook/* register hooks */
};