@@ -29,6 +29,7 @@ typedef struct {
2929
3030typedef struct {
3131 char * dirwalk_uri_path ;
32+ const char * local_redirect ; /* URI path of a CGI local redirect response */
3233} fcgi_req_config_t ;
3334
3435/* We will assume FPM, but still differentiate */
@@ -593,6 +594,48 @@ static int handle_headers(request_rec *r, int *state,
593594 return 0 ;
594595}
595596
597+ /* What the CGI response headers did to the request, see local_redirect(). */
598+ typedef struct {
599+ int nheaders ;
600+ int nerr_headers ;
601+ const char * content_type ;
602+ const char * status_line ;
603+ } resp_snapshot ;
604+
605+ static void snapshot_response (request_rec * r , resp_snapshot * snap )
606+ {
607+ snap -> nheaders = apr_table_elts (r -> headers_out )-> nelts ;
608+ snap -> nerr_headers = apr_table_elts (r -> err_headers_out )-> nelts ;
609+ snap -> content_type = r -> content_type ;
610+ snap -> status_line = r -> status_line ;
611+ }
612+
613+ /*
614+ * Returns the local URI path of a CGI Local Redirect Response (RFC 3875,
615+ * 6.2.2), which is a response without a status or a content type and with
616+ * nothing but a Location of a URI path in its headers.
617+ */
618+ static const char * local_redirect (request_rec * r , const resp_snapshot * before )
619+ {
620+ resp_snapshot now ;
621+ const char * location ;
622+
623+ snapshot_response (r , & now );
624+ if (r -> status != HTTP_OK
625+ || now .status_line != before -> status_line
626+ || now .content_type != before -> content_type
627+ || now .nheaders != before -> nheaders + 1
628+ || now .nerr_headers != before -> nerr_headers ) {
629+ return NULL ;
630+ }
631+
632+ location = apr_table_get (r -> headers_out , "Location" );
633+ if (location && location [0 ] == '/' && location [1 ] != '/' ) {
634+ return location ;
635+ }
636+ return NULL ;
637+ }
638+
596639static apr_status_t dispatch (proxy_conn_rec * conn , proxy_dir_conf * conf ,
597640 request_rec * r , apr_pool_t * setaside_pool ,
598641 apr_uint16_t request_id , const char * * err ,
@@ -601,6 +644,7 @@ static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf,
601644{
602645 apr_bucket_brigade * ib , * ob ;
603646 int seen_end_of_headers = 0 , done = 0 , ignore_body = 0 ;
647+ int redirected = 0 ;
604648 apr_status_t rv = APR_SUCCESS ;
605649 int script_error_status = HTTP_OK ;
606650 conn_rec * c = r -> connection ;
@@ -828,11 +872,35 @@ static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf,
828872
829873 if (st == 1 ) {
830874 int status ;
875+ const char * location ;
876+ resp_snapshot before ;
831877 seen_end_of_headers = 1 ;
832878
879+ snapshot_response (r , & before );
833880 status = ap_scan_script_header_err_brigade_ex (r , ob ,
834881 NULL , APLOG_MODULE_INDEX );
835882
883+ /* A local redirect is done by fcgi_request_status()
884+ * once the response is complete and the connection
885+ * to the backend is released, so read and drop the
886+ * rest of it like the body of a 304. */
887+ if (status == OK
888+ && (location = local_redirect (r , & before ))) {
889+ fcgi_req_config_t * rconf =
890+ ap_get_module_config (r -> request_config ,
891+ & proxy_fcgi_module );
892+
893+ if (rconf == NULL ) {
894+ rconf = apr_pcalloc (r -> pool , sizeof (* rconf ));
895+ ap_set_module_config (r -> request_config ,
896+ & proxy_fcgi_module ,
897+ rconf );
898+ }
899+ rconf -> local_redirect = location ;
900+ apr_table_unset (r -> headers_out , "Location" );
901+ redirected = ignore_body = 1 ;
902+ }
903+
836904 /* FCGI has its own body framing mechanism which we don't
837905 * match against any provided Content-Length, so let the
838906 * core determine C-L vs T-E based on what's actually sent.
@@ -942,7 +1010,7 @@ static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf,
9421010 } else {
9431011 /* XXX what if we haven't seen end of the headers yet? */
9441012
945- if (script_error_status == HTTP_OK ) {
1013+ if (script_error_status == HTTP_OK && ! redirected ) {
9461014 b = apr_bucket_eos_create (c -> bucket_alloc );
9471015 APR_BRIGADE_INSERT_TAIL (ob , b );
9481016
@@ -1384,10 +1452,42 @@ static const char *cmd_setenv(cmd_parms *cmd, void *in_dconf,
13841452
13851453 return NULL ;
13861454}
1455+
1456+ /*
1457+ * Does the local redirect found by dispatch() once the request to the backend
1458+ * is complete, on the request_status hook.
1459+ */
1460+ static int fcgi_request_status (int * status , request_rec * r )
1461+ {
1462+ fcgi_req_config_t * rconf = ap_get_module_config (r -> request_config ,
1463+ & proxy_fcgi_module );
1464+
1465+ if (* status == OK && rconf && rconf -> local_redirect ) {
1466+ const char * location = rconf -> local_redirect ;
1467+
1468+ ap_log_rerror (APLOG_MARK , APLOG_DEBUG , 0 , r , APLOGNO (10635 )
1469+ "Internal redirect to %s" , location );
1470+
1471+ r -> status_line = NULL ;
1472+ if (r -> method_number != M_GET ) {
1473+ /* keep HEAD, which is passed around as M_GET, too */
1474+ r -> method = "GET" ;
1475+ r -> method_number = M_GET ;
1476+ }
1477+ apr_table_unset (r -> headers_in , "Content-Length" );
1478+ ap_internal_redirect_handler (location , r );
1479+ return OK ;
1480+ }
1481+
1482+ return DECLINED ;
1483+ }
1484+
13871485static void register_hooks (apr_pool_t * p )
13881486{
13891487 proxy_hook_scheme_handler (proxy_fcgi_handler , NULL , NULL , APR_HOOK_FIRST );
13901488 proxy_hook_canon_handler (proxy_fcgi_canon , NULL , NULL , APR_HOOK_FIRST );
1489+ APR_OPTIONAL_HOOK (proxy , request_status , fcgi_request_status , NULL , NULL ,
1490+ APR_HOOK_MIDDLE );
13911491}
13921492
13931493static const command_rec command_table [] = {
0 commit comments