123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- <?php
- /**
- * @package Grav\Framework\Form
- *
- * @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
- * @license MIT License; see LICENSE file for details.
- */
- namespace Grav\Framework\Form;
- use Grav\Common\Security;
- use Grav\Common\Utils;
- use Grav\Framework\Psr7\Stream;
- use InvalidArgumentException;
- use JsonSerializable;
- use Psr\Http\Message\StreamInterface;
- use Psr\Http\Message\UploadedFileInterface;
- use RuntimeException;
- use function copy;
- use function fopen;
- use function is_string;
- use function sprintf;
- /**
- * Class FormFlashFile
- * @package Grav\Framework\Form
- */
- class FormFlashFile implements UploadedFileInterface, JsonSerializable
- {
- /** @var string */
- private $id;
- /** @var string */
- private $field;
- /** @var bool */
- private $moved = false;
- /** @var array */
- private $upload;
- /** @var FormFlash */
- private $flash;
- /**
- * FormFlashFile constructor.
- * @param string $field
- * @param array $upload
- * @param FormFlash $flash
- */
- public function __construct(string $field, array $upload, FormFlash $flash)
- {
- $this->id = $flash->getId() ?: $flash->getUniqueId();
- $this->field = $field;
- $this->upload = $upload;
- $this->flash = $flash;
- $tmpFile = $this->getTmpFile();
- if (!$tmpFile && $this->isOk()) {
- $this->upload['error'] = \UPLOAD_ERR_NO_FILE;
- }
- if (!isset($this->upload['size'])) {
- $this->upload['size'] = $tmpFile && $this->isOk() ? filesize($tmpFile) : 0;
- }
- }
- /**
- * @return StreamInterface
- */
- public function getStream()
- {
- $this->validateActive();
- $tmpFile = $this->getTmpFile();
- if (null === $tmpFile) {
- throw new RuntimeException('No temporary file');
- }
- $resource = fopen($tmpFile, 'rb');
- if (false === $resource) {
- throw new RuntimeException('No temporary file');
- }
- return Stream::create($resource);
- }
- /**
- * @param string $targetPath
- * @return void
- */
- public function moveTo($targetPath)
- {
- $this->validateActive();
- if (!is_string($targetPath) || empty($targetPath)) {
- throw new InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
- }
- $tmpFile = $this->getTmpFile();
- if (null === $tmpFile) {
- throw new RuntimeException('No temporary file');
- }
- $this->moved = copy($tmpFile, $targetPath);
- if (false === $this->moved) {
- throw new RuntimeException(sprintf('Uploaded file could not be moved to %s', $targetPath));
- }
- $filename = $this->getClientFilename();
- if ($filename) {
- $this->flash->removeFile($filename, $this->field);
- }
- }
- public function getId(): string
- {
- return $this->id;
- }
- /**
- * @return string
- */
- public function getField(): string
- {
- return $this->field;
- }
- /**
- * @return int
- */
- public function getSize()
- {
- return $this->upload['size'];
- }
- /**
- * @return int
- */
- public function getError()
- {
- return $this->upload['error'] ?? \UPLOAD_ERR_OK;
- }
- /**
- * @return string
- */
- public function getClientFilename()
- {
- return $this->upload['name'] ?? 'unknown';
- }
- /**
- * @return string
- */
- public function getClientMediaType()
- {
- return $this->upload['type'] ?? 'application/octet-stream';
- }
- /**
- * @return bool
- */
- public function isMoved(): bool
- {
- return $this->moved;
- }
- /**
- * @return array
- */
- public function getMetaData(): array
- {
- if (isset($this->upload['crop'])) {
- return ['crop' => $this->upload['crop']];
- }
- return [];
- }
- /**
- * @return string
- */
- public function getDestination()
- {
- return $this->upload['path'] ?? '';
- }
- /**
- * @return array
- */
- #[\ReturnTypeWillChange]
- public function jsonSerialize()
- {
- return $this->upload;
- }
- /**
- * @return void
- */
- public function checkXss(): void
- {
- $tmpFile = $this->getTmpFile();
- $mime = $this->getClientMediaType();
- if (Utils::contains($mime, 'svg', false)) {
- $response = Security::detectXssFromSvgFile($tmpFile);
- if ($response) {
- throw new RuntimeException(sprintf('SVG file XSS check failed on %s', $response));
- }
- }
- }
- /**
- * @return string|null
- */
- public function getTmpFile(): ?string
- {
- $tmpName = $this->upload['tmp_name'] ?? null;
- if (!$tmpName) {
- return null;
- }
- $tmpFile = $this->flash->getTmpDir() . '/' . $tmpName;
- return file_exists($tmpFile) ? $tmpFile : null;
- }
- /**
- * @return array
- */
- #[\ReturnTypeWillChange]
- public function __debugInfo()
- {
- return [
- 'id:private' => $this->id,
- 'field:private' => $this->field,
- 'moved:private' => $this->moved,
- 'upload:private' => $this->upload,
- ];
- }
- /**
- * @return void
- * @throws RuntimeException if is moved or not ok
- */
- private function validateActive(): void
- {
- if (!$this->isOk()) {
- throw new RuntimeException('Cannot retrieve stream due to upload error');
- }
- if ($this->moved) {
- throw new RuntimeException('Cannot retrieve stream after it has already been moved');
- }
- if (!$this->getTmpFile()) {
- throw new RuntimeException('Cannot retrieve stream as the file is missing');
- }
- }
- /**
- * @return bool return true if there is no upload error
- */
- private function isOk(): bool
- {
- return \UPLOAD_ERR_OK === $this->getError();
- }
- }
|