Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\HttpFoundation\File; Chris@0: Chris@0: use Symfony\Component\HttpFoundation\File\Exception\FileException; Chris@0: use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; Chris@0: use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; Chris@0: Chris@0: /** Chris@0: * A file uploaded through a form. Chris@0: * Chris@0: * @author Bernhard Schussek Chris@0: * @author Florian Eckerstorfer Chris@0: * @author Fabien Potencier Chris@0: */ Chris@0: class UploadedFile extends File Chris@0: { Chris@0: private $test = false; Chris@0: private $originalName; Chris@0: private $mimeType; Chris@0: private $size; Chris@0: private $error; Chris@0: Chris@0: /** Chris@0: * Accepts the information of the uploaded file as provided by the PHP global $_FILES. Chris@0: * Chris@0: * The file object is only created when the uploaded file is valid (i.e. when the Chris@0: * isValid() method returns true). Otherwise the only methods that could be called Chris@0: * on an UploadedFile instance are: Chris@0: * Chris@0: * * getClientOriginalName, Chris@0: * * getClientMimeType, Chris@0: * * isValid, Chris@0: * * getError. Chris@0: * Chris@0: * Calling any other method on an non-valid instance will cause an unpredictable result. Chris@0: * Chris@0: * @param string $path The full temporary path to the file Chris@14: * @param string $originalName The original file name of the uploaded file Chris@0: * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream Chris@14: * @param int|null $size The file size provided by the uploader Chris@0: * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK Chris@0: * @param bool $test Whether the test mode is active Chris@14: * Local files are used in test mode hence the code should not enforce HTTP uploads Chris@0: * Chris@0: * @throws FileException If file_uploads is disabled Chris@0: * @throws FileNotFoundException If the file does not exist Chris@0: */ Chris@0: public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false) Chris@0: { Chris@0: $this->originalName = $this->getName($originalName); Chris@0: $this->mimeType = $mimeType ?: 'application/octet-stream'; Chris@0: $this->size = $size; Chris@0: $this->error = $error ?: UPLOAD_ERR_OK; Chris@0: $this->test = (bool) $test; Chris@0: Chris@0: parent::__construct($path, UPLOAD_ERR_OK === $this->error); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the original file name. Chris@0: * Chris@0: * It is extracted from the request from which the file has been uploaded. Chris@0: * Then it should not be considered as a safe value. Chris@0: * Chris@0: * @return string|null The original name Chris@0: */ Chris@0: public function getClientOriginalName() Chris@0: { Chris@0: return $this->originalName; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the original file extension. Chris@0: * Chris@0: * It is extracted from the original file name that was uploaded. Chris@0: * Then it should not be considered as a safe value. Chris@0: * Chris@0: * @return string The extension Chris@0: */ Chris@0: public function getClientOriginalExtension() Chris@0: { Chris@0: return pathinfo($this->originalName, PATHINFO_EXTENSION); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the file mime type. Chris@0: * Chris@0: * The client mime type is extracted from the request from which the file Chris@0: * was uploaded, so it should not be considered as a safe value. Chris@0: * Chris@0: * For a trusted mime type, use getMimeType() instead (which guesses the mime Chris@0: * type based on the file content). Chris@0: * Chris@0: * @return string|null The mime type Chris@0: * Chris@0: * @see getMimeType() Chris@0: */ Chris@0: public function getClientMimeType() Chris@0: { Chris@0: return $this->mimeType; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the extension based on the client mime type. Chris@0: * Chris@0: * If the mime type is unknown, returns null. Chris@0: * Chris@0: * This method uses the mime type as guessed by getClientMimeType() Chris@0: * to guess the file extension. As such, the extension returned Chris@0: * by this method cannot be trusted. Chris@0: * Chris@0: * For a trusted extension, use guessExtension() instead (which guesses Chris@0: * the extension based on the guessed mime type for the file). Chris@0: * Chris@0: * @return string|null The guessed extension or null if it cannot be guessed Chris@0: * Chris@0: * @see guessExtension() Chris@0: * @see getClientMimeType() Chris@0: */ Chris@0: public function guessClientExtension() Chris@0: { Chris@0: $type = $this->getClientMimeType(); Chris@0: $guesser = ExtensionGuesser::getInstance(); Chris@0: Chris@0: return $guesser->guess($type); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the file size. Chris@0: * Chris@0: * It is extracted from the request from which the file has been uploaded. Chris@0: * Then it should not be considered as a safe value. Chris@0: * Chris@0: * @return int|null The file size Chris@0: */ Chris@0: public function getClientSize() Chris@0: { Chris@0: return $this->size; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the upload error. Chris@0: * Chris@0: * If the upload was successful, the constant UPLOAD_ERR_OK is returned. Chris@0: * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. Chris@0: * Chris@0: * @return int The upload error Chris@0: */ Chris@0: public function getError() Chris@0: { Chris@0: return $this->error; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns whether the file was uploaded successfully. Chris@0: * Chris@0: * @return bool True if the file has been uploaded with HTTP and no error occurred Chris@0: */ Chris@0: public function isValid() Chris@0: { Chris@14: $isOk = UPLOAD_ERR_OK === $this->error; Chris@0: Chris@0: return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname()); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Moves the file to a new location. Chris@0: * Chris@0: * @param string $directory The destination folder Chris@0: * @param string $name The new file name Chris@0: * Chris@0: * @return File A File object representing the new file Chris@0: * Chris@0: * @throws FileException if, for any reason, the file could not have been moved Chris@0: */ Chris@0: public function move($directory, $name = null) Chris@0: { Chris@0: if ($this->isValid()) { Chris@0: if ($this->test) { Chris@0: return parent::move($directory, $name); Chris@0: } Chris@0: Chris@0: $target = $this->getTargetFile($directory, $name); Chris@0: Chris@16: set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); Chris@16: $moved = move_uploaded_file($this->getPathname(), $target); Chris@16: restore_error_handler(); Chris@16: if (!$moved) { Chris@16: throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); Chris@0: } Chris@0: Chris@0: @chmod($target, 0666 & ~umask()); Chris@0: Chris@0: return $target; Chris@0: } Chris@0: Chris@0: throw new FileException($this->getErrorMessage()); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the maximum size of an uploaded file as configured in php.ini. Chris@0: * Chris@0: * @return int The maximum size of an uploaded file in bytes Chris@0: */ Chris@0: public static function getMaxFilesize() Chris@0: { Chris@0: $iniMax = strtolower(ini_get('upload_max_filesize')); Chris@0: Chris@0: if ('' === $iniMax) { Chris@0: return PHP_INT_MAX; Chris@0: } Chris@0: Chris@0: $max = ltrim($iniMax, '+'); Chris@0: if (0 === strpos($max, '0x')) { Chris@17: $max = \intval($max, 16); Chris@0: } elseif (0 === strpos($max, '0')) { Chris@17: $max = \intval($max, 8); Chris@0: } else { Chris@0: $max = (int) $max; Chris@0: } Chris@0: Chris@0: switch (substr($iniMax, -1)) { Chris@0: case 't': $max *= 1024; Chris@14: // no break Chris@0: case 'g': $max *= 1024; Chris@14: // no break Chris@0: case 'm': $max *= 1024; Chris@14: // no break Chris@0: case 'k': $max *= 1024; Chris@0: } Chris@0: Chris@0: return $max; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns an informative upload error message. Chris@0: * Chris@0: * @return string The error message regarding the specified error code Chris@0: */ Chris@0: public function getErrorMessage() Chris@0: { Chris@17: static $errors = [ Chris@0: UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).', Chris@0: UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.', Chris@0: UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.', Chris@0: UPLOAD_ERR_NO_FILE => 'No file was uploaded.', Chris@0: UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.', Chris@0: UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.', Chris@0: UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.', Chris@17: ]; Chris@0: Chris@0: $errorCode = $this->error; Chris@14: $maxFilesize = UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0; Chris@0: $message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.'; Chris@0: Chris@0: return sprintf($message, $this->getClientOriginalName(), $maxFilesize); Chris@0: } Chris@0: }