Skip to content

Store taskprocessing output files with extension to ease mimtype guessing#61689

Open
marcelklehr wants to merge 12 commits into
masterfrom
feat/taskprocessing-output-files-with-mimetypes
Open

Store taskprocessing output files with extension to ease mimtype guessing#61689
marcelklehr wants to merge 12 commits into
masterfrom
feat/taskprocessing-output-files-with-mimetypes

Conversation

@marcelklehr

@marcelklehr marcelklehr commented Jul 1, 2026

Copy link
Copy Markdown
Member

Summary

Currently, we have to guess the mime type entirely from the data which is sometimes hard. With this we allow synchronous providers to hint the file extension and mime type.

Also adds some needed attributes.

TODO

  • How should we allow ex apps to pass this information?

Checklist

AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@marcelklehr marcelklehr added this to the Nextcloud 35 milestone Jul 1, 2026
@marcelklehr marcelklehr requested a review from a team as a code owner July 1, 2026 11:29
@marcelklehr marcelklehr requested review from Altahrim and salmart-dev and removed request for a team July 1, 2026 11:29
@marcelklehr marcelklehr added the 2. developing Work in progress label Jul 1, 2026
Comment thread lib/public/TaskProcessing/EShapeType.php Outdated
@marcelklehr marcelklehr force-pushed the feat/taskprocessing-output-files-with-mimetypes branch from a499488 to 944e422 Compare July 1, 2026 12:07
@marcelklehr marcelklehr requested review from Copilot and julien-nc July 1, 2026 12:08
@marcelklehr marcelklehr added 3. to review Waiting for reviews and removed 2. developing Work in progress labels Jul 1, 2026

@julien-nc julien-nc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm 👍

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the TaskProcessing public API and implementation to better handle “file-shaped” task outputs by allowing providers (and ExApps uploads) to influence stored output filenames via extensions, with the goal of improving MIME type detection downstream. It also annotates several public TaskProcessing interfaces/data objects with #[Implementable] / #[Consumable] attributes.

Changes:

  • Introduces OCP\TaskProcessing\FileShaped and updates synchronous provider return type annotations to allow returning FileShaped values.
  • Extends output validation (EShapeType::validateOutputWithFileData) and output encapsulation (Manager::encapsulateOutputFileData) to accept file-shaped outputs.
  • Appends file extensions when storing uploaded/provider output data in AppData, and adds Implementable/Consumable attributes to several public APIs.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
lib/public/TaskProcessing/SynchronousProviderOptions.php Marks the options class as #[Consumable] for public API consumption.
lib/public/TaskProcessing/ShapeEnumValue.php Marks the DTO as #[Consumable].
lib/public/TaskProcessing/ShapeDescriptor.php Marks the DTO as #[Consumable].
lib/public/TaskProcessing/ITriggerableProvider.php Marks provider interface as #[Implementable] (but since metadata needs correction).
lib/public/TaskProcessing/ITaskType.php Marks task type interface as #[Implementable].
lib/public/TaskProcessing/ISynchronousProvider.php Marks interface as #[Implementable] and expands psalm return types to include FileShaped.
lib/public/TaskProcessing/ISynchronousOptionsAwareProvider.php Marks interface as #[Implementable] and expands psalm return types to include FileShaped.
lib/public/TaskProcessing/IProvider.php Marks provider interface as #[Implementable].
lib/public/TaskProcessing/IInternalTaskType.php Marks internal task type interface as #[Implementable] (minor formatting consistency).
lib/public/TaskProcessing/FileShaped.php Adds new public DTO to carry file-shaped output data + metadata (extension/mime type).
lib/public/TaskProcessing/EShapeType.php Updates output validation to allow FileShaped (but list validation currently has critical logic errors).
lib/private/TaskProcessing/Manager.php Stores file outputs using provided extensions (but list branch currently clears unrelated output keys and needs extension normalization).
core/Controller/TaskProcessingApiController.php Appends extension when storing ExApp uploaded files (needs extension sanitization/normalization).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/public/TaskProcessing/EShapeType.php Outdated
Comment thread lib/public/TaskProcessing/EShapeType.php Outdated
Comment thread lib/public/TaskProcessing/EShapeType.php Outdated
Comment thread lib/public/TaskProcessing/EShapeType.php Outdated
Comment thread lib/private/TaskProcessing/Manager.php
Comment thread lib/public/TaskProcessing/FileShaped.php
Comment thread lib/public/TaskProcessing/FileShaped.php Outdated
Comment thread lib/public/TaskProcessing/FileShaped.php Outdated
Comment thread lib/public/TaskProcessing/FileShaped.php Outdated
Comment thread lib/private/TaskProcessing/Manager.php
marcelklehr and others added 10 commits July 14, 2026 12:25
…ype guessing

Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
…erty

Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
@marcelklehr marcelklehr force-pushed the feat/taskprocessing-output-files-with-mimetypes branch from 8d2c8e2 to 70e7fea Compare July 14, 2026 10:26

@julien-nc julien-nc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm. A few nitpicks.

Comment thread lib/public/TaskProcessing/FileShaped.php Outdated
Comment thread lib/public/TaskProcessing/FileShaped.php Outdated
Comment thread lib/public/TaskProcessing/FileShaped.php
Comment thread lib/public/TaskProcessing/FileShaped.php Outdated
Comment thread core/Controller/TaskProcessingApiController.php
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
@marcelklehr marcelklehr force-pushed the feat/taskprocessing-output-files-with-mimetypes branch from 3c6bd44 to 326b94a Compare July 15, 2026 05:50
@@ -534,7 +535,8 @@ public function setFileContentsExApp(int $taskId): DataResponse {
if (!$handle) {
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
}
$fileId = $this->setFileContentsInternal($handle);
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$ext = pathinfo(($file['name'] ?? ''), PATHINFO_EXTENSION);

Comment on lines -159 to +164
if ($this === EShapeType::Image && !is_string($value)) {
if ($this === EShapeType::Image && !is_string($value) && !($value instanceof FileShaped && $value->getShapeType() === EShapeType::Image)) {
throw new ValidationException('Non-image item provided for Image slot');
}
if ($this === EShapeType::ListOfImages && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item))) > 0)) {
if ($this === EShapeType::ListOfImages && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item) && !($item instanceof FileShaped && $item->getShapeType() === EShapeType::Image))) > 0)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it would be nice to exact these checks in functions with separate ifs for different checks so they are easier to parse.
they could even be reused for the array checks then.

something along these lines but feel free to do it your way:

	/**
	 * Check whether a single value is valid file output data for the given shape type.
	 * Accepts raw string data or a FileShaped wrapper whose shape type matches.
	 */
	private static function isValidFileData(mixed $value, EShapeType $expected): bool {
		if (is_string($value)) {
			return true;
		}
		return $value instanceof FileShaped && $value->getShapeType() === $expected;
	}

	/**
	 * Check whether a value is a valid list of file output data items.
	 */
	private static function isValidFileDataList(mixed $value, EShapeType $scalarType): bool {
		if (!is_array($value)) {
			return false;
		}
		foreach ($value as $item) {
			if (!self::isValidFileData($item, $scalarType)) {
				return false;
			}
		}
		return true;
	}

and can be used like:

		if ($this === EShapeType::Image && !self::isValidFileData($value, EShapeType::Image)) {
			throw new ValidationException('Non-image item provided for Image slot');
		}

throw new ValidationException('Non-file item provided for File slot');
}
if ($this === EShapeType::ListOfFiles && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item))) > 0)) {
if ($this === EShapeType::ListOfFiles && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item) && !($item instanceof FileShaped && $item->getShapeType() === EShapeType::File))) > 0)) {
throw new ValidationException('Non-audio list item provided for ListOfFiles slot');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new ValidationException('Non-audio list item provided for ListOfFiles slot');
throw new ValidationException('Non-file list item provided for ListOfFiles slot');

Comment on lines -28 to +30
* @psalm-return array<string, list<numeric|string>|numeric|string>
* @psalm-return array<string, list<numeric|string|FileShaped>|numeric|string|FileShaped>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it can be noted that FileShaped addition in the return type was made in 35.0.0
like in

* @since 31.0.0 Added the `showDisabled` argument.

/**
* @since 35.0.0
*/
public static function sanitizeExtension(string $ext): string {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the extension provider is within the trust boundary of ex-apps and php apps but it may not hurt to reject .php, .phar and .htaccess extensions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants