From a16f32484bdd35fd344ce808cff90c6dc3b059a8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 14 Aug 2020 15:55:32 +0100 Subject: [PATCH] Downloader: add a max_file_size to prevent too big files to be downloaded --- .../MaxFileSizeExceededException.php | 7 +++++ src/Composer/Util/Http/CurlDownloader.php | 13 +++++++++ src/Composer/Util/RemoteFilesystem.php | 29 +++++++++++++++---- 3 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 src/Composer/Downloader/MaxFileSizeExceededException.php diff --git a/src/Composer/Downloader/MaxFileSizeExceededException.php b/src/Composer/Downloader/MaxFileSizeExceededException.php new file mode 100644 index 000000000..f50b52e79 --- /dev/null +++ b/src/Composer/Downloader/MaxFileSizeExceededException.php @@ -0,0 +1,7 @@ +jobs[$i]['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 //$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress); } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 615de0adb..676438c92 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -13,6 +13,7 @@ namespace Composer\Util; use Composer\Config; +use Composer\Downloader\MaxFileSizeExceededException; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; @@ -244,6 +245,11 @@ class RemoteFilesystem $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'))); $actualContextOptions = stream_context_get_options($ctx); @@ -273,7 +279,7 @@ class RemoteFilesystem }); $http_response_header = array(); 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])) { $statusCode = $this->findStatusCode($http_response_header); @@ -532,23 +538,34 @@ class RemoteFilesystem /** * Get contents of remote URL. * - * @param string $originUrl The origin URL - * @param string $fileUrl The file URL - * @param resource $context The stream context + * @param string $originUrl The origin URL + * @param string $fileUrl The file URL + * @param resource $context The stream context + * @param int $maxFileSize The maximum allowed file size * * @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; try { $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 (\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(); if (null !== $e) {