diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php index 8fa6d67faa3cd..c50218a01a482 100644 --- a/lib/private/Files/ObjectStore/S3ObjectTrait.php +++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php @@ -154,16 +154,24 @@ protected function writeMultiPart(string $urn, StreamInterface $stream, string $ public function writeObject($urn, $stream, string $mimetype = null) { $psrStream = Utils::streamFor($stream); - // ($psrStream->isSeekable() && $psrStream->getSize() !== null) evaluates to true for a On-Seekable stream - // so the optimisation does not apply - $buffer = new Psr7\Stream(fopen("php://memory", 'rwb+')); - Utils::copyToStream($psrStream, $buffer, $this->putSizeLimit); - $buffer->seek(0); - if ($buffer->getSize() < $this->putSizeLimit) { + $streamSize = $psrStream->getSize(); + if ($psrStream->isSeekable() && (int)$streamSize !== 0) { + $loadStream = $psrStream; + } else { + // For cases where the stream reports seekable but no size is returned + $buffer = new Psr7\Stream(fopen("php://temp", 'rwb+')); + Utils::copyToStream($psrStream, $buffer, $this->putSizeLimit); + $buffer->seek(0); + $streamSize = $buffer->getSize(); + $loadStream = $streamSize < $this->putSizeLimit + ? $buffer + : new Psr7\AppendStream([$buffer, $psrStream]); + } + + if ($streamSize < $this->putSizeLimit) { // buffer is fully seekable, so use it directly for the small upload - $this->writeSingle($urn, $buffer, $mimetype); + $this->writeSingle($urn, $loadStream, $mimetype); } else { - $loadStream = new Psr7\AppendStream([$buffer, $psrStream]); $this->writeMultiPart($urn, $loadStream, $mimetype); } } diff --git a/lib/private/Files/Stream/SeekableHttpStream.php b/lib/private/Files/Stream/SeekableHttpStream.php index 51ccaeba998cb..ef930cef9000b 100644 --- a/lib/private/Files/Stream/SeekableHttpStream.php +++ b/lib/private/Files/Stream/SeekableHttpStream.php @@ -219,6 +219,7 @@ public function stream_tell() { public function stream_stat() { if ($this->getCurrent()) { $stat = fstat($this->getCurrent()); + $stat = $stat ? $stat : []; $stat['size'] = $this->totalSize; return $stat; } else { diff --git a/tests/lib/Files/ObjectStore/S3Test.php b/tests/lib/Files/ObjectStore/S3Test.php index fd451dc3c016f..e1212ab89d104 100644 --- a/tests/lib/Files/ObjectStore/S3Test.php +++ b/tests/lib/Files/ObjectStore/S3Test.php @@ -80,7 +80,15 @@ public function testUploadNonSeekable() { $s3 = $this->getInstance(); - $s3->writeObject('multiparttest', NonSeekableStream::wrap(fopen(__FILE__, 'r'))); + // We need an actual non-seekable resource here as NonSeekableStream won't be enough + // when it is passed to the GuzzleHttp\Psr7\Utils::streamFor + // which checks the actual stream meta data using stream_get_meta_data + @posix_mkfifo('/tmp/fifo', 0644); + $stream = fopen('/tmp/fifo', 'rw+'); + stream_set_blocking($stream, false); + fwrite($stream, file_get_contents(__FILE__)); + + $s3->writeObject('multiparttest', NonSeekableStream::wrap($stream)); $result = $s3->readObject('multiparttest'); @@ -119,7 +127,7 @@ public function testEmptyUpload() { $s3 = $this->getInstance(); $emptyStream = fopen("php://memory", "r"); - fwrite($emptyStream, null); + fwrite($emptyStream, ''); $s3->writeObject('emptystream', $emptyStream);