| @@ -204,22 +204,10 @@ | ||
| 204 | 204 | $body = wp_remote_retrieve_body( $response ); |
| 205 | 205 | $http_code = wp_remote_retrieve_response_code( $response ); |
| 206 | 206 | $response_headers = wp_remote_retrieve_headers( $response ); |
| 207 | 207 | |
| 208 | - $skip_headers = array( | |
| 209 | - 'transfer-encoding', | |
| 210 | - 'content-encoding', | |
| 211 | - 'content-length', | |
| 212 | - 'connection', | |
| 213 | - 'keep-alive', | |
| 214 | - ); | |
| 215 | - | |
| 216 | - foreach ( $response_headers as $name => $value ) { | |
| 217 | - if ( in_array( strtolower( $name ), $skip_headers, true ) ) { | |
| 218 | - continue; | |
| 219 | - } | |
| 220 | - $safe_value = str_replace( array( "\r", "\n" ), '', $value ); | |
| 221 | - header( "$name: $safe_value" ); | |
| 208 | + foreach ( $this->build_passthrough_headers( $response_headers ) as $passthrough_header ) { | |
| 209 | + header( $passthrough_header['name'] . ': ' . $passthrough_header['value'], $passthrough_header['replace'] ); | |
| 222 | 210 | } |
| 223 | 211 | |
| 224 | 212 | status_header( $http_code ); |
| 225 | 213 | header( 'X-HS-WP-Plugin-Proxy-URL: ' . $target_url ); |
| @@ -412,14 +400,54 @@ | ||
| 412 | 400 | return $headers; |
| 413 | 401 | } |
| 414 | 402 | |
| 415 | 403 | private function strip_headers( $headers ) { |
| 416 | - $headers_to_strip = array( 'host', 'content-length', 'cf-connecting-ip', 'true-client-ip' ); | |
| 404 | + $headers_to_strip = array( 'host', 'content-length', 'cf-connecting-ip', 'true-client-ip', 'accept-encoding' ); | |
| 417 | 405 | foreach ( array_keys( $headers ) as $header_key ) { |
| 418 | 406 | if ( in_array( strtolower( $header_key ), $headers_to_strip, true ) ) { |
| 419 | 407 | unset( $headers[ $header_key ] ); |
| 420 | 408 | } |
| 421 | 409 | } |
| 410 | + return $headers; | |
| 411 | + } | |
| 412 | + | |
| 413 | + /** | |
| 414 | + * Builds the list of response headers to forward back to the client. | |
| 415 | + * | |
| 416 | + * Skips hop-by-hop headers, strips CR/LF to prevent header injection, and | |
| 417 | + * expands multi-value headers (e.g. multiple Set-Cookie) into separate | |
| 418 | + * entries so an array value is never stringified to the literal "Array". | |
| 419 | + * | |
| 420 | + * @param iterable $response_headers The upstream response headers. | |
| 421 | + * @return array List of array( 'name' => string, 'value' => string, 'replace' => bool ). | |
| 422 | + */ | |
| 423 | + private function build_passthrough_headers( $response_headers ) { | |
| 424 | + $skip_headers = array( | |
| 425 | + 'transfer-encoding', | |
| 426 | + 'content-encoding', | |
| 427 | + 'content-length', | |
| 428 | + 'connection', | |
| 429 | + 'keep-alive', | |
| 430 | + ); | |
| 431 | + | |
| 432 | + $headers = array(); | |
| 433 | + foreach ( $response_headers as $name => $value ) { | |
| 434 | + if ( in_array( strtolower( $name ), $skip_headers, true ) ) { | |
| 435 | + continue; | |
| 436 | + } | |
| 437 | + | |
| 438 | + $values = is_array( $value ) ? array_values( $value ) : array( $value ); | |
| 439 | + $replace = true; | |
| 440 | + foreach ( $values as $single_value ) { | |
| 441 | + $headers[] = array( | |
| 442 | + 'name' => $name, | |
| 443 | + 'value' => str_replace( array( "\r", "\n" ), '', $single_value ), | |
| 444 | + 'replace' => $replace, | |
| 445 | + ); | |
| 446 | + $replace = false; | |
| 447 | + } | |
| 448 | + } | |
| 449 | + | |
| 422 | 450 | return $headers; |
| 423 | 451 | } |
| 424 | 452 | |
| 425 | 453 | private function normalize_header_name( $name ) { |