From f2f613d72105df7c8b26bdd6e71565bf1830c26d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 25 Jun 2021 16:28:37 +0545 Subject: [PATCH 1/3] backward compatibility for get requests --- app/controllers/api/storage.php | 242 ++------------------------------ 1 file changed, 12 insertions(+), 230 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index f66621b768..5c80ba8da9 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -1031,17 +1031,12 @@ App::get('/v1/storage/files') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') - ->inject('dbForInternal') - ->action(function ($search, $limit, $offset, $orderType, $response, $dbForInternal) { + ->action(function ($search, $limit, $offset, $orderType, $response) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ - $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, $search)] : []; - - $response->dynamic2(new Document([ - 'files' => $dbForInternal->find('files', $queries, $limit, $offset, ['_id'], [$orderType]), - 'sum' => $dbForInternal->count('files', $queries, APP_LIMIT_COUNT), - ]), Response::MODEL_FILE_LIST); + $response->redirect('/v1/storage/buckets/default/files?=' + .\http_build_query(['search' => $search, 'limit' => $limit, 'offset' => $offset, 'orderType' => $orderType])); }); App::get('/v1/storage/files/:fileId') @@ -1057,18 +1052,11 @@ App::get('/v1/storage/files/:fileId') ->label('sdk.response.model', Response::MODEL_FILE) ->param('fileId', '', new UID(), 'File unique ID.') ->inject('response') - ->inject('dbForInternal') - ->action(function ($fileId, $response, $dbForInternal) { + ->action(function ($fileId, $response) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ - $file = $dbForInternal->getDocument('files', $fileId); - - if (empty($file->getId())) { - throw new Exception('File not found', 404); - } - - $response->dynamic2($file, Response::MODEL_FILE); + $response->redirect('/v1/storage/buckets/default/files/'.$fileId); }); App::get('/v1/storage/files/:fileId/preview') @@ -1094,135 +1082,15 @@ App::get('/v1/storage/files/:fileId/preview') ->param('rotation', 0, new Range(0,360), 'Preview image rotation in degrees. Pass an integer between 0 and 360.', true) ->param('background', '', new HexColor(), 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true) ->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true) - ->inject('request') ->inject('response') - ->inject('project') - ->inject('dbForInternal') - ->action(function ($fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $dbForInternal) { + ->action(function ($fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $response) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Database $dbForInternal */ - $storage = 'files'; - - if (!\extension_loaded('imagick')) { - throw new Exception('Imagick extension is missing', 500); - } - - if (!Storage::exists($storage)) { - throw new Exception('No such storage device', 400); - } - - if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' == $output)) { // Fallback webp to jpeg when no browser support - $output = 'jpg'; - } - - $inputs = Config::getParam('storage-inputs'); - $outputs = Config::getParam('storage-outputs'); - $fileLogos = Config::getParam('storage-logos'); - - $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache - $key = \md5($fileId.$width.$height.$quality.$borderWidth.$borderColor.$borderRadius.$opacity.$rotation.$background.$storage.$output); - - $file = $dbForInternal->getDocument('files', $fileId); - - if (empty($file->getId())) { - throw new Exception('File not found', 404); - } - - $path = $file->getAttribute('path'); - $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); - $algorithm = $file->getAttribute('algorithm'); - $cipher = $file->getAttribute('openSSLCipher'); - $mime = $file->getAttribute('mimeType'); - - if (!\in_array($mime, $inputs)) { - $path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default']; - $algorithm = null; - $cipher = null; - $background = (empty($background)) ? 'eceff1' : $background; - $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); - $key = \md5($path.$width.$height.$quality.$borderWidth.$borderColor.$borderRadius.$opacity.$rotation.$background.$storage.$output); - } - - $compressor = new GZIP(); - $device = Storage::getDevice('files'); - - if (!\file_exists($path)) { - throw new Exception('File not found', 404); - } - - $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-'.$project->getId())); // Limit file number or size - $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */); - - if ($data) { - $output = (empty($output)) ? $type : $output; - - return $response - ->setContentType((\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg']) - ->addHeader('Expires', $date) - ->addHeader('X-Appwrite-Cache', 'hit') - ->send($data) - ; - } - - $source = $device->read($path); - - if (!empty($cipher)) { // Decrypt - $source = OpenSSL::decrypt( - $source, - $file->getAttribute('openSSLCipher'), - App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('openSSLVersion')), - 0, - \hex2bin($file->getAttribute('openSSLIV')), - \hex2bin($file->getAttribute('openSSLTag')) - ); - } - - if (!empty($algorithm)) { - $source = $compressor->decompress($source); - } - - $image = new Image($source); - - $image->crop((int) $width, (int) $height, $gravity); - - if (!empty($opacity) || $opacity==0) { - $image->setOpacity($opacity); - } - - if (!empty($background)) { - $image->setBackground('#'.$background); - } - - - if (!empty($borderWidth) ) { - $image->setBorder($borderWidth, '#'.$borderColor); - } - - if (!empty($borderRadius)) { - $image->setBorderRadius($borderRadius); - } - - if (!empty($rotation)) { - $image->setRotation($rotation); - } - - $output = (empty($output)) ? $type : $output; - - $data = $image->output($output, $quality); - - $cache->save($key, $data); - - $response - ->setContentType($outputs[$output]) - ->addHeader('Expires', $date) - ->addHeader('X-Appwrite-Cache', 'miss') - ->send($data) - ; - - unset($image); + $response->redirect('/v1/storage/buckets/default/files/'.$fileId.'/preview?' + .\http_build_query(['width' => $width, 'height' => $height, 'gravity' => $gravity, 'quality' => $quality, 'borderWidth' => $borderWidth, 'borderColor' => $borderColor, 'borderRadius' => $borderRadius, 'opacity' => $opacity, 'rotation' => $rotation, 'background' => $background, 'output' => $output])); }); App::get('/v1/storage/files/:fileId/download') @@ -1238,49 +1106,11 @@ App::get('/v1/storage/files/:fileId/download') ->label('sdk.methodType', 'location') ->param('fileId', '', new UID(), 'File unique ID.') ->inject('response') - ->inject('dbForInternal') - ->action(function ($fileId, $response, $dbForInternal) { + ->action(function ($fileId, $response) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ - $file = $dbForInternal->getDocument('files', $fileId); - - if (empty($file->getId())) { - throw new Exception('File not found', 404); - } - - $path = $file->getAttribute('path', ''); - - if (!\file_exists($path)) { - throw new Exception('File not found in '.$path, 404); - } - - $compressor = new GZIP(); - $device = Storage::getDevice('files'); - - $source = $device->read($path); - - if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt - $source = OpenSSL::decrypt( - $source, - $file->getAttribute('openSSLCipher'), - App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('openSSLVersion')), - 0, - \hex2bin($file->getAttribute('openSSLIV')), - \hex2bin($file->getAttribute('openSSLTag')) - ); - } - - $source = $compressor->decompress($source); - - // Response - $response - ->setContentType($file->getAttribute('mimeType')) - ->addHeader('Content-Disposition', 'attachment; filename="'.$file->getAttribute('name', '').'"') - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache - ->addHeader('X-Peak', \memory_get_peak_usage()) - ->send($source) - ; + $response->redirect('/v1/storage/buckets/default/files/'.$fileId.'/download'); }); App::get('/v1/storage/files/:fileId/view') @@ -1296,59 +1126,11 @@ App::get('/v1/storage/files/:fileId/view') ->label('sdk.methodType', 'location') ->param('fileId', '', new UID(), 'File unique ID.') ->inject('response') - ->inject('dbForInternal') - ->action(function ($fileId, $response, $dbForInternal) { + ->action(function ($fileId, $response) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ - $file = $dbForInternal->getDocument('files', $fileId); - $mimes = Config::getParam('storage-mimes'); - - if (empty($file->getId())) { - throw new Exception('File not found', 404); - } - - $path = $file->getAttribute('path', ''); - - if (!\file_exists($path)) { - throw new Exception('File not found in '.$path, 404); - } - - $compressor = new GZIP(); - $device = Storage::getDevice('files'); - - $contentType = 'text/plain'; - - if (\in_array($file->getAttribute('mimeType'), $mimes)) { - $contentType = $file->getAttribute('mimeType'); - } - - $source = $device->read($path); - - if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt - $source = OpenSSL::decrypt( - $source, - $file->getAttribute('openSSLCipher'), - App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('openSSLVersion')), - 0, - \hex2bin($file->getAttribute('openSSLIV')), - \hex2bin($file->getAttribute('openSSLTag')) - ); - } - - $output = $compressor->decompress($source); - $fileName = $file->getAttribute('name', ''); - - // Response - $response - ->setContentType($contentType) - ->addHeader('Content-Security-Policy', 'script-src none;') - ->addHeader('X-Content-Type-Options', 'nosniff') - ->addHeader('Content-Disposition', 'inline; filename="'.$fileName.'"') - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache - ->addHeader('X-Peak', \memory_get_peak_usage()) - ->send($output) - ; + $response->redirect('/v1/storage/buckets/default/files/'.$fileId.'/view'); }); App::put('/v1/storage/files/:fileId') From 0e4736038b005779159ce0a4fe319ffe3c02bd06 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 25 Jun 2021 16:40:26 +0545 Subject: [PATCH 2/3] create file backward compatibility --- app/controllers/api/storage.php | 64 ++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 5c80ba8da9..67bf955458 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -915,13 +915,27 @@ App::post('/v1/storage/files') /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $usage */ + $bucketId = 'default'; + $bucket = $dbForInternal->getDocument('buckets', $bucketId); + + if($bucket->isEmpty()) { + throw new Exception('Bucket not found', 404); + } + $file = $request->getFiles('file'); /* * Validators */ - //$fileType = new FileType(array(FileType::FILE_TYPE_PNG, FileType::FILE_TYPE_GIF, FileType::FILE_TYPE_JPEG)); - $fileSize = new FileSize(App::getEnv('_APP_STORAGE_LIMIT', 0)); + $allowedFileExtensions = $bucket->getAttribute('allowedFileExtensions', []); + $fileExt = new FileExt($allowedFileExtensions); + + $maximumFileSize = $bucket->getAttribute('maximumFileSize', 0); + if($maximumFileSize > (int) App::getEnv('_APP_STORAGE_LIMIT',0)) { + throw new Exception('Server error', 500); + } + + $fileSize = new FileSize($maximumFileSize); $upload = new Upload(); if (empty($file)) { @@ -934,9 +948,9 @@ App::post('/v1/storage/files') $file['size'] = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size']; // Check if file type is allowed (feature for project settings?) - //if (!$fileType->isValid($file['tmp_name'])) { - //throw new Exception('File type not allowed', 400); - //} + if (!empty($allowedFileExtensions) && !$fileExt->isValid($file['name'])) { + throw new Exception('File extension not allowed', 400); + } if (!$fileSize->isValid($file['size'])) { // Check if file size is exceeding allowed limit throw new Exception('File size not allowed', 400); @@ -951,6 +965,7 @@ App::post('/v1/storage/files') // Save to storage $size = $device->getFileSize($file['tmp_name']); $path = $device->getPath(\uniqid().'.'.\pathinfo($file['name'], PATHINFO_EXTENSION)); + $path = $bucket->getId() . '/' . $path; if (!$device->upload($file['tmp_name'], $path)) { // TODO deprecate 'upload' and replace with 'move' throw new Exception('Failed moving file', 500); @@ -958,7 +973,7 @@ App::post('/v1/storage/files') $mimeType = $device->getFileMimeType($path); // Get mime-type before compression and encryption - if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled + if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled' && $bucket->getAttribute('antiVirus', true) && $size <= APP_LIMIT_ANTIVIRUS) { $antiVirus = new Network(App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'), (int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310)); @@ -969,12 +984,17 @@ App::post('/v1/storage/files') } // Compression - $compressor = new GZIP(); $data = $device->read($path); - $data = $compressor->compress($data); - $key = App::getEnv('_APP_OPENSSL_KEY_V1'); - $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); - $data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag); + if($size <= APP_LIMIT_COMPRESSION) { + $compressor = new GZIP(); + $data = $compressor->compress($data); + } + + if($bucket->getAttribute('encryption', true) && $size <= APP_LIMIT_ENCRYPTION) { + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag); + } if (!$device->write($path, $data, $mimeType)) { throw new Exception('Failed to save file', 500); @@ -982,24 +1002,29 @@ App::post('/v1/storage/files') $sizeActual = $device->getFileSize($path); - $file = $dbForInternal->createDocument('files', new Document([ + $data = [ '$read' => (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? [], // By default set read permissions for user '$write' => (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? [], // By default set write permissions for user 'dateCreated' => \time(), - 'bucketId' => '', + 'bucketId' => $bucket->getId(), 'name' => $file['name'], 'path' => $path, 'signature' => $device->getFileHash($path), 'mimeType' => $mimeType, 'sizeOriginal' => $size, 'sizeActual' => $sizeActual, - 'algorithm' => $compressor->getName(), + 'algorithm' => empty($compressor) ? '' : $compressor->getName(), 'comment' => '', - 'openSSLVersion' => '1', - 'openSSLCipher' => OpenSSL::CIPHER_AES_128_GCM, - 'openSSLTag' => \bin2hex($tag), - 'openSSLIV' => \bin2hex($iv), - ])); + ]; + + if($bucket->getAttribute('encryption', true) && $size <= APP_LIMIT_ENCRYPTION) { + $data['openSSLVersion'] = '1'; + $data['openSSLCipher'] = OpenSSL::CIPHER_AES_128_GCM; + $data['openSSLTag'] = \bin2hex($tag); + $data['openSSLIV'] = \bin2hex($iv); + } + + $file = $dbForInternal->createDocument('files', new Document($data)); $audits ->setParam('event', 'storage.files.create') @@ -1012,7 +1037,6 @@ App::post('/v1/storage/files') $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic2($file, Response::MODEL_FILE); - ; }); App::get('/v1/storage/files') From bae1fd5cfb6b2728b668729c00ff77dec970d44e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 25 Jun 2021 16:41:39 +0545 Subject: [PATCH 3/3] update and delete backward compatibility --- app/controllers/api/storage.php | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 67bf955458..22d0cb79dd 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -1180,17 +1180,23 @@ App::put('/v1/storage/files/:fileId') /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $audits */ + $bucketId = 'default'; + $bucket = $dbForInternal->getDocument('buckets', $bucketId); + + if($bucket->isEmpty()) { + throw new Exception('Bucket not found', 404); + } + $file = $dbForInternal->getDocument('files', $fileId); - if (empty($file->getId())) { + if ($file->isEmpty() || $file->getAttribute('bucketId') != $bucketId) { throw new Exception('File not found', 404); } - $file = $dbForInternal->updateDocument('files', $fileId, new Document(\array_merge($file->getArrayCopy(), [ - '$read' => $read, - '$write' => $write, - 'bucketId' => '', - ]))); + $file = $dbForInternal->updateDocument('files', $fileId, $file + ->setAttribute('$read', $read) + ->setAttribute('$write', $write) + ); $audits ->setParam('event', 'storage.files.update') @@ -1224,9 +1230,16 @@ App::delete('/v1/storage/files/:fileId') /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $usage */ + $bucketId = 'default'; + $bucket = $dbForInternal->getDocument('buckets', $bucketId); + + if($bucket->isEmpty()) { + throw new Exception('Bucket not found', 404); + } + $file = $dbForInternal->getDocument('files', $fileId); - if (empty($file->getId())) { + if ($file->isEmpty() || $file->getAttribute('bucketId') != $bucketId) { throw new Exception('File not found', 404); }