Skip to content
This repository has been archived by the owner on Jan 15, 2021. It is now read-only.

Commit

Permalink
feature #48 removed dependency on ext-curl (fabpot)
Browse files Browse the repository at this point in the history
This PR was merged into the 2.0-dev branch.

Discussion
----------

removed dependency on ext-curl

Having a hard dependency on cURL is problematic. For instance, this library is a dep of SensioDistributionBundle, which is also a dep of Symfony Standard Edition. So, it means that we are forcing everyone using Symfony SE to have the PHP cURL extension installed, which is not what we want.

So, this PR falls back to using file_get_contents when cURL is not installed.

Commits
-------

d7e788a removed dependency on ext-curl
  • Loading branch information
fabpot committed Aug 3, 2015
2 parents 0a9aa5b + d7e788a commit 8d32035
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 107 deletions.
2 changes: 1 addition & 1 deletion SensioLabs/Security/Command/SecurityCheckerCommand.php
Expand Up @@ -3,7 +3,7 @@
/*
* This file is part of the SensioLabs Security Checker.
*
* (c) 2013 Fabien Potencier
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand Down
83 changes: 83 additions & 0 deletions SensioLabs/Security/Crawler/BaseCrawler.php
@@ -0,0 +1,83 @@
<?php

/*
* This file is part of the SensioLabs Security Checker.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace SensioLabs\Security\Crawler;

use SensioLabs\Security\Exception\RuntimeException;

/**
* @internal
*/
abstract class BaseCrawler implements CrawlerInterface
{
protected $endPoint = 'https://security.sensiolabs.org/check_lock';
protected $timeout = 20;

/**
* {@inheritdoc}
*/
public function setTimeout($timeout)
{
$this->timeout = $timeout;
}

/**
* {@inheritdoc}
*/
public function setEndPoint($endPoint)
{
$this->endPoint = $endPoint;
}

/**
* {@inheritdoc}
*/
public function check($lock)
{
$certFile = $this->getCertFile();

try {
list($headers, $body) = $this->doCheck($lock, $certFile);
} catch (\Exception $e) {
if (__DIR__.'/../Resources/security.sensiolabs.org.crt' !== $certFile) {
unlink($certFile);
}

throw $e;
}

if (!(preg_match('/X-Alerts: (\d+)/', $headers, $matches) || 2 == count($matches))) {
throw new RuntimeException('The web service did not return alerts count.');
}

return array(intval($matches[1]), json_decode($body, true));
}

/**
* @return array An array where the first element is a headers string and second one the response body
*/
abstract protected function doCheck($lock, $certFile);

private function getCertFile()
{
$certFile = __DIR__.'/../Resources/security.sensiolabs.org.crt';
if ('phar://' !== substr(__FILE__, 0, 7)) {
return $certFile;
}

$tmpFile = tempnam(sys_get_temp_dir(), 'sls');
if (false === @copy($certFile, $tmpFile)) {
throw new RuntimeException(sprintf('Unable to copy the certificate in "%s".', $tmpFile));
}

return $tmpFile;
}
}
31 changes: 31 additions & 0 deletions SensioLabs/Security/Crawler/CrawlerInterface.php
@@ -0,0 +1,31 @@
<?php

/*
* This file is part of the SensioLabs Security Checker.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace SensioLabs\Security\Crawler;

/**
* @internal
*/
interface CrawlerInterface
{
/**
* Checks a Composer lock file.
*
* @param string $lock The path to the composer.lock file
*
* @return An array of two items: the number of vulnerabilities and an array of vulnerabilities
*/
public function check($lock);

public function setTimeout($timeout);

public function setEndPoint($endPoint);
}
82 changes: 82 additions & 0 deletions SensioLabs/Security/Crawler/CurlCrawler.php
@@ -0,0 +1,82 @@
<?php

/*
* This file is part of the SensioLabs Security Checker.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace SensioLabs\Security\Crawler;

use SensioLabs\Security\Exception\RuntimeException;

/**
* @internal
*/
class CurlCrawler extends BaseCrawler
{
public function __construct()
{
if (!function_exists('curl_init')) {
throw new RuntimeException('cURL is required to use the cURL crawler.');
}
}

/**
* {@inheritdoc}
*/
protected function doCheck($lock, $certFile)
{
if (false === $curl = curl_init()) {
throw new RuntimeException('Unable to create a cURL handle.');
}

$postFields = array('lock' => PHP_VERSION_ID >= 50500 ? new \CurlFile($lock) : '@'.$lock);

curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_URL, $this->endPoint);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/json'));
curl_setopt($curl, CURLOPT_POSTFIELDS, $postFields);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->timeout);
curl_setopt($curl, CURLOPT_TIMEOUT, 10);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_MAXREDIRS, 3);
curl_setopt($curl, CURLOPT_FAILONERROR, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curl, CURLOPT_CAINFO, $certFile);

$response = curl_exec($curl);

if (false === $response) {
$error = curl_error($curl);
curl_close($curl);

throw new RuntimeException(sprintf('An error occurred: %s.', $error));
}

$headersSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
$headers = substr($response, 0, $headersSize);
$body = substr($response, $headersSize);

$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

if (400 == $statusCode) {
$data = json_decode($body, true);
$error = $data['error'];

throw new RuntimeException($error);
}

if (200 != $statusCode) {
throw new RuntimeException(sprintf('The web service failed for an unknown reason (HTTP %s).', $statusCode));
}

return array($headers, $body);
}
}
49 changes: 49 additions & 0 deletions SensioLabs/Security/Crawler/DefaultCrawler.php
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the SensioLabs Security Checker.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace SensioLabs\Security\Crawler;

/**
* @internal
*/
class DefaultCrawler implements CrawlerInterface
{
private $crawler;

public function __construct()
{
$this->crawler = function_exists('curl_init') ? new CurlCrawler() : new FileGetContentsCrawler();
}

/**
* {@inheritdoc}
*/
public function check($lock)
{
return $this->crawler->check($lock);
}

/**
* {@inheritdoc}
*/
public function setTimeout($timeout)
{
$this->crawler->setTimeout($timeout);
}

/**
* {@inheritdoc}
*/
public function setEndPoint($endPoint)
{
$this->crawler->setEndPoint($endPoint);
}
}
78 changes: 78 additions & 0 deletions SensioLabs/Security/Crawler/FileGetContentsCrawler.php
@@ -0,0 +1,78 @@
<?php

/*
* This file is part of the SensioLabs Security Checker.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace SensioLabs\Security\Crawler;

use SensioLabs\Security\Exception\RuntimeException;

/**
* @internal
*/
class FileGetContentsCrawler extends BaseCrawler
{
/**
* {@inheritdoc}
*/
protected function doCheck($lock, $certFile)
{
$boundary = '------------------------'.md5(microtime(true));
$context = stream_context_create(array(
'http' => array(
'method' => 'POST',
'header' => "Content-Type: multipart/form-data; boundary=$boundary\r\nAccept: application/json",
'content' => "--$boundary\r\nContent-Disposition: form-data; name=\"lock\"; filename=\"$lock\"\r\nContent-Type: application/octet-stream\r\n\r\n".file_get_contents($lock)."\r\n--$boundary\r\n--\r\n",
'ignore_errors' => true,
'follow_location' => true,
'max_redirects' => 3,
'timeout' => $this->timeout,
),
'ssl' => array(
'cafile' => $certFile,
'verify_peer' => 1,
'verify_host' => 2,
),
));

$level = error_reporting(0);
$body = file_get_contents($this->endPoint, 0, $context);
error_reporting($level);
if (false === $body) {
$error = error_get_last();

throw new RuntimeException(sprintf('An error occurred: %s.', $error['message']));
}

// status code
if (!preg_match('{HTTP/\d\.\d (\d+) }i', $http_response_header[0], $match)) {
throw new RuntimeException('An unknown error occurred.');
}

$statusCode = $match[1];
if (400 == $statusCode) {
$data = json_decode($body, true);

throw new RuntimeException($data['error']);
}

if (200 != $statusCode) {
throw new RuntimeException(sprintf('The web service failed for an unknown reason (HTTP %s).', $statusCode));
}

$headers = '';
foreach ($http_response_header as $header) {
if (false !== strpos($header, 'X-Alerts: ')) {
$headers = $header;
}
}

return array($headers, $body);
}
}
2 changes: 1 addition & 1 deletion SensioLabs/Security/Formatters/JsonFormatter.php
Expand Up @@ -3,7 +3,7 @@
/*
* This file is part of the SensioLabs Security Checker.
*
* (c) 2013 Fabien Potencier
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand Down
2 changes: 1 addition & 1 deletion SensioLabs/Security/Formatters/SimpleFormatter.php
Expand Up @@ -3,7 +3,7 @@
/*
* This file is part of the SensioLabs Security Checker.
*
* (c) 2013 Fabien Potencier
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand Down
6 changes: 3 additions & 3 deletions SensioLabs/Security/Formatters/TextFormatter.php
Expand Up @@ -3,7 +3,7 @@
/*
* This file is part of the SensioLabs Security Checker.
*
* (c) 2013 Fabien Potencier
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand Down Expand Up @@ -67,8 +67,8 @@ public function displayResults(OutputInterface $output, $lockFilePath, array $vu
}
}

$output->writeln("<bg=yellow;fg=white> </> This checker can only detect vulnerabilities that are referenced");
$output->writeln("<bg=yellow;fg=white> Disclaimer </> in the SensioLabs security advisories database. Execute this");
$output->writeln('<bg=yellow;fg=white> </> This checker can only detect vulnerabilities that are referenced');
$output->writeln('<bg=yellow;fg=white> Disclaimer </> in the SensioLabs security advisories database. Execute this');
$output->writeln("<bg=yellow;fg=white> </> command regularly to check the newly discovered vulnerabilities.\n");
}
}

0 comments on commit 8d32035

Please sign in to comment.