[Validator] Support "maxSize" given in KiB

This commit is contained in:
Jérémy Derussé 2014-05-31 23:12:08 +02:00 committed by Fabien Potencier
parent 5c782607ad
commit 48ed754fd3
4 changed files with 213 additions and 23 deletions

View File

@ -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));
}
}
}
}

View File

@ -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")
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);
}

View 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),
);
}
}

View File

@ -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