From 24d7b0134586a90b5b31a10179aa7c796ab4e223 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 26 May 2017 16:25:47 +0100 Subject: [PATCH] [WIP] Added SSL support. --- .travis.yml | 5 + src/Net/Http/HttpConnector.php | 4 + .../Porter/Net/Http/HttpConnectorTest.php | 98 ++++++++++++++++--- 3 files changed, 92 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2582b2c..9fddda0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,11 @@ notifications: sudo: false +addons: + apt: + packages: + - stunnel4 + language: php php: diff --git a/src/Net/Http/HttpConnector.php b/src/Net/Http/HttpConnector.php index b7462a6..b6adc06 100644 --- a/src/Net/Http/HttpConnector.php +++ b/src/Net/Http/HttpConnector.php @@ -59,6 +59,10 @@ public function fetchFreshData($source, EncapsulatedOptions $options = null) $this->options->extractHttpContextOptions(), $options ? $options->extractHttpContextOptions() : [] ), + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + ], ]) )) { $error = error_get_last(); diff --git a/test/Functional/Porter/Net/Http/HttpConnectorTest.php b/test/Functional/Porter/Net/Http/HttpConnectorTest.php index 277c7dd..2f02683 100644 --- a/test/Functional/Porter/Net/Http/HttpConnectorTest.php +++ b/test/Functional/Porter/Net/Http/HttpConnectorTest.php @@ -6,12 +6,14 @@ use ScriptFUSION\Porter\Net\Http\HttpConnector; use ScriptFUSION\Porter\Net\Http\HttpOptions; use ScriptFUSION\Porter\Net\Http\HttpServerException; +use ScriptFUSION\Porter\Specification\ImportSpecification; use ScriptFUSION\Retry\ExceptionHandler\ExponentialBackoffExceptionHandler; use Symfony\Component\Process\Process; final class HttpConnectorTest extends \PHPUnit_Framework_TestCase { const HOST = '[::1]:12345'; + const SSL_HOST = '[::1]:6666'; const URI = '/test?baz=qux'; private static $dir; @@ -39,6 +41,20 @@ public function testConnectionToLocalWebserver() self::assertRegExp("[^$header$]m", $response); } + /** + * @requires OS Linux + */ + public function testSslConnectionToLocalWebserver() + { + $server = $this->startServer('feedback'); + $this->startSsl(); + $response = $this->fetchViaSsl(); + $this->stopSsl(); + $this->stopServer($server); + + self::assertRegExp('[\AGET \Q' . self::SSL_HOST . '\E/ HTTP/\d+\.\d+$]m', $response); + } + public function testConnectionTimeout() { $this->setExpectedException(HttpConnectionException::class); @@ -70,29 +86,20 @@ public function testErrorResponse() */ private function startServer($script) { - $server = ( - new Process(sprintf( + $server = new Process( + sprintf( '%sphp -S %s %s.php', // Prevent forking on some Unix systems. file_exists('/bin/sh') ? 'exec ' : '', self::HOST, $script - )) - )->setWorkingDirectory(self::$dir); + ), + self::$dir + ); $server->start(); - // Wait for server to spawn. - \ScriptFUSION\Retry\retry(5, function () { + self::waitForHttpServer(function () { $this->fetch(); - }, function (\Exception $exception) { - static $handler; - $handler = $handler ?: new ExponentialBackoffExceptionHandler(); - - if (!$exception instanceof HttpConnectionException) { - return false; - } - - return $handler(); }); return $server; @@ -103,10 +110,71 @@ private function stopServer(Process $server) $server->stop(); } + private function startSsl() + { + $accept = str_replace($filter = ['[', ']'], null, self::SSL_HOST); + $connect = str_replace($filter, null, self::HOST); + $temp = tempnam(sys_get_temp_dir(), 'Porter'); + + (new Process( + // Generate blank, self-signed SSL certificate. + "openssl req -new -x509 -nodes -subj / -keyout '$temp' -out '$temp' + + { stunnel4 -fd 0 || stunnel -fd 0; } <<. + # Disable PID to run as non-root user. + pid= + # Must run as foreground process on Travis, for some reason. + foreground=yes + + [] + cert=$temp + accept=$accept + connect=$connect +." + ))->start(); + + self::waitForHttpServer(function () { + $this->fetchViaSsl(); + }); + } + + private function stopSsl() + { + `pkill stunnel`; + } + private function fetch(Connector $connector = null) { $connector = $connector ?: $this->connector; return $connector->fetch('http://' . self::HOST . self::URI); } + + private function fetchViaSsl() + { + return $this->connector->fetch('https://' . self::SSL_HOST); + } + + /** + * Waits for the specified HTTP server invoker to stop throwing errors. + * + * @param \Closure $serviceInvoker HTTP service invoker. + */ + private static function waitForHttpServer(\Closure $serviceInvoker) + { + \ScriptFUSION\Retry\retry( + ImportSpecification::DEFAULT_FETCH_ATTEMPTS, + $serviceInvoker, + function (\Exception $exception) { + static $handler; + $handler = $handler ?: new ExponentialBackoffExceptionHandler; + + if (!$exception instanceof HttpConnectionException) { + return false; + } + + return $handler(); + } + ); + } }