use TYPO3\CMS\Core\Resource\ProcessedFileRepository;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\HttpUtility;
/**
* Class FileDumpController
}
if ($file === null) {
- HttpUtility::setResponseCodeAndExit(HttpUtility::HTTP_STATUS_404);
+ return (new Response)->withStatus(404);
}
- // Hook: allow some other process to do some security/access checks. Hook should issue 403 if access is rejected
+ // Hook: allow some other process to do some security/access checks. Hook should return 403 response if access is rejected, void otherwise
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['FileDumpEID.php']['checkFileAccess'] ?? [] as $className) {
$hookObject = GeneralUtility::makeInstance($className);
if (!$hookObject instanceof FileDumpEIDHookInterface) {
throw new \UnexpectedValueException($className . ' must implement interface ' . FileDumpEIDHookInterface::class, 1394442417);
}
- $hookObject->checkFileAccess($file);
+ $response = $hookObject->checkFileAccess($file);
+ if ($response instanceof ResponseInterface) {
+ return $response;
+ }
}
- $file->getStorage()->dumpFileContents($file);
- // @todo Refactor FAL to not echo directly, but to implement a stream for output here and use response
- return null;
+
+ return $file->getStorage()->streamFile($file);
}
return (new Response)->withStatus(403);
}
*/
protected function sendResponse(ResponseInterface $response)
{
- if ($response instanceof \TYPO3\CMS\Core\Http\NullResponse) {
+ if ($response instanceof NullResponse) {
return;
}
header($name . ': ' . implode(', ', $values));
}
}
- echo $response->getBody()->__toString();
+ $body = $response->getBody();
+ if ($body instanceof SelfEmittableStreamInterface) {
+ // Optimization for streams that use php functions like readfile() as fastpath for serving files.
+ $body->emit();
+ } else {
+ echo $body->__toString();
+ }
}
/**
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\Http;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use GuzzleHttp\Psr7\StreamDecoratorTrait;
+use Psr\Http\Message\StreamInterface;
+use TYPO3\CMS\Core\Resource\Driver\DriverInterface;
+
+/**
+ * A lazy stream, that wraps the FAL dumpFileContents() method to send file contents
+ * using emit(), as defined in SelfEmittableStreamInterface.
+ * This call will fall back to the FAL getFileContents() method if the fastpath possibility
+ * using SelfEmittableStreamInterface is not used.
+ *
+ * @internal
+ */
+class FalDumpFileContentsDecoratorStream implements StreamInterface, SelfEmittableStreamInterface
+{
+ use StreamDecoratorTrait;
+
+ /**
+ * @var string
+ */
+ protected $identifier;
+
+ /**
+ * @var DriverInterface
+ */
+ protected $driver;
+
+ /**
+ * @var int
+ */
+ protected $size;
+
+ /**
+ * @param string $identifier
+ * @param DriverInterface $driver
+ * @param int $size
+ */
+ public function __construct(string $identifier, DriverInterface $driver, int $size)
+ {
+ $this->identifier = $identifier;
+ $this->driver = $driver;
+ $this->size = $size;
+ }
+
+ /**
+ * Emit the response to stdout, as specified in SelfEmittableStreamInterface.
+ * Offload to the driver method dumpFileContents.
+ */
+ public function emit()
+ {
+ $this->driver->dumpFileContents($this->identifier);
+ }
+
+ /**
+ * Creates a stream (on demand). This method is consumed by the guzzle StreamDecoratorTrait
+ * and is used when this stream is used without the emit() fastpath.
+ *
+ * @return StreamInterface
+ */
+ protected function createStream(): StreamInterface
+ {
+ $stream = new Stream('php://temp', 'rw');
+ $stream->write($this->driver->getFileContents($this->identifier));
+ return $stream;
+ }
+
+ /**
+ * @return int
+ */
+ public function getSize(): int
+ {
+ return $this->size;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isWritable(): bool
+ {
+ return false;
+ }
+
+ /**
+ * @param string $string
+ * @throws \RuntimeException on failure.
+ */
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to a ' . self::class, 1538331852);
+ }
+}
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\Http;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use GuzzleHttp\Psr7\LazyOpenStream;
+
+/**
+ * This class implements a stream that can be used like a usual PSR-7 stream
+ * but is additionally able to provide a file-serving fastpath using readfile().
+ * The file this stream refers to is opened on demand.
+ *
+ * @internal
+ */
+class SelfEmittableLazyOpenStream extends LazyOpenStream implements SelfEmittableStreamInterface
+{
+ /**
+ * @var string
+ */
+ protected $filename;
+
+ /**
+ * Constructor setting up the PHP resource
+ *
+ * @param string $filename
+ */
+ public function __construct($filename)
+ {
+ parent::__construct($filename, 'r');
+ $this->filename = $filename;
+ }
+
+ /**
+ * Output the contents of the file to the output buffer
+ */
+ public function emit()
+ {
+ readfile($this->filename, false);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isWritable(): bool
+ {
+ return false;
+ }
+
+ /**
+ * @param string $string
+ * @throws \RuntimeException on failure.
+ */
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to a ' . self::class, 1538331833);
+ }
+}
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\Http;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * A PSR-7 stream which allows to be emitted on its own.
+ *
+ * @internal
+ */
+interface SelfEmittableStreamInterface extends StreamInterface
+{
+ /**
+ * Output the contents of the stream to the output buffer
+ */
+ public function emit();
+}
* The TYPO3 project - inspiring people to share!
*/
+use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Charset\CharsetConverter;
use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Http\Response;
+use TYPO3\CMS\Core\Http\SelfEmittableLazyOpenStream;
use TYPO3\CMS\Core\Resource\Exception;
use TYPO3\CMS\Core\Resource\FolderInterface;
use TYPO3\CMS\Core\Resource\ResourceStorage;
/**
* Driver for the local file system
*/
-class LocalDriver extends AbstractHierarchicalFilesystemDriver
+class LocalDriver extends AbstractHierarchicalFilesystemDriver implements StreamableDriverInterface
{
/**
* @var string
readfile($this->getAbsolutePath($this->canonicalizeAndCheckFileIdentifier($identifier)), 0);
}
+ /**
+ * Stream file using a PSR-7 Response object.
+ *
+ * @param string $identifier
+ * @param array $properties
+ * @return ResponseInterface
+ */
+ public function streamFile(string $identifier, array $properties): ResponseInterface
+ {
+ $fileInfo = $this->getFileInfoByIdentifier($identifier, ['name', 'mimetype', 'mtime', 'size']);
+ $downloadName = $properties['filename_overwrite'] ?? $fileInfo['name'] ?? '';
+ $mimeType = $properties['mimetype_overwrite'] ?? $fileInfo['mimetype'] ?? '';
+ $contentDisposition = ($properties['as_download'] ?? false) ? 'attachment' : 'inline';
+
+ $filePath = $this->getAbsolutePath($this->canonicalizeAndCheckFileIdentifier($identifier));
+
+ return new Response(
+ new SelfEmittableLazyOpenStream($filePath),
+ 200,
+ [
+ 'Content-Disposition' => $contentDisposition . '; filename="' . $downloadName . '"',
+ 'Content-Type' => $mimeType,
+ 'Content-Length' => (string)$fileInfo['size'],
+ 'Last-Modified' => gmdate('D, d M Y H:i:s', $fileInfo['mtime']) . ' GMT',
+ // Cache-Control header is needed here to solve an issue with browser IE8 and lower
+ // See for more information: http://support.microsoft.com/kb/323308
+ 'Cache-Control' => '',
+ ]
+ );
+ }
+
/**
* Get the path of the nearest recycler folder of a given $path.
* Return an empty string if there is no recycler folder available.
--- /dev/null
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\Resource\Driver;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * An interface FAL drivers have to implement to fulfil the needs
+ * of streaming files using PSR-7 Response objects.
+ *
+ * @internal
+ */
+interface StreamableDriverInterface
+{
+ /**
+ * Streams a file using a PSR-7 Response object.
+ *
+ * @param string $identifier
+ * @param array $properties
+ * @return ResponseInterface
+ */
+ public function streamFile(string $identifier, array $properties): ResponseInterface;
+}
* The TYPO3 project - inspiring people to share!
*/
+use Psr\Http\Message\ResponseInterface;
+
/**
* Interface for FileDumpEID Hook to perform some custom security/access checks
* when accessing file thought FileDumpEID
* A 401 header must be accompanied by a www-authenticate header!
*
* @param \TYPO3\CMS\Core\Resource\ResourceInterface $file
+ * @return ResponseInterface|null
*/
public function checkFileAccess(\TYPO3\CMS\Core\Resource\ResourceInterface $file);
}
* The TYPO3 project - inspiring people to share!
*/
+use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Http\FalDumpFileContentsDecoratorStream;
+use TYPO3\CMS\Core\Http\Response;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Registry;
+use TYPO3\CMS\Core\Resource\Driver\StreamableDriverInterface;
use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException;
use TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException;
use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
* @param bool $asDownload If set Content-Disposition attachment is sent, inline otherwise
* @param string $alternativeFilename the filename for the download (if $asDownload is set)
* @param string $overrideMimeType If set this will be used as Content-Type header instead of the automatically detected mime type.
+ * @deprecated since TYPO3 v9.5, will be removed in TYPO3 v10.0.
*/
public function dumpFileContents(FileInterface $file, $asDownload = false, $alternativeFilename = null, $overrideMimeType = null)
{
+ trigger_error('ResourceStorage->dumpFileContents() will be removed in TYPO3 v10.0. Use streamFile() instead.', E_USER_DEPRECATED);
+
$downloadName = $alternativeFilename ?: $file->getName();
$contentDisposition = $asDownload ? 'attachment' : 'inline';
header('Content-Disposition: ' . $contentDisposition . '; filename="' . $downloadName . '"');
$this->driver->dumpFileContents($file->getIdentifier());
}
+ /**
+ * Returns a PSR-7 Response which can be used to stream the requested file
+ *
+ * @param FileInterface $file
+ * @param bool $asDownload If set Content-Disposition attachment is sent, inline otherwise
+ * @param string $alternativeFilename the filename for the download (if $asDownload is set)
+ * @param string $overrideMimeType If set this will be used as Content-Type header instead of the automatically detected mime type.
+ * @return ResponseInterface
+ */
+ public function streamFile(
+ FileInterface $file,
+ bool $asDownload = false,
+ string $alternativeFilename = null,
+ string $overrideMimeType = null
+ ): ResponseInterface {
+ if (!$this->driver instanceof StreamableDriverInterface) {
+ return $this->getPseudoStream($file, $asDownload, $alternativeFilename, $overrideMimeType);
+ }
+
+ $properties = [
+ 'as_download' => $asDownload,
+ 'filename_overwrite' => $alternativeFilename,
+ 'mimetype_overwrite' => $overrideMimeType,
+ ];
+ return $this->driver->streamFile($file->getIdentifier(), $properties);
+ }
+
+ /**
+ * Wrap DriverInterface::dumpFileContents into a SelfEmittableStreamInterface
+ *
+ * @param FileInterface $file
+ * @param bool $asDownload If set Content-Disposition attachment is sent, inline otherwise
+ * @param string $alternativeFilename the filename for the download (if $asDownload is set)
+ * @param string $overrideMimeType If set this will be used as Content-Type header instead of the automatically detected mime type.
+ * @return ResponseInterface
+ */
+ protected function getPseudoStream(
+ FileInterface $file,
+ bool $asDownload = false,
+ string $alternativeFilename = null,
+ string $overrideMimeType = null
+ ) {
+ $downloadName = $alternativeFilename ?: $file->getName();
+ $contentDisposition = $asDownload ? 'attachment' : 'inline';
+
+ $stream = new FalDumpFileContentsDecoratorStream($file->getIdentifier(), $this->driver, $file->getSize());
+ $headers = [
+ 'Content-Disposition' => $contentDisposition . '; filename="' . $downloadName . '"',
+ 'Content-Type' => $overrideMimeType ?: $file->getMimeType(),
+ 'Content-Length' => (string)$file->getSize(),
+ 'Last-Modified' => gmdate('D, d M Y H:i:s', array_pop($this->driver->getFileInfoByIdentifier($file->getIdentifier(), ['mtime']))) . ' GMT',
+ // Cache-Control header is needed here to solve an issue with browser IE8 and lower
+ // See for more information: http://support.microsoft.com/kb/323308
+ 'Cache-Control' => '',
+ ];
+
+ return new Response($stream, 200, $headers);
+ }
+
/**
* Set contents of a file object.
*
--- /dev/null
+.. include:: ../../Includes.txt
+
+=============================================================
+Deprecation: #83793 - FAL ResourceStorage->dumpFileContents()
+=============================================================
+
+See :issue:`83793`
+
+Description
+===========
+
+The method :php:`ResourceStorage->dumpFileContents()` has been marked as deprecated.
+
+
+Impact
+======
+
+Calling this method will trigger a PHP deprecation notice.
+
+
+Affected Installations
+======================
+
+TYPO3 installations with extensions, which use the method.
+
+
+Migration
+=========
+
+Use :php:`ResourceStorage->streamFile()` instead.
+
+.. index:: FAL, PHP-API, FullyScanned
'Deprecation-86461-MarkVariousTypoScriptParsingFunctionalityAsInternal.rst'
],
],
+ 'TYPO3\CMS\Core\Resource\ResourceStorage->dumpFileContents' => [
+ 'numberOfMandatoryArguments' => 1,
+ 'maximumNumberOfArguments' => 4,
+ 'restFiles' => [
+ 'Deprecation-83793-FALResourceStorage-dumpFileContents.rst'
+ ],
+ ],
];