diff --git a/includes/http/GuzzleHttpRequest.php b/includes/http/GuzzleHttpRequest.php index f857826e4a0..624c215740f 100644 --- a/includes/http/GuzzleHttpRequest.php +++ b/includes/http/GuzzleHttpRequest.php @@ -19,7 +19,10 @@ */ use GuzzleHttp\Client; +use GuzzleHttp\HandlerStack; +use GuzzleHttp\Middleware; use GuzzleHttp\Psr7\Request; +use Psr\Http\Message\RequestInterface; /** * MWHttpRequest implemented using the Guzzle library @@ -154,9 +157,32 @@ class GuzzleHttpRequest extends MWHttpRequest { $this->guzzleOptions['headers'] = $this->reqHeaders; - if ( $this->handler ) { - $this->guzzleOptions['handler'] = $this->handler; - } + // Create Middleware to use cookies from $this->getCookieJar(), + // which is in MediaWiki CookieJar format, not in Guzzle-specific CookieJar format. + // Note: received cookies (from HTTP response) don't need to be handled here, + // they will be added back into the CookieJar by MWHttpRequest::parseCookies(). + $stack = HandlerStack::create( $this->handler ); + + // @phan-suppress-next-line PhanUndeclaredFunctionInCallable + $stack->remove( 'cookies' ); + + $mwCookieJar = $this->getCookieJar(); + $stack->push( Middleware::mapRequest( + function ( RequestInterface $request ) use ( $mwCookieJar ) { + $uri = $request->getUri(); + $cookieHeader = $mwCookieJar->serializeToHttpRequest( + $uri->getPath() ?: '/', + $uri->getHost() + ); + if ( !$cookieHeader ) { + return $request; + } + + return $request->withHeader( 'Cookie', $cookieHeader ); + } + ), 'cookies' ); + + $this->guzzleOptions['handler'] = $stack; if ( $this->sink ) { $this->guzzleOptions['sink'] = $this->sink; diff --git a/tests/phpunit/includes/http/GuzzleHttpRequestTest.php b/tests/phpunit/includes/http/GuzzleHttpRequestTest.php index 8ae6d33f364..0f726973d19 100644 --- a/tests/phpunit/includes/http/GuzzleHttpRequestTest.php +++ b/tests/phpunit/includes/http/GuzzleHttpRequestTest.php @@ -156,7 +156,7 @@ class GuzzleHttpRequestTest extends MediaWikiTestCase { public function testPostBody() { $container = []; $history = Middleware::history( $container ); - $stack = HandlerStack::create(); + $stack = HandlerStack::create( new MockHandler( [ new Response() ] ) ); $stack->push( $history ); $client = new GuzzleHttpRequest( $this->exampleUrl, [ 'method' => 'POST', @@ -170,4 +170,48 @@ class GuzzleHttpRequestTest extends MediaWikiTestCase { $this->assertEquals( 'application/x-www-form-urlencoded', $request->getHeader( 'Content-Type' )[0] ); } + + /* + * Test that cookies from CookieJar were sent in the outgoing request. + */ + public function testCookieSent() { + $domain = wfParseUrl( $this->exampleUrl )['host']; + $expectedCookies = [ 'cookie1' => 'value1', 'anothercookie' => 'secondvalue' ]; + $jar = new CookieJar; + foreach ( $expectedCookies as $key => $val ) { + $jar->setCookie( $key, $val, [ 'domain' => $domain ] ); + } + + $container = []; + $history = Middleware::history( $container ); + $stack = HandlerStack::create( new MockHandler( [ new Response() ] ) ); + $stack->push( $history ); + $client = new GuzzleHttpRequest( $this->exampleUrl, [ + 'method' => 'POST', + 'handler' => $stack, + 'post' => 'key=value', + ] ); + $client->setCookieJar( $jar ); + $client->execute(); + + $request = $container[0]['request']; + $this->assertEquals( [ 'cookie1=value1; anothercookie=secondvalue' ], + $request->getHeader( 'Cookie' ) ); + } + + /* + * Test that cookies returned by HTTP response were added back into the CookieJar. + */ + public function testCookieReceived() { + $handler = HandlerStack::create( new MockHandler( [ new Response( 200, [ + 'status' => 200, + 'Set-Cookie' => [ 'cookie1=value1', 'anothercookie=secondvalue' ] + ] ) ] ) ); + $r = new GuzzleHttpRequest( $this->exampleUrl, [ 'handler' => $handler ] ); + $r->execute(); + + $domain = wfParseUrl( $this->exampleUrl )['host']; + $this->assertEquals( 'cookie1=value1; anothercookie=secondvalue', + $r->getCookieJar()->serializeToHttpRequest( '/', $domain ) ); + } }