From e071070b2d57e6c0b71de568ddd9a516a2d7ccf8 Mon Sep 17 00:00:00 2001 From: Christian Holladay Date: Wed, 10 Mar 2021 16:12:52 -0600 Subject: [PATCH 1/9] Create php.yml --- .github/workflows/php.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/php.yml diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000..ee8428e --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,33 @@ +name: PHP Composer + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Tests + run: php ./vendor/bin/pest --colors=always From 1c478ad9afffbac410894e61606c51b4b9beb198 Mon Sep 17 00:00:00 2001 From: Christian Holladay Date: Wed, 10 Mar 2021 16:29:41 -0600 Subject: [PATCH 2/9] Update CurlFormatter.php --- src/Formatter/CurlFormatter.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Formatter/CurlFormatter.php b/src/Formatter/CurlFormatter.php index 12b2639..4339aac 100644 --- a/src/Formatter/CurlFormatter.php +++ b/src/Formatter/CurlFormatter.php @@ -5,6 +5,7 @@ use GuzzleHttp\Cookie\CookieJarInterface; use GuzzleHttp\Cookie\SetCookie; use Psr\Http\Message\RequestInterface; +use Symfony\Component\Process\Process; class CurlFormatter { @@ -140,7 +141,7 @@ protected function extractBodyArgument(RequestInterface $request) if ($contents) { // clean input of null bytes $contents = str_replace(chr(0), '', $contents); - $this->addOption('d', escapeshellarg($contents)); + $this->addOption('d', $this->escapeShellArgument($contents)); } //if get request has data Add G otherwise curl will make a post request @@ -176,7 +177,7 @@ protected function extractCookiesArgument(RequestInterface $request, array $opti } if ($values) { - $this->addOption('b', escapeshellarg(implode('; ', $values))); + $this->addOption('b', $this->escapeShellArgument(implode('; ', $values))); } } @@ -193,12 +194,12 @@ protected function extractHeadersArgument(RequestInterface $request) } if ('user-agent' === strtolower($name)) { - $this->addOption('A', escapeshellarg($header[0])); + $this->addOption('A', $this->escapeShellArgument($header[0])); continue; } foreach ((array)$header as $headerValue) { - $this->addOption('H', escapeshellarg("{$name}: {$headerValue}")); + $this->addOption('H', $this->escapeShellArgument("{$name}: {$headerValue}")); } } } @@ -245,6 +246,13 @@ protected function extractArguments(RequestInterface $request, array $options) */ protected function extractUrlArgument(RequestInterface $request) { - $this->addCommandPart(escapeshellarg((string)$request->getUri()->withFragment(''))); + $this->addCommandPart($this->escapeShellArgument((string)$request->getUri()->withFragment(''))); + } + + protected function escapeShellArgument($argument) + { + $process = new Process([$argument]); + $escaped = $process->getCommandLine(); + return $escaped; } } From ff70953cfbf542085fc396604018c3ab97976660 Mon Sep 17 00:00:00 2001 From: Christian Holladay Date: Wed, 10 Mar 2021 16:29:48 -0600 Subject: [PATCH 3/9] Update composer.json --- composer.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 5876bee..3a0be46 100644 --- a/composer.json +++ b/composer.json @@ -4,11 +4,12 @@ "require": { "php": ">=7.3", "guzzlehttp/guzzle": "^7.2", - "psr/log": "^1.1" + "psr/log": "^1.1", + "symfony/process": "^5.2" }, "require-dev": { "phpunit/phpunit": "^9.4.3", - "pestphp/pest": "^0.3.14", + "pestphp/pest": "^1.0", "phpstan/phpstan": "^0.12.58" }, "autoload": { From b6b919e42bdc45bdbe5bbf11a80cc82f3a85ec2f Mon Sep 17 00:00:00 2001 From: Christian Holladay Date: Wed, 10 Mar 2021 16:30:54 -0600 Subject: [PATCH 4/9] Update CurlFormatterTest.php --- tests/Formatter/CurlFormatterTest.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/Formatter/CurlFormatterTest.php b/tests/Formatter/CurlFormatterTest.php index 5bc46e4..bc952b5 100644 --- a/tests/Formatter/CurlFormatterTest.php +++ b/tests/Formatter/CurlFormatterTest.php @@ -74,6 +74,17 @@ expect($curl)->toContain("-d 'foo=bar&hello=world'"); }); +test('large POST request', function () { + ini_set('memory_limit', -1); + + $body = str_repeat('A', 1024*1024*64); + + $request = new Request('POST', 'http://example.local', [], \GuzzleHttp\Psr7\stream_for($body)); + $curl = $this->curlFormatter->format($request); + + expect($curl)->not()->toBeNull(); +}); + test('HEAD', function () { $request = new Request('HEAD', 'http://example.local'); $curl = $this->curlFormatter->format($request); @@ -124,4 +135,4 @@ ['X-Foo' => 'Bar'], chr(0). 'foo=bar&hello=world', ] -]); \ No newline at end of file +]); From 86cd15570b4a461792f7b83fa9895f03dd19e8d3 Mon Sep 17 00:00:00 2001 From: Christian Holladay Date: Wed, 10 Mar 2021 17:17:24 -0600 Subject: [PATCH 5/9] Delete php.yml --- .github/workflows/php.yml | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 .github/workflows/php.yml diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml deleted file mode 100644 index ee8428e..0000000 --- a/.github/workflows/php.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: PHP Composer - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Validate composer.json and composer.lock - run: composer validate --strict - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v2 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-suggest - - - name: Tests - run: php ./vendor/bin/pest --colors=always From f8a0b4b3f5cba2d7b866b752dfd422e97d39d738 Mon Sep 17 00:00:00 2001 From: Christian Holladay Date: Wed, 10 Mar 2021 17:21:42 -0600 Subject: [PATCH 6/9] Reversing String Test --- tests/Formatter/CurlFormatterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Formatter/CurlFormatterTest.php b/tests/Formatter/CurlFormatterTest.php index bc952b5..12f87fb 100644 --- a/tests/Formatter/CurlFormatterTest.php +++ b/tests/Formatter/CurlFormatterTest.php @@ -71,7 +71,7 @@ $curl = $this->curlFormatter->format($request); expect($curl)->not()->toContain(" -G "); - expect($curl)->toContain("-d 'foo=bar&hello=world'"); + expect($curl)->toContain('-d "foo=bar&hello=world"'); }); test('large POST request', function () { From d480cb61a39c60e90e85f8a4f27b37d873ee3690 Mon Sep 17 00:00:00 2001 From: Christian Holladay Date: Wed, 10 Mar 2021 17:23:13 -0600 Subject: [PATCH 7/9] Update CurlFormatterTest.php --- tests/Formatter/CurlFormatterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Formatter/CurlFormatterTest.php b/tests/Formatter/CurlFormatterTest.php index 12f87fb..6a73f60 100644 --- a/tests/Formatter/CurlFormatterTest.php +++ b/tests/Formatter/CurlFormatterTest.php @@ -71,7 +71,7 @@ $curl = $this->curlFormatter->format($request); expect($curl)->not()->toContain(" -G "); - expect($curl)->toContain('-d "foo=bar&hello=world"'); + expect($curl)->toContain("-d \"foo=bar&hello=world\""); }); test('large POST request', function () { From 2ac3f553abf8203b13e3b17b9ec044cd717bd0b1 Mon Sep 17 00:00:00 2001 From: Christian Holladay Date: Wed, 10 Mar 2021 17:26:12 -0600 Subject: [PATCH 8/9] Update CurlFormatterTest.php --- tests/Formatter/CurlFormatterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Formatter/CurlFormatterTest.php b/tests/Formatter/CurlFormatterTest.php index 6a73f60..bc952b5 100644 --- a/tests/Formatter/CurlFormatterTest.php +++ b/tests/Formatter/CurlFormatterTest.php @@ -71,7 +71,7 @@ $curl = $this->curlFormatter->format($request); expect($curl)->not()->toContain(" -G "); - expect($curl)->toContain("-d \"foo=bar&hello=world\""); + expect($curl)->toContain("-d 'foo=bar&hello=world'"); }); test('large POST request', function () { From 683dc0c5d548a4f0e6f437f234fc9080cd0e8e41 Mon Sep 17 00:00:00 2001 From: Christian Holladay Date: Wed, 10 Mar 2021 17:34:27 -0600 Subject: [PATCH 9/9] System Agnostic Tests --- tests/Client/RequestTest.php | 12 ++++++------ tests/Formatter/CurlFormatterTest.php | 21 +++++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/tests/Client/RequestTest.php b/tests/Client/RequestTest.php index 32430a2..1d6accd 100644 --- a/tests/Client/RequestTest.php +++ b/tests/Client/RequestTest.php @@ -18,22 +18,22 @@ $jar = CookieJar::fromArray(['Foo' => 'Bar', 'identity' => 'xyz'], 'local.example'); $curl = $this->curlFormatter->format($request, ['cookies' => $jar]); - expect($curl)->not()->toContain("-H 'Host: local.example'"); - expect($curl)->toContain("-b 'Foo=Bar; identity=xyz'"); + expect(str_replace('"', '\'', $curl))->not()->toContain("-H 'Host: local.example'"); + expect(str_replace('"', '\'', $curl))->toContain("-b 'Foo=Bar; identity=xyz'"); }); test('POST', function () { $request = new Request('POST', 'http://local.example', [], Utils::streamFor('foo=bar&hello=world')); $curl = $this->curlFormatter->format($request); - expect($curl)->toContain("-d 'foo=bar&hello=world'"); + expect(str_replace('"', '\'', $curl))->toContain("-d 'foo=bar&hello=world'"); }); test('PUT', function () { $request = new Request('PUT', 'http://local.example', [], Utils::streamFor('foo=bar&hello=world')); $curl = $this->curlFormatter->format($request); - expect($curl)->toContain("-d 'foo=bar&hello=world'"); + expect(str_replace('"', '\'', $curl))->toContain("-d 'foo=bar&hello=world'"); expect($curl)->toContain('-X PUT'); }); @@ -48,7 +48,7 @@ $request = new Request('HEAD', 'http://local.example'); $curl = $this->curlFormatter->format($request); - expect($curl)->toContain("curl 'http://local.example' --head"); + expect(str_replace('"', '\'', $curl))->toContain("curl 'http://local.example' --head"); }); test('OPTIONS', function () { @@ -56,4 +56,4 @@ $curl = $this->curlFormatter->format($request); expect($curl)->toContain('-X OPTIONS'); -}); \ No newline at end of file +}); diff --git a/tests/Formatter/CurlFormatterTest.php b/tests/Formatter/CurlFormatterTest.php index bc952b5..ee5dfd4 100644 --- a/tests/Formatter/CurlFormatterTest.php +++ b/tests/Formatter/CurlFormatterTest.php @@ -21,47 +21,47 @@ $request = new Request('GET', 'http://example.local'); $curl = $this->curlFormatter->format($request); - $this->assertEquals("curl 'http://example.local'", $curl); + $this->assertEquals("curl 'http://example.local'", str_replace('"', '\'', $curl)); }); test('simple get', function () { $request = new Request('GET', 'http://example.local'); $curl = $this->curlFormatter->format($request); - $this->assertEquals("curl 'http://example.local'", $curl); + $this->assertEquals("curl 'http://example.local'", str_replace('"', '\'', $curl)); }); test('simple GET with header', function () { $request = new Request('GET', 'http://example.local', ['foo' => 'bar']); $curl = $this->curlFormatter->format($request); - $this->assertEquals("curl 'http://example.local' -H 'foo: bar'", $curl); + $this->assertEquals("curl 'http://example.local' -H 'foo: bar'", str_replace('"', '\'', $curl)); }); test('simple GET with multiple header', function () { $request = new Request('GET', 'http://example.local', ['foo' => 'bar', 'Accept-Encoding' => 'gzip,deflate,sdch']); $curl = $this->curlFormatter->format($request); - $this->assertEquals("curl 'http://example.local' -H 'foo: bar' -H 'Accept-Encoding: gzip,deflate,sdch'", $curl); + $this->assertEquals("curl 'http://example.local' -H 'foo: bar' -H 'Accept-Encoding: gzip,deflate,sdch'", str_replace('"', '\'', $curl)); }); test('GET With Query String', function () { $request = new Request('GET', 'http://example.local?foo=bar'); $curl = $this->curlFormatter->format($request); - $this->assertEquals("curl 'http://example.local?foo=bar'", $curl); + $this->assertEquals("curl 'http://example.local?foo=bar'", str_replace('"', '\'', $curl)); $request = new Request('GET', 'http://example.local?foo=bar'); $curl = $this->curlFormatter->format($request); - $this->assertEquals("curl 'http://example.local?foo=bar'", $curl); + $this->assertEquals("curl 'http://example.local?foo=bar'", str_replace('"', '\'', $curl)); $body = Utils::streamFor(http_build_query(['foo' => 'bar', 'hello' => 'world'], '', '&')); $request = new Request('GET', 'http://example.local',[],$body); $curl = $this->curlFormatter->format($request); - $this->assertEquals("curl 'http://example.local' -G -d 'foo=bar&hello=world'",$curl); + $this->assertEquals("curl 'http://example.local' -G -d 'foo=bar&hello=world'", str_replace('"', '\'', $curl)); }); test('POST', function () { @@ -71,7 +71,8 @@ $curl = $this->curlFormatter->format($request); expect($curl)->not()->toContain(" -G "); - expect($curl)->toContain("-d 'foo=bar&hello=world'"); + expect(str_replace('"', '\'', $curl))->toContain("-d 'foo=bar&hello=world'"); + }); test('large POST request', function () { @@ -110,7 +111,7 @@ $request = new Request('PUT', 'http://example.local', [], Utils::streamFor('foo=bar&hello=world')); $curl = $this->curlFormatter->format($request); - expect($curl)->toContain("-d 'foo=bar&hello=world'"); + expect(str_replace('"', '\'', $curl))->toContain("-d 'foo=bar&hello=world'"); expect($curl)->toContain("-X PUT"); }); @@ -118,7 +119,7 @@ $request = new Request('PUT', 'http://example.local', [], Utils::streamFor('foo=bar&hello=world')); $curl = $this->curlFormatter->format($request); - expect($curl)->toContain("-d 'foo=bar&hello=world'"); + expect(str_replace('"', '\'', $curl))->toContain("-d 'foo=bar&hello=world'"); expect($curl)->toContain("-X PUT"); });