From aecca9778e3ef42e3c2c7794243ca8cd6c13c00d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Fri, 5 Apr 2019 19:29:13 +0200 Subject: [PATCH] Use FormUrlEncoded when posting non-binary data --- .../Component/BrowserKit/HttpBrowser.php | 35 +++++++---- .../BrowserKit/Tests/HttpBrowserTest.php | 60 +++++++++++++++++++ 2 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/BrowserKit/HttpBrowser.php b/src/Symfony/Component/BrowserKit/HttpBrowser.php index cb13c738d38f..7492e58907cb 100644 --- a/src/Symfony/Component/BrowserKit/HttpBrowser.php +++ b/src/Symfony/Component/BrowserKit/HttpBrowser.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Mime\Part\DataPart; use Symfony\Component\Mime\Part\Multipart\FormDataPart; use Symfony\Component\Mime\Part\TextPart; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -43,13 +44,10 @@ public function __construct(HttpClientInterface $client = null, History $history protected function doRequest($request) { $headers = $this->getHeaders($request); - $body = ''; - if (null !== $part = $this->getBody($request)) { - $headers = array_merge($headers, $part->getPreparedHeaders()->toArray()); - $body = $part->bodyToIterable(); - } + [$body, $extraHeaders] = $this->getBodyAndExtraHeaders($request); + $response = $this->client->request($request->getMethod(), $request->getUri(), [ - 'headers' => $headers, + 'headers' => array_merge($headers, $extraHeaders), 'body' => $body, 'max_redirects' => 0, ]); @@ -57,10 +55,13 @@ protected function doRequest($request) return new Response($response->getContent(false), $response->getStatusCode(), $response->getHeaders(false)); } - private function getBody(Request $request): ?AbstractPart + /** + * @return array [$body, $headers] + */ + private function getBodyAndExtraHeaders(Request $request): array { if (\in_array($request->getMethod(), ['GET', 'HEAD'])) { - return null; + return ['', []]; } if (!class_exists(AbstractPart::class)) { @@ -68,19 +69,33 @@ private function getBody(Request $request): ?AbstractPart } if (null !== $content = $request->getContent()) { - return new TextPart($content, 'utf-8', 'plain', '8bit'); + $part = new TextPart($content, 'utf-8', 'plain', '8bit'); + + return [$part->bodyToString(), $part->getPreparedHeaders()->toArray()]; } $fields = $request->getParameters(); + $hasFile = false; foreach ($request->getFiles() as $name => $file) { if (!isset($file['tmp_name'])) { continue; } + $hasFile = true; $fields[$name] = DataPart::fromPath($file['tmp_name'], $file['name']); } - return new FormDataPart($fields); + if ($hasFile) { + $part = new FormDataPart($fields); + + return [$part->bodyToIterable(), $part->getPreparedHeaders()->toArray()]; + } + + if (empty($fields)) { + return ['', []]; + } + + return [http_build_query($fields, '', '&', PHP_QUERY_RFC1738), ['Content-Type' => 'application/x-www-form-urlencoded']]; } private function getHeaders(Request $request): array diff --git a/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php b/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php index 266dd9a7ebe9..cd3b2c60b6ff 100644 --- a/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php @@ -17,6 +17,8 @@ use Symfony\Component\BrowserKit\Response; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; class SpecialHttpResponse extends Response { @@ -112,4 +114,62 @@ public function testGetInternalResponse() $this->assertNotInstanceOf('Symfony\Component\BrowserKit\Tests\SpecialHttpResponse', $client->getInternalResponse()); $this->assertInstanceOf('Symfony\Component\BrowserKit\Tests\SpecialHttpResponse', $client->getResponse()); } + + /** + * @dataProvider validContentTypes + */ + public function testRequestHeaders(array $request, array $exepectedCall) + { + $client = $this->createMock(HttpClientInterface::class); + $client + ->expects($this->once()) + ->method('request') + ->with(...$exepectedCall) + ->willReturn($this->createMock(ResponseInterface::class)); + + $browser = new HttpBrowser($client); + $browser->request(...$request); + } + + public function validContentTypes() + { + $defaultHeaders = ['user-agent' => 'Symfony BrowserKit', 'host' => 'example.com']; + yield 'GET/HEAD' => [ + ['GET', 'http://example.com/', ['key' => 'value']], + ['GET', 'http://example.com/', ['headers' => $defaultHeaders, 'body' => '', 'max_redirects' => 0]], + ]; + yield 'empty form' => [ + ['POST', 'http://example.com/'], + ['POST', 'http://example.com/', ['headers' => $defaultHeaders, 'body' => '', 'max_redirects' => 0]], + ]; + yield 'form' => [ + ['POST', 'http://example.com/', ['key' => 'value', 'key2' => 'value']], + ['POST', 'http://example.com/', ['headers' => $defaultHeaders + ['Content-Type' => 'application/x-www-form-urlencoded'], 'body' => 'key=value&key2=value', 'max_redirects' => 0]], + ]; + yield 'content' => [ + ['POST', 'http://example.com/', [], [], [], 'content'], + ['POST', 'http://example.com/', ['headers' => $defaultHeaders + ['Content-Type: text/plain; charset=utf-8', 'Content-Transfer-Encoding: 8bit'], 'body' => 'content', 'max_redirects' => 0]], + ]; + } + + public function testMultiPartRequest() + { + $client = $this->createMock(HttpClientInterface::class); + $client + ->expects($this->once()) + ->method('request') + ->with('POST', 'http://example.com/', $this->callback(function ($options) { + $this->assertContains('Content-Type: multipart/form-data', implode('', $options['headers'])); + $this->assertInstanceOf('\Generator', $options['body']); + $this->assertContains('my_file', implode('', iterator_to_array($options['body']))); + + return true; + })) + ->willReturn($this->createMock(ResponseInterface::class)); + + $browser = new HttpBrowser($client); + $path = tempnam(sys_get_temp_dir(), 'http'); + file_put_contents($path, 'my_file'); + $browser->request('POST', 'http://example.com/', [], ['file' => ['tmp_name' => $path, 'name' => 'foo']]); + } }