Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure the setAllowedMaxFileSize wont allow config values to exceed PHP limits #469

Merged
merged 1 commit into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 36 additions & 15 deletions src/Upload_Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ public function getLargestAllowedMaxFileSize()
*/
public function getAllowedMaxFileSize($ext = null)
{

// Check if there is any defined instance max file sizes
if (empty($this->allowedMaxFileSize)) {
// Set default max file sizes if there isn't
Expand All @@ -131,9 +130,7 @@ public function getAllowedMaxFileSize($ext = null)
$this->setAllowedMaxFileSize($fileSize);
} else {
// When no default is present, use maximum set by PHP
$maxUpload = Convert::memstring2bytes(ini_get('upload_max_filesize'));
$maxPost = Convert::memstring2bytes(ini_get('post_max_size'));
$this->setAllowedMaxFileSize(min($maxUpload, $maxPost));
$this->setAllowedMaxFileSize($this->maxUploadSizeFromPHPIni());
}
}

Expand Down Expand Up @@ -174,25 +171,49 @@ public function setAllowedMaxFileSize($rules)
$finalRules = [];

foreach ($rules as $rule => $value) {
if (is_numeric($value)) {
$tmpSize = $value;
} else {
$tmpSize = Convert::memstring2bytes($value);
}

$finalRules[$rule] = (int)$tmpSize;
$finalRules[$rule] = $this->parseAndVerifyAllowedSize($value);
}

$this->allowedMaxFileSize = $finalRules;
} elseif (is_string($rules)) {
$this->allowedMaxFileSize['*'] = Convert::memstring2bytes($rules);
} elseif ((int)$rules > 0) {
$this->allowedMaxFileSize['*'] = (int)$rules;
} else {
$this->allowedMaxFileSize['*'] = $this->parseAndVerifyAllowedSize($rules);
}

return $this;
}

/**
* Converts values in bytes or INI format to bytes.
* Will fall back to the default PHP size if null|0|''
* Verify the parsed size will not exceed PHP's ini settings
*/
private function parseAndVerifyAllowedSize($value): int
{
$maxPHPAllows = $this->maxUploadSizeFromPHPIni();

if (is_numeric($value)) {
$tmpSize = (int)$value;
} elseif (is_string($value)) {
$tmpSize = Convert::memstring2bytes($value);
}

if (empty($tmpSize)) {
$tmpSize = $maxPHPAllows;
}

return min($maxPHPAllows, $tmpSize);
}

/**
* Checks the two ini settings to work out the max upload size php would allow
*/
private function maxUploadSizeFromPHPIni(): int
{
$maxUpload = Convert::memstring2bytes(ini_get('upload_max_filesize'));
$maxPost = Convert::memstring2bytes(ini_get('post_max_size'));
return min($maxUpload, $maxPost);
}

/**
* @return array
*/
Expand Down
61 changes: 61 additions & 0 deletions tests/php/UploadTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace SilverStripe\Assets\Tests;

use ReflectionMethod;
use SilverStripe\Core\Convert;
use Silverstripe\Assets\Dev\TestAssetStore;
use SilverStripe\Assets\File;
use SilverStripe\Assets\Folder;
Expand Down Expand Up @@ -181,6 +183,65 @@ public function testAllowedFilesize()
$this->assertFalse($result, 'Load failed because size was too big');
}

public function testAllowedSizeWontExceedPHPConfig()
{
$v = new Upload_Validator();
$reflectionMethod = new ReflectionMethod($v, 'maxUploadSizeFromPHPIni');
$reflectionMethod->setAccessible(true);
$maxUploadSize = $reflectionMethod->invoke($v);

// Bump up the size by about 2MB.
// This would result in a hard PHP error if upload was attempted.
$exceedsMaxPHP = $maxUploadSize + 2000000;

$v->setAllowedMaxFileSize([
'jpg' => Convert::bytes2memstring($exceedsMaxPHP),
'*' => Convert::bytes2memstring($exceedsMaxPHP),
]);

// Our config exceeded the ini settings for PHP, so fall back to that size
$this->assertEquals($maxUploadSize, $v->getAllowedMaxFileSize('jpg'));
$this->assertEquals($maxUploadSize, $v->getAllowedMaxFileSize('png'));
}

public function testWeFallBackToMaxPHPConfigIfEmptyArrayPassedForConfig()
{
$v = new Upload_Validator();
$reflectionMethod = new ReflectionMethod($v, 'maxUploadSizeFromPHPIni');
$reflectionMethod->setAccessible(true);
$maxUploadSize = $reflectionMethod->invoke($v);

$v->setAllowedMaxFileSize([]);

// We just fall back to default, which is the max php upload allowed
$this->assertEquals($maxUploadSize, $v->getAllowedMaxFileSize('jpg'));
$this->assertEquals($maxUploadSize, $v->getAllowedMaxFileSize('png'));
}

public function testWeHandleEmptyOrNullFileSizeValues()
{
$v = new Upload_Validator();
$reflectionMethod = new ReflectionMethod($v, 'maxUploadSizeFromPHPIni');
$reflectionMethod->setAccessible(true);
$maxUploadSize = $reflectionMethod->invoke($v);

/*
The test for the zip extension should be enabled once
the Convert::memstring2bytes if fixed to handle empty strings.
*/
$v->setAllowedMaxFileSize([
'jpg' => '50',
'png' => 0,
'txt' => null,
// 'zip' => '',
]);

$this->assertEquals(50, $v->getAllowedMaxFileSize('jpg'));
$this->assertEquals($maxUploadSize, $v->getAllowedMaxFileSize('png'));
$this->assertEquals($maxUploadSize, $v->getAllowedMaxFileSize('txt'));
// $this->assertEquals($maxUploadSize, $v->getAllowedMaxFileSize('zip'));
}

public function testPHPUploadErrors()
{
$configMaxFileSizes = ['*' => '1k'];
Expand Down