123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- <?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\HttpFoundation\File;
-
- use Symfony\Component\HttpFoundation\File\Exception\CannotWriteFileException;
- use Symfony\Component\HttpFoundation\File\Exception\ExtensionFileException;
- use Symfony\Component\HttpFoundation\File\Exception\FileException;
- use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
- use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException;
- use Symfony\Component\HttpFoundation\File\Exception\IniSizeFileException;
- use Symfony\Component\HttpFoundation\File\Exception\NoFileException;
- use Symfony\Component\HttpFoundation\File\Exception\NoTmpDirFileException;
- use Symfony\Component\HttpFoundation\File\Exception\PartialFileException;
- use Symfony\Component\Mime\MimeTypes;
-
- /**
- * A file uploaded through a form.
- *
- * @author Bernhard Schussek <bschussek@gmail.com>
- * @author Florian Eckerstorfer <florian@eckerstorfer.org>
- * @author Fabien Potencier <fabien@symfony.com>
- */
- class UploadedFile extends File
- {
- private $test;
- private $originalName;
- private $mimeType;
- private $error;
-
- /**
- * Accepts the information of the uploaded file as provided by the PHP global $_FILES.
- *
- * The file object is only created when the uploaded file is valid (i.e. when the
- * isValid() method returns true). Otherwise the only methods that could be called
- * on an UploadedFile instance are:
- *
- * * getClientOriginalName,
- * * getClientMimeType,
- * * isValid,
- * * getError.
- *
- * Calling any other method on an non-valid instance will cause an unpredictable result.
- *
- * @param string $path The full temporary path to the file
- * @param string $originalName The original file name of the uploaded file
- * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream
- * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK
- * @param bool $test Whether the test mode is active
- * Local files are used in test mode hence the code should not enforce HTTP uploads
- *
- * @throws FileException If file_uploads is disabled
- * @throws FileNotFoundException If the file does not exist
- */
- public function __construct(string $path, string $originalName, ?string $mimeType = null, ?int $error = null, bool $test = false)
- {
- $this->originalName = $this->getName($originalName);
- $this->mimeType = $mimeType ?: 'application/octet-stream';
- $this->error = $error ?: \UPLOAD_ERR_OK;
- $this->test = $test;
-
- parent::__construct($path, \UPLOAD_ERR_OK === $this->error);
- }
-
- /**
- * Returns the original file name.
- *
- * It is extracted from the request from which the file has been uploaded.
- * This should not be considered as a safe value to use for a file name on your servers.
- *
- * @return string
- */
- public function getClientOriginalName()
- {
- return $this->originalName;
- }
-
- /**
- * Returns the original file extension.
- *
- * It is extracted from the original file name that was uploaded.
- * This should not be considered as a safe value to use for a file name on your servers.
- *
- * @return string
- */
- public function getClientOriginalExtension()
- {
- return pathinfo($this->originalName, \PATHINFO_EXTENSION);
- }
-
- /**
- * Returns the file mime type.
- *
- * The client mime type is extracted from the request from which the file
- * was uploaded, so it should not be considered as a safe value.
- *
- * For a trusted mime type, use getMimeType() instead (which guesses the mime
- * type based on the file content).
- *
- * @return string
- *
- * @see getMimeType()
- */
- public function getClientMimeType()
- {
- return $this->mimeType;
- }
-
- /**
- * Returns the extension based on the client mime type.
- *
- * If the mime type is unknown, returns null.
- *
- * This method uses the mime type as guessed by getClientMimeType()
- * to guess the file extension. As such, the extension returned
- * by this method cannot be trusted.
- *
- * For a trusted extension, use guessExtension() instead (which guesses
- * the extension based on the guessed mime type for the file).
- *
- * @return string|null
- *
- * @see guessExtension()
- * @see getClientMimeType()
- */
- public function guessClientExtension()
- {
- if (!class_exists(MimeTypes::class)) {
- throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".');
- }
-
- return MimeTypes::getDefault()->getExtensions($this->getClientMimeType())[0] ?? null;
- }
-
- /**
- * Returns the upload error.
- *
- * If the upload was successful, the constant UPLOAD_ERR_OK is returned.
- * Otherwise one of the other UPLOAD_ERR_XXX constants is returned.
- *
- * @return int
- */
- public function getError()
- {
- return $this->error;
- }
-
- /**
- * Returns whether the file has been uploaded with HTTP and no error occurred.
- *
- * @return bool
- */
- public function isValid()
- {
- $isOk = \UPLOAD_ERR_OK === $this->error;
-
- return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
- }
-
- /**
- * Moves the file to a new location.
- *
- * @return File
- *
- * @throws FileException if, for any reason, the file could not have been moved
- */
- public function move(string $directory, ?string $name = null)
- {
- if ($this->isValid()) {
- if ($this->test) {
- return parent::move($directory, $name);
- }
-
- $target = $this->getTargetFile($directory, $name);
-
- set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
- try {
- $moved = move_uploaded_file($this->getPathname(), $target);
- } finally {
- restore_error_handler();
- }
- if (!$moved) {
- throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
- }
-
- @chmod($target, 0666 & ~umask());
-
- return $target;
- }
-
- switch ($this->error) {
- case \UPLOAD_ERR_INI_SIZE:
- throw new IniSizeFileException($this->getErrorMessage());
- case \UPLOAD_ERR_FORM_SIZE:
- throw new FormSizeFileException($this->getErrorMessage());
- case \UPLOAD_ERR_PARTIAL:
- throw new PartialFileException($this->getErrorMessage());
- case \UPLOAD_ERR_NO_FILE:
- throw new NoFileException($this->getErrorMessage());
- case \UPLOAD_ERR_CANT_WRITE:
- throw new CannotWriteFileException($this->getErrorMessage());
- case \UPLOAD_ERR_NO_TMP_DIR:
- throw new NoTmpDirFileException($this->getErrorMessage());
- case \UPLOAD_ERR_EXTENSION:
- throw new ExtensionFileException($this->getErrorMessage());
- }
-
- throw new FileException($this->getErrorMessage());
- }
-
- /**
- * Returns the maximum size of an uploaded file as configured in php.ini.
- *
- * @return int|float The maximum size of an uploaded file in bytes (returns float if size > PHP_INT_MAX)
- */
- public static function getMaxFilesize()
- {
- $sizePostMax = self::parseFilesize(\ini_get('post_max_size'));
- $sizeUploadMax = self::parseFilesize(\ini_get('upload_max_filesize'));
-
- return min($sizePostMax ?: \PHP_INT_MAX, $sizeUploadMax ?: \PHP_INT_MAX);
- }
-
- /**
- * Returns the given size from an ini value in bytes.
- *
- * @return int|float Returns float if size > PHP_INT_MAX
- */
- private static function parseFilesize(string $size)
- {
- if ('' === $size) {
- return 0;
- }
-
- $size = strtolower($size);
-
- $max = ltrim($size, '+');
- if (str_starts_with($max, '0x')) {
- $max = \intval($max, 16);
- } elseif (str_starts_with($max, '0')) {
- $max = \intval($max, 8);
- } else {
- $max = (int) $max;
- }
-
- switch (substr($size, -1)) {
- case 't': $max *= 1024;
- // no break
- case 'g': $max *= 1024;
- // no break
- case 'm': $max *= 1024;
- // no break
- case 'k': $max *= 1024;
- }
-
- return $max;
- }
-
- /**
- * Returns an informative upload error message.
- *
- * @return string
- */
- public function getErrorMessage()
- {
- static $errors = [
- \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.',
- \UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.',
- \UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
- \UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.',
- ];
-
- $errorCode = $this->error;
- $maxFilesize = \UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0;
- $message = $errors[$errorCode] ?? 'The file "%s" was not uploaded due to an unknown error.';
-
- return sprintf($message, $this->getClientOriginalName(), $maxFilesize);
- }
- }
|