1
0
Fork 0

Merge pull request #9130 from glaubinix/t/max-file-size

Downloader: add a max_file_size option to prevent too big files to be downloaded
pull/9146/head
Jordi Boggiano 2020-08-23 13:37:12 +02:00 committed by GitHub
commit 9ea9d20b21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 43 additions and 6 deletions

View File

@ -0,0 +1,7 @@
<?php
namespace Composer\Downloader;
class MaxFileSizeExceededException extends TransportException
{
}

View File

@ -13,6 +13,7 @@
namespace Composer\Util\Http; namespace Composer\Util\Http;
use Composer\Config; use Composer\Config;
use Composer\Downloader\MaxFileSizeExceededException;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Downloader\TransportException; use Composer\Downloader\TransportException;
use Composer\CaBundle\CaBundle; use Composer\CaBundle\CaBundle;
@ -365,6 +366,18 @@ class CurlDownloader
$previousProgress = $this->jobs[$i]['progress']; $previousProgress = $this->jobs[$i]['progress'];
$this->jobs[$i]['progress'] = $progress; $this->jobs[$i]['progress'] = $progress;
if (isset($this->jobs[$i]['options']['max_file_size'])) {
// Compare max_file_size with the content-length header this value will be -1 until the header is parsed
if ($this->jobs[$i]['options']['max_file_size'] < $progress['download_content_length']) {
throw new MaxFileSizeExceededException('Maximum allowed download size reached. Content-length header indicates ' . $progress['download_content_length'] . ' bytes. Allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes');
}
// Compare max_file_size with the download size in bytes
if ($this->jobs[$i]['options']['max_file_size'] < $progress['size_download']) {
throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . $progress['size_download'] . ' of allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes');
}
}
// TODO // TODO
//$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress); //$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress);
} }

View File

@ -13,6 +13,7 @@
namespace Composer\Util; namespace Composer\Util;
use Composer\Config; use Composer\Config;
use Composer\Downloader\MaxFileSizeExceededException;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Downloader\TransportException; use Composer\Downloader\TransportException;
use Composer\CaBundle\CaBundle; use Composer\CaBundle\CaBundle;
@ -244,6 +245,11 @@ class RemoteFilesystem
$degradedPackagist = true; $degradedPackagist = true;
} }
$maxFileSize = null;
if (isset($options['max_file_size'])) {
$maxFileSize = $options['max_file_size'];
}
$ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet'))); $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet')));
$actualContextOptions = stream_context_get_options($ctx); $actualContextOptions = stream_context_get_options($ctx);
@ -273,7 +279,7 @@ class RemoteFilesystem
}); });
$http_response_header = array(); $http_response_header = array();
try { try {
$result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header); $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header, $maxFileSize);
if (!empty($http_response_header[0])) { if (!empty($http_response_header[0])) {
$statusCode = $this->findStatusCode($http_response_header); $statusCode = $this->findStatusCode($http_response_header);
@ -532,23 +538,34 @@ class RemoteFilesystem
/** /**
* Get contents of remote URL. * Get contents of remote URL.
* *
* @param string $originUrl The origin URL * @param string $originUrl The origin URL
* @param string $fileUrl The file URL * @param string $fileUrl The file URL
* @param resource $context The stream context * @param resource $context The stream context
* @param int $maxFileSize The maximum allowed file size
* *
* @return string|false The response contents or false on failure * @return string|false The response contents or false on failure
*/ */
protected function getRemoteContents($originUrl, $fileUrl, $context, array &$responseHeaders = null) protected function getRemoteContents($originUrl, $fileUrl, $context, array &$responseHeaders = null, $maxFileSize = null)
{ {
$result = false; $result = false;
try { try {
$e = null; $e = null;
$result = file_get_contents($fileUrl, false, $context); if ($maxFileSize !== null) {
$result = file_get_contents($fileUrl, false, $context, 0, $maxFileSize);
} else {
// passing `null` to file_get_contents will convert `null` to `0` and return 0 bytes
$result = file_get_contents($fileUrl, false, $context);
}
} catch (\Throwable $e) { } catch (\Throwable $e) {
} catch (\Exception $e) { } catch (\Exception $e) {
} }
if ($maxFileSize !== null && Platform::strlen($result) >= $maxFileSize) {
throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($result) . ' of allowed ' . $maxFileSize . ' bytes');
}
$responseHeaders = isset($http_response_header) ? $http_response_header : array(); $responseHeaders = isset($http_response_header) ? $http_response_header : array();
if (null !== $e) { if (null !== $e) {