diff --git a/app/config/errors.php b/app/config/errors.php index 00b177480d..eb8c040c0e 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -313,6 +313,11 @@ return [ ], /** Storage */ + Exception::STORAGE_FILE_ALREADY_EXISTS => [ + 'name' => Exception::STORAGE_FILE_ALREADY_EXISTS, + 'description' => 'A storage file with the requested ID already exists.', + 'code' => 409, + ], Exception::STORAGE_FILE_NOT_FOUND => [ 'name' => Exception::STORAGE_FILE_NOT_FOUND, 'description' => 'The requested file could not be found.', diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 3adfbcd722..b712c767ae 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -1,6 +1,7 @@ $deviceLocal->getFileMimeType($fileTmpName)]; if (!$file->isEmpty()) { $chunks = $file->getAttribute('chunksTotal', 1); + $uploaded = $file->getAttribute('chunksUploaded', 0); $metadata = $file->getAttribute('metadata', []); + if ($chunk === -1) { $chunk = $chunks; } + + if ($uploaded === $chunks) { + throw new Exception(Exception::STORAGE_FILE_ALREADY_EXISTS); + } } $chunksUploaded = $deviceFiles->upload($fileTmpName, $path, $chunk, $chunks, $metadata); + if (empty($chunksUploaded)) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed uploading file'); } diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 0d1b285d86..5d0e0b400b 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -105,6 +105,7 @@ class Exception extends \Exception public const AVATAR_ICON_NOT_FOUND = 'avatar_icon_not_found'; /** Storage */ + public const STORAGE_FILE_ALREADY_EXISTS = 'storage_file_already_exists'; public const STORAGE_FILE_NOT_FOUND = 'storage_file_not_found'; public const STORAGE_DEVICE_NOT_FOUND = 'storage_device_not_found'; public const STORAGE_FILE_EMPTY = 'storage_file_empty'; diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index 4ea1a2d0ad..16ab930af5 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -316,6 +316,54 @@ trait StorageBase return ['bucketId' => $bucketId]; } + public function testCreateBucketFileNoCollidingId(): void + { + $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'bucketId' => ID::unique(), + 'name' => 'Test Bucket', + 'maximumFileSize' => 2000000, //2MB + 'allowedFileExtensions' => ["jpg", "png"], + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $this->assertEquals(201, $bucket['headers']['status-code']); + $this->assertNotEmpty($bucket['body']['$id']); + + $bucketId = $bucket['body']['$id']; + + $fileId = ID::unique(); + + $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'fileId' => $fileId, + 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'), + ]); + + $this->assertEquals(201, $file['headers']['status-code']); + $this->assertEquals($fileId, $file['body']['$id']); + + $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'fileId' => $fileId, + 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/file.png'), 'image/png', 'file.png'), + ]); + + $this->assertEquals(409, $file['headers']['status-code']); + } + /** * @depends testCreateBucketFile */