Skip to content

Commit

Permalink
Merge branch '6.4' into 7.0
Browse files Browse the repository at this point in the history
* 6.4: (28 commits)
  [Serializer] Fix `@method` annotation
  fix compatibility with Doctrine DBAL 4
  ensure string type with mbstring func overloading enabled
  [HttpKernel] Fix quotes expectations in tests
  [Validator] updated Greek translation
  [Cache][HttpFoundation][Lock] Fix empty username/password for PDO PostgreSQL
  [HttpClient][WebProfilerBundle] Do not generate cURL command when files are uploaded
  render newline in front of all script elements
  fix test fixture
  fix tests
  [Cache] Fix property types in PdoAdapter
  PHP files cannot be executable without shebang
  [TwigBridge] Mark CodeExtension as @internal
  Remove full DSNs from exception messages
  [Yaml] Fix uid binary parsing
  Disable the "Copy as cURL" button when the debug info are disabled
  [HttpClient] Replace `escapeshellarg` to prevent overpassing `ARG_MAX`
  Fix missing `profile` option for console commands
  [HttpFoundation][Lock] Makes MongoDB adapters usable with `ext-mongodb` only
  [HttpKernel] Preventing error 500 when function putenv is disabled
  ...
  • Loading branch information
nicolas-grekas committed Nov 7, 2023
2 parents 5c8eddf + 2e2dff5 commit 2c09ff8
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 53 deletions.
47 changes: 25 additions & 22 deletions DataCollector/HttpClientDataCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@

namespace Symfony\Component\HttpClient\DataCollector;

use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\HttpClient\HttpClientTrait;
use Symfony\Component\HttpClient\TraceableHttpClient;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
use Symfony\Component\Process\Process;
use Symfony\Component\VarDumper\Caster\ImgStub;

/**
Expand Down Expand Up @@ -193,27 +195,18 @@ private function getCurlCommand(array $trace): ?string
$dataArg = [];

if ($json = $trace['options']['json'] ?? null) {
if (!$this->argMaxLengthIsSafe($payload = self::jsonEncode($json))) {
return null;
}
$dataArg[] = '--data '.escapeshellarg($payload);
$dataArg[] = '--data-raw '.$this->escapePayload(self::jsonEncode($json));
} elseif ($body = $trace['options']['body'] ?? null) {
if (\is_string($body)) {
if (!$this->argMaxLengthIsSafe($body)) {
return null;
}
$dataArg[] = '--data-raw '.$this->escapePayload($body);
} elseif (\is_array($body)) {
try {
$dataArg[] = '--data '.escapeshellarg($body);
} catch (\ValueError) {
$body = explode('&', self::normalizeBody($body));
} catch (TransportException) {
return null;
}
} elseif (\is_array($body)) {
$body = explode('&', self::normalizeBody($body));
foreach ($body as $value) {
if (!$this->argMaxLengthIsSafe($payload = urldecode($value))) {
return null;
}
$dataArg[] = '--data '.escapeshellarg($payload);
$dataArg[] = '--data-raw '.$this->escapePayload(urldecode($value));
}
} else {
return null;
Expand All @@ -230,6 +223,11 @@ private function getCurlCommand(array $trace): ?string
break;
}

if (str_starts_with('Due to a bug in curl ', $line)) {
// When the curl client disables debug info due to a curl bug, we cannot build the command.
return null;
}

if ('' === $line || preg_match('/^[*<]|(Host: )/', $line)) {
continue;
}
Expand All @@ -250,13 +248,18 @@ private function getCurlCommand(array $trace): ?string
return implode(" \\\n ", $command);
}

/**
* Let's be defensive : we authorize only size of 8kio on Windows for escapeshellarg() argument to avoid a fatal error.
*
* @see https://github.com/php/php-src/blob/9458f5f2c8a8e3d6c65cc181747a5a75654b7c6e/ext/standard/exec.c#L397
*/
private function argMaxLengthIsSafe(string $payload): bool
private function escapePayload(string $payload): string
{
return \strlen($payload) < ('\\' === \DIRECTORY_SEPARATOR ? 8100 : 256000);
static $useProcess;

if ($useProcess ??= class_exists(Process::class)) {
return (new Process([$payload]))->getCommandLine();
}

if ('\\' === \DIRECTORY_SEPARATOR) {
return '"'.str_replace('"', '""', $payload).'"';
}

return "'".str_replace("'", "'\\''", $payload)."'";
}
}
11 changes: 8 additions & 3 deletions NativeHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,14 @@ public function request(string $method, string $url, array $options = []): Respo
}

switch ($cryptoMethod = $options['crypto_method']) {
case \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT: $cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
case \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT: $cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
case \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT: $cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
case \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT:
$cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
// no break
case \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT:
$cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
// no break
case \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT:
$cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
}

$context = [
Expand Down
3 changes: 0 additions & 3 deletions Psr18Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,8 @@
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\HttpClient\Internal\HttplugWaitLoop;
use Symfony\Component\HttpClient\Response\StreamableInterface;
use Symfony\Component\HttpClient\Response\StreamWrapper;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface as HttpClientResponseInterface;
use Symfony\Contracts\Service\ResetInterface;

if (!interface_exists(ClientInterface::class)) {
Expand Down
38 changes: 13 additions & 25 deletions Tests/DataCollector/HttpClientDataCollectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ public function testItIsEmptyAfterReset()
}

/**
* @requires extension openssl
*
* @dataProvider provideCurlRequests
*/
public function testItGeneratesCurlCommandsAsExpected(array $request, string $expectedCurlCommand)
Expand All @@ -177,7 +175,9 @@ public function testItGeneratesCurlCommandsAsExpected(array $request, string $ex
$collectedData = $sut->getClients();
self::assertCount(1, $collectedData['http_client']['traces']);
$curlCommand = $collectedData['http_client']['traces'][0]['curlCommand'];
self::assertEquals(sprintf($expectedCurlCommand, '\\' === \DIRECTORY_SEPARATOR ? '"' : "'"), $curlCommand);

$isWindows = '\\' === \DIRECTORY_SEPARATOR;
self::assertEquals(sprintf($expectedCurlCommand, $isWindows ? '"' : "'", $isWindows ? '' : "'"), $curlCommand);
}

public static function provideCurlRequests(): iterable
Expand Down Expand Up @@ -236,19 +236,19 @@ public static function provideCurlRequests(): iterable
'method' => 'POST',
'url' => 'http://localhost:8057/json',
'options' => [
'body' => 'foobarbaz',
'body' => 'foo bar baz',
],
],
'curl \\
--compressed \\
--request POST \\
--url %1$shttp://localhost:8057/json%1$s \\
--header %1$sAccept: */*%1$s \\
--header %1$sContent-Length: 9%1$s \\
--header %1$sContent-Length: 11%1$s \\
--header %1$sContent-Type: application/x-www-form-urlencoded%1$s \\
--header %1$sAccept-Encoding: gzip%1$s \\
--header %1$sUser-Agent: Symfony HttpClient (Native)%1$s \\
--data %1$sfoobarbaz%1$s',
--data-raw %1$sfoo bar baz%1$s',
];
yield 'POST with array body' => [
[
Expand Down Expand Up @@ -286,7 +286,7 @@ public function __toString(): string
--header %1$sContent-Length: 211%1$s \\
--header %1$sAccept-Encoding: gzip%1$s \\
--header %1$sUser-Agent: Symfony HttpClient (Native)%1$s \\
--data %1$sfoo=fooval%1$s --data %1$sbar=barval%1$s --data %1$sbaz=bazval%1$s --data %1$sfoobar[baz]=bazval%1$s --data %1$sfoobar[qux]=quxval%1$s --data %1$sbazqux[0]=bazquxval1%1$s --data %1$sbazqux[1]=bazquxval2%1$s --data %1$sobject[fooprop]=foopropval%1$s --data %1$sobject[barprop]=barpropval%1$s --data %1$stostring=tostringval%1$s',
--data-raw %2$sfoo=fooval%2$s --data-raw %2$sbar=barval%2$s --data-raw %2$sbaz=bazval%2$s --data-raw %2$sfoobar[baz]=bazval%2$s --data-raw %2$sfoobar[qux]=quxval%2$s --data-raw %2$sbazqux[0]=bazquxval1%2$s --data-raw %2$sbazqux[1]=bazquxval2%2$s --data-raw %2$sobject[fooprop]=foopropval%2$s --data-raw %2$sobject[barprop]=barpropval%2$s --data-raw %2$stostring=tostringval%2$s',
];

// escapeshellarg on Windows replaces double quotes & percent signs with spaces
Expand Down Expand Up @@ -337,14 +337,11 @@ public function __toString(): string
--header %1$sContent-Length: 120%1$s \\
--header %1$sAccept-Encoding: gzip%1$s \\
--header %1$sUser-Agent: Symfony HttpClient (Native)%1$s \\
--data %1$s{"foo":{"bar":"baz","qux":[1.1,1.0],"fred":["\u003Cfoo\u003E","\u0027bar\u0027","\u0022baz\u0022","\u0026blong\u0026"]}}%1$s',
--data-raw %1$s{"foo":{"bar":"baz","qux":[1.1,1.0],"fred":["\u003Cfoo\u003E","\u0027bar\u0027","\u0022baz\u0022","\u0026blong\u0026"]}}%1$s',
];
}
}

/**
* @requires extension openssl
*/
public function testItDoesNotFollowRedirectionsWhenGeneratingCurlCommands()
{
$sut = new HttpClientDataCollector();
Expand Down Expand Up @@ -372,9 +369,6 @@ public function testItDoesNotFollowRedirectionsWhenGeneratingCurlCommands()
);
}

/**
* @requires extension openssl
*/
public function testItDoesNotGeneratesCurlCommandsForUnsupportedBodyType()
{
$sut = new HttpClientDataCollector();
Expand All @@ -394,40 +388,34 @@ public function testItDoesNotGeneratesCurlCommandsForUnsupportedBodyType()
self::assertNull($curlCommand);
}

/**
* @requires extension openssl
*/
public function testItDoesNotGeneratesCurlCommandsForNotEncodableBody()
public function testItDoesGenerateCurlCommandsForBigData()
{
$sut = new HttpClientDataCollector();
$sut->registerClient('http_client', $this->httpClientThatHasTracedRequests([
[
'method' => 'POST',
'url' => 'http://localhost:8057/json',
'options' => [
'body' => "\0",
'body' => str_repeat('1', 257000),
],
],
]));
$sut->lateCollect();
$collectedData = $sut->getClients();
self::assertCount(1, $collectedData['http_client']['traces']);
$curlCommand = $collectedData['http_client']['traces'][0]['curlCommand'];
self::assertNull($curlCommand);
self::assertNotNull($curlCommand);
}

/**
* @requires extension openssl
*/
public function testItDoesNotGeneratesCurlCommandsForTooBigData()
public function testItDoesNotGeneratesCurlCommandsForUploadedFiles()
{
$sut = new HttpClientDataCollector();
$sut->registerClient('http_client', $this->httpClientThatHasTracedRequests([
[
'method' => 'POST',
'url' => 'http://localhost:8057/json',
'options' => [
'body' => str_repeat('1', 257000),
'body' => ['file' => fopen('data://text/plain,', 'r')],
],
],
]));
Expand Down

0 comments on commit 2c09ff8

Please sign in to comment.