diff options
Diffstat (limited to 'modules/proxy/mod_proxy_fcgi.c')
-rw-r--r-- | modules/proxy/mod_proxy_fcgi.c | 196 |
1 files changed, 175 insertions, 21 deletions
diff --git a/modules/proxy/mod_proxy_fcgi.c b/modules/proxy/mod_proxy_fcgi.c index 2e97408..d420df6 100644 --- a/modules/proxy/mod_proxy_fcgi.c +++ b/modules/proxy/mod_proxy_fcgi.c @@ -92,15 +92,30 @@ static int proxy_fcgi_canon(request_rec *r, char *url) host = apr_pstrcat(r->pool, "[", host, "]", NULL); } - if (apr_table_get(r->notes, "proxy-nocanon")) { - path = url; /* this is the raw path */ + if (apr_table_get(r->notes, "proxy-nocanon") + || apr_table_get(r->notes, "proxy-noencode")) { + path = url; /* this is the raw/encoded path */ } else { - path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, - r->proxyreq); + core_dir_config *d = ap_get_core_module_config(r->per_dir_config); + int flags = d->allow_encoded_slashes && !d->decode_encoded_slashes ? PROXY_CANONENC_NOENCODEDSLASHENCODING : 0; + + path = ap_proxy_canonenc_ex(r->pool, url, strlen(url), enc_path, flags, + r->proxyreq); + if (!path) { + return HTTP_BAD_REQUEST; + } + } + /* + * If we have a raw control character or a ' ' in nocanon path, + * correct encoding was missed. + */ + if (path == url && *ap_scan_vchar_obstext(path)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10414) + "To be forwarded path contains control " + "characters or spaces"); + return HTTP_FORBIDDEN; } - if (path == NULL) - return HTTP_BAD_REQUEST; r->filename = apr_pstrcat(r->pool, "proxy:fcgi://", host, sport, "/", path, NULL); @@ -164,7 +179,7 @@ static int proxy_fcgi_canon(request_rec *r, char *url) ProxyFCGISetEnvIf "reqenv('PATH_INFO') =~ m#/foo(\d+)\.php$#" PATH_INFO "/foo.php" ProxyFCGISetEnvIf "reqenv('PATH_TRANSLATED') =~ m#(/.*foo)(\d+)(.*)#" PATH_TRANSLATED "$1$3" */ -static void fix_cgivars(request_rec *r, fcgi_dirconf_t *dconf) +static apr_status_t fix_cgivars(request_rec *r, fcgi_dirconf_t *dconf) { sei_entry *entries; const char *err, *src; @@ -175,10 +190,21 @@ static void fix_cgivars(request_rec *r, fcgi_dirconf_t *dconf) for (i = 0; i < dconf->env_fixups->nelts; i++) { sei_entry *entry = &entries[i]; + rc = ap_expr_exec_re(r, entry->cond, AP_MAX_REG_MATCH, regm, &src, &err); + if (rc < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10241) + "fix_cgivars: Condition eval returned %d: %s", + rc, err); + return APR_EGENERAL; + } + else if (rc == 0) { + continue; /* evaluated false */ + } + if (entry->envname[0] == '!') { apr_table_unset(r->subprocess_env, entry->envname+1); } - else if (0 < (rc = ap_expr_exec_re(r, entry->cond, AP_MAX_REG_MATCH, regm, &src, &err))) { + else { const char *val = ap_expr_str_exec_re(r, entry->subst, AP_MAX_REG_MATCH, regm, &src, &err); if (err) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03514) @@ -195,10 +221,8 @@ static void fix_cgivars(request_rec *r, fcgi_dirconf_t *dconf) } apr_table_setn(r->subprocess_env, entry->envname, val); } - else { - ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, "fix_cgivars: Condition returned %d", rc); - } } + return APR_SUCCESS; } /* Wrapper for apr_socket_sendv that handles updating the worker stats. */ @@ -367,7 +391,9 @@ static apr_status_t send_environment(proxy_conn_rec *conn, request_rec *r, /* XXX are there any FastCGI specific env vars we need to send? */ /* Give admins final option to fine-tune env vars */ - fix_cgivars(r, dconf); + if (APR_SUCCESS != (rv = fix_cgivars(r, dconf))) { + return rv; + } /* XXX mod_cgi/mod_cgid use ap_create_environment here, which fills in * the TZ value specially. We could use that, but it would mean @@ -521,7 +547,8 @@ static int handle_headers(request_rec *r, int *state, static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf, request_rec *r, apr_pool_t *setaside_pool, apr_uint16_t request_id, const char **err, - int *bad_request, int *has_responded) + int *bad_request, int *has_responded, + apr_bucket_brigade *input_brigade) { apr_bucket_brigade *ib, *ob; int seen_end_of_headers = 0, done = 0, ignore_body = 0; @@ -583,9 +610,26 @@ static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf, int last_stdin = 0; char *iobuf_cursor; - rv = ap_get_brigade(r->input_filters, ib, - AP_MODE_READBYTES, APR_BLOCK_READ, - iobuf_size); + if (APR_BRIGADE_EMPTY(input_brigade)) { + rv = ap_get_brigade(r->input_filters, ib, + AP_MODE_READBYTES, APR_BLOCK_READ, + iobuf_size); + } + else { + apr_bucket *e; + APR_BRIGADE_CONCAT(ib, input_brigade); + rv = apr_brigade_partition(ib, iobuf_size, &e); + if (rv == APR_SUCCESS) { + while (e != APR_BRIGADE_SENTINEL(ib) + && APR_BUCKET_IS_METADATA(e)) { + e = APR_BUCKET_NEXT(e); + } + apr_brigade_split_ex(ib, e, input_brigade); + } + else if (rv == APR_INCOMPLETE) { + rv = APR_SUCCESS; + } + } if (rv != APR_SUCCESS) { *err = "reading input brigade"; *bad_request = 1; @@ -735,6 +779,15 @@ recv_again: status = ap_scan_script_header_err_brigade_ex(r, ob, NULL, APLOG_MODULE_INDEX); + + /* FCGI has its own body framing mechanism which we don't + * match against any provided Content-Length, so let the + * core determine C-L vs T-E based on what's actually sent. + */ + if (!apr_table_get(r->subprocess_env, AP_TRUST_CGILIKE_CL_ENVVAR)) + apr_table_unset(r->headers_out, "Content-Length"); + apr_table_unset(r->headers_out, "Transfer-Encoding"); + /* suck in all the rest */ if (status != OK) { apr_bucket *tmp_b; @@ -771,8 +824,7 @@ recv_again: } } - if (conf->error_override - && ap_is_HTTP_ERROR(r->status) && ap_is_initial_req(r)) { + if (ap_proxy_should_override(conf, r->status) && ap_is_initial_req(r)) { /* * set script_error_status to discard * everything after the headers @@ -924,7 +976,8 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r, conn_rec *origin, proxy_dir_conf *conf, apr_uri_t *uri, - char *url, char *server_portstr) + char *url, char *server_portstr, + apr_bucket_brigade *input_brigade) { /* Request IDs are arbitrary numbers that we assign to a * single request. This would allow multiplex/pipelining of @@ -948,6 +1001,7 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r, } apr_pool_create(&temp_pool, r->pool); + apr_pool_tag(temp_pool, "proxy_fcgi_do_request"); /* Step 2: Send Environment via FCGI_PARAMS */ rv = send_environment(conn, r, temp_pool, request_id); @@ -960,7 +1014,8 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r, /* Step 3: Read records from the back end server and handle them. */ rv = dispatch(conn, conf, r, temp_pool, request_id, - &err, &bad_request, &has_responded); + &err, &bad_request, &has_responded, + input_brigade); if (rv != APR_SUCCESS) { /* If the client aborted the connection during retrieval or (partially) * sending the response, don't return a HTTP_SERVICE_UNAVAILABLE, since @@ -996,6 +1051,8 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r, #define FCGI_SCHEME "FCGI" +#define MAX_MEM_SPOOL 16384 + /* * This handles fcgi:(dest) URLs */ @@ -1008,6 +1065,8 @@ static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker, char server_portstr[32]; conn_rec *origin = NULL; proxy_conn_rec *backend = NULL; + apr_bucket_brigade *input_brigade; + apr_off_t input_bytes = 0; apr_uri_t *uri; proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config, @@ -1050,6 +1109,101 @@ static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker, goto cleanup; } + /* We possibly reuse input data prefetched in previous call(s), e.g. for a + * balancer fallback scenario. + */ + apr_pool_userdata_get((void **)&input_brigade, "proxy-fcgi-input", p); + if (input_brigade == NULL) { + const char *old_te = apr_table_get(r->headers_in, "Transfer-Encoding"); + const char *old_cl = NULL; + if (old_te) { + apr_table_unset(r->headers_in, "Content-Length"); + } + else { + old_cl = apr_table_get(r->headers_in, "Content-Length"); + } + + input_brigade = apr_brigade_create(p, r->connection->bucket_alloc); + apr_pool_userdata_setn(input_brigade, "proxy-fcgi-input", NULL, p); + + /* Prefetch (nonlocking) the request body so to increase the chance + * to get the whole (or enough) body and determine Content-Length vs + * chunked or spooled. By doing this before connecting or reusing the + * backend, we want to minimize the delay between this connection is + * considered alive and the first bytes sent (should the client's link + * be slow or some input filter retain the data). This is a best effort + * to prevent the backend from closing (from under us) what it thinks is + * an idle connection, hence to reduce to the minimum the unavoidable + * local is_socket_connected() vs remote keepalive race condition. + */ + status = ap_proxy_prefetch_input(r, backend, input_brigade, + APR_NONBLOCK_READ, &input_bytes, + MAX_MEM_SPOOL); + if (status != OK) { + goto cleanup; + } + + /* + * The request body is streamed by default, using either C-L or + * chunked T-E, like this: + * + * The whole body (including no body) was received on prefetch, i.e. + * the input brigade ends with EOS => C-L = input_bytes. + * + * C-L is known and reliable, i.e. only protocol filters in the input + * chain thus none should change the body => use C-L from client. + * + * The administrator has not "proxy-sendcl" which prevents T-E => use + * T-E and chunks. + * + * Otherwise we need to determine and set a content-length, so spool + * the entire request body to memory/temporary file (MAX_MEM_SPOOL), + * such that we finally know its length => C-L = input_bytes. + */ + if (!APR_BRIGADE_EMPTY(input_brigade) + && APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(input_brigade))) { + /* The whole thing fit, so our decision is trivial, use the input + * bytes for the Content-Length. If we expected no body, and read + * no body, do not set the Content-Length. + */ + if (old_cl || old_te || input_bytes) { + apr_table_setn(r->headers_in, "Content-Length", + apr_off_t_toa(p, input_bytes)); + if (old_te) { + apr_table_unset(r->headers_in, "Transfer-Encoding"); + } + } + } + else if (old_cl && r->input_filters == r->proto_input_filters) { + /* Streaming is possible by preserving the existing C-L */ + } + else if (!apr_table_get(r->subprocess_env, "proxy-sendcl")) { + /* Streaming is possible using T-E: chunked */ + } + else { + /* No streaming, C-L is the only option so spool to memory/file */ + apr_bucket_brigade *tmp_bb; + apr_off_t remaining_bytes = 0; + + AP_DEBUG_ASSERT(MAX_MEM_SPOOL >= input_bytes); + tmp_bb = apr_brigade_create(p, r->connection->bucket_alloc); + status = ap_proxy_spool_input(r, backend, tmp_bb, &remaining_bytes, + MAX_MEM_SPOOL - input_bytes); + if (status != OK) { + goto cleanup; + } + + APR_BRIGADE_CONCAT(input_brigade, tmp_bb); + input_bytes += remaining_bytes; + + apr_table_setn(r->headers_in, "Content-Length", + apr_off_t_toa(p, input_bytes)); + if (old_te) { + apr_table_unset(r->headers_in, "Transfer-Encoding"); + } + } + } + /* This scheme handler does not reuse connections by default, to * avoid tying up a fastcgi that isn't expecting to work on * parallel requests. But if the user went out of their way to @@ -1074,7 +1228,7 @@ static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker, /* Step Three: Process the Request */ status = fcgi_do_request(p, r, backend, origin, dconf, uri, url, - server_portstr); + server_portstr, input_brigade); cleanup: ap_proxy_release_connection(FCGI_SCHEME, backend, r->server); |