From e522f3cc50ab237a41946da1b07f1472faf5f3b4 Mon Sep 17 00:00:00 2001 From: Simon Holzman Date: Sun, 28 Jun 2026 17:22:23 -0400 Subject: [PATCH 1/4] fix: avoid mime_content_type warning on wrapped streams mime_content_type() requires stream_cast() support to operate on a stream resource directly. Streams returned by File::fopen() can be wrapped in Icewind\Streams\CallbackWrapper, which does not implement stream_cast(), causing a PHP warning to be logged on every preview/output-file request even though the mime type is still detected correctly. This drains the stream into a temporary file first, then calls mime_content_type() against the file path instead. No behavior change; only removes log noise. Signed-off-by: Simon Holzman (cherry picked from commit 3ece3ab15b1a98f3417351dda0a8b940f15f7020) Signed-off-by: Misha M.-Kupriyanov --- lib/Service/AssistantService.php | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/Service/AssistantService.php b/lib/Service/AssistantService.php index 7376546e1..15f576662 100644 --- a/lib/Service/AssistantService.php +++ b/lib/Service/AssistantService.php @@ -628,7 +628,7 @@ public function saveOutputFile(string $userId, int $ocpTaskId, int $fileId): arr * @throws NotPermittedException */ private function getTargetFileName(File $file): string { - $mimeType = mime_content_type($file->fopen('rb')); + $mimeType = $this->detectMimeType($file->fopen('rb')); $fileName = $file->getName(); $mimes = new \Mimey\MimeTypes; @@ -640,6 +640,29 @@ private function getTargetFileName(File $file): string { return $fileName; } + /** + * Wraps mime_content_type() to avoid a PHP warning when given a stream + * that does not implement stream_cast() (e.g. Icewind\Streams\CallbackWrapper, + * returned by File::fopen() for some storage backends). The stream is + * drained into a temp file first so mime_content_type() can operate on a + * real path instead of the unsupported stream resource. + * + * @param resource $stream + * @return string|false + */ + private function detectMimeType($stream) { + $tmpFile = tempnam(sys_get_temp_dir(), 'nc_assistant_mime_'); + $tmpHandle = fopen($tmpFile, 'wb'); + stream_copy_to_stream($stream, $tmpHandle); + fclose($tmpHandle); + if (is_resource($stream)) { + fclose($stream); + } + $mimeType = mime_content_type($tmpFile); + unlink($tmpFile); + return $mimeType; + } + /** * @param Task $task * @return array @@ -692,7 +715,7 @@ private function extractFileIdsFromTask(Task $task): array { */ public function getOutputFilePreviewFile(string $userId, int $taskId, int $fileId, ?int $x = 100, ?int $y = 100): ?array { $taskOutputFile = $this->getTaskOutputFile($userId, $taskId, $fileId); - $realMime = mime_content_type($taskOutputFile->fopen('rb')); + $realMime = $this->detectMimeType($taskOutputFile->fopen('rb')); return $this->previewService->getFilePreviewFile($taskOutputFile, $x, $y, $realMime ?: null); } From cc750bd611c59514fd89dcb68e302e51d1aa9a48 Mon Sep 17 00:00:00 2001 From: Simon Holzman Date: Mon, 13 Jul 2026 23:32:00 -0400 Subject: [PATCH 2/4] fix: use finfo buffer instead of mime_content_type on streams Avoids copying the whole file to disk for mime detection. Reads only the first 4096 bytes into a buffer and uses finfo::buffer() to detect the mime type, which does not require stream_cast() support. Addresses maintainer feedback on #592. Signed-off-by: Simon Holzman (cherry picked from commit 2be918a414d922f8258db0b14d30f3e8c3bb2caa) Signed-off-by: Misha M.-Kupriyanov --- lib/Service/AssistantService.php | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/lib/Service/AssistantService.php b/lib/Service/AssistantService.php index 15f576662..be045b45b 100644 --- a/lib/Service/AssistantService.php +++ b/lib/Service/AssistantService.php @@ -628,7 +628,8 @@ public function saveOutputFile(string $userId, int $ocpTaskId, int $fileId): arr * @throws NotPermittedException */ private function getTargetFileName(File $file): string { - $mimeType = $this->detectMimeType($file->fopen('rb')); + $head = fread($file->fopen('rb'), 4096); + $mimeType = (new \finfo(FILEINFO_MIME_TYPE))->buffer($head); $fileName = $file->getName(); $mimes = new \Mimey\MimeTypes; @@ -640,29 +641,6 @@ private function getTargetFileName(File $file): string { return $fileName; } - /** - * Wraps mime_content_type() to avoid a PHP warning when given a stream - * that does not implement stream_cast() (e.g. Icewind\Streams\CallbackWrapper, - * returned by File::fopen() for some storage backends). The stream is - * drained into a temp file first so mime_content_type() can operate on a - * real path instead of the unsupported stream resource. - * - * @param resource $stream - * @return string|false - */ - private function detectMimeType($stream) { - $tmpFile = tempnam(sys_get_temp_dir(), 'nc_assistant_mime_'); - $tmpHandle = fopen($tmpFile, 'wb'); - stream_copy_to_stream($stream, $tmpHandle); - fclose($tmpHandle); - if (is_resource($stream)) { - fclose($stream); - } - $mimeType = mime_content_type($tmpFile); - unlink($tmpFile); - return $mimeType; - } - /** * @param Task $task * @return array @@ -715,7 +693,8 @@ private function extractFileIdsFromTask(Task $task): array { */ public function getOutputFilePreviewFile(string $userId, int $taskId, int $fileId, ?int $x = 100, ?int $y = 100): ?array { $taskOutputFile = $this->getTaskOutputFile($userId, $taskId, $fileId); - $realMime = $this->detectMimeType($taskOutputFile->fopen('rb')); + $head = fread($taskOutputFile->fopen('rb'), 4096); + $realMime = (new \finfo(FILEINFO_MIME_TYPE))->buffer($head); return $this->previewService->getFilePreviewFile($taskOutputFile, $x, $y, $realMime ?: null); } From 201d6ce498791b613921d05acf79edea406fdfa2 Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Wed, 23 Sep 2026 17:20:36 +0200 Subject: [PATCH 3/4] fix: improve mimetype detection when serving a task output file (just like when saving and generating a preview) Signed-off-by: Julien Veyssier (cherry picked from commit 66e2ad46be370c3d24ae78ba90f1fa2453307d8e) Signed-off-by: Misha M.-Kupriyanov --- lib/Controller/AssistantApiController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Controller/AssistantApiController.php b/lib/Controller/AssistantApiController.php index 6f0270721..58a48dd20 100644 --- a/lib/Controller/AssistantApiController.php +++ b/lib/Controller/AssistantApiController.php @@ -399,7 +399,8 @@ public function getOutputFilePreview(int $ocpTaskId, int $fileId, ?int $x = 100, public function getOutputFile(int $ocpTaskId, int $fileId): DataDownloadResponse|DataResponse { try { $taskOutputFile = $this->assistantService->getTaskOutputFile($this->userId, $ocpTaskId, $fileId); - $realMime = mime_content_type($taskOutputFile->fopen('rb')); + $head = fread($taskOutputFile->fopen('rb'), 4096); + $realMime = (new \finfo(FILEINFO_MIME_TYPE))->buffer($head); $response = new DataDownloadResponse( $taskOutputFile->getContent(), $ocpTaskId . '-' . $fileId, From ffec40001b76a8dd2865610a1623c7b4df2f2f60 Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Thu, 24 Sep 2026 15:29:46 +0200 Subject: [PATCH 4/4] use the appData file extension if there is one Signed-off-by: Julien Veyssier (cherry picked from commit 5cde2e7943d9b9799ce5750f0bebb92f94656028) Signed-off-by: Misha M.-Kupriyanov --- lib/Controller/AssistantApiController.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/Controller/AssistantApiController.php b/lib/Controller/AssistantApiController.php index 58a48dd20..1b9ff145b 100644 --- a/lib/Controller/AssistantApiController.php +++ b/lib/Controller/AssistantApiController.php @@ -401,9 +401,16 @@ public function getOutputFile(int $ocpTaskId, int $fileId): DataDownloadResponse $taskOutputFile = $this->assistantService->getTaskOutputFile($this->userId, $ocpTaskId, $fileId); $head = fread($taskOutputFile->fopen('rb'), 4096); $realMime = (new \finfo(FILEINFO_MIME_TYPE))->buffer($head); + + $fileName = $ocpTaskId . '-' . $fileId; + $extension = pathinfo($taskOutputFile->getName(), PATHINFO_EXTENSION); + if ($extension !== '') { + $fileName .= '.' . $extension; + } + $response = new DataDownloadResponse( $taskOutputFile->getContent(), - $ocpTaskId . '-' . $fileId, + $fileName, $realMime ?: 'application/octet-stream', ); $response->cacheFor(60 * 60 * 24, false, true);