From 162f45c7619470126dbf66dc4e0bac15af66f6ce Mon Sep 17 00:00:00 2001 From: darkweak Date: Wed, 5 Nov 2025 20:31:04 +0100 Subject: [PATCH] [WebProfiler] add cURL copy/paste to request tab --- DataCollector/RequestDataCollector.php | 60 ++++++++++ .../RequestDataCollectorTest.php | 107 ++++++++++++++++++ 2 files changed, 167 insertions(+) diff --git a/DataCollector/RequestDataCollector.php b/DataCollector/RequestDataCollector.php index ab5c6054..f5be438c 100644 --- a/DataCollector/RequestDataCollector.php +++ b/DataCollector/RequestDataCollector.php @@ -22,6 +22,7 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Process\Process; use Symfony\Component\VarDumper\Cloner\Data; /** @@ -130,6 +131,8 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter $this->data['content'] = $content; + $this->data['curlCommand'] = $this->computeCurlCommand($request, $content); + foreach ($this->data as $key => $value) { if (!\is_array($value)) { continue; @@ -495,4 +498,61 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter return \is_string($controller) ? $controller : 'n/a'; } + + private function computeCurlCommand(Request $request, ?string $content): string + { + $command = ['curl', '--compressed']; + + $method = $request->getMethod(); + + if (Request::METHOD_HEAD === $method) { + $command[] = '--head'; + } elseif (Request::METHOD_GET !== $method) { + $command[] = \sprintf('--request %s', $method); + } + + $command[] = \sprintf('--url %s', escapeshellarg($request->getUri())); + + foreach ($request->headers->all() as $name => $values) { + if (\in_array(strtolower($name), ['host', 'cookie'], true)) { + continue; + } + + $command[] = '--header '.escapeshellarg(ucwords($name, '-').': '.implode(', ', $values)); + } + + if ($request->cookies->all()) { + $cookies = []; + foreach ($request->cookies->all() as $name => $value) { + $cookies[] = urlencode($name).'='.urlencode($value); + } + $command[] = '--cookie '.escapeshellarg(implode('; ', $cookies)); + } + + if ($content && \in_array($method, [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE], true)) { + $command[] = '--data-raw '.$this->escapePayload($content); + } + + return implode(" \\\n ", $command); + } + + public function getCurlCommand(): string + { + return $this->data['curlCommand'] ?? ''; + } + + private function escapePayload(string $payload): string + { + static $useProcess; + + if ($useProcess ??= \function_exists('proc_open') && class_exists(Process::class)) { + return substr((new Process(['', $payload]))->getCommandLine(), 3); + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + return '"'.str_replace('"', '""', $payload).'"'; + } + + return "'".str_replace("'", "'\\''", $payload)."'"; + } } diff --git a/Tests/DataCollector/RequestDataCollectorTest.php b/Tests/DataCollector/RequestDataCollectorTest.php index 57a63aea..6d5e7492 100644 --- a/Tests/DataCollector/RequestDataCollectorTest.php +++ b/Tests/DataCollector/RequestDataCollectorTest.php @@ -462,4 +462,111 @@ class RequestDataCollectorTest extends TestCase ['', null], ]; } + + public function testCurlCommandGet() + { + $request = Request::create('http://test.com/foo?bar=baz'); + + $c = new RequestDataCollector(); + $c->collect($request, $this->createResponse()); + + $curlCommand = $c->getCurlCommand(); + $this->assertStringStartsWith("curl \\\n --compressed", $curlCommand); + $this->assertStringContainsString("--url 'http://test.com/foo?bar=baz'", $curlCommand); + $this->assertStringNotContainsString('--request', $curlCommand); + } + + public function testCurlCommandPost() + { + $request = Request::create('http://test.com/foo', 'POST', [], [], [], [], '{"key":"value"}'); + + $c = new RequestDataCollector(); + $c->collect($request, $this->createResponse()); + + $curlCommand = $c->getCurlCommand(); + $this->assertStringContainsString('--request POST', $curlCommand); + $this->assertStringContainsString('--data-raw', $curlCommand); + $this->assertStringContainsString('{"key":"value"}', $curlCommand); + } + + public function testCurlCommandHead() + { + $request = Request::create('http://test.com/foo', 'HEAD'); + + $c = new RequestDataCollector(); + $c->collect($request, $this->createResponse()); + + $curlCommand = $c->getCurlCommand(); + $this->assertStringContainsString('--head', $curlCommand); + $this->assertStringNotContainsString('--request', $curlCommand); + } + + public function testCurlCommandWithHeaders() + { + $request = Request::create('http://test.com/foo'); + $request->headers->set('Accept', 'application/json'); + $request->headers->set('X-Custom-Header', 'custom-value'); + + $c = new RequestDataCollector(); + $c->collect($request, $this->createResponse()); + + $curlCommand = $c->getCurlCommand(); + $this->assertStringContainsString("--header 'Accept: application/json'", $curlCommand); + $this->assertStringContainsString("--header 'X-Custom-Header: custom-value'", $curlCommand); + $this->assertStringNotContainsString('Host:', $curlCommand); + } + + public function testCurlCommandWithCookies() + { + $request = Request::create('http://test.com/foo', 'GET', [], ['session' => 'abc123', 'lang' => 'en']); + + $c = new RequestDataCollector(); + $c->collect($request, $this->createResponse()); + + $curlCommand = $c->getCurlCommand(); + $this->assertStringContainsString('--cookie', $curlCommand); + $this->assertStringContainsString('session=abc123', $curlCommand); + $this->assertStringContainsString('lang=en', $curlCommand); + } + + public function testCurlCommandPutWithBody() + { + $request = Request::create('http://test.com/resource/1', 'PUT', [], [], [], [], 'updated data'); + + $c = new RequestDataCollector(); + $c->collect($request, $this->createResponse()); + + $curlCommand = $c->getCurlCommand(); + $this->assertStringContainsString('--request PUT', $curlCommand); + $this->assertStringContainsString('--data-raw', $curlCommand); + } + + public function testCurlCommandDoesNotDuplicateQueryString() + { + $request = Request::create('http://test.com/path?foo=bar&baz=qux'); + + $c = new RequestDataCollector(); + $c->collect($request, $this->createResponse()); + + $curlCommand = $c->getCurlCommand(); + $this->assertSame(1, substr_count($curlCommand, 'foo=bar')); + $this->assertSame(1, substr_count($curlCommand, 'baz=qux')); + } + + public function testCurlCommandGetWithNoBody() + { + $request = Request::create('http://test.com/foo', 'GET'); + + $c = new RequestDataCollector(); + $c->collect($request, $this->createResponse()); + + $curlCommand = $c->getCurlCommand(); + $this->assertStringNotContainsString('--data-raw', $curlCommand); + } + + public function testCurlCommandIsEmptyStringWhenNotCollected() + { + $c = new RequestDataCollector(); + $this->assertSame('', $c->getCurlCommand()); + } }