From eb3f858c0313b63f64d01864fae518aed15a3dcb Mon Sep 17 00:00:00 2001 From: Stefan Scheu Date: Fri, 29 Mar 2024 14:57:45 +0100 Subject: [PATCH] fix: added changes from Peter only for src files without test files --- src/FinfoMimeTypeDetector.php | 53 +++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/src/FinfoMimeTypeDetector.php b/src/FinfoMimeTypeDetector.php index 8084f92..b95f253 100644 --- a/src/FinfoMimeTypeDetector.php +++ b/src/FinfoMimeTypeDetector.php @@ -5,7 +5,6 @@ namespace League\MimeTypeDetection; use const FILEINFO_MIME_TYPE; - use const PATHINFO_EXTENSION; use finfo; @@ -39,6 +38,11 @@ class FinfoMimeTypeDetector implements MimeTypeDetector, ExtensionLookup */ private $inconclusiveMimetypes; + /** + * Buffer size to read from streams if no other bufferSampleSize is defined. + */ + private const STREAM_BUFFER_SAMPLE_SIZE_DEFAULT = 4100; + public function __construct( string $magicFile = '', ExtensionToMimeTypeMap $extensionMap = null, @@ -53,9 +57,17 @@ public function __construct( public function detectMimeType(string $path, $contents): ?string { - $mimeType = is_string($contents) - ? (@$this->finfo->buffer($this->takeSample($contents)) ?: null) - : null; + $mimeType = null; + if (is_string($contents)) { + $mimeType = @$this->finfo->buffer($this->takeSample($contents)); + } elseif ( + is_resource($contents) + && get_resource_type($contents) === 'stream' + && ($streamMetaData = stream_get_meta_data($contents)) + && !empty($streamMetaData['seekable']) + ) { + $mimeType = @$this->finfo->buffer($this->takeResourceSample($contents)); + } if ($mimeType !== null && ! in_array($mimeType, $this->inconclusiveMimetypes)) { return $mimeType; @@ -90,6 +102,37 @@ private function takeSample(string $contents): string return (string) substr($contents, 0, $this->bufferSampleSize); } + /** + * Fetches a sample of a resource while maintaining its pointer. + */ + private function takeResourceSample($contents): string + { + if (is_resource($contents) && get_resource_type($contents) === 'stream') { + // Memory optimization: given a length stream_get_contents() + // immediately allocates an internal buffer. + // However, stream_copy_to_stream() reads up to the defined length + // without pre-allocating any extra buffer. + // Given the relatively large STREAM_BUFFER_SAMPLE_SIZE_DEFAULT this + // avoids unnecessary memory hogging. + $streamContentBuffer = fopen('php://temp/maxmemory:' . self::STREAM_BUFFER_SAMPLE_SIZE_DEFAULT, 'w+b'); + + $streamPosition = ftell($contents); + rewind($contents); + stream_copy_to_stream( + $contents, + $streamContentBuffer, + $this->bufferSampleSize ?? self::STREAM_BUFFER_SAMPLE_SIZE_DEFAULT, + 0 + ); + rewind($streamContentBuffer); + $streamSample = stream_get_contents($streamContentBuffer); + fclose($streamContentBuffer); + fseek($contents, $streamPosition); + return $streamSample; + } + return ''; + } + public function lookupExtension(string $mimetype): ?string { return $this->extensionMap instanceof ExtensionLookup @@ -103,4 +146,4 @@ public function lookupAllExtensions(string $mimetype): array ? $this->extensionMap->lookupAllExtensions($mimetype) : []; } -} +} \ No newline at end of file