Skip to content

Commit 2c09ff8

Browse files
Merge branch '6.4' into 7.0
* 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 ...
2 parents 5c8eddf + 2e2dff5 commit 2c09ff8

File tree

4 files changed

+46
-53
lines changed

4 files changed

+46
-53
lines changed

DataCollector/HttpClientDataCollector.php

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111

1212
namespace Symfony\Component\HttpClient\DataCollector;
1313

14+
use Symfony\Component\HttpClient\Exception\TransportException;
1415
use Symfony\Component\HttpClient\HttpClientTrait;
1516
use Symfony\Component\HttpClient\TraceableHttpClient;
1617
use Symfony\Component\HttpFoundation\Request;
1718
use Symfony\Component\HttpFoundation\Response;
1819
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
1920
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
21+
use Symfony\Component\Process\Process;
2022
use Symfony\Component\VarDumper\Caster\ImgStub;
2123

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

195197
if ($json = $trace['options']['json'] ?? null) {
196-
if (!$this->argMaxLengthIsSafe($payload = self::jsonEncode($json))) {
197-
return null;
198-
}
199-
$dataArg[] = '--data '.escapeshellarg($payload);
198+
$dataArg[] = '--data-raw '.$this->escapePayload(self::jsonEncode($json));
200199
} elseif ($body = $trace['options']['body'] ?? null) {
201200
if (\is_string($body)) {
202-
if (!$this->argMaxLengthIsSafe($body)) {
203-
return null;
204-
}
201+
$dataArg[] = '--data-raw '.$this->escapePayload($body);
202+
} elseif (\is_array($body)) {
205203
try {
206-
$dataArg[] = '--data '.escapeshellarg($body);
207-
} catch (\ValueError) {
204+
$body = explode('&', self::normalizeBody($body));
205+
} catch (TransportException) {
208206
return null;
209207
}
210-
} elseif (\is_array($body)) {
211-
$body = explode('&', self::normalizeBody($body));
212208
foreach ($body as $value) {
213-
if (!$this->argMaxLengthIsSafe($payload = urldecode($value))) {
214-
return null;
215-
}
216-
$dataArg[] = '--data '.escapeshellarg($payload);
209+
$dataArg[] = '--data-raw '.$this->escapePayload(urldecode($value));
217210
}
218211
} else {
219212
return null;
@@ -230,6 +223,11 @@ private function getCurlCommand(array $trace): ?string
230223
break;
231224
}
232225

226+
if (str_starts_with('Due to a bug in curl ', $line)) {
227+
// When the curl client disables debug info due to a curl bug, we cannot build the command.
228+
return null;
229+
}
230+
233231
if ('' === $line || preg_match('/^[*<]|(Host: )/', $line)) {
234232
continue;
235233
}
@@ -250,13 +248,18 @@ private function getCurlCommand(array $trace): ?string
250248
return implode(" \\\n ", $command);
251249
}
252250

253-
/**
254-
* Let's be defensive : we authorize only size of 8kio on Windows for escapeshellarg() argument to avoid a fatal error.
255-
*
256-
* @see https://github.com/php/php-src/blob/9458f5f2c8a8e3d6c65cc181747a5a75654b7c6e/ext/standard/exec.c#L397
257-
*/
258-
private function argMaxLengthIsSafe(string $payload): bool
251+
private function escapePayload(string $payload): string
259252
{
260-
return \strlen($payload) < ('\\' === \DIRECTORY_SEPARATOR ? 8100 : 256000);
253+
static $useProcess;
254+
255+
if ($useProcess ??= class_exists(Process::class)) {
256+
return (new Process([$payload]))->getCommandLine();
257+
}
258+
259+
if ('\\' === \DIRECTORY_SEPARATOR) {
260+
return '"'.str_replace('"', '""', $payload).'"';
261+
}
262+
263+
return "'".str_replace("'", "'\\''", $payload)."'";
261264
}
262265
}

NativeHttpClient.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,14 @@ public function request(string $method, string $url, array $options = []): Respo
203203
}
204204

205205
switch ($cryptoMethod = $options['crypto_method']) {
206-
case \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT: $cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
207-
case \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT: $cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
208-
case \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT: $cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
206+
case \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT:
207+
$cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
208+
// no break
209+
case \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT:
210+
$cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
211+
// no break
212+
case \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT:
213+
$cryptoMethod |= \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
209214
}
210215

211216
$context = [

Psr18Client.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,8 @@
2828
use Psr\Http\Message\UriFactoryInterface;
2929
use Psr\Http\Message\UriInterface;
3030
use Symfony\Component\HttpClient\Internal\HttplugWaitLoop;
31-
use Symfony\Component\HttpClient\Response\StreamableInterface;
32-
use Symfony\Component\HttpClient\Response\StreamWrapper;
3331
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
3432
use Symfony\Contracts\HttpClient\HttpClientInterface;
35-
use Symfony\Contracts\HttpClient\ResponseInterface as HttpClientResponseInterface;
3633
use Symfony\Contracts\Service\ResetInterface;
3734

3835
if (!interface_exists(ClientInterface::class)) {

Tests/DataCollector/HttpClientDataCollectorTest.php

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,6 @@ public function testItIsEmptyAfterReset()
165165
}
166166

167167
/**
168-
* @requires extension openssl
169-
*
170168
* @dataProvider provideCurlRequests
171169
*/
172170
public function testItGeneratesCurlCommandsAsExpected(array $request, string $expectedCurlCommand)
@@ -177,7 +175,9 @@ public function testItGeneratesCurlCommandsAsExpected(array $request, string $ex
177175
$collectedData = $sut->getClients();
178176
self::assertCount(1, $collectedData['http_client']['traces']);
179177
$curlCommand = $collectedData['http_client']['traces'][0]['curlCommand'];
180-
self::assertEquals(sprintf($expectedCurlCommand, '\\' === \DIRECTORY_SEPARATOR ? '"' : "'"), $curlCommand);
178+
179+
$isWindows = '\\' === \DIRECTORY_SEPARATOR;
180+
self::assertEquals(sprintf($expectedCurlCommand, $isWindows ? '"' : "'", $isWindows ? '' : "'"), $curlCommand);
181181
}
182182

183183
public static function provideCurlRequests(): iterable
@@ -236,19 +236,19 @@ public static function provideCurlRequests(): iterable
236236
'method' => 'POST',
237237
'url' => 'http://localhost:8057/json',
238238
'options' => [
239-
'body' => 'foobarbaz',
239+
'body' => 'foo bar baz',
240240
],
241241
],
242242
'curl \\
243243
--compressed \\
244244
--request POST \\
245245
--url %1$shttp://localhost:8057/json%1$s \\
246246
--header %1$sAccept: */*%1$s \\
247-
--header %1$sContent-Length: 9%1$s \\
247+
--header %1$sContent-Length: 11%1$s \\
248248
--header %1$sContent-Type: application/x-www-form-urlencoded%1$s \\
249249
--header %1$sAccept-Encoding: gzip%1$s \\
250250
--header %1$sUser-Agent: Symfony HttpClient (Native)%1$s \\
251-
--data %1$sfoobarbaz%1$s',
251+
--data-raw %1$sfoo bar baz%1$s',
252252
];
253253
yield 'POST with array body' => [
254254
[
@@ -286,7 +286,7 @@ public function __toString(): string
286286
--header %1$sContent-Length: 211%1$s \\
287287
--header %1$sAccept-Encoding: gzip%1$s \\
288288
--header %1$sUser-Agent: Symfony HttpClient (Native)%1$s \\
289-
--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',
289+
--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',
290290
];
291291

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

345-
/**
346-
* @requires extension openssl
347-
*/
348345
public function testItDoesNotFollowRedirectionsWhenGeneratingCurlCommands()
349346
{
350347
$sut = new HttpClientDataCollector();
@@ -372,9 +369,6 @@ public function testItDoesNotFollowRedirectionsWhenGeneratingCurlCommands()
372369
);
373370
}
374371

375-
/**
376-
* @requires extension openssl
377-
*/
378372
public function testItDoesNotGeneratesCurlCommandsForUnsupportedBodyType()
379373
{
380374
$sut = new HttpClientDataCollector();
@@ -394,40 +388,34 @@ public function testItDoesNotGeneratesCurlCommandsForUnsupportedBodyType()
394388
self::assertNull($curlCommand);
395389
}
396390

397-
/**
398-
* @requires extension openssl
399-
*/
400-
public function testItDoesNotGeneratesCurlCommandsForNotEncodableBody()
391+
public function testItDoesGenerateCurlCommandsForBigData()
401392
{
402393
$sut = new HttpClientDataCollector();
403394
$sut->registerClient('http_client', $this->httpClientThatHasTracedRequests([
404395
[
405396
'method' => 'POST',
406397
'url' => 'http://localhost:8057/json',
407398
'options' => [
408-
'body' => "\0",
399+
'body' => str_repeat('1', 257000),
409400
],
410401
],
411402
]));
412403
$sut->lateCollect();
413404
$collectedData = $sut->getClients();
414405
self::assertCount(1, $collectedData['http_client']['traces']);
415406
$curlCommand = $collectedData['http_client']['traces'][0]['curlCommand'];
416-
self::assertNull($curlCommand);
407+
self::assertNotNull($curlCommand);
417408
}
418409

419-
/**
420-
* @requires extension openssl
421-
*/
422-
public function testItDoesNotGeneratesCurlCommandsForTooBigData()
410+
public function testItDoesNotGeneratesCurlCommandsForUploadedFiles()
423411
{
424412
$sut = new HttpClientDataCollector();
425413
$sut->registerClient('http_client', $this->httpClientThatHasTracedRequests([
426414
[
427415
'method' => 'POST',
428416
'url' => 'http://localhost:8057/json',
429417
'options' => [
430-
'body' => str_repeat('1', 257000),
418+
'body' => ['file' => fopen('data://text/plain,', 'r')],
431419
],
432420
],
433421
]));

0 commit comments

Comments
 (0)