From 5d0310363aec504c952c3fd5798c0c200aa084ea Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Thu, 28 Jul 2016 21:33:44 +0200 Subject: [PATCH 1/9] Added cURL formatter --- spec/Formatter/CurlCommandFormatterSpec.php | 58 +++++++++++++++++++++ src/Formatter/CurlCommandFormatter.php | 50 ++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 spec/Formatter/CurlCommandFormatterSpec.php create mode 100644 src/Formatter/CurlCommandFormatter.php diff --git a/spec/Formatter/CurlCommandFormatterSpec.php b/spec/Formatter/CurlCommandFormatterSpec.php new file mode 100644 index 0000000..029a9a3 --- /dev/null +++ b/spec/Formatter/CurlCommandFormatterSpec.php @@ -0,0 +1,58 @@ +shouldHaveType('Http\Message\Formatter\CurlCommandFormatter'); + } + + function it_is_a_formatter() + { + $this->shouldImplement('Http\Message\Formatter'); + } + + function it_formats_the_request(RequestInterface $request, UriInterface $uri, StreamInterface $body) + { + $request->getUri()->willReturn($uri); + $request->getBody()->willReturn($body); + + $uri->__toString()->willReturn('http://foo.com/bar'); + $request->getMethod()->willReturn('GET'); + $request->getProtocolVersion()->willReturn('1.1'); + + $request->getHeaders()->willReturn(['foo'=>['bar', 'baz']]); + $request->getHeaderLine('foo')->willReturn('bar, baz'); + + $this->formatRequest($request)->shouldReturn('curl \'http://foo.com/bar\' --request GET -H \'foo: bar, baz\''); + } + + function it_formats_post_request(RequestInterface $request, UriInterface $uri, StreamInterface $body) + { + $request->getUri()->willReturn($uri); + $request->getBody()->willReturn($body); + + $body->__toString()->willReturn('body data'); + + $uri->__toString()->willReturn('http://foo.com/bar'); + $request->getMethod()->willReturn('POST'); + $request->getProtocolVersion()->willReturn('2.0'); + + $request->getHeaders()->willReturn([]); + + $this->formatRequest($request)->shouldReturn('curl \'http://foo.com/bar\' --http2 --request POST --data \'body data\''); + } + + function it_does_nothing_for_response(ResponseInterface $response) + { + $this->formatResponse($response)->shouldReturn(''); + } +} diff --git a/src/Formatter/CurlCommandFormatter.php b/src/Formatter/CurlCommandFormatter.php new file mode 100644 index 0000000..484ba83 --- /dev/null +++ b/src/Formatter/CurlCommandFormatter.php @@ -0,0 +1,50 @@ + + */ +class CurlCommandFormatter implements Formatter +{ + /** + * {@inheritdoc} + */ + public function formatRequest(RequestInterface $request) + { + $command = sprintf('curl \'%s\'', $request->getUri()); + if ($request->getProtocolVersion() === '1.0') { + $command.=' --http1.0'; + } elseif ($request->getProtocolVersion() === '2.0') { + $command.=' --http2'; + } + + $command .= ' --request '.$request->getMethod(); + + foreach ($request->getHeaders() as $name => $values) { + $command .= sprintf(' -H \'%s: %s\'',$name, $request->getHeaderLine($name)); + } + + $body = $request->getBody()->__toString(); + if (!empty($body)) { + $command .= sprintf(' --data \'%s\'',$body); + } + + return $command; + } + + /** + * {@inheritdoc} + */ + public function formatResponse(ResponseInterface $response) + { + return ''; + } +} From 60563076298a09108a4fbe64f38db57b092e15ba Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Thu, 28 Jul 2016 21:35:43 +0200 Subject: [PATCH 2/9] style fix --- src/Formatter/CurlCommandFormatter.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Formatter/CurlCommandFormatter.php b/src/Formatter/CurlCommandFormatter.php index 484ba83..785f734 100644 --- a/src/Formatter/CurlCommandFormatter.php +++ b/src/Formatter/CurlCommandFormatter.php @@ -3,7 +3,6 @@ namespace Http\Message\Formatter; use Http\Message\Formatter; -use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -21,20 +20,20 @@ public function formatRequest(RequestInterface $request) { $command = sprintf('curl \'%s\'', $request->getUri()); if ($request->getProtocolVersion() === '1.0') { - $command.=' --http1.0'; + $command .= ' --http1.0'; } elseif ($request->getProtocolVersion() === '2.0') { - $command.=' --http2'; + $command .= ' --http2'; } $command .= ' --request '.$request->getMethod(); foreach ($request->getHeaders() as $name => $values) { - $command .= sprintf(' -H \'%s: %s\'',$name, $request->getHeaderLine($name)); + $command .= sprintf(' -H \'%s: %s\'', $name, $request->getHeaderLine($name)); } $body = $request->getBody()->__toString(); if (!empty($body)) { - $command .= sprintf(' --data \'%s\'',$body); + $command .= sprintf(' --data \'%s\'', $body); } return $command; From 26687cc58dbbaacd80690533fc821ef5fef0c22c Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Sun, 31 Jul 2016 20:14:17 +0200 Subject: [PATCH 3/9] Escape body --- src/Formatter/CurlCommandFormatter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Formatter/CurlCommandFormatter.php b/src/Formatter/CurlCommandFormatter.php index 785f734..b5aac9f 100644 --- a/src/Formatter/CurlCommandFormatter.php +++ b/src/Formatter/CurlCommandFormatter.php @@ -33,7 +33,7 @@ public function formatRequest(RequestInterface $request) $body = $request->getBody()->__toString(); if (!empty($body)) { - $command .= sprintf(' --data \'%s\'', $body); + $command .= sprintf(' --data \'%s\'', escapeshellarg($body)); } return $command; From 105d882fdf4b6f25839cb65b099dffe38456a4ea Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Mon, 1 Aug 2016 10:23:29 +0200 Subject: [PATCH 4/9] Added tests for escaped body --- spec/Formatter/CurlCommandFormatterSpec.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/Formatter/CurlCommandFormatterSpec.php b/spec/Formatter/CurlCommandFormatterSpec.php index 029a9a3..fc5220b 100644 --- a/spec/Formatter/CurlCommandFormatterSpec.php +++ b/spec/Formatter/CurlCommandFormatterSpec.php @@ -40,7 +40,7 @@ function it_formats_post_request(RequestInterface $request, UriInterface $uri, S $request->getUri()->willReturn($uri); $request->getBody()->willReturn($body); - $body->__toString()->willReturn('body data'); + $body->__toString()->willReturn('body " data'." test' bar"); $uri->__toString()->willReturn('http://foo.com/bar'); $request->getMethod()->willReturn('POST'); @@ -48,7 +48,7 @@ function it_formats_post_request(RequestInterface $request, UriInterface $uri, S $request->getHeaders()->willReturn([]); - $this->formatRequest($request)->shouldReturn('curl \'http://foo.com/bar\' --http2 --request POST --data \'body data\''); + $this->formatRequest($request)->shouldReturn("curl 'http://foo.com/bar' --http2 --request POST --data 'body \" data test'\'' bar'"); } function it_does_nothing_for_response(ResponseInterface $response) From ebe52028dc13a85813107f5ef8d71d5b29e1fa63 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Mon, 1 Aug 2016 10:24:08 +0200 Subject: [PATCH 5/9] Make sure to rewind stream. --- src/Formatter/CurlCommandFormatter.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Formatter/CurlCommandFormatter.php b/src/Formatter/CurlCommandFormatter.php index b5aac9f..117c33b 100644 --- a/src/Formatter/CurlCommandFormatter.php +++ b/src/Formatter/CurlCommandFormatter.php @@ -31,9 +31,13 @@ public function formatRequest(RequestInterface $request) $command .= sprintf(' -H \'%s: %s\'', $name, $request->getHeaderLine($name)); } - $body = $request->getBody()->__toString(); - if (!empty($body)) { - $command .= sprintf(' --data \'%s\'', escapeshellarg($body)); + $body = $request->getBody(); + if ($body->getSize() > 0) { + if (!$body->isSeekable()) { + throw new \RuntimeException('Cannot take data from a stream that is not seekable'); + } + $command .= sprintf(' --data %s', escapeshellarg($body->__toString())); + $body->rewind(); } return $command; From bd069f707b19c12dd2275331a67109ad813d2631 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Mon, 1 Aug 2016 10:25:23 +0200 Subject: [PATCH 6/9] Quote everything properly --- src/Formatter/CurlCommandFormatter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Formatter/CurlCommandFormatter.php b/src/Formatter/CurlCommandFormatter.php index 117c33b..a0c3284 100644 --- a/src/Formatter/CurlCommandFormatter.php +++ b/src/Formatter/CurlCommandFormatter.php @@ -18,7 +18,7 @@ class CurlCommandFormatter implements Formatter */ public function formatRequest(RequestInterface $request) { - $command = sprintf('curl \'%s\'', $request->getUri()); + $command = sprintf('curl %s', escapeshellarg($request->getUri())); if ($request->getProtocolVersion() === '1.0') { $command .= ' --http1.0'; } elseif ($request->getProtocolVersion() === '2.0') { @@ -28,7 +28,7 @@ public function formatRequest(RequestInterface $request) $command .= ' --request '.$request->getMethod(); foreach ($request->getHeaders() as $name => $values) { - $command .= sprintf(' -H \'%s: %s\'', $name, $request->getHeaderLine($name)); + $command .= sprintf(' -H %s', escapeshellarg($name.': '.$request->getHeaderLine($name))); } $body = $request->getBody(); From 24f35dfff547ab76ee70e4f65cd7b7482371a093 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Mon, 1 Aug 2016 10:28:17 +0200 Subject: [PATCH 7/9] Update tests --- spec/Formatter/CurlCommandFormatterSpec.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/Formatter/CurlCommandFormatterSpec.php b/spec/Formatter/CurlCommandFormatterSpec.php index fc5220b..09e110e 100644 --- a/spec/Formatter/CurlCommandFormatterSpec.php +++ b/spec/Formatter/CurlCommandFormatterSpec.php @@ -41,6 +41,9 @@ function it_formats_post_request(RequestInterface $request, UriInterface $uri, S $request->getBody()->willReturn($body); $body->__toString()->willReturn('body " data'." test' bar"); + $body->getSize()->willReturn(1); + $body->isSeekable()->willReturn(true); + $body->rewind()->willReturn(true); $uri->__toString()->willReturn('http://foo.com/bar'); $request->getMethod()->willReturn('POST'); From df3912932bda7644be47fd22359f61f4a5385388 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 2 Aug 2016 13:47:57 +0200 Subject: [PATCH 8/9] Fixes borrowed from namshi/cuzzle --- spec/Formatter/CurlCommandFormatterSpec.php | 6 ++-- src/Formatter/CurlCommandFormatter.php | 39 +++++++++++++++++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/spec/Formatter/CurlCommandFormatterSpec.php b/spec/Formatter/CurlCommandFormatterSpec.php index 09e110e..50fd374 100644 --- a/spec/Formatter/CurlCommandFormatterSpec.php +++ b/spec/Formatter/CurlCommandFormatterSpec.php @@ -25,14 +25,14 @@ function it_formats_the_request(RequestInterface $request, UriInterface $uri, St $request->getUri()->willReturn($uri); $request->getBody()->willReturn($body); - $uri->__toString()->willReturn('http://foo.com/bar'); + $uri->withFragment('')->shouldBeCalled()->willReturn('http://foo.com/bar'); $request->getMethod()->willReturn('GET'); $request->getProtocolVersion()->willReturn('1.1'); $request->getHeaders()->willReturn(['foo'=>['bar', 'baz']]); $request->getHeaderLine('foo')->willReturn('bar, baz'); - $this->formatRequest($request)->shouldReturn('curl \'http://foo.com/bar\' --request GET -H \'foo: bar, baz\''); + $this->formatRequest($request)->shouldReturn('curl \'http://foo.com/bar\' -H \'foo: bar, baz\''); } function it_formats_post_request(RequestInterface $request, UriInterface $uri, StreamInterface $body) @@ -45,7 +45,7 @@ function it_formats_post_request(RequestInterface $request, UriInterface $uri, S $body->isSeekable()->willReturn(true); $body->rewind()->willReturn(true); - $uri->__toString()->willReturn('http://foo.com/bar'); + $uri->withFragment('')->shouldBeCalled()->willReturn('http://foo.com/bar'); $request->getMethod()->willReturn('POST'); $request->getProtocolVersion()->willReturn('2.0'); diff --git a/src/Formatter/CurlCommandFormatter.php b/src/Formatter/CurlCommandFormatter.php index a0c3284..5364ccc 100644 --- a/src/Formatter/CurlCommandFormatter.php +++ b/src/Formatter/CurlCommandFormatter.php @@ -18,23 +18,26 @@ class CurlCommandFormatter implements Formatter */ public function formatRequest(RequestInterface $request) { - $command = sprintf('curl %s', escapeshellarg($request->getUri())); + $command = sprintf('curl %s', escapeshellarg((string) $request->getUri()->withFragment(''))); if ($request->getProtocolVersion() === '1.0') { $command .= ' --http1.0'; } elseif ($request->getProtocolVersion() === '2.0') { $command .= ' --http2'; } - $command .= ' --request '.$request->getMethod(); - - foreach ($request->getHeaders() as $name => $values) { - $command .= sprintf(' -H %s', escapeshellarg($name.': '.$request->getHeaderLine($name))); + $method = strtoupper($request->getMethod()); + if ('HEAD' === $method) { + $command .= ' --head'; + } elseif ('GET' !== $method) { + $command .= ' --request '.$method; } + $command .= $this->getHeadersAsCommandOptions($request); + $body = $request->getBody(); if ($body->getSize() > 0) { if (!$body->isSeekable()) { - throw new \RuntimeException('Cannot take data from a stream that is not seekable'); + return 'Cant format Request as cUrl command if body stream is not seekable.'; } $command .= sprintf(' --data %s', escapeshellarg($body->__toString())); $body->rewind(); @@ -50,4 +53,28 @@ public function formatResponse(ResponseInterface $response) { return ''; } + + /** + * @param RequestInterface $request + * + * @return string + */ + private function getHeadersAsCommandOptions(RequestInterface $request) + { + $command = ''; + foreach ($request->getHeaders() as $name => $values) { + if ('host' === strtolower($name) && $values[0] === $request->getUri()->getHost()) { + continue; + } + + if ('user-agent' === strtolower($name)) { + $command .= sprintf('-A %s', escapeshellarg($values[0])); + continue; + } + + $command .= sprintf(' -H %s', escapeshellarg($name.': '.$request->getHeaderLine($name))); + } + + return $command; + } } From 2aa729b7c8bcf8494ff8edc27ca5df5b8fb737b9 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 2 Aug 2016 14:14:41 +0200 Subject: [PATCH 9/9] Added credits section --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 34580fe..338b415 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,9 @@ $ composer test Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html). +## Cretids + +Thanks to [Cuzzle](https://github.com/namshi/cuzzle) for inpiration for the `CurlCommandFormatter`. ## Security