2019-05-09 18:54:39 +12:00
< ? php
2021-07-07 22:07:11 +12:00
use Appwrite\ClamAV\Network ;
use Appwrite\Database\Validator\UID ;
use Appwrite\OpenSSL\OpenSSL ;
use Appwrite\Utopia\Response ;
2021-07-12 22:23:01 +12:00
use Swoole\HTTP\Response as SwooleResponse ;
2020-06-29 05:31:21 +12:00
use Utopia\App ;
2019-05-09 18:54:39 +12:00
use Utopia\Cache\Adapter\Filesystem ;
2021-07-07 22:07:11 +12:00
use Utopia\Cache\Cache ;
use Utopia\Config\Config ;
2021-05-03 20:28:31 +12:00
use Utopia\Database\Document ;
2021-07-07 22:07:11 +12:00
use Utopia\Database\Query ;
use Utopia\Exception ;
use Utopia\Image\Image ;
use Utopia\Storage\Compression\Algorithms\GZIP ;
2021-01-22 21:28:33 +13:00
use Utopia\Storage\Storage ;
use Utopia\Storage\Validator\File ;
2021-07-07 22:07:11 +12:00
use Utopia\Storage\Validator\FileExt ;
2021-01-22 21:28:33 +13:00
use Utopia\Storage\Validator\FileSize ;
use Utopia\Storage\Validator\Upload ;
2021-07-07 22:07:11 +12:00
use Utopia\Validator\ArrayList ;
use Utopia\Validator\Boolean ;
use Utopia\Validator\HexColor ;
2021-06-14 23:59:47 +12:00
use Utopia\Validator\Integer ;
2021-07-07 22:07:11 +12:00
use Utopia\Validator\Range ;
use Utopia\Validator\Text ;
use Utopia\Validator\WhiteList ;
2019-05-09 18:54:39 +12:00
2021-06-14 23:32:09 +12:00
App :: post ( '/v1/storage/buckets' )
-> desc ( 'Create storage bucket' )
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'buckets.write' )
-> label ( 'event' , 'storage.buckets.create' )
2021-06-16 17:26:34 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_KEY ])
2021-06-14 23:32:09 +12:00
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'createBucket' )
-> label ( 'sdk.description' , '/docs/references/storage/create-bucket.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_CREATED )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_BUCKET )
2021-06-14 23:59:47 +12:00
-> param ( 'name' , '' , new Text ( 128 ), 'Bucket name' , false )
2021-06-16 17:50:54 +12:00
-> param ( 'read' , [], new ArrayList ( new Text ( 64 )), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.' , true )
-> param ( 'write' , [], new ArrayList ( new Text ( 64 )), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.' , true )
2021-07-07 22:07:11 +12:00
-> param ( 'maximumFileSize' , ( int ) App :: getEnv ( '_APP_STORAGE_LIMIT' , 0 ), new Integer (), 'Maximum file size allowed in bytes. Maximum allowed value is ' . App :: getEnv ( '_APP_STORAGE_LIMIT' , 0 ) . '. For self-hosted setups you can change the max limit by changing the `_APP_STORAGE_LIMIT` environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)' , true )
2021-06-18 19:34:52 +12:00
-> param ( 'allowedFileExtensions' , [], new ArrayList ( new Text ( 64 )), 'Allowed file extensions' , true )
2021-06-16 17:26:34 +12:00
-> param ( 'enabled' , true , new Boolean (), 'Is bucket enabled?' , true )
-> param ( 'adapter' , 'local' , new WhiteList ([ 'local' ]), 'Storage adapter.' , true )
2021-07-15 22:35:08 +12:00
-> param ( 'encryption' , true , new Boolean (), 'Is encryption enabled? For file size above ' . Storage :: human ( APP_STORAGE_READ_BUFFER ) . ' encryption is skipped even if it\'s enabled' , true )
2021-06-20 22:49:10 +12:00
-> param ( 'antiVirus' , true , new Boolean (), 'Is virus scanning enabled? For file size above ' . Storage :: human ( APP_LIMIT_ANTIVIRUS ) . ' AntiVirus scanning is skipped even if it\'s enabled' , true )
2021-06-14 23:32:09 +12:00
-> inject ( 'response' )
2021-06-15 20:34:49 +12:00
-> inject ( 'dbForInternal' )
2021-06-14 23:32:09 +12:00
-> inject ( 'audits' )
2021-06-16 21:59:47 +12:00
-> action ( function ( $name , $read , $write , $maximumFileSize , $allowedFileExtensions , $enabled , $adapter , $encryption , $antiVirus , $response , $dbForInternal , $audits ) {
2021-06-14 23:59:47 +12:00
/** @var Appwrite\Utopia\Response $response */
2021-06-15 20:34:49 +12:00
/** @var Utopia\Database\Database $dbForInternal */
2021-06-14 23:59:47 +12:00
/** @var Appwrite\Event\Event $audits */
2021-06-16 17:50:54 +12:00
$data = $dbForInternal -> createDocument ( 'buckets' , new Document ([
2021-06-16 17:31:07 +12:00
'$collection' => 'buckets' ,
'dateCreated' => \time (),
'dateUpdated' => \time (),
'name' => $name ,
'maximumFileSize' => $maximumFileSize ,
'allowedFileExtensions' => $allowedFileExtensions ,
'enabled' => $enabled ,
'adapter' => $adapter ,
'encryption' => $encryption ,
'antiVirus' => $antiVirus ,
2021-06-16 17:50:54 +12:00
'$read' => $read ,
'$write' => $write ,
]));
2021-06-14 23:59:47 +12:00
$audits
2021-06-16 17:50:54 +12:00
-> setParam ( 'event' , 'storage.buckets.create' )
-> setParam ( 'resource' , 'storage/buckets/' . $data -> getId ())
2021-06-14 23:59:47 +12:00
-> setParam ( 'data' , $data -> getArrayCopy ())
;
2021-06-15 20:34:49 +12:00
$response -> setStatusCode ( Response :: STATUS_CODE_CREATED );
$response -> dynamic2 ( $data , Response :: MODEL_BUCKET );
2021-06-14 23:32:09 +12:00
});
2021-06-15 19:23:22 +12:00
App :: get ( '/v1/storage/buckets' )
-> desc ( 'List buckets' )
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'buckets.read' )
2021-06-16 17:43:59 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_KEY ])
2021-06-15 19:23:22 +12:00
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'listBuckets' )
-> label ( 'sdk.description' , '/docs/references/storage/list-buckets.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_BUCKET_LIST )
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
-> param ( 'limit' , 25 , new Range ( 0 , 100 ), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.' , true )
-> 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' )
2021-06-15 20:37:23 +12:00
-> inject ( 'dbForInternal' )
-> action ( function ( $search , $limit , $offset , $orderType , $response , $dbForInternal ) {
2021-06-15 19:23:22 +12:00
/** @var Appwrite\Utopia\Response $response */
2021-06-15 20:37:23 +12:00
/** @var Utopia\Database\Database $dbForInternal */
2021-06-15 19:23:22 +12:00
2021-06-15 20:37:23 +12:00
$queries = ( $search ) ? [ new Query ( 'name' , Query :: TYPE_SEARCH , $search )] : [];
2021-06-15 19:23:22 +12:00
2021-06-15 20:37:23 +12:00
$response -> dynamic2 ( new Document ([
'buckets' => $dbForInternal -> find ( 'buckets' , $queries , $limit , $offset , [ '_id' ], [ $orderType ]),
'sum' => $dbForInternal -> count ( 'buckets' , $queries , APP_LIMIT_COUNT ),
2021-06-15 19:23:22 +12:00
]), Response :: MODEL_BUCKET_LIST );
});
2021-06-15 19:48:59 +12:00
App :: get ( '/v1/storage/buckets/:bucketId' )
-> desc ( 'Get Bucket' )
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'buckets.read' )
2021-06-16 17:43:59 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_KEY ])
2021-06-15 19:48:59 +12:00
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'getBucket' )
-> label ( 'sdk.description' , '/docs/references/storage/get-bucket.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_BUCKET )
-> param ( 'bucketId' , '' , new UID (), 'Bucket unique ID.' )
-> inject ( 'response' )
2021-06-15 20:39:36 +12:00
-> inject ( 'dbForInternal' )
-> action ( function ( $bucketId , $response , $dbForInternal ) {
2021-06-15 19:48:59 +12:00
/** @var Appwrite\Utopia\Response $response */
2021-06-15 20:39:36 +12:00
/** @var Utopia\Database\Database $dbForInternal */
2021-06-15 19:48:59 +12:00
2021-06-15 20:39:36 +12:00
$bucket = $dbForInternal -> getDocument ( 'buckets' , $bucketId );
2021-06-15 19:48:59 +12:00
2021-06-17 17:28:27 +12:00
if ( $bucket -> isEmpty ()) {
2021-06-15 19:48:59 +12:00
throw new Exception ( 'Bucket not found' , 404 );
}
2021-06-15 20:39:36 +12:00
$response -> dynamic2 ( $bucket , Response :: MODEL_BUCKET );
2021-06-15 19:48:59 +12:00
});
2021-06-16 21:59:47 +12:00
App :: put ( '/v1/storage/buckets/:bucketId' )
2021-06-16 22:03:44 +12:00
-> desc ( 'Update Bucket' )
2021-06-16 21:59:47 +12:00
-> groups ([ 'api' , 'storage' ])
2021-06-16 22:03:44 +12:00
-> label ( 'scope' , 'buckets.write' )
2021-06-17 19:49:49 +12:00
-> label ( 'event' , 'storage.buckets.update' )
2021-06-16 21:59:47 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_KEY ])
-> label ( 'sdk.namespace' , 'storage' )
2021-06-16 22:03:44 +12:00
-> label ( 'sdk.method' , 'updateBucket' )
-> label ( 'sdk.description' , '/docs/references/storage/update-bucket.md' )
2021-06-16 21:59:47 +12:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_BUCKET )
-> param ( 'bucketId' , '' , new UID (), 'Bucket unique ID.' )
-> param ( 'name' , null , new Text ( 128 ), 'Bucket name' , false )
-> param ( 'read' , null , new ArrayList ( new Text ( 64 )), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.' , true )
-> param ( 'write' , null , new ArrayList ( new Text ( 64 )), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.' , true )
2021-06-18 19:48:57 +12:00
-> param ( 'maximumFileSize' , null , new Integer (), 'Maximum file size allowed in bytes. Maximum allowed value is ' . App :: getEnv ( '_APP_STORAGE_LIMIT' , 0 ) . '. For self hosted version you can change the limit by changing _APP_STORAGE_LIMIT environment variable. [Learn more about storage environment variables](docs/environment-variables#storage)' , true )
2021-06-18 19:34:52 +12:00
-> param ( 'allowedFileExtensions' , [], new ArrayList ( new Text ( 64 )), 'Allowed file extensions' , true )
2021-06-16 21:59:47 +12:00
-> param ( 'enabled' , true , new Boolean (), 'Is bucket enabled?' , true )
2021-07-15 22:35:08 +12:00
-> param ( 'encryption' , true , new Boolean (), 'Is encryption enabled? For file size above ' . Storage :: human ( APP_STORAGE_READ_BUFFER ) . ' encryption is skipped even if it\'s enabled' , true )
2021-06-20 22:49:10 +12:00
-> param ( 'antiVirus' , true , new Boolean (), 'Is virus scanning enabled? For file size above ' . Storage :: human ( APP_LIMIT_ANTIVIRUS ) . ' AntiVirus scanning is skipped even if it\'s enabled' , true )
2021-06-16 21:59:47 +12:00
-> inject ( 'response' )
-> inject ( 'dbForInternal' )
-> inject ( 'audits' )
2021-06-17 20:01:19 +12:00
-> action ( function ( $bucketId , $name , $read , $write , $maximumFileSize , $allowedFileExtensions , $enabled , $encryption , $antiVirus , $response , $dbForInternal , $audits ) {
2021-06-16 21:59:47 +12:00
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
$bucket = $dbForInternal -> getDocument ( 'buckets' , $bucketId );
2021-06-17 20:29:02 +12:00
if ( $bucket -> isEmpty ()) {
2021-06-16 21:59:47 +12:00
throw new Exception ( 'Bucket not found' , 404 );
}
2021-07-07 22:07:11 +12:00
$read ? ? = $bucket -> getAttribute ( '$read' , []); // By default inherit read permissions
$write ? ? = $bucket -> getAttribute ( '$write' , []); // By default inherit write permissions
2021-06-16 21:59:47 +12:00
$bucket = $dbForInternal -> updateDocument ( 'buckets' , $bucket -> getId (), $bucket
2021-07-07 22:07:11 +12:00
-> setAttribute ( 'name' , $name )
-> setAttribute ( '$read' , $read )
-> setAttribute ( '$write' , $write )
-> setAttribute ( 'maximumFileSize' , $maximumFileSize )
-> setAttribute ( 'allowedFileExtensions' , $allowedFileExtensions )
-> setAttribute ( 'enabled' , $enabled )
-> setAttribute ( 'encryption' , $encryption )
-> setAttribute ( 'antiVirus' , $antiVirus )
2021-06-16 21:59:47 +12:00
);
$audits
-> setParam ( 'event' , 'storage.buckets.update' )
-> setParam ( 'resource' , 'storage/buckets/' . $bucket -> getId ())
-> setParam ( 'data' , $bucket -> getArrayCopy ())
;
$response -> dynamic2 ( $bucket , Response :: MODEL_BUCKET );
});
2021-06-16 22:17:14 +12:00
App :: delete ( '/v1/storage/buckets/:bucketId' )
-> desc ( 'Delete Bucket' )
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'buckets.write' )
2021-06-17 19:49:49 +12:00
-> label ( 'event' , 'storage.buckets.delete' )
2021-06-16 22:17:14 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_KEY ])
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'deleteBucket' )
-> label ( 'sdk.description' , '/docs/references/storage/delete-bucket.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_NOCONTENT )
-> label ( 'sdk.response.model' , Response :: MODEL_NONE )
-> param ( 'bucketId' , '' , new UID (), 'Bucket unique ID.' )
-> inject ( 'response' )
-> inject ( 'dbForInternal' )
-> inject ( 'audits' )
-> inject ( 'deletes' )
2021-06-17 20:23:21 +12:00
-> inject ( 'events' )
-> action ( function ( $bucketId , $response , $dbForInternal , $audits , $deletes , $events ) {
2021-06-16 22:17:14 +12:00
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $deletes */
2021-06-17 20:23:21 +12:00
/** @var Appwrite\Event\Event $events */
2021-06-16 22:17:14 +12:00
$bucket = $dbForInternal -> getDocument ( 'buckets' , $bucketId );
2021-06-17 20:27:40 +12:00
if ( $bucket -> isEmpty ()) {
2021-06-16 22:17:14 +12:00
throw new Exception ( 'Bucket not found' , 404 );
}
$deletes
-> setParam ( 'type' , DELETE_TYPE_DOCUMENT )
-> setParam ( 'document' , $bucket )
;
2021-07-07 22:07:11 +12:00
if ( ! $dbForInternal -> deleteDocument ( 'buckets' , $bucketId )) {
2021-06-16 22:17:14 +12:00
throw new Exception ( 'Failed to remove project from DB' , 500 );
}
2021-06-17 20:23:21 +12:00
$events
-> setParam ( 'eventData' , $response -> output2 ( $bucket , Response :: MODEL_BUCKET ))
;
2021-06-16 22:17:14 +12:00
$audits
-> setParam ( 'event' , 'storage.buckets.delete' )
-> setParam ( 'resource' , 'storage/buckets/' . $bucket -> getId ())
-> setParam ( 'data' , $bucket -> getArrayCopy ())
;
$response -> noContent ();
});
2021-06-17 22:10:58 +12:00
App :: post ( '/v1/storage/buckets/:bucketId/files' )
2021-07-07 22:07:11 +12:00
-> alias ( '/v1/storage/files' , [ 'bucketId' => 'default' ])
2021-06-17 22:10:58 +12:00
-> desc ( 'Create File' )
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'files.write' )
-> label ( 'event' , 'storage.files.create' )
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'createFile' )
-> label ( 'sdk.description' , '/docs/references/storage/create-file.md' )
-> label ( 'sdk.request.type' , 'multipart/form-data' )
-> label ( 'sdk.methodType' , 'upload' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_CREATED )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_FILE )
-> param ( 'bucketId' , null , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
-> param ( 'file' , [], new File (), 'Binary file.' , false )
-> param ( 'read' , null , new ArrayList ( new Text ( 64 )), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.' , true )
-> param ( 'write' , null , new ArrayList ( new Text ( 64 )), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.' , true )
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'dbForInternal' )
-> inject ( 'user' )
-> inject ( 'audits' )
-> inject ( 'usage' )
-> action ( function ( $bucketId , $file , $read , $write , $request , $response , $dbForInternal , $user , $audits , $usage ) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Database\Document $user */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $usage */
2021-06-20 22:49:10 +12:00
$bucket = $dbForInternal -> getDocument ( 'buckets' , $bucketId );
2021-06-17 22:10:58 +12:00
2021-07-07 22:07:11 +12:00
if ( $bucket -> isEmpty ()) {
2021-06-24 19:01:29 +12:00
throw new Exception ( 'Bucket not found' , 404 );
2021-06-17 22:10:58 +12:00
}
$file = $request -> getFiles ( 'file' );
2021-07-08 23:26:11 +12:00
/**
* Validators
*/
2021-06-23 00:12:46 +12:00
$allowedFileExtensions = $bucket -> getAttribute ( 'allowedFileExtensions' , []);
$fileExt = new FileExt ( $allowedFileExtensions );
2021-06-24 19:10:21 +12:00
$maximumFileSize = $bucket -> getAttribute ( 'maximumFileSize' , 0 );
2021-07-07 22:07:11 +12:00
if ( $maximumFileSize > ( int ) App :: getEnv ( '_APP_STORAGE_LIMIT' , 0 )) {
2021-06-28 17:52:45 +12:00
throw new Exception ( 'Error bucket maximum file size is larger than _APP_STORAGE_LIMIT' , 500 );
2021-06-24 19:10:21 +12:00
}
$fileSize = new FileSize ( $maximumFileSize );
2021-06-17 22:10:58 +12:00
$upload = new Upload ();
if ( empty ( $file )) {
throw new Exception ( 'No file sent' , 400 );
}
// Make sure we handle a single file and multiple files the same way
2021-07-07 22:07:11 +12:00
$fileName = ( \is_array ( $file [ 'name' ]) && isset ( $file [ 'name' ][ 0 ])) ? $file [ 'name' ][ 0 ] : $file [ 'name' ];
$fileTmpName = ( \is_array ( $file [ 'tmp_name' ]) && isset ( $file [ 'tmp_name' ][ 0 ])) ? $file [ 'tmp_name' ][ 0 ] : $file [ 'tmp_name' ];
$size = ( \is_array ( $file [ 'size' ]) && isset ( $file [ 'size' ][ 0 ])) ? $file [ 'size' ][ 0 ] : $file [ 'size' ];
$contentRange = $request -> getHeader ( 'content-range' );
2021-07-13 19:58:16 +12:00
$fileId = $dbForInternal -> getId ();
2021-07-07 22:07:11 +12:00
$chunk = 1 ;
$chunks = 1 ;
if ( ! empty ( $contentRange )) {
2021-07-15 21:40:41 +12:00
$start = $request -> getContentRangeStart ();
$end = $request -> getContentRangeEnd ();
$size = $request -> getContentRangeSize ();
2021-07-07 22:07:11 +12:00
2021-07-15 21:40:41 +12:00
$fileId = $request -> getHeader ( 'x-appwrite-file-id' , $fileId );
2021-07-15 23:34:05 +12:00
if ( is_null ( $start ) || is_null ( $end ) || is_null ( $size )) {
2021-07-07 22:07:11 +12:00
throw new Exception ( 'Invalid content-range header' , 400 );
}
if ( $end == $size ) {
2021-07-13 19:58:16 +12:00
//if it's a last chunks the chunk size might differ, so we set the $chunks and $chunk to notify it's last chunk
2021-07-07 22:07:11 +12:00
$chunks = $chunk = - 1 ;
} else {
2021-07-13 19:58:16 +12:00
// Calculate total number of chunks based on the chunk size i.e ($rangeEnd - $rangeStart)
2021-07-07 22:07:11 +12:00
$chunks = ( int ) ceil ( $size / ( $end + 1 - $start ));
$chunk = ( int ) ( $start / ( $end + 1 - $start ));
}
}
2021-06-17 22:10:58 +12:00
// Check if file type is allowed (feature for project settings?)
2021-07-07 22:07:11 +12:00
if ( ! empty ( $allowedFileExtensions ) && ! $fileExt -> isValid ( $fileName )) {
2021-06-23 00:12:46 +12:00
throw new Exception ( 'File extension not allowed' , 400 );
}
2021-06-17 22:10:58 +12:00
2021-07-07 22:07:11 +12:00
if ( ! $fileSize -> isValid ( $size )) { // Check if file size is exceeding allowed limit
2021-06-17 22:10:58 +12:00
throw new Exception ( 'File size not allowed' , 400 );
}
$device = Storage :: getDevice ( 'files' );
2021-07-07 22:07:11 +12:00
if ( ! $upload -> isValid ( $fileTmpName )) {
2021-06-17 22:10:58 +12:00
throw new Exception ( 'Invalid file' , 403 );
}
// Save to storage
2021-07-07 22:07:11 +12:00
$size = $size ? ? $device -> getFileSize ( $fileTmpName );
2021-07-13 19:58:16 +12:00
$path = $device -> getPath ( $fileId . '.' . \pathinfo ( $fileName , PATHINFO_EXTENSION ));
2021-07-08 23:26:11 +12:00
$path = str_ireplace ( $device -> getRoot (), $device -> getRoot () . DIRECTORY_SEPARATOR . $bucket -> getId (), $path );
2021-06-17 22:10:58 +12:00
2021-07-13 19:58:16 +12:00
$file = $dbForInternal -> getDocument ( 'files' , $fileId );
2021-06-17 22:10:58 +12:00
2021-07-07 22:07:11 +12:00
if ( ! $file -> isEmpty ()) {
2021-07-15 23:34:05 +12:00
$chunks = $file -> getAttribute ( 'chunksTotal' , 1 );
2021-07-07 22:07:11 +12:00
if ( $chunk == - 1 ) {
$chunk = $chunks - 1 ;
2021-06-17 22:10:58 +12:00
}
}
2021-07-13 19:58:16 +12:00
$chunksUploaded = $device -> upload ( $fileTmpName , $path , $chunk , $chunks );
if ( empty ( $chunksUploaded )) {
2021-07-07 22:07:11 +12:00
throw new Exception ( 'Failed uploading file' , 500 );
2021-06-17 22:10:58 +12:00
}
2021-07-13 20:16:24 +12:00
$read = ( is_null ( $read ) && ! $user -> isEmpty ()) ? [ 'user:' . $user -> getId ()] : $read ? ? [];
$write = ( is_null ( $write ) && ! $user -> isEmpty ()) ? [ 'user:' . $user -> getId ()] : $write ? ? [];
2021-07-13 19:58:16 +12:00
if ( $chunksUploaded == $chunks ) {
2021-07-07 22:07:11 +12:00
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' ),
2021-07-13 20:16:24 +12:00
( int ) App :: getEnv ( '_APP_STORAGE_ANTIVIRUS_PORT' , 3310 ));
2021-07-07 22:07:11 +12:00
if ( ! $antiVirus -> fileScan ( $path )) {
$device -> delete ( $path );
throw new Exception ( 'Invalid file' , 403 );
}
}
2021-07-13 20:16:24 +12:00
$mimeType = $device -> getFileMimeType ( $path ); // Get mime-type before compression and encryption
$data = '' ;
2021-07-07 22:07:11 +12:00
// Compression
2021-07-15 22:35:08 +12:00
if ( $size <= APP_STORAGE_READ_BUFFER ) {
2021-07-08 23:26:11 +12:00
$data = $device -> read ( $path );
2021-07-13 20:16:24 +12:00
$compressor = new GZIP ();
$data = $compressor -> compress ( $data );
}
2021-07-15 22:35:08 +12:00
if ( $bucket -> getAttribute ( 'encryption' , true ) && $size <= APP_STORAGE_READ_BUFFER ) {
2021-07-13 20:16:24 +12:00
if ( empty ( $data )) {
$data = $device -> read ( $path );
2021-07-08 23:26:11 +12:00
}
2021-07-13 20:16:24 +12:00
$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 ( ! empty ( $data )) {
2021-07-08 23:26:11 +12:00
if ( ! $device -> write ( $path , $data , $mimeType )) {
throw new Exception ( 'Failed to save file' , 500 );
}
2021-07-07 22:07:11 +12:00
}
$sizeActual = $device -> getFileSize ( $path );
$algorithm = empty ( $compressor ) ? '' : $compressor -> getName ();
$fileHash = $device -> getFileHash ( $path );
2021-07-15 22:35:08 +12:00
if ( $bucket -> getAttribute ( 'encryption' , true ) && $size <= APP_STORAGE_READ_BUFFER ) {
2021-07-07 22:07:11 +12:00
$openSSLVersion = '1' ;
$openSSLCipher = OpenSSL :: CIPHER_AES_128_GCM ;
$openSSLTag = \bin2hex ( $tag );
$openSSLIV = \bin2hex ( $iv );
}
if ( $file -> isEmpty ()) {
2021-07-13 19:58:16 +12:00
$file = $dbForInternal -> createDocument ( 'files' , new Document ([
2021-07-07 22:07:11 +12:00
'$read' => $read ,
'$write' => $write ,
'dateCreated' => \time (),
'bucketId' => $bucket -> getId (),
'name' => $fileName ,
'path' => $path ,
'signature' => $fileHash ,
'mimeType' => $mimeType ,
'sizeOriginal' => $size ,
'sizeActual' => $sizeActual ,
'algorithm' => $algorithm ,
'comment' => '' ,
2021-07-13 19:58:16 +12:00
'chunksTotal' => $chunks ,
'chunksUploaded' => $chunksUploaded ,
2021-07-07 22:07:11 +12:00
'openSSLVersion' => $openSSLVersion ,
'openSSLCipher' => $openSSLCipher ,
'openSSLTag' => $openSSLTag ,
'openSSLIV' => $openSSLIV ,
2021-07-13 19:58:16 +12:00
]));
2021-07-07 22:07:11 +12:00
} else {
2021-07-13 19:58:16 +12:00
$file = $dbForInternal -> updateDocument ( 'files' , $fileId , $file
2021-07-07 22:07:11 +12:00
-> setAttribute ( '$read' , $read )
-> setAttribute ( '$write' , $write )
-> setAttribute ( 'signature' , $fileHash )
-> setAttribute ( 'mimeType' , $mimeType )
-> setAttribute ( 'sizeActual' , $sizeActual )
-> setAttribute ( 'algorithm' , $algorithm )
-> setAttribute ( 'openSSLVersion' , $openSSLVersion )
-> setAttribute ( 'openSSLCipher' , $openSSLCipher )
-> setAttribute ( 'openSSLTag' , $openSSLTag )
-> setAttribute ( 'openSSLIV' , $openSSLIV )
);
}
} else {
if ( $file -> isEmpty ()) {
2021-07-13 19:58:16 +12:00
$file = $dbForInternal -> createDocument ( 'files' , new Document ([
'$id' => $fileId ,
2021-07-13 20:16:24 +12:00
'$read' => $read ,
'$write' => $write ,
2021-07-07 22:07:11 +12:00
'dateCreated' => \time (),
'bucketId' => $bucket -> getId (),
'name' => $fileName ,
'path' => $path ,
'signature' => '' ,
'mimeType' => '' ,
'sizeOriginal' => $size ,
'sizeActual' => 0 ,
'algorithm' => '' ,
'comment' => '' ,
2021-07-13 19:58:16 +12:00
'chunksTotal' => $chunks ,
'chunksUploaded' => $chunksUploaded ,
]));
2021-07-07 22:07:11 +12:00
} else {
2021-07-13 19:58:16 +12:00
$file = $dbForInternal -> updateDocument ( 'files' , $fileId , $file
-> setAttribute ( 'chunksUploaded' , $chunksUploaded )
2021-07-07 22:07:11 +12:00
);
}
}
2021-06-17 22:10:58 +12:00
$audits
-> setParam ( 'event' , 'storage.files.create' )
2021-07-07 22:07:11 +12:00
-> setParam ( 'resource' , 'storage/files/' . $file -> getId ())
2021-06-17 22:10:58 +12:00
;
2021-07-07 22:07:11 +12:00
if ( ! empty ( $sizeActual )) {
$usage
-> setParam ( 'storage' , $sizeActual )
;
}
2021-06-17 22:10:58 +12:00
$response -> setStatusCode ( Response :: STATUS_CODE_CREATED );
$response -> dynamic2 ( $file , Response :: MODEL_FILE );
});
2021-06-18 21:24:16 +12:00
App :: get ( '/v1/storage/buckets/:bucketId/files' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files' , [ 'bucketId' => 'default' ])
2021-06-18 21:24:16 +12:00
-> desc ( 'List Files' )
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'files.read' )
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'listFiles' )
-> label ( 'sdk.description' , '/docs/references/storage/list-files.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_FILE_LIST )
-> param ( 'bucketId' , null , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
-> param ( 'limit' , 25 , new Range ( 0 , 100 ), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.' , true )
-> 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 ( $bucketId , $search , $limit , $offset , $orderType , $response , $dbForInternal ) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
2021-06-20 22:55:24 +12:00
$bucket = $dbForInternal -> getDocument ( 'buckets' , $bucketId );
2021-07-07 22:07:11 +12:00
if ( $bucket -> isEmpty ()) {
2021-06-24 19:01:29 +12:00
throw new Exception ( 'Bucket not found' , 404 );
2021-06-20 22:55:24 +12:00
}
2021-06-18 21:24:16 +12:00
$queries = [ new Query ( 'bucketId' , Query :: TYPE_EQUAL , [ $bucketId ])];
2021-07-07 22:07:11 +12:00
if ( $search ) {
2021-06-18 21:24:16 +12:00
$queries [] = [ 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 );
});
2021-06-18 21:33:00 +12:00
App :: get ( '/v1/storage/buckets/:bucketId/files/:fileId' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files/:fileId' , [ 'bucketId' => 'default' ])
2021-06-18 21:33:00 +12:00
-> desc ( 'Get File' )
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'files.read' )
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'getFile' )
-> label ( 'sdk.description' , '/docs/references/storage/get-file.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_FILE )
-> param ( 'bucketId' , null , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
-> param ( 'fileId' , '' , new UID (), 'File unique ID.' )
-> inject ( 'response' )
-> inject ( 'dbForInternal' )
-> action ( function ( $bucketId , $fileId , $response , $dbForInternal ) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
$bucket = $dbForInternal -> getDocument ( 'buckets' , $bucketId );
2021-06-20 22:55:24 +12:00
2021-07-07 22:07:11 +12:00
if ( $bucket -> isEmpty ()) {
2021-06-24 19:01:29 +12:00
throw new Exception ( 'Bucket not found' , 404 );
2021-06-20 22:55:24 +12:00
}
2021-06-18 21:33:00 +12:00
$file = $dbForInternal -> getDocument ( 'files' , $fileId );
2021-07-07 22:07:11 +12:00
if ( $file -> isEmpty () || $file -> getAttribute ( 'bucketId' ) != $bucketId ) {
2021-06-18 21:33:00 +12:00
throw new Exception ( 'File not found' , 404 );
}
$response -> dynamic2 ( $file , Response :: MODEL_FILE );
});
2021-06-20 23:20:35 +12:00
App :: get ( '/v1/storage/buckets/:bucketId/files/:fileId/preview' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files/:fileId/preview' , [ 'bucketId' => 'default' ])
2021-06-20 23:20:35 +12:00
-> desc ( 'Get File Preview' )
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'files.read' )
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'getFilePreview' )
-> label ( 'sdk.description' , '/docs/references/storage/get-file-preview.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_IMAGE )
-> label ( 'sdk.methodType' , 'location' )
-> param ( 'bucketId' , null , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
-> param ( 'fileId' , '' , new UID (), 'File unique ID' )
-> param ( 'width' , 0 , new Range ( 0 , 4000 ), 'Resize preview image width, Pass an integer between 0 to 4000.' , true )
-> param ( 'height' , 0 , new Range ( 0 , 4000 ), 'Resize preview image height, Pass an integer between 0 to 4000.' , true )
2021-07-05 00:37:00 +12:00
-> param ( 'gravity' , Image :: GRAVITY_CENTER , new WhiteList ( Image :: getGravityTypes ()), 'Image crop gravity. Can be one of ' . implode ( " , " , Image :: getGravityTypes ()), true )
2021-06-20 23:20:35 +12:00
-> param ( 'quality' , 100 , new Range ( 0 , 100 ), 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.' , true )
-> param ( 'borderWidth' , 0 , new Range ( 0 , 100 ), 'Preview image border in pixels. Pass an integer between 0 to 100. Defaults to 0.' , true )
-> param ( 'borderColor' , '' , new HexColor (), 'Preview image border color. Use a valid HEX color, no # is needed for prefix.' , true )
-> param ( 'borderRadius' , 0 , new Range ( 0 , 4000 ), 'Preview image border radius in pixels. Pass an integer between 0 to 4000.' , true )
2021-07-07 22:07:11 +12:00
-> param ( 'opacity' , 1 , new Range ( 0 , 1 , Range :: TYPE_FLOAT ), 'Preview image opacity. Only works with images having an alpha channel (like png). Pass a number between 0 to 1.' , true )
-> param ( 'rotation' , 0 , new Range ( 0 , 360 ), 'Preview image rotation in degrees. Pass an integer between 0 and 360.' , true )
2021-06-20 23:20:35 +12:00
-> 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 ( $bucketId , $fileId , $width , $height , $gravity , $quality , $borderWidth , $borderColor , $borderRadius , $opacity , $rotation , $background , $output , $request , $response , $project , $dbForInternal ) {
/** @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 );
}
2021-06-21 20:05:11 +12:00
$bucket = $dbForInternal -> getDocument ( 'buckets' , $bucketId );
2021-07-07 22:07:11 +12:00
if ( $bucket -> isEmpty ()) {
2021-06-24 19:01:29 +12:00
throw new Exception ( 'Bucket not found' , 404 );
2021-06-21 20:05:11 +12:00
}
2021-06-20 23:20:35 +12:00
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' );
2021-07-07 22:07:11 +12:00
$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 );
2021-06-20 23:20:35 +12:00
$file = $dbForInternal -> getDocument ( 'files' , $fileId );
2021-06-20 23:25:48 +12:00
if ( $file -> isEmpty () || $file -> getAttribute ( 'bucketId' ) != $bucketId ) {
2021-06-20 23:20:35 +12:00
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 ));
2021-07-07 22:07:11 +12:00
$key = \md5 ( $path . $width . $height . $quality . $borderWidth . $borderColor . $borderRadius . $opacity . $rotation . $background . $storage . $output );
2021-06-20 23:20:35 +12:00
}
$compressor = new GZIP ();
$device = Storage :: getDevice ( 'files' );
if ( ! \file_exists ( $path )) {
throw new Exception ( 'File not found' , 404 );
}
2021-07-07 22:07:11 +12:00
$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 */ );
2021-06-20 23:20:35 +12:00
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' ),
2021-07-07 22:07:11 +12:00
App :: getEnv ( '_APP_OPENSSL_KEY_V' . $file -> getAttribute ( 'openSSLVersion' )),
2021-06-20 23:20:35 +12:00
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 );
2021-07-07 22:07:11 +12:00
if ( ! empty ( $opacity ) || $opacity == 0 ) {
2021-06-20 23:20:35 +12:00
$image -> setOpacity ( $opacity );
}
if ( ! empty ( $background )) {
2021-07-07 22:07:11 +12:00
$image -> setBackground ( '#' . $background );
2021-06-20 23:20:35 +12:00
}
2021-07-07 22:07:11 +12:00
if ( ! empty ( $borderWidth )) {
$image -> setBorder ( $borderWidth , '#' . $borderColor );
2021-06-20 23:20:35 +12:00
}
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 );
});
App :: get ( '/v1/storage/buckets/:bucketId/files/:fileId/download' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files/:fileId/download' , [ 'bucketId' => 'default' ])
2021-06-20 23:20:35 +12:00
-> desc ( 'Get File for Download' )
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'files.read' )
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'getFileDownload' )
-> label ( 'sdk.description' , '/docs/references/storage/get-file-download.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , '*/*' )
-> label ( 'sdk.methodType' , 'location' )
-> param ( 'bucketId' , null , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
-> param ( 'fileId' , '' , new UID (), 'File unique ID.' )
-> inject ( 'response' )
-> inject ( 'dbForInternal' )
2021-07-13 19:49:45 +12:00
-> action ( function ( $bucketId , $fileId , $response , $dbForInternal ) {
2021-07-12 22:23:01 +12:00
/** @var Utopia\Swoole\Request $response */
2021-06-20 23:20:35 +12:00
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
$bucket = $dbForInternal -> getDocument ( 'buckets' , $bucketId );
2021-07-07 22:07:11 +12:00
if ( $bucket -> isEmpty ()) {
2021-06-24 19:01:29 +12:00
throw new Exception ( 'Bucket not found' , 404 );
2021-06-20 23:20:35 +12:00
}
$file = $dbForInternal -> getDocument ( 'files' , $fileId );
2021-06-20 23:25:48 +12:00
if ( $file -> isEmpty () || $file -> getAttribute ( 'bucketId' ) != $bucketId ) {
2021-06-20 23:20:35 +12:00
throw new Exception ( 'File not found' , 404 );
}
$path = $file -> getAttribute ( 'path' , '' );
2021-07-12 22:23:01 +12:00
$device = Storage :: getDevice ( 'files' );
if ( ! $device -> exists ( $path )) {
2021-07-07 22:07:11 +12:00
throw new Exception ( 'File not found in ' . $path , 404 );
2021-06-20 23:20:35 +12:00
}
2021-07-13 19:49:19 +12:00
$response
-> setContentType ( $file -> getAttribute ( 'mimeType' ))
-> 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 ())
-> addHeader ( 'Content-Disposition' , 'attachment; filename="' . $file -> getAttribute ( 'name' , '' ) . '"' )
;
2021-07-15 22:35:08 +12:00
$source = '' ;
2021-07-13 19:49:19 +12:00
if ( ! empty ( $file -> getAttribute ( 'openSSLCipher' ))) { // Decrypt
2021-07-15 22:35:08 +12:00
$source = $device -> read ( $path );
2021-07-13 19:49:19 +12:00
$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' ))
);
}
2021-07-15 22:35:08 +12:00
2021-07-13 19:49:19 +12:00
if ( ! empty ( $file -> getAttribute ( 'algorithm' , '' ))) {
2021-07-15 22:35:08 +12:00
if ( empty ( $source )) {
$source = $device -> read ( $path );
}
2021-07-13 19:49:19 +12:00
$compressor = new GZIP ();
$source = $compressor -> decompress ( $source );
2021-06-22 20:36:44 +12:00
}
2021-07-13 19:49:19 +12:00
2021-07-15 22:35:08 +12:00
if ( ! empty ( $source )) {
$response -> send ( $source );
}
$size = $device -> getFileSize ( $path );
if ( $size > APP_STORAGE_READ_BUFFER ) {
$response -> addHeader ( 'Content-Length' , $device -> getFileSize ( $path ));
$chunk = 2000000 ; // Max chunk of 2 mb
for ( $i = 0 ; $i < ceil ( $size / $chunk ); $i ++ ) {
$response -> chunk ( $device -> read ( $path , ( $i * $chunk ), min ( $chunk , $size - ( $i * $chunk ))), ( $i * $chunk ) >= $size );
}
} else {
$response -> send ( $device -> read ( $path ));
}
2021-06-20 23:20:35 +12:00
});
App :: get ( '/v1/storage/buckets/:bucketId/files/:fileId/view' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files/:fileId/view' , [ 'bucketId' => 'default' ])
2021-06-20 23:20:35 +12:00
-> desc ( 'Get File for View' )
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'files.read' )
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'getFileView' )
-> label ( 'sdk.description' , '/docs/references/storage/get-file-view.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , '*/*' )
-> label ( 'sdk.methodType' , 'location' )
-> param ( 'bucketId' , null , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
-> param ( 'fileId' , '' , new UID (), 'File unique ID.' )
-> inject ( 'response' )
-> inject ( 'dbForInternal' )
-> action ( function ( $bucketId , $fileId , $response , $dbForInternal ) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
$bucket = $dbForInternal -> getDocument ( 'buckets' , $bucketId );
2021-07-07 22:07:11 +12:00
if ( $bucket -> isEmpty ()) {
2021-06-24 19:01:29 +12:00
throw new Exception ( 'Bucket not found' , 404 );
2021-06-20 23:20:35 +12:00
}
2021-07-07 22:07:11 +12:00
$file = $dbForInternal -> getDocument ( 'files' , $fileId );
2021-06-20 23:20:35 +12:00
$mimes = Config :: getParam ( 'storage-mimes' );
2021-06-20 23:25:48 +12:00
if ( $file -> isEmpty () || $file -> getAttribute ( 'bucketId' ) != $bucketId ) {
2021-06-20 23:20:35 +12:00
throw new Exception ( 'File not found' , 404 );
}
$path = $file -> getAttribute ( 'path' , '' );
if ( ! \file_exists ( $path )) {
2021-07-07 22:07:11 +12:00
throw new Exception ( 'File not found in ' . $path , 404 );
2021-06-20 23:20:35 +12:00
}
$compressor = new GZIP ();
$device = Storage :: getDevice ( 'files' );
$contentType = 'text/plain' ;
if ( \in_array ( $file -> getAttribute ( 'mimeType' ), $mimes )) {
$contentType = $file -> getAttribute ( 'mimeType' );
}
2021-07-13 19:49:19 +12:00
$response
-> setContentType ( $contentType )
-> addHeader ( 'Content-Security-Policy' , 'script-src none;' )
-> addHeader ( 'X-Content-Type-Options' , 'nosniff' )
-> addHeader ( 'Content-Disposition' , 'inline; 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 ())
;
2021-06-20 23:20:35 +12:00
2021-07-15 22:35:08 +12:00
$source = '' ;
2021-07-13 19:49:19 +12:00
if ( ! empty ( $file -> getAttribute ( 'openSSLCipher' ))) { // Decrypt
2021-07-15 22:35:08 +12:00
$source = $device -> read ( $path );
2021-07-13 19:49:19 +12:00
$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' ))
);
2021-07-12 22:23:01 +12:00
}
2021-07-13 19:49:19 +12:00
2021-07-15 22:35:08 +12:00
if ( ! empty ( $file -> getAttribute ( 'algorithm' , '' ))) {
if ( empty ( $source )) {
$source = $device -> read ( $path );
}
$compressor = new GZIP ();
$source = $compressor -> decompress ( $source );
}
2021-07-13 19:49:19 +12:00
2021-07-15 22:35:08 +12:00
if ( ! empty ( $source )) {
$response -> send ( $source );
}
$size = $device -> getFileSize ( $path );
if ( $size > APP_STORAGE_READ_BUFFER ) {
$response -> addHeader ( 'Content-Length' , $device -> getFileSize ( $path ));
$chunk = 2000000 ; // Max chunk of 2 mb
for ( $i = 0 ; $i < ceil ( $size / $chunk ); $i ++ ) {
$response -> chunk ( $device -> read ( $path , ( $i * $chunk ), min ( $chunk , $size - ( $i * $chunk ))), ( $i * $chunk ) >= $size );
}
} else {
$response -> send ( $device -> read ( $path ));
}
2021-06-20 23:20:35 +12:00
});
App :: put ( '/v1/storage/buckets/:bucketId/files/:fileId' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files/:fileId' , [ 'bucketId' => 'default' ])
2021-06-20 23:20:35 +12:00
-> desc ( 'Update File' )
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'files.write' )
-> label ( 'event' , 'storage.files.update' )
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'updateFile' )
-> label ( 'sdk.description' , '/docs/references/storage/update-file.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_FILE )
-> param ( 'bucketId' , null , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
-> param ( 'fileId' , '' , new UID (), 'File unique ID.' )
-> param ( 'read' , [], new ArrayList ( new Text ( 64 )), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.' )
-> param ( 'write' , [], new ArrayList ( new Text ( 64 )), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.' )
-> inject ( 'response' )
-> inject ( 'dbForInternal' )
-> inject ( 'audits' )
-> action ( function ( $bucketId , $fileId , $read , $write , $response , $dbForInternal , $audits ) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */
$bucket = $dbForInternal -> getDocument ( 'buckets' , $bucketId );
2021-07-07 22:07:11 +12:00
if ( $bucket -> isEmpty ()) {
2021-06-24 19:01:29 +12:00
throw new Exception ( 'Bucket not found' , 404 );
2021-06-20 23:20:35 +12:00
}
$file = $dbForInternal -> getDocument ( 'files' , $fileId );
2021-06-20 23:25:48 +12:00
if ( $file -> isEmpty () || $file -> getAttribute ( 'bucketId' ) != $bucketId ) {
2021-06-20 23:20:35 +12:00
throw new Exception ( 'File not found' , 404 );
}
$file = $dbForInternal -> updateDocument ( 'files' , $fileId , $file
-> setAttribute ( '$read' , $read )
-> setAttribute ( '$write' , $write )
);
$audits
-> setParam ( 'event' , 'storage.files.update' )
2021-07-07 22:07:11 +12:00
-> setParam ( 'resource' , 'storage/files/' . $file -> getId ())
2021-06-20 23:20:35 +12:00
;
$response -> dynamic2 ( $file , Response :: MODEL_FILE );
});
App :: delete ( '/v1/storage/buckets/:bucketId/files/:fileId' )
2021-07-05 20:07:45 +12:00
-> alias ( '/v1/storage/files/:fileId' , [ 'bucketId' => 'default' ])
2021-06-20 23:20:35 +12:00
-> desc ( 'Delete File' )
-> groups ([ 'api' , 'storage' ])
-> label ( 'scope' , 'files.write' )
-> label ( 'event' , 'storage.files.delete' )
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
-> label ( 'sdk.namespace' , 'storage' )
-> label ( 'sdk.method' , 'deleteFile' )
-> label ( 'sdk.description' , '/docs/references/storage/delete-file.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_NOCONTENT )
-> label ( 'sdk.response.model' , Response :: MODEL_NONE )
-> param ( 'bucketId' , null , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).' )
-> param ( 'fileId' , '' , new UID (), 'File unique ID.' )
-> inject ( 'response' )
-> inject ( 'dbForInternal' )
-> inject ( 'events' )
-> inject ( 'audits' )
-> inject ( 'usage' )
-> action ( function ( $bucketId , $fileId , $response , $dbForInternal , $events , $audits , $usage ) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Event $usage */
2021-07-07 22:07:11 +12:00
2021-06-20 23:20:35 +12:00
$bucket = $dbForInternal -> getDocument ( 'buckets' , $bucketId );
2021-06-25 22:56:39 +12:00
2021-07-07 22:07:11 +12:00
if ( $bucket -> isEmpty ()) {
2021-06-25 22:56:39 +12:00
throw new Exception ( 'Bucket not found' , 404 );
}
2021-05-03 20:28:31 +12:00
$file = $dbForInternal -> getDocument ( 'files' , $fileId );
2019-05-09 18:54:39 +12:00
2021-06-25 22:56:39 +12:00
if ( $file -> isEmpty () || $file -> getAttribute ( 'bucketId' ) != $bucketId ) {
2020-06-30 23:09:28 +12:00
throw new Exception ( 'File not found' , 404 );
}
2019-05-09 18:54:39 +12:00
2020-07-15 09:20:46 +12:00
$device = Storage :: getDevice ( 'files' );
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
if ( $device -> delete ( $file -> getAttribute ( 'path' , '' ))) {
2021-05-03 20:28:31 +12:00
if ( ! $dbForInternal -> deleteDocument ( 'files' , $fileId )) {
2020-06-30 23:09:28 +12:00
throw new Exception ( 'Failed to remove file from DB' , 500 );
2019-05-09 18:54:39 +12:00
}
2021-07-08 23:26:11 +12:00
} else {
throw new Exception ( 'Failed to delete file from device' , 500 );
2020-06-30 23:09:28 +12:00
}
2021-07-07 22:07:11 +12:00
2020-07-06 02:19:59 +12:00
$audits
2020-06-30 23:09:28 +12:00
-> setParam ( 'event' , 'storage.files.delete' )
2021-07-07 22:07:11 +12:00
-> setParam ( 'resource' , 'storage/files/' . $file -> getId ())
2020-06-30 23:09:28 +12:00
;
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
$usage
-> setParam ( 'storage' , $file -> getAttribute ( 'size' , 0 ) * - 1 )
;
2019-05-09 18:54:39 +12:00
2020-12-07 11:14:57 +13:00
$events
2021-05-03 20:28:31 +12:00
-> setParam ( 'eventData' , $response -> output2 ( $file , Response :: MODEL_FILE ))
2020-10-31 08:53:27 +13:00
;
2020-06-30 23:09:28 +12:00
$response -> noContent ();
2021-06-16 17:26:34 +12:00
});