diff --git a/src/Symfony/Component/Console/Helper/Helper.php b/src/Symfony/Component/Console/Helper/Helper.php index 05e895f765a1a..cc3205522e657 100644 --- a/src/Symfony/Component/Console/Helper/Helper.php +++ b/src/Symfony/Component/Console/Helper/Helper.php @@ -92,15 +92,15 @@ public static function formatTime($secs) public static function formatMemory($memory) { if ($memory >= 1024 * 1024 * 1024) { - return sprintf('%.1f GB', $memory / 1024 / 1024 / 1024); + return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); } if ($memory >= 1024 * 1024) { - return sprintf('%.1f MB', $memory / 1024 / 1024); + return sprintf('%.1f MiB', $memory / 1024 / 1024); } if ($memory >= 1024) { - return sprintf('%d kB', $memory / 1024); + return sprintf('%d KiB', $memory / 1024); } return sprintf('%d B', $memory); diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index 7bcfed026b9a9..ba5db2921bd2c 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -367,17 +367,17 @@ public function testAnsiColorsAndEmojis() $this->generateOutput( " \033[44;37m Starting the demo... fingers crossed \033[0m\n". " 0/15 ".$progress.str_repeat($empty, 26)." 0%\n". - " 🏁 1 sec \033[44;37m 0 B \033[0m" + " \xf0\x9f\x8f\x81 1 sec \033[44;37m 0 B \033[0m" ). $this->generateOutput( " \033[44;37m Looks good to me... \033[0m\n". " 4/15 ".str_repeat($done, 7).$progress.str_repeat($empty, 19)." 26%\n". - " 🏁 1 sec \033[41;37m 97 kB \033[0m" + " \xf0\x9f\x8f\x81 1 sec \033[41;37m 97 KiB \033[0m" ). $this->generateOutput( " \033[44;37m Thanks, bye \033[0m\n". " 15/15 ".str_repeat($done, 28)." 100%\n". - " 🏁 1 sec \033[41;37m 195 kB \033[0m" + " \xf0\x9f\x8f\x81 1 sec \033[41;37m 195 KiB \033[0m" ), stream_get_contents($output->getStream()) ); diff --git a/src/Symfony/Component/HttpFoundation/File/UploadedFile.php b/src/Symfony/Component/HttpFoundation/File/UploadedFile.php index 0bba18fb099c0..31f349c759a95 100644 --- a/src/Symfony/Component/HttpFoundation/File/UploadedFile.php +++ b/src/Symfony/Component/HttpFoundation/File/UploadedFile.php @@ -291,7 +291,7 @@ public static function getMaxFilesize() public function getErrorMessage() { static $errors = array( - UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d kb).', + UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).', UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.', UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.', UPLOAD_ERR_NO_FILE => 'No file was uploaded.', diff --git a/src/Symfony/Component/Validator/Constraints/FileValidator.php b/src/Symfony/Component/Validator/Constraints/FileValidator.php index b9d4fe1644362..22273c4dcb900 100644 --- a/src/Symfony/Component/Validator/Constraints/FileValidator.php +++ b/src/Symfony/Component/Validator/Constraints/FileValidator.php @@ -25,6 +25,16 @@ */ class FileValidator extends ConstraintValidator { + const KB_BYTES = 1000; + + const MB_BYTES = 1000000; + + private static $suffices = array( + 1 => 'bytes', + self::KB_BYTES => 'kB', + self::MB_BYTES => 'MB', + ); + /** * {@inheritdoc} */ @@ -43,21 +53,21 @@ public function validate($value, Constraint $constraint) case UPLOAD_ERR_INI_SIZE: if ($constraint->maxSize) { if (ctype_digit((string) $constraint->maxSize)) { - $maxSize = (int) $constraint->maxSize; + $limitInBytes = (int) $constraint->maxSize; } elseif (preg_match('/^\d++k$/', $constraint->maxSize)) { - $maxSize = $constraint->maxSize * 1024; + $limitInBytes = $constraint->maxSize * self::KB_BYTES; } elseif (preg_match('/^\d++M$/', $constraint->maxSize)) { - $maxSize = $constraint->maxSize * 1048576; + $limitInBytes = $constraint->maxSize * self::MB_BYTES; } else { throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size', $constraint->maxSize)); } - $maxSize = min(UploadedFile::getMaxFilesize(), $maxSize); + $limitInBytes = min(UploadedFile::getMaxFilesize(), $limitInBytes); } else { - $maxSize = UploadedFile::getMaxFilesize(); + $limitInBytes = UploadedFile::getMaxFilesize(); } $this->context->addViolation($constraint->uploadIniSizeErrorMessage, array( - '{{ limit }}' => $maxSize, + '{{ limit }}' => $limitInBytes, '{{ suffix }}' => 'bytes', )); @@ -112,27 +122,45 @@ public function validate($value, Constraint $constraint) } if ($constraint->maxSize) { - if (ctype_digit((string) $constraint->maxSize)) { - $size = filesize($path); - $limit = (int) $constraint->maxSize; - $suffix = 'bytes'; - } elseif (preg_match('/^\d++k$/', $constraint->maxSize)) { - $size = round(filesize($path) / 1000, 2); - $limit = (int) $constraint->maxSize; - $suffix = 'kB'; + $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)) { - $size = round(filesize($path) / 1000000, 2); - $limit = (int) $constraint->maxSize; - $suffix = 'MB'; - } else { + $limitInBytes *= self::MB_BYTES; + } elseif (!ctype_digit((string) $constraint->maxSize)) { throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size', $constraint->maxSize)); } - if ($size > $limit) { + if ($sizeInBytes > $limitInBytes) { + // Convert the limit to the smallest possible number + // (i.e. try "MB", then "kB", then "bytes") + $coef = self::MB_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; + $limitAsString = (string) ($limitInBytes / $coef); + } + + // Convert size to the same measure, but round to 2 decimals + $sizeAsString = (string) round($sizeInBytes / $coef, 2); + + // If the size and limit produce the same string output + // (due to rounding), reduce the coefficient + while ($sizeAsString === $limitAsString) { + $coef /= 1000; + $limitAsString = (string) ($limitInBytes / $coef); + $sizeAsString = (string) round($sizeInBytes / $coef, 2); + } + $this->context->addViolation($constraint->maxSizeMessage, array( - '{{ size }}' => $size, - '{{ limit }}' => $limit, - '{{ suffix }}' => $suffix, + '{{ size }}' => $sizeAsString, + '{{ limit }}' => $limitAsString, + '{{ suffix }}' => static::$suffices[$coef], '{{ file }}' => $path, )); @@ -172,4 +200,9 @@ public function validate($value, Constraint $constraint) } } } + + private static function moreDecimalsThan($double, $numberOfDecimals) + { + return strlen((string) $double) > strlen(round($double, $numberOfDecimals)); + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php index f5178cc029af7..d2d2bcb34f257 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php @@ -33,7 +33,13 @@ protected function setUp() protected function tearDown() { - fclose($this->file); + if (is_resource($this->file)) { + fclose($this->file); + } + + if (file_exists($this->path)) { + unlink($this->path); + } $this->context = null; $this->validator = null; @@ -82,65 +88,97 @@ public function testValidUploadedfile() $this->validator->validate($file, new File()); } - public function testTooLargeBytes() + public function provideMaxSizeExceededTests() { - fwrite($this->file, str_repeat('0', 11)); + return array( + array(11, 10, '11', '10', 'bytes'), - $constraint = new File(array( - 'maxSize' => 10, - 'maxSizeMessage' => 'myMessage', - )); + array(ceil(1.005*1000), ceil(1.005*1000) - 1, '1005', '1004', 'bytes'), + array(ceil(1.005*1000*1000), ceil(1.005*1000*1000) - 1, '1005000', '1004999', 'bytes'), - $this->context->expects($this->once()) - ->method('addViolation') - ->with('myMessage', array( - '{{ limit }}' => '10', - '{{ size }}' => '11', - '{{ suffix }}' => 'bytes', - '{{ file }}' => $this->path, - )); + // 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'), - $this->validator->validate($this->getFile($this->path), $constraint); + // 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(1000 + 1, 1000, '1001', '1000', 'bytes'), + array(1000 + 1, '1k', '1001', '1000', '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'), + + // 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(1000*1000 + 1, 1000*1000, '1000001', '1000000', 'bytes'), + array(1000*1000 + 1, '1000k', '1000001', '1000000', 'bytes'), + array(1000*1000 + 1, '1M', '1000001', '1000000', 'bytes'), + ); } - public function testTooLargeKiloBytes() + /** + * @dataProvider provideMaxSizeExceededTests + */ + public function testMaxSizeExceeded($bytesWritten, $limit, $sizeAsString, $limitAsString, $suffix) { - fwrite($this->file, str_repeat('0', 1400)); + fseek($this->file, $bytesWritten-1, SEEK_SET); + fwrite($this->file, '0'); + fclose($this->file); $constraint = new File(array( - 'maxSize' => '1k', + 'maxSize' => $limit, 'maxSizeMessage' => 'myMessage', )); $this->context->expects($this->once()) ->method('addViolation') ->with('myMessage', array( - '{{ limit }}' => '1', - '{{ size }}' => '1.4', - '{{ suffix }}' => 'kB', + '{{ limit }}' => $limitAsString, + '{{ size }}' => $sizeAsString, + '{{ suffix }}' => $suffix, '{{ file }}' => $this->path, )); $this->validator->validate($this->getFile($this->path), $constraint); } - public function testTooLargeMegaBytes() + public function provideMaxSizeNotExceededTests() + { + return array( + array(10, 10), + array(9, 10), + + array(1000, '1k'), + array(1000 - 1, '1k'), + + array(1000*1000, '1M'), + array(1000*1000 - 1, '1M'), + ); + } + + /** + * @dataProvider provideMaxSizeNotExceededTests + */ + public function testMaxSizeNotExceeded($bytesWritten, $limit) { - fwrite($this->file, str_repeat('0', 1400000)); + fseek($this->file, $bytesWritten-1, SEEK_SET); + fwrite($this->file, '0'); + fclose($this->file); $constraint = new File(array( - 'maxSize' => '1M', + 'maxSize' => $limit, 'maxSizeMessage' => 'myMessage', )); - $this->context->expects($this->once()) - ->method('addViolation') - ->with('myMessage', array( - '{{ limit }}' => '1', - '{{ size }}' => '1.4', - '{{ suffix }}' => 'MB', - '{{ file }}' => $this->path, - )); + $this->context->expects($this->never()) + ->method('addViolation'); $this->validator->validate($this->getFile($this->path), $constraint); }