[Validator] Support "maxSize" given in KiB
This commit is contained in:
parent
5c782607ad
commit
48ed754fd3
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
@ -24,6 +25,7 @@ use Symfony\Component\Validator\Constraint;
|
||||
class File extends Constraint
|
||||
{
|
||||
public $maxSize = null;
|
||||
public $binaryFormat = null;
|
||||
public $mimeTypes = array();
|
||||
public $notFoundMessage = 'The file could not be found.';
|
||||
public $notReadableMessage = 'The file is not readable.';
|
||||
@ -38,4 +40,30 @@ class File extends Constraint
|
||||
public $uploadCantWriteErrorMessage = 'Cannot write temporary file to disk.';
|
||||
public $uploadExtensionErrorMessage = 'A PHP extension caused the upload to fail.';
|
||||
public $uploadErrorMessage = 'The file could not be uploaded.';
|
||||
|
||||
public function __construct($options = null)
|
||||
{
|
||||
parent::__construct($options);
|
||||
|
||||
if ($this->maxSize) {
|
||||
if (ctype_digit((string) $this->maxSize)) {
|
||||
$this->maxSize = (int) $this->maxSize;
|
||||
$this->binaryFormat = $this->binaryFormat === null ? false : $this->binaryFormat;
|
||||
} elseif (preg_match('/^\d++k$/i', $this->maxSize)) {
|
||||
$this->maxSize = $this->maxSize * 1000;
|
||||
$this->binaryFormat = $this->binaryFormat === null ? false : $this->binaryFormat;
|
||||
} elseif (preg_match('/^\d++M$/i', $this->maxSize)) {
|
||||
$this->maxSize = $this->maxSize * 1000000;
|
||||
$this->binaryFormat = $this->binaryFormat === null ? false : $this->binaryFormat;
|
||||
} elseif (preg_match('/^\d++ki$/i', $this->maxSize)) {
|
||||
$this->maxSize = $this->maxSize << 10;
|
||||
$this->binaryFormat = $this->binaryFormat === null ? true : $this->binaryFormat;
|
||||
} elseif (preg_match('/^\d++Mi$/i', $this->maxSize)) {
|
||||
$this->maxSize = $this->maxSize << 20;
|
||||
$this->binaryFormat = $this->binaryFormat === null ? true : $this->binaryFormat;
|
||||
} else {
|
||||
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size', $this->maxSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ use Symfony\Component\HttpFoundation\File\File as FileObject;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
@ -26,13 +25,16 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
class FileValidator extends ConstraintValidator
|
||||
{
|
||||
const KB_BYTES = 1000;
|
||||
|
||||
const MB_BYTES = 1000000;
|
||||
const KIB_BYTES = 1024;
|
||||
const MIB_BYTES = 1048576;
|
||||
|
||||
private static $suffices = array(
|
||||
1 => 'bytes',
|
||||
self::KB_BYTES => 'kB',
|
||||
self::MB_BYTES => 'MB',
|
||||
self::KIB_BYTES => 'KiB',
|
||||
self::MIB_BYTES => 'MiB',
|
||||
);
|
||||
|
||||
/**
|
||||
@ -52,16 +54,7 @@ class FileValidator extends ConstraintValidator
|
||||
switch ($value->getError()) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
if ($constraint->maxSize) {
|
||||
if (ctype_digit((string) $constraint->maxSize)) {
|
||||
$limitInBytes = (int) $constraint->maxSize;
|
||||
} elseif (preg_match('/^\d++k$/', $constraint->maxSize)) {
|
||||
$limitInBytes = $constraint->maxSize * self::KB_BYTES;
|
||||
} elseif (preg_match('/^\d++M$/', $constraint->maxSize)) {
|
||||
$limitInBytes = $constraint->maxSize * self::MB_BYTES;
|
||||
} else {
|
||||
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size', $constraint->maxSize));
|
||||
}
|
||||
$limitInBytes = min(UploadedFile::getMaxFilesize(), $limitInBytes);
|
||||
$limitInBytes = min(UploadedFile::getMaxFilesize(), (int) $constraint->maxSize);
|
||||
} else {
|
||||
$limitInBytes = UploadedFile::getMaxFilesize();
|
||||
}
|
||||
@ -125,24 +118,23 @@ class FileValidator extends ConstraintValidator
|
||||
$sizeInBytes = filesize($path);
|
||||
$limitInBytes = (int) $constraint->maxSize;
|
||||
|
||||
if (preg_match('/^\d++k$/', $constraint->maxSize)) {
|
||||
$limitInBytes *= self::KB_BYTES;
|
||||
} elseif (preg_match('/^\d++M$/', $constraint->maxSize)) {
|
||||
$limitInBytes *= self::MB_BYTES;
|
||||
} elseif (!ctype_digit((string) $constraint->maxSize)) {
|
||||
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size', $constraint->maxSize));
|
||||
}
|
||||
|
||||
if ($sizeInBytes > $limitInBytes) {
|
||||
// Convert the limit to the smallest possible number
|
||||
// (i.e. try "MB", then "kB", then "bytes")
|
||||
$coef = self::MB_BYTES;
|
||||
if ($constraint->binaryFormat) {
|
||||
$coef = self::MIB_BYTES;
|
||||
$coefFactor = self::KIB_BYTES;
|
||||
} else {
|
||||
$coef = self::MB_BYTES;
|
||||
$coefFactor = self::KB_BYTES;
|
||||
}
|
||||
|
||||
$limitAsString = (string) ($limitInBytes / $coef);
|
||||
|
||||
// Restrict the limit to 2 decimals (without rounding! we
|
||||
// need the precise value)
|
||||
while (self::moreDecimalsThan($limitAsString, 2)) {
|
||||
$coef /= 1000;
|
||||
$coef /= $coefFactor;
|
||||
$limitAsString = (string) ($limitInBytes / $coef);
|
||||
}
|
||||
|
||||
@ -152,7 +144,7 @@ class FileValidator extends ConstraintValidator
|
||||
// If the size and limit produce the same string output
|
||||
// (due to rounding), reduce the coefficient
|
||||
while ($sizeAsString === $limitAsString) {
|
||||
$coef /= 1000;
|
||||
$coef /= $coefFactor;
|
||||
$limitAsString = (string) ($limitInBytes / $coef);
|
||||
$sizeAsString = (string) round($sizeInBytes / $coef, 2);
|
||||
}
|
||||
|
108
src/Symfony/Component/Validator/Tests/Constraints/FileTest.php
Normal file
108
src/Symfony/Component/Validator/Tests/Constraints/FileTest.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Tests\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraints\File;
|
||||
|
||||
class FileTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* @param mixed $maxSize
|
||||
* @param int bytes
|
||||
* @param bool $bytes
|
||||
* @dataProvider provideValidSizes
|
||||
*/
|
||||
public function testMaxSize($maxSize, $bytes, $binaryFormat)
|
||||
{
|
||||
$file = new File(array('maxSize' => $maxSize));
|
||||
|
||||
$this->assertSame($bytes, $file->maxSize);
|
||||
$this->assertSame($binaryFormat, $file->binaryFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $maxSize
|
||||
* @param int $bytes
|
||||
* @dataProvider provideInValidSizes
|
||||
* @expectedException Symfony\Component\Validator\Exception\ConstraintDefinitionException
|
||||
*/
|
||||
public function testInvalideMaxSize($maxSize)
|
||||
{
|
||||
$file = new File(array('maxSize' => $maxSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function provideValidSizes()
|
||||
{
|
||||
return array(
|
||||
array('500', 500, false),
|
||||
array(12300, 12300, false),
|
||||
array('1ki', 1024, true),
|
||||
array('1KI', 1024, true),
|
||||
array('2k', 2000, false),
|
||||
array('2K', 2000, false),
|
||||
array('1mi', 1048576, true),
|
||||
array('1MI', 1048576, true),
|
||||
array('3m', 3000000, false),
|
||||
array('3M', 3000000, false),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function provideInvalidSizes()
|
||||
{
|
||||
return array(
|
||||
array('+100'),
|
||||
array('foo'),
|
||||
array('1Ko'),
|
||||
array('1kio'),
|
||||
array('1G'),
|
||||
array('1Gi'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $maxSize
|
||||
* @param bool $guessedFormat
|
||||
* @param bool $binaryFormat
|
||||
* @dataProvider provideFormats
|
||||
*/
|
||||
public function testBinaryFormat($maxSize, $guessedFormat, $binaryFormat)
|
||||
{
|
||||
$file = new File(array('maxSize' => $maxSize, 'binaryFormat' => $guessedFormat));
|
||||
|
||||
$this->assertSame($binaryFormat, $file->binaryFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function provideFormats()
|
||||
{
|
||||
return array(
|
||||
array(100, null, false),
|
||||
array(100, true, true),
|
||||
array(100, false, false),
|
||||
array('100K', null, false),
|
||||
array('100K', true, true),
|
||||
array('100K', false, false),
|
||||
array('100Ki', null, true),
|
||||
array('100Ki', true, true),
|
||||
array('100Ki', false, false),
|
||||
);
|
||||
}
|
||||
}
|
@ -99,27 +99,36 @@ abstract class FileValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
// round(size) == 1.01kB, limit == 1kB
|
||||
array(ceil(1.005*1000), 1000, '1.01', '1', 'kB'),
|
||||
array(ceil(1.005*1000), '1k', '1.01', '1', 'kB'),
|
||||
array(ceil(1.005*1024), '1Ki', '1.01', '1', 'KiB'),
|
||||
|
||||
// round(size) == 1kB, limit == 1kB -> use bytes
|
||||
array(ceil(1.004*1000), 1000, '1004', '1000', 'bytes'),
|
||||
array(ceil(1.004*1000), '1k', '1004', '1000', 'bytes'),
|
||||
array(ceil(1.004*1024), '1Ki', '1029', '1024', 'bytes'),
|
||||
|
||||
array(1000 + 1, 1000, '1001', '1000', 'bytes'),
|
||||
array(1000 + 1, '1k', '1001', '1000', 'bytes'),
|
||||
array(1024 + 1, '1Ki', '1025', '1024', 'bytes'),
|
||||
|
||||
// round(size) == 1.01MB, limit == 1MB
|
||||
array(ceil(1.005*1000*1000), 1000*1000, '1.01', '1', 'MB'),
|
||||
array(ceil(1.005*1000*1000), '1000k', '1.01', '1', 'MB'),
|
||||
array(ceil(1.005*1000*1000), '1M', '1.01', '1', 'MB'),
|
||||
array(ceil(1.005*1024*1024), '1024Ki', '1.01', '1', 'MiB'),
|
||||
array(ceil(1.005*1024*1024), '1Mi', '1.01', '1', 'MiB'),
|
||||
|
||||
// round(size) == 1MB, limit == 1MB -> use kB
|
||||
array(ceil(1.004*1000*1000), 1000*1000, '1004', '1000', 'kB'),
|
||||
array(ceil(1.004*1000*1000), '1000k', '1004', '1000', 'kB'),
|
||||
array(ceil(1.004*1000*1000), '1M', '1004', '1000', 'kB'),
|
||||
array(ceil(1.004*1024*1024), '1024Ki', '1028.1', '1024', 'KiB'),
|
||||
array(ceil(1.004*1024*1024), '1Mi', '1028.1', '1024', 'KiB'),
|
||||
|
||||
array(1000*1000 + 1, 1000*1000, '1000001', '1000000', 'bytes'),
|
||||
array(1000*1000 + 1, '1000k', '1000001', '1000000', 'bytes'),
|
||||
array(1000*1000 + 1, '1M', '1000001', '1000000', 'bytes'),
|
||||
array(1024*1024 + 1, '1024Ki', '1048577', '1048576', 'bytes'),
|
||||
array(1024*1024 + 1, '1Mi', '1048577', '1048576', 'bytes'),
|
||||
);
|
||||
}
|
||||
|
||||
@ -157,9 +166,13 @@ abstract class FileValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
array(1000, '1k'),
|
||||
array(1000 - 1, '1k'),
|
||||
array(1024, '1Ki'),
|
||||
array(1024 - 1, '1Ki'),
|
||||
|
||||
array(1000*1000, '1M'),
|
||||
array(1000*1000 - 1, '1M'),
|
||||
array(1024*1024, '1Mi'),
|
||||
array(1024*1024 - 1, '1Mi'),
|
||||
);
|
||||
}
|
||||
|
||||
@ -195,6 +208,55 @@ abstract class FileValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
$this->validator->validate($this->path, $constraint);
|
||||
}
|
||||
|
||||
public function provideBinaryFormatTests()
|
||||
{
|
||||
return array(
|
||||
array(11, 10, null, '11', '10', 'bytes'),
|
||||
array(11, 10, true, '11', '10', 'bytes'),
|
||||
array(11, 10, false, '11', '10', 'bytes'),
|
||||
|
||||
// round(size) == 1.01kB, limit == 1kB
|
||||
array(ceil(1000*1.01), 1000, null, '1.01', '1', 'kB'),
|
||||
array(ceil(1000*1.01), '1k', null, '1.01', '1', 'kB'),
|
||||
array(ceil(1024*1.01), '1Ki', null, '1.01', '1', 'KiB'),
|
||||
|
||||
array(ceil(1024*1.01), 1024, true, '1.01', '1', 'KiB'),
|
||||
array(ceil(1024*1.01*1000), '1024k', true, '1010', '1000', 'KiB'),
|
||||
array(ceil(1024*1.01), '1Ki', true, '1.01', '1', 'KiB'),
|
||||
|
||||
array(ceil(1000*1.01), 1000, false, '1.01', '1', 'kB'),
|
||||
array(ceil(1000*1.01), '1k', false, '1.01', '1', 'kB'),
|
||||
array(ceil(1024*1.01*10), '10Ki', false, '10.34', '10.24', 'kB'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideBinaryFormatTests
|
||||
*/
|
||||
public function testBinaryFormat($bytesWritten, $limit, $binaryFormat, $sizeAsString, $limitAsString, $suffix)
|
||||
{
|
||||
fseek($this->file, $bytesWritten-1, SEEK_SET);
|
||||
fwrite($this->file, '0');
|
||||
fclose($this->file);
|
||||
|
||||
$constraint = new File(array(
|
||||
'maxSize' => $limit,
|
||||
'binaryFormat' => $binaryFormat,
|
||||
'maxSizeMessage' => 'myMessage',
|
||||
));
|
||||
|
||||
$this->context->expects($this->once())
|
||||
->method('addViolation')
|
||||
->with('myMessage', array(
|
||||
'{{ limit }}' => $limitAsString,
|
||||
'{{ size }}' => $sizeAsString,
|
||||
'{{ suffix }}' => $suffix,
|
||||
'{{ file }}' => $this->path,
|
||||
));
|
||||
|
||||
$this->validator->validate($this->getFile($this->path), $constraint);
|
||||
}
|
||||
|
||||
public function testValidMimeType()
|
||||
{
|
||||
$file = $this
|
||||
|
Reference in New Issue
Block a user