proxy_http.c revision 87396d56ac610785156706de7f0d3009317d906c
/* 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;
}
static const char *ap_proxy_location_reverse_map(request_rec *r, proxy_server_conf *conf, const char *url)
{
struct proxy_alias *ent;
char *u;
/* XXX FIXME: Make sure this handled the ambiguous case of the :80
* after the hostname */
return ap_construct_url(r->pool, u, r);
}
}
return url;
}
/* cookies are a bit trickier to match: we've got two substrings to worry
* about, and we can't just find them with strstr 'cos of case. Regexp
* matching would be an easy fix, but for better consistency with all the
* other matches we'll refrain and use apr_strmatch to find path=/domain=
* and stick to plain strings for the config values.
*/
static const char *proxy_cookie_reverse_map(request_rec *r,
{
struct proxy_alias *ent;
const char* pathp ;
const char* domainp ;
int i;
int ddiff = 0 ;
int pdiff = 0 ;
char* ret ;
/* find the match and replacement, but save replacing until we've done
both path and domain so we know the new strlen
*/
pathp += 5 ;
break ;
}
}
}
domainp += 7 ;
break ;
}
}
}
if ( newpath ) {
if ( newdomain ) {
} else {
}
} else {
}
} else {
if ( newdomain ) {
} else {
}
}
return ret ;
}
/* Clear all connection-based headers from the incoming headers table */
{
const char *name;
if (!next)
return;
while (*next) {
++next;
}
*next = '\0';
++next;
}
}
}
static
char *url, char *server_portstr)
{
conn_rec *c = r->connection;
char *buf;
const apr_array_header_t *headers_in_array;
const apr_table_entry_t *headers_in;
/*
* 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.
*/
send_chunks = 0;
} else {
send_chunks = 1;
}
}
}
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 have no way of knowing whether this Content-Length will
* be accurate, so we must not include it.
*/
/* 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);
}
/* If we can send chunks, do so! */
if (send_chunks) {
}
else {
}
/* add empty line at the end of the headers */
#else
#endif
e = apr_bucket_flush_create(c->bucket_alloc);
if (send_chunks) {
if (status != APR_SUCCESS) {
"proxy: request failed to %pI (%s)",
return status;
}
}
/* send the request data, if any. */
seen_eos = 0;
do {
if (status != APR_SUCCESS) {
return status;
}
/* If this brigade contain 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 (send_chunks) {
#define ASCII_CRLF "\015\012"
#define ASCII_ZERO "\060"
/*
* Append the end-of-chunk CRLF
*/
}
else {
/* The send_chunks case does not need to be setaside, but this
* case does because we may have transient buckets that may get
* overwritten in the next iteration of the loop.
*/
while (e != APR_BRIGADE_SENTINEL(input_brigade)) {
apr_bucket_setaside(e, p);
e = APR_BUCKET_NEXT(e);
}
}
e = apr_bucket_flush_create(c->bucket_alloc);
if (send_chunks) {
if (status != APR_SUCCESS) {
"proxy: pass request data failed to %pI (%s)",
return status;
}
}
} while (!seen_eos);
if (send_chunks) {
/* <trailers> */
}
if (!send_chunks) {
if (bytes) {
}
else {
/* A client might really have sent a C-L of 0. Pass it on. */
c->bucket_alloc);
}
}
if (status != APR_SUCCESS) {
"proxy: pass request data failed to %pI (%s)",
return status;
}
}
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", 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");
}
/* 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 */
bb,
#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 */
/* Ack! Phbtt! Die! User aborted! */
}
/* make sure we always clean up after ourselves */
/* if we are done, leave */
break;
}
}
}
"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 */
};