Skip to content

Commit abd3cb6

Browse files
committed
fix(files): more conversion tests and translate error messages
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
1 parent 6afe125 commit abd3cb6

File tree

3 files changed

+66
-11
lines changed

3 files changed

+66
-11
lines changed

apps/files/lib/Controller/ConversionApiController.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@
1010
namespace OCA\Files\Controller;
1111

1212
use OC\Files\Utils\PathHelper;
13+
use OC\ForbiddenException;
1314
use OCP\AppFramework\Http;
1415
use OCP\AppFramework\Http\Attribute\ApiRoute;
1516
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
1617
use OCP\AppFramework\Http\Attribute\UserRateLimit;
1718
use OCP\AppFramework\Http\DataResponse;
19+
use OCP\AppFramework\OCS\OCSBadRequestException;
1820
use OCP\AppFramework\OCS\OCSException;
1921
use OCP\AppFramework\OCS\OCSForbiddenException;
2022
use OCP\AppFramework\OCS\OCSNotFoundException;
2123
use OCP\AppFramework\OCSController;
2224
use OCP\Files\Conversion\IConversionManager;
2325
use OCP\Files\File;
26+
use OCP\Files\GenericFileException;
2427
use OCP\Files\IRootFolder;
2528
use OCP\IL10N;
2629
use OCP\IRequest;
@@ -59,7 +62,8 @@ public function convert(int $fileId, string $targetMimeType, ?string $destinatio
5962
$userFolder = $this->rootFolder->getUserFolder($this->userId);
6063
$file = $userFolder->getFirstNodeById($fileId);
6164

62-
if (!($file instanceof File)) {
65+
// Also throw a 404 if the file is not readable to not leak information
66+
if (!($file instanceof File) || $file->isReadable() === false) {
6367
throw new OCSNotFoundException($this->l10n->t('The file cannot be found'));
6468
}
6569

@@ -72,14 +76,18 @@ public function convert(int $fileId, string $targetMimeType, ?string $destinatio
7276
}
7377

7478
if (!$userFolder->get($parentDir)->isCreatable()) {
75-
throw new OCSForbiddenException();
79+
throw new OCSForbiddenException($this->l10n->t('You do not have permission to create a file at the specified location'));
7680
}
7781

7882
$destination = $userFolder->getFullPath($destination);
7983
}
8084

8185
try {
8286
$convertedFile = $this->fileConversionManager->convert($file, $targetMimeType, $destination);
87+
} catch (ForbiddenException $e) {
88+
throw new OCSForbiddenException($e->getMessage());
89+
} catch (GenericFileException $e) {
90+
throw new OCSBadRequestException($e->getMessage());
8391
} catch (\Exception $e) {
8492
logger('files')->error($e->getMessage(), ['exception' => $e]);
8593
throw new OCSException($this->l10n->t('The file could not be converted.'));

build/integration/file_conversions/file_conversions.feature

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,27 @@ Feature: conversions
7676
Then as "user0" the file "/image.jpg" exists
7777
Then as "user0" the file "/image.png" does not exist
7878

79+
Scenario: Converting a file to a given path without extension fails
80+
Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
81+
And User "user0" created a folder "/folder"
82+
Then as "user0" the file "/image.jpg" exists
83+
Then as "user0" the folder "/folder" exists
84+
When user "user0" converts file "/image.jpg" to "image/png" and saves it to "/folder/image"
85+
Then the HTTP status code should be "400"
86+
Then the OCS status code should be "400"
87+
Then as "user0" the file "/folder/image.png" does not exist
88+
Then as "user0" the file "/image.png" does not exist
89+
90+
@local_storage
91+
Scenario: Converting a file bigger than 100 MiB fails
92+
Given file "/image.jpg" of size 108003328 is created in local storage
93+
Then as "user0" the folder "/local_storage" exists
94+
Then as "user0" the file "/local_storage/image.jpg" exists
95+
When user "user0" converts file "/local_storage/image.jpg" to "image/png" and saves it to "/image.png"
96+
Then the HTTP status code should be "400"
97+
Then the OCS status code should be "400"
98+
Then as "user0" the file "/image.png" does not exist
99+
79100
Scenario: Forbid conversion to a destination without create permission
80101
Given user "user1" exists
81102
# Share the folder with user1

lib/private/Files/Conversion/ConversionManager.php

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@
1010
namespace OC\Files\Conversion;
1111

1212
use OC\AppFramework\Bootstrap\Coordinator;
13+
use OC\ForbiddenException;
1314
use OC\SystemConfig;
1415
use OCP\Files\Conversion\IConversionManager;
1516
use OCP\Files\Conversion\IConversionProvider;
1617
use OCP\Files\File;
1718
use OCP\Files\GenericFileException;
1819
use OCP\Files\IRootFolder;
20+
use OCP\IL10N;
1921
use OCP\ITempManager;
22+
use OCP\L10N\IFactory;
2023
use OCP\PreConditionNotMetException;
2124
use Psr\Container\ContainerExceptionInterface;
2225
use Psr\Container\ContainerInterface;
@@ -37,14 +40,17 @@ class ConversionManager implements IConversionManager {
3740
/** @var list<IConversionProvider> */
3841
private array $providers = [];
3942

43+
private IL10N $l10n;
4044
public function __construct(
4145
private Coordinator $coordinator,
4246
private ContainerInterface $serverContainer,
4347
private IRootFolder $rootFolder,
4448
private ITempManager $tempManager,
4549
private LoggerInterface $logger,
4650
private SystemConfig $config,
51+
IFactory $l10nFactory,
4752
) {
53+
$this->l10n = $l10nFactory->get('files');
4854
}
4955

5056
public function hasProviders(): bool {
@@ -62,30 +68,29 @@ public function getProviders(): array {
6268

6369
public function convert(File $file, string $targetMimeType, ?string $destination = null): string {
6470
if (!$this->hasProviders()) {
65-
throw new PreConditionNotMetException('No file conversion providers available');
71+
throw new PreConditionNotMetException($this->l10n->t('No file conversion providers available'));
6672
}
6773

6874
// Operate in mebibytes
6975
$fileSize = $file->getSize() / (1024 * 1024);
7076
$threshold = $this->config->getValue('max_file_conversion_filesize', 100);
7177
if ($fileSize > $threshold) {
72-
throw new GenericFileException('File is too large to convert');
78+
throw new GenericFileException($this->l10n->t('File is too large to convert'));
7379
}
7480

7581
$fileMimeType = $file->getMimetype();
7682
$validProvider = $this->getValidProvider($fileMimeType, $targetMimeType);
7783

7884
if ($validProvider !== null) {
79-
$convertedFile = $validProvider->convertFile($file, $targetMimeType);
80-
85+
// Get the target extension given by the provider
8186
$targetExtension = '';
8287
foreach ($validProvider->getSupportedMimeTypes() as $mimeProvider) {
8388
if ($mimeProvider->getTo() === $targetMimeType) {
8489
$targetExtension = $mimeProvider->getExtension();
8590
break;
8691
}
8792
}
88-
93+
8994
// If destination not provided, we use the same path
9095
// as the original file, but with the new extension
9196
if ($destination === null) {
@@ -94,11 +99,21 @@ public function convert(File $file, string $targetMimeType, ?string $destination
9499
$destination = $parent->getFullPath($basename . '.' . $targetExtension);
95100
}
96101

102+
// If destination doesn't match the target extension, we throw an error
103+
if (pathinfo($destination, PATHINFO_EXTENSION) !== $targetExtension) {
104+
throw new GenericFileException($this->l10n->t('Destination does not match conversion extension'));
105+
}
106+
107+
// Check destination before converting
108+
$this->checkDestination($destination);
109+
110+
// Convert the file and write it to the destination
111+
$convertedFile = $validProvider->convertFile($file, $targetMimeType);
97112
$convertedFile = $this->writeToDestination($destination, $convertedFile);
98113
return $convertedFile->getPath();
99114
}
100115

101-
throw new RuntimeException('Could not convert file');
116+
throw new RuntimeException($this->l10n->t('Could not convert file'));
102117
}
103118

104119
/**
@@ -127,14 +142,25 @@ private function getRegisteredProviders(): array {
127142
return array_values(array_merge([], $this->preferredProviders, $this->providers));
128143
}
129144

145+
private function checkDestination(string $destination): void {
146+
if (!$this->rootFolder->nodeExists(dirname($destination))) {
147+
throw new ForbiddenException($this->l10n->t('Destination does not exist'));
148+
}
149+
150+
$folder = $this->rootFolder->get(dirname($destination));
151+
if (!$folder->isCreatable()) {
152+
throw new ForbiddenException($this->l10n->t('Destination is not creatable'));
153+
}
154+
}
155+
130156
private function writeToDestination(string $destination, mixed $content): File {
157+
$this->checkDestination($destination);
158+
131159
if ($this->rootFolder->nodeExists($destination)) {
132160
$file = $this->rootFolder->get($destination);
133161
$parent = $file->getParent();
134-
if (!$parent->isCreatable()) {
135-
throw new GenericFileException('Destination is not creatable');
136-
}
137162

163+
// Folder permissions is already checked in checkDestination method
138164
$newName = $parent->getNonExistingName(basename($destination));
139165
$destination = $parent->getFullPath($newName);
140166
}

0 commit comments

Comments
 (0)