@@ -473,6 +473,31 @@ public function test_no_redirection_on_PUT() {
473473 $ this ->assertNotEmpty ( $ res ['headers ' ]['location ' ] );
474474 }
475475
476+ /**
477+ * Parses the plain-text body from the redirection script `header-check` endpoint.
478+ *
479+ * @param string $body Response body.
480+ * @return array Header name => value pairs.
481+ */
482+ protected function parse_header_check_body ( $ body ) {
483+ $ headers = array ();
484+
485+ foreach ( explode ( "\n" , $ body ) as $ line ) {
486+ if ( empty ( $ line ) ) {
487+ continue ;
488+ }
489+
490+ $ parts = explode ( ': ' , $ line , 2 );
491+ if ( 2 !== count ( $ parts ) ) {
492+ continue ;
493+ }
494+
495+ $ headers [ $ parts [0 ] ] = $ parts [1 ];
496+ }
497+
498+ return $ headers ;
499+ }
500+
476501 /**
477502 * @ticket 11888
478503 *
@@ -489,15 +514,7 @@ public function test_send_headers() {
489514
490515 $ this ->assertNotWPError ( $ res );
491516
492- $ headers = array ();
493- foreach ( explode ( "\n" , $ res ['body ' ] ) as $ key => $ value ) {
494- if ( empty ( $ value ) ) {
495- continue ;
496- }
497- $ parts = explode ( ': ' , $ value , 2 );
498- unset( $ headers [ $ key ] );
499- $ headers [ $ parts [0 ] ] = $ parts [1 ];
500- }
517+ $ headers = $ this ->parse_header_check_body ( $ res ['body ' ] );
501518
502519 $ this ->assertArrayHasKey ( 'test1 ' , $ headers );
503520 $ this ->assertSame ( 'test ' , $ headers ['test1 ' ] );
@@ -665,17 +682,49 @@ public static function data_post_redirect_to_method_300() {
665682 /**
666683 * Test HTTP Requests using an IP URL, with a HOST header specified.
667684 *
685+ * Prefers the local redirection fixture when available. Falls back to the
686+ * live api.wordpress.org script only when the fixture was not started.
687+ *
688+ * Uses `header-check` so the assertion proves the custom Host header reached
689+ * the server, not only that some response body was returned.
690+ *
668691 * @ticket 24182
692+ * @ticket 63914
669693 *
670694 * @covers ::wp_remote_get
671695 * @covers ::wp_remote_retrieve_body
672696 */
673697 public function test_ip_url_with_host_header () {
674- $ ip = gethostbyname ( 'api.wordpress.org ' );
675- $ url = 'http:// ' . $ ip . '/core/tests/1.0/redirection.php?print-pass=1 ' ;
698+ $ parts = wp_parse_url ( $ this ->redirection_script );
699+ $ this ->assertIsArray ( $ parts );
700+ $ this ->assertNotEmpty ( $ parts ['host ' ] );
701+ $ this ->assertNotEmpty ( $ parts ['path ' ] );
702+
703+ $ host = $ parts ['host ' ];
704+ $ port = isset ( $ parts ['port ' ] ) ? (int ) $ parts ['port ' ] : null ;
705+
706+ if ( filter_var ( $ host , FILTER_VALIDATE_IP ) ) {
707+ /*
708+ * Local fixture host is already an IP. Send a different Host value so
709+ * the test fails if the transport ignores the caller-supplied header.
710+ */
711+ $ ip = $ host ;
712+ $ host_header = 'localhost ' ;
713+ } else {
714+ $ ip = gethostbyname ( $ host );
715+ $ host_header = $ host ;
716+ }
717+
718+ $ url = 'http:// ' . $ ip ;
719+ if ( $ port ) {
720+ $ url .= ': ' . $ port ;
721+ $ host_header .= ': ' . $ port ;
722+ }
723+ $ url .= $ parts ['path ' ] . '?header-check ' ;
724+
676725 $ args = array (
677726 'headers ' => array (
678- 'Host ' => ' api.wordpress.org ' ,
727+ 'Host ' => $ host_header ,
679728 ),
680729 'timeout ' => 30 ,
681730 'redirection ' => 0 ,
@@ -684,7 +733,11 @@ public function test_ip_url_with_host_header() {
684733 $ res = $ this ->wp_remote_get ( $ url , $ args );
685734
686735 $ this ->assertNotWPError ( $ res );
687- $ this ->assertSame ( 'PASS ' , wp_remote_retrieve_body ( $ res ) );
736+
737+ $ received_headers = $ this ->parse_header_check_body ( wp_remote_retrieve_body ( $ res ) );
738+
739+ $ this ->assertArrayHasKey ( 'host ' , $ received_headers );
740+ $ this ->assertSame ( $ host_header , $ received_headers ['host ' ] );
688741 }
689742
690743 /**
@@ -713,20 +766,66 @@ public function test_https_url_without_ssl_verification() {
713766 /**
714767 * Test HTTP Cookie handling.
715768 *
769+ * Uses the local redirection fixture when available so cookie acceptance and
770+ * rejection checks do not depend on a live api.wordpress.org request.
771+ *
772+ * Parent-domain cookie acceptance is covered by
773+ * test_parent_domain_cookie_is_sent_on_redirect().
774+ *
716775 * @ticket 21182
776+ * @ticket 63914
717777 *
718778 * @covers ::wp_remote_get
719779 * @covers ::wp_remote_retrieve_body
720780 */
721781 public function test_cookie_handling () {
722- $ url = ' http://api.wordpress.org/core/tests/1.0/redirection.php ?cookie-test=1 ' ;
782+ $ url = $ this -> redirection_script . ' ?cookie-test=1 ' ;
723783
724784 $ res = $ this ->wp_remote_get ( $ url );
725785
726786 $ this ->assertNotWPError ( $ res );
727787 $ this ->assertSame ( 'PASS ' , wp_remote_retrieve_body ( $ res ) );
728788 }
729789
790+ /**
791+ * Verifies parent-domain cookies are accepted and sent on redirect.
792+ *
793+ * Complements test_cookie_handling(): the IP-based local fixture intentionally
794+ * omits parent-domain cookies, so this named-origin mock keeps Domain=.example.test
795+ * acceptance deterministic without a live network request.
796+ *
797+ * @ticket 21182
798+ * @ticket 63914
799+ *
800+ * @covers ::wp_remote_get
801+ * @covers WP_Http::normalize_cookies
802+ */
803+ public function test_parent_domain_cookie_is_sent_on_redirect () {
804+ add_action ( 'requests-requests.before_request ' , array ( $ this , 'inject_parent_domain_cookie_test_transport ' ), 10 , 5 );
805+
806+ try {
807+ $ res = $ this ->wp_remote_get ( 'http://api.example.test/cookie-wildcard-test ' );
808+ } finally {
809+ remove_action ( 'requests-requests.before_request ' , array ( $ this , 'inject_parent_domain_cookie_test_transport ' ), 10 );
810+ }
811+
812+ $ this ->assertNotWPError ( $ res );
813+ $ this ->assertSame ( 'PASS ' , wp_remote_retrieve_body ( $ res ) );
814+ }
815+
816+ /**
817+ * Injects the parent-domain cookie test transport into Requests options.
818+ *
819+ * @param string $url Request URL.
820+ * @param array $headers Request headers.
821+ * @param string|array $data Request data.
822+ * @param string $type HTTP method.
823+ * @param array $options Request options (passed by reference).
824+ */
825+ public function inject_parent_domain_cookie_test_transport ( $ url , $ headers , $ data , $ type , &$ options ) {
826+ $ options ['transport ' ] = new WP_HTTP_Parent_Domain_Cookie_Test_Transport ();
827+ }
828+
730829 /**
731830 * Test if HTTPS support works.
732831 *
@@ -761,3 +860,90 @@ public function test_url_with_double_slashes_path() {
761860 $ this ->assertNotWPError ( $ res );
762861 }
763862}
863+
864+ /**
865+ * Test-only Requests transport for parent-domain cookie acceptance.
866+ *
867+ * Used by WP_HTTP_UnitTestCase::test_parent_domain_cookie_is_sent_on_redirect().
868+ */
869+ class WP_HTTP_Parent_Domain_Cookie_Test_Transport implements WpOrg \Requests \Transport {
870+
871+ /**
872+ * Performs a request against the canned cookie-redirect responses.
873+ *
874+ * @param string $url URL to request.
875+ * @param array $headers Associative array of request headers.
876+ * @param string|array $data Request data.
877+ * @param array $options Request options.
878+ * @return string Raw HTTP response.
879+ *
880+ * @throws WpOrg\Requests\Exception When no canned response exists for the URL.
881+ */
882+ public function request ( $ url , $ headers = array (), $ data = array (), $ options = array () ) {
883+ if ( 'http://api.example.test/cookie-wildcard-test ' === $ url ) {
884+ return "HTTP/1.1 302 Found \r\n"
885+ . "Set-Cookie: api_test_wildcard_domain=value; Domain=.example.test; Path=/ \r\n"
886+ . "Set-Cookie: api_test_cookie_wrong_host=value; Domain=example.com; Path=/ \r\n"
887+ . "Location: http://api.example.test/cookie-wildcard-test?done=1 \r\n\r\n" ;
888+ }
889+
890+ if ( 'http://api.example.test/cookie-wildcard-test?done=1 ' === $ url ) {
891+ $ cookie_header = '' ;
892+ foreach ( $ headers as $ header_name => $ header_value ) {
893+ if ( 0 !== strcasecmp ( (string ) $ header_name , 'Cookie ' ) ) {
894+ continue ;
895+ }
896+
897+ $ cookie_header = is_array ( $ header_value ) ? implode ( '; ' , $ header_value ) : (string ) $ header_value ;
898+ break ;
899+ }
900+
901+ if ( false === strpos ( $ cookie_header , 'api_test_wildcard_domain=value ' ) ) {
902+ return "HTTP/1.1 200 OK \r\n\r\nFAIL_NO_WILDCARD " ;
903+ }
904+
905+ if ( false !== strpos ( $ cookie_header , 'api_test_cookie_wrong_host= ' ) ) {
906+ return "HTTP/1.1 200 OK \r\n\r\nFAIL_WRONG_HOST " ;
907+ }
908+
909+ return "HTTP/1.1 200 OK \r\n\r\nPASS " ;
910+ }
911+
912+ throw new WpOrg \Requests \Exception (
913+ sprintf ( 'Unexpected HTTP request for URL: %s ' , $ url ),
914+ 'testtransport.unhandled '
915+ );
916+ }
917+
918+ /**
919+ * Performs multiple requests sequentially.
920+ *
921+ * @param array $requests Request data.
922+ * @param array $options Global options.
923+ * @return array Array of responses.
924+ */
925+ public function request_multiple ( $ requests , $ options ) {
926+ $ responses = array ();
927+
928+ foreach ( $ requests as $ key => $ request ) {
929+ $ responses [ $ key ] = $ this ->request (
930+ $ request ['url ' ],
931+ $ request ['headers ' ],
932+ $ request ['data ' ],
933+ $ request ['options ' ]
934+ );
935+ }
936+
937+ return $ responses ;
938+ }
939+
940+ /**
941+ * Self-test whether the transport can be used.
942+ *
943+ * @param array<string, bool> $capabilities Optional. Capabilities to test against.
944+ * @return bool Always true for the test transport.
945+ */
946+ public static function test ( $ capabilities = array () ) {
947+ return true ;
948+ }
949+ }
0 commit comments