Skip to content
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
61 changes: 61 additions & 0 deletions spec/Formatter/CurlCommandFormatterSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace spec\Http\Message\Formatter;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use PhpSpec\ObjectBehavior;

class CurlCommandFormatterSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->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->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\' -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'." test' bar");
$body->getSize()->willReturn(1);
$body->isSeekable()->willReturn(true);
$body->rewind()->willReturn(true);

$uri->withFragment('')->shouldBeCalled()->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 test'\'' bar'");
}

function it_does_nothing_for_response(ResponseInterface $response)
{
$this->formatResponse($response)->shouldReturn('');
}
}
80 changes: 80 additions & 0 deletions src/Formatter/CurlCommandFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace Http\Message\Formatter;

use Http\Message\Formatter;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
* A formatter that prints a cURL command for HTTP requests.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class CurlCommandFormatter implements Formatter
{
/**
* {@inheritdoc}
*/
public function formatRequest(RequestInterface $request)
{
$command = sprintf('curl %s', escapeshellarg((string) $request->getUri()->withFragment('')));
if ($request->getProtocolVersion() === '1.0') {
$command .= ' --http1.0';
} elseif ($request->getProtocolVersion() === '2.0') {
$command .= ' --http2';
}

$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()) {
return 'Cant format Request as cUrl command if body stream is not seekable.';
}
$command .= sprintf(' --data %s', escapeshellarg($body->__toString()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder if we should have some sort of sanity check to not put huge files in here? say if the body size > 5000 we truncate or just do something like [too large body] in the command? if we use this formatter eg in the symfony debug toolbar, it could break on large files

$body->rewind();
}

return $command;
}

/**
* {@inheritdoc}
*/
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;
}
}