2011-07-20 09:53:34 -04:00
< ? php
2024-05-27 11:39:07 -04:00
2015-02-26 05:37:37 -05:00
/**
2024-05-27 11:39:07 -04:00
* SPDX - FileCopyrightText : 2016 - 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX - FileCopyrightText : 2016 ownCloud , Inc .
* SPDX - License - Identifier : AGPL - 3.0 - only
2015-02-26 05:37:37 -05:00
*/
2015-08-30 13:13:01 -04:00
namespace OCA\DAV\Connector\Sabre ;
2015-02-12 06:29:01 -05:00
2019-02-14 10:46:30 -05:00
use Icewind\Streams\CallbackWrapper ;
2017-11-27 13:41:34 -05:00
use OC\AppFramework\Http\Request ;
2015-08-12 09:06:59 -04:00
use OC\Files\Filesystem ;
2020-02-07 08:22:12 -05:00
use OC\Files\Stream\HashWrapper ;
2017-11-27 13:41:34 -05:00
use OC\Files\View ;
2022-02-04 14:02:32 -05:00
use OCA\DAV\AppInfo\Application ;
2015-08-30 13:13:01 -04:00
use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge ;
use OCA\DAV\Connector\Sabre\Exception\FileLocked ;
2015-11-13 08:13:16 -05:00
use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException ;
2015-08-30 13:13:01 -04:00
use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType ;
2024-10-10 06:40:31 -04:00
use OCP\App\IAppManager ;
2015-04-01 10:36:08 -04:00
use OCP\Encryption\Exceptions\GenericEncryptionException ;
2025-05-15 18:07:33 -04:00
use OCP\Files ;
2015-04-07 08:17:28 -04:00
use OCP\Files\EntityTooLargeException ;
2017-11-27 13:41:34 -05:00
use OCP\Files\FileInfo ;
2015-11-13 08:13:16 -05:00
use OCP\Files\ForbiddenException ;
2020-09-09 03:46:44 -04:00
use OCP\Files\GenericFileException ;
2025-02-03 09:34:01 -05:00
use OCP\Files\IMimeTypeDetector ;
2015-04-07 08:17:28 -04:00
use OCP\Files\InvalidContentException ;
use OCP\Files\InvalidPathException ;
use OCP\Files\LockNotAcquiredException ;
2018-11-16 14:21:21 -05:00
use OCP\Files\NotFoundException ;
2015-04-07 08:17:28 -04:00
use OCP\Files\NotPermittedException ;
2024-09-15 08:13:29 -04:00
use OCP\Files\Storage\IWriteStreamStorage ;
2015-04-07 08:17:28 -04:00
use OCP\Files\StorageNotAvailableException ;
2025-02-03 09:34:01 -05:00
use OCP\IConfig ;
2022-02-04 14:02:32 -05:00
use OCP\IL10N ;
2023-08-14 10:11:34 -04:00
use OCP\IRequest ;
2022-02-04 14:02:32 -05:00
use OCP\L10N\IFactory as IL10NFactory ;
2015-05-27 09:19:46 -04:00
use OCP\Lock\ILockingProvider ;
2015-05-29 03:59:20 -04:00
use OCP\Lock\LockedException ;
2024-10-10 06:40:31 -04:00
use OCP\Server ;
2017-11-27 13:41:34 -05:00
use OCP\Share\IManager ;
2022-03-31 09:34:57 -04:00
use Psr\Log\LoggerInterface ;
2015-04-07 08:17:28 -04:00
use Sabre\DAV\Exception ;
use Sabre\DAV\Exception\BadRequest ;
use Sabre\DAV\Exception\Forbidden ;
2019-11-22 14:52:10 -05:00
use Sabre\DAV\Exception\NotFound ;
2015-04-07 08:17:28 -04:00
use Sabre\DAV\Exception\ServiceUnavailable ;
use Sabre\DAV\IFile ;
class File extends Node implements IFile {
2023-08-14 10:11:34 -04:00
protected IRequest $request ;
2022-02-04 14:02:32 -05:00
protected IL10N $l10n ;
2017-11-27 13:41:34 -05:00
/**
* Sets up the node , expects a full path name
*
2024-10-18 06:04:22 -04:00
* @ param View $view
* @ param FileInfo $info
2023-08-14 10:11:34 -04:00
* @ param ? \OCP\Share\IManager $shareManager
* @ param ? IRequest $request
* @ param ? IL10N $l10n
2017-11-27 13:41:34 -05:00
*/
2024-03-28 11:13:19 -04:00
public function __construct ( View $view , FileInfo $info , ? IManager $shareManager = null , ? IRequest $request = null , ? IL10N $l10n = null ) {
2017-11-27 13:41:34 -05:00
parent :: __construct ( $view , $info , $shareManager );
2023-08-14 10:11:34 -04:00
if ( $l10n ) {
$this -> l10n = $l10n ;
} else {
// Querying IL10N directly results in a dependency loop
/** @var IL10NFactory $l10nFactory */
2025-02-03 09:34:01 -05:00
$l10nFactory = Server :: get ( IL10NFactory :: class );
2023-08-14 10:11:34 -04:00
$this -> l10n = $l10nFactory -> get ( Application :: APP_ID );
}
2022-02-04 14:02:32 -05:00
2017-11-27 13:41:34 -05:00
if ( isset ( $request )) {
$this -> request = $request ;
} else {
2025-02-03 09:34:01 -05:00
$this -> request = Server :: get ( IRequest :: class );
2017-11-27 13:41:34 -05:00
}
}
2011-07-20 09:53:34 -04:00
/**
* Updates the data
*
2012-07-20 17:52:47 -04:00
* The data argument is a readable stream resource .
*
2013-09-30 04:46:50 -04:00
* After a successful put operation , you may choose to return an ETag . The
2012-07-20 17:52:47 -04:00
* etag must always be surrounded by double - quotes . These quotes must
* appear in the actual string you ' re returning .
*
* Clients may use the ETag from a PUT request to later on make sure that
* when they update the file , the contents haven ' t changed in the mean
* time .
*
* If you don ' t plan to store the file byte - by - byte , and you return a
* different object on a subsequent GET you are strongly recommended to not
* return an ETag , and just return null .
*
2024-04-29 09:19:51 -04:00
* @ param resource | string $data
2015-02-12 06:29:01 -05:00
*
2015-04-07 08:17:28 -04:00
* @ throws Forbidden
* @ throws UnsupportedMediaType
* @ throws BadRequest
* @ throws Exception
* @ throws EntityTooLarge
* @ throws ServiceUnavailable
2015-05-29 03:59:20 -04:00
* @ throws FileLocked
2012-07-20 17:52:47 -04:00
* @ return string | null
2011-07-20 09:53:34 -04:00
*/
public function put ( $data ) {
2014-11-06 04:59:36 -05:00
try {
2014-11-06 10:48:20 -05:00
$exists = $this -> fileView -> file_exists ( $this -> path );
2023-01-24 05:40:56 -05:00
if ( $exists && ! $this -> info -> isUpdateable ()) {
2015-04-07 08:17:28 -04:00
throw new Forbidden ();
2014-11-06 04:59:36 -05:00
}
2015-04-07 08:17:28 -04:00
} catch ( StorageNotAvailableException $e ) {
2022-02-04 14:02:32 -05:00
throw new ServiceUnavailable ( $this -> l10n -> t ( 'File is not updatable: %1$s' , [ $e -> getMessage ()]));
2013-06-25 11:04:25 -04:00
}
2015-02-18 11:44:13 -05:00
// verify path of the target
$this -> verifyPath ();
2014-01-13 07:14:05 -05:00
2021-01-12 04:15:48 -05:00
[ $partStorage ] = $this -> fileView -> resolvePath ( $this -> path );
2024-09-15 08:13:29 -04:00
if ( $partStorage === null ) {
throw new ServiceUnavailable ( $this -> l10n -> t ( 'Failed to get storage for file' ));
}
2018-04-30 06:27:45 -04:00
$needsPartFile = $partStorage -> needsPartFile () && ( strlen ( $this -> path ) > 1 );
2015-01-07 15:21:51 -05:00
2024-10-10 06:40:31 -04:00
$view = Filesystem :: getView ();
2018-10-19 06:19:53 -04:00
2015-01-07 15:21:51 -05:00
if ( $needsPartFile ) {
2025-03-14 12:59:47 -04:00
$transferId = \rand ();
2015-01-07 15:21:51 -05:00
// mark file as partial while uploading (ignored by the scanner)
2025-03-14 12:59:47 -04:00
$partFilePath = $this -> getPartFileBasePath ( $this -> path ) . '.ocTransferId' . $transferId . '.part' ;
2019-05-28 07:52:32 -04:00
if ( ! $view -> isCreatable ( $partFilePath ) && $view -> isUpdatable ( $this -> path )) {
$needsPartFile = false ;
}
}
if ( ! $needsPartFile ) {
2015-01-07 15:21:51 -05:00
// upload file directly as the final path
$partFilePath = $this -> path ;
2018-03-08 07:02:35 -05:00
2018-10-19 06:19:53 -04:00
if ( $view && ! $this -> emitPreHooks ( $exists )) {
2022-02-04 14:02:32 -05:00
throw new Exception ( $this -> l10n -> t ( 'Could not write to final file, canceled by hook' ));
2018-10-19 06:19:53 -04:00
}
2015-01-07 15:21:51 -05:00
}
2013-02-22 11:21:57 -05:00
2015-05-07 08:28:31 -04:00
// the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
2021-01-12 04:15:48 -05:00
[ $partStorage , $internalPartPath ] = $this -> fileView -> resolvePath ( $partFilePath );
[ $storage , $internalPath ] = $this -> fileView -> resolvePath ( $this -> path );
2024-09-15 08:13:29 -04:00
if ( $partStorage === null || $storage === null ) {
throw new ServiceUnavailable ( $this -> l10n -> t ( 'Failed to get storage for file' ));
}
2013-09-26 05:50:46 -04:00
try {
2018-05-14 10:22:30 -04:00
if ( ! $needsPartFile ) {
2022-02-25 10:11:38 -05:00
try {
$this -> changeLock ( ILockingProvider :: LOCK_EXCLUSIVE );
} catch ( LockedException $e ) {
// during very large uploads, the shared lock we got at the start might have been expired
// meaning that the above lock can fail not just only because somebody else got a shared lock
// or because there is no existing shared lock to make exclusive
//
// Thus we try to get a new exclusive lock, if the original lock failed because of a different shared
// lock this will still fail, if our original shared lock expired the new lock will be successful and
// the entire operation will be safe
try {
$this -> acquireLock ( ILockingProvider :: LOCK_EXCLUSIVE );
} catch ( LockedException $ex ) {
throw new FileLocked ( $e -> getMessage (), $e -> getCode (), $e );
}
}
2018-05-14 10:22:30 -04:00
}
2020-02-07 08:22:12 -05:00
if ( ! is_resource ( $data )) {
$tmpData = fopen ( 'php://temp' , 'r+' );
if ( $data !== null ) {
fwrite ( $tmpData , $data );
rewind ( $tmpData );
2019-02-18 08:25:57 -05:00
}
2020-02-07 08:22:12 -05:00
$data = $tmpData ;
}
2019-02-18 08:25:57 -05:00
2022-06-01 04:59:04 -04:00
if ( $this -> request -> getHeader ( 'X-HASH' ) !== '' ) {
$hash = $this -> request -> getHeader ( 'X-HASH' );
if ( $hash === 'all' || $hash === 'md5' ) {
2024-09-20 11:38:36 -04:00
$data = HashWrapper :: wrap ( $data , 'md5' , function ( $hash ) : void {
2022-06-01 04:59:04 -04:00
$this -> header ( 'X-Hash-MD5: ' . $hash );
});
}
if ( $hash === 'all' || $hash === 'sha1' ) {
2024-09-20 11:38:36 -04:00
$data = HashWrapper :: wrap ( $data , 'sha1' , function ( $hash ) : void {
2022-06-01 04:59:04 -04:00
$this -> header ( 'X-Hash-SHA1: ' . $hash );
});
}
if ( $hash === 'all' || $hash === 'sha256' ) {
2024-09-20 11:38:36 -04:00
$data = HashWrapper :: wrap ( $data , 'sha256' , function ( $hash ) : void {
2022-06-01 04:59:04 -04:00
$this -> header ( 'X-Hash-SHA256: ' . $hash );
});
}
}
2020-02-07 08:22:12 -05:00
2025-07-28 13:55:20 -04:00
$lengthHeader = $this -> request -> getHeader ( 'content-length' );
$expected = $lengthHeader !== '' ? ( int ) $lengthHeader : null ;
2024-09-15 08:13:29 -04:00
if ( $partStorage -> instanceOfStorage ( IWriteStreamStorage :: class )) {
2019-02-14 10:46:30 -05:00
$isEOF = false ;
2024-09-20 11:38:36 -04:00
$wrappedData = CallbackWrapper :: wrap ( $data , null , null , null , null , function ( $stream ) use ( & $isEOF ) : void {
2019-02-14 10:46:30 -05:00
$isEOF = feof ( $stream );
});
2025-02-18 10:41:10 -05:00
$result = is_resource ( $wrappedData );
if ( $result ) {
$count = - 1 ;
try {
/** @var IWriteStreamStorage $partStorage */
2025-07-28 13:55:20 -04:00
$count = $partStorage -> writeStream ( $internalPartPath , $wrappedData , $expected );
2025-05-27 12:56:26 -04:00
} catch ( GenericFileException $e ) {
$logger = Server :: get ( LoggerInterface :: class );
$logger -> error ( 'Error while writing stream to storage: ' . $e -> getMessage (), [ 'exception' => $e , 'app' => 'webdav' ]);
2025-02-18 10:41:10 -05:00
$result = $isEOF ;
if ( is_resource ( $wrappedData )) {
$result = feof ( $wrappedData );
}
2019-02-20 03:49:06 -05:00
}
2018-11-05 11:00:04 -05:00
}
2018-10-26 13:15:23 -04:00
} else {
$target = $partStorage -> fopen ( $internalPartPath , 'wb' );
if ( $target === false ) {
2025-02-03 09:34:01 -05:00
Server :: get ( LoggerInterface :: class ) -> error ( '\OC\Files\Filesystem::fopen() failed' , [ 'app' => 'webdav' ]);
2018-10-26 13:15:23 -04:00
// because we have no clue about the cause we can only throw back a 500/Internal Server Error
2022-02-04 14:02:32 -05:00
throw new Exception ( $this -> l10n -> t ( 'Could not write file contents' ));
2018-10-26 13:15:23 -04:00
}
2025-05-15 18:07:33 -04:00
[ $count , $result ] = Files :: streamCopy ( $data , $target , true );
2018-10-26 13:15:23 -04:00
fclose ( $target );
2013-09-26 05:50:46 -04:00
}
2025-07-28 13:55:20 -04:00
if ( $result === false && $expected !== null ) {
2025-03-14 04:12:24 -04:00
throw new Exception (
$this -> l10n -> t (
'Error while copying file to target location (copied: %1$s, expected filesize: %2$s)' ,
[
$this -> l10n -> n ( '%n byte' , '%n bytes' , $count ),
$this -> l10n -> n ( '%n byte' , '%n bytes' , $expected ),
],
)
);
2015-12-03 09:27:56 -05:00
}
2014-11-06 10:48:20 -05:00
// if content length is sent by client:
// double check if the file was fully received
// compare expected and actual size
2025-07-28 13:55:20 -04:00
if ( $expected !== null
2025-03-14 04:12:24 -04:00
&& $expected !== $count
&& $this -> request -> getMethod () === 'PUT'
) {
throw new BadRequest (
$this -> l10n -> t (
'Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side.' ,
[
$this -> l10n -> n ( '%n byte' , '%n bytes' , $expected ),
$this -> l10n -> n ( '%n byte' , '%n bytes' , $count ),
],
)
);
2014-11-06 10:48:20 -05:00
}
2015-06-26 04:38:59 -04:00
} catch ( \Exception $e ) {
2019-07-26 08:29:13 -04:00
if ( $e instanceof LockedException ) {
2025-02-03 09:34:01 -05:00
Server :: get ( LoggerInterface :: class ) -> debug ( $e -> getMessage (), [ 'exception' => $e ]);
2022-03-31 09:34:57 -04:00
} else {
2025-02-03 09:34:01 -05:00
Server :: get ( LoggerInterface :: class ) -> error ( $e -> getMessage (), [ 'exception' => $e ]);
2019-07-26 08:29:13 -04:00
}
2015-07-01 06:52:06 -04:00
if ( $needsPartFile ) {
$partStorage -> unlink ( $internalPartPath );
}
2015-06-26 04:38:59 -04:00
$this -> convertToSabreException ( $e );
2013-09-24 08:25:56 -04:00
}
2013-02-22 11:21:57 -05:00
2014-11-06 04:59:36 -05:00
try {
2018-05-14 10:22:30 -04:00
if ( $needsPartFile ) {
if ( $view && ! $this -> emitPreHooks ( $exists )) {
2015-07-01 06:52:06 -04:00
$partStorage -> unlink ( $internalPartPath );
2022-02-04 14:02:32 -05:00
throw new Exception ( $this -> l10n -> t ( 'Could not rename part file to final file, canceled by hook' ));
2018-05-14 10:22:30 -04:00
}
try {
$this -> changeLock ( ILockingProvider :: LOCK_EXCLUSIVE );
} catch ( LockedException $e ) {
2019-11-13 08:54:22 -05:00
// during very large uploads, the shared lock we got at the start might have been expired
// meaning that the above lock can fail not just only because somebody else got a shared lock
// or because there is no existing shared lock to make exclusive
//
// Thus we try to get a new exclusive lock, if the original lock failed because of a different shared
// lock this will still fail, if our original shared lock expired the new lock will be successful and
// the entire operation will be safe
try {
$this -> acquireLock ( ILockingProvider :: LOCK_EXCLUSIVE );
2020-03-02 11:47:48 -05:00
} catch ( LockedException $ex ) {
2019-11-13 08:54:22 -05:00
if ( $needsPartFile ) {
$partStorage -> unlink ( $internalPartPath );
}
throw new FileLocked ( $e -> getMessage (), $e -> getCode (), $e );
2018-05-14 10:22:30 -04:00
}
2015-07-01 06:52:06 -04:00
}
2015-06-12 12:50:49 -04:00
2015-01-07 15:21:51 -05:00
// rename to correct path
try {
2018-05-14 10:22:30 -04:00
$renameOkay = $storage -> moveFromStorage ( $partStorage , $internalPartPath , $internalPath );
$fileExists = $storage -> file_exists ( $internalPath );
if ( $renameOkay === false || $fileExists === false ) {
2025-02-03 09:34:01 -05:00
Server :: get ( LoggerInterface :: class ) -> error ( 'renaming part file to final file failed $renameOkay: ' . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')' , [ 'app' => 'webdav' ]);
2022-02-04 14:02:32 -05:00
throw new Exception ( $this -> l10n -> t ( 'Could not rename part file to final file' ));
2015-01-07 15:21:51 -05:00
}
2015-11-13 08:13:16 -05:00
} catch ( ForbiddenException $ex ) {
2020-09-03 05:00:24 -04:00
if ( ! $ex -> getRetry ()) {
$partStorage -> unlink ( $internalPartPath );
}
2015-11-13 08:13:16 -05:00
throw new DAVForbiddenException ( $ex -> getMessage (), $ex -> getRetry ());
2015-06-26 04:38:59 -04:00
} catch ( \Exception $e ) {
$partStorage -> unlink ( $internalPartPath );
$this -> convertToSabreException ( $e );
2014-11-06 04:59:36 -05:00
}
2014-05-24 09:18:24 -04:00
}
2013-02-22 11:21:57 -05:00
2016-02-10 10:21:13 -05:00
// since we skipped the view we need to scan and emit the hooks ourselves
$storage -> getUpdater () -> update ( $internalPath );
2015-06-12 12:50:49 -04:00
try {
2015-07-22 09:28:56 -04:00
$this -> changeLock ( ILockingProvider :: LOCK_SHARED );
2015-06-12 12:50:49 -04:00
} catch ( LockedException $e ) {
throw new FileLocked ( $e -> getMessage (), $e -> getCode (), $e );
}
2014-11-06 04:59:36 -05:00
// allow sync clients to send the mtime along in a header
2023-08-14 10:11:34 -04:00
$mtimeHeader = $this -> request -> getHeader ( 'x-oc-mtime' );
if ( $mtimeHeader !== '' ) {
$mtime = $this -> sanitizeMtime ( $mtimeHeader );
2017-03-10 00:45:02 -05:00
if ( $this -> fileView -> touch ( $this -> path , $mtime )) {
2017-11-27 13:41:48 -05:00
$this -> header ( 'X-OC-MTime: accepted' );
2014-11-06 04:59:36 -05:00
}
2013-02-10 05:44:34 -05:00
}
2018-10-26 13:15:23 -04:00
2019-10-30 12:24:55 -04:00
$fileInfoUpdate = [
'upload_time' => time ()
];
// allow sync clients to send the creation time along in a header
2023-08-14 10:11:34 -04:00
$ctimeHeader = $this -> request -> getHeader ( 'x-oc-ctime' );
if ( $ctimeHeader ) {
$ctime = $this -> sanitizeMtime ( $ctimeHeader );
2019-10-30 12:24:55 -04:00
$fileInfoUpdate [ 'creation_time' ] = $ctime ;
$this -> header ( 'X-OC-CTime: accepted' );
}
$this -> fileView -> putFileInfo ( $this -> path , $fileInfoUpdate );
2016-10-17 06:20:41 -04:00
if ( $view ) {
$this -> emitPostHooks ( $exists );
}
2016-01-29 15:50:48 -05:00
2016-02-29 04:29:48 -05:00
$this -> refreshInfo ();
2023-08-14 10:11:34 -04:00
$checksumHeader = $this -> request -> getHeader ( 'oc-checksum' );
if ( $checksumHeader ) {
$checksum = trim ( $checksumHeader );
2021-06-04 10:22:39 -04:00
$this -> setChecksum ( $checksum );
2020-04-10 04:35:09 -04:00
} elseif ( $this -> getChecksum () !== null && $this -> getChecksum () !== '' ) {
2021-06-04 10:22:39 -04:00
$this -> setChecksum ( '' );
2016-01-29 15:50:48 -05:00
}
2015-04-07 08:17:28 -04:00
} catch ( StorageNotAvailableException $e ) {
2022-02-04 14:02:32 -05:00
throw new ServiceUnavailable ( $this -> l10n -> t ( 'Failed to check file size: %1$s' , [ $e -> getMessage ()]), 0 , $e );
2013-02-10 05:05:43 -05:00
}
2011-07-20 09:53:34 -04:00
2014-02-25 10:23:09 -05:00
return '"' . $this -> info -> getEtag () . '"' ;
2011-07-20 09:53:34 -04:00
}
2016-02-26 10:29:42 -05:00
private function getPartFileBasePath ( $path ) {
2025-02-03 09:34:01 -05:00
$partFileInStorage = Server :: get ( IConfig :: class ) -> getSystemValue ( 'part_file_in_storage' , true );
2016-02-26 10:29:42 -05:00
if ( $partFileInStorage ) {
2025-03-14 12:59:47 -04:00
$filename = basename ( $path );
// hash does not need to be secure but fast and semi unique
$hashedFilename = hash ( 'xxh128' , $filename );
return substr ( $path , 0 , strlen ( $path ) - strlen ( $filename )) . $hashedFilename ;
2016-02-26 10:29:42 -05:00
} else {
2025-03-14 12:59:47 -04:00
// will place the .part file in the users root directory
// therefor we need to make the name (semi) unique - hash does not need to be secure but fast.
return hash ( 'xxh128' , $path );
2016-02-26 10:29:42 -05:00
}
}
2023-02-28 06:16:37 -05:00
private function emitPreHooks ( bool $exists , ? string $path = null ) : bool {
2015-08-12 09:22:33 -04:00
if ( is_null ( $path )) {
$path = $this -> path ;
}
$hookPath = Filesystem :: getView () -> getRelativePath ( $this -> fileView -> getAbsolutePath ( $path ));
2023-02-28 06:16:37 -05:00
if ( $hookPath === null ) {
// We only trigger hooks from inside default view
return true ;
}
2015-08-12 09:06:59 -04:00
$run = true ;
if ( ! $exists ) {
2024-10-10 06:40:31 -04:00
\OC_Hook :: emit ( Filesystem :: CLASSNAME , Filesystem :: signal_create , [
Filesystem :: signal_param_path => $hookPath ,
Filesystem :: signal_param_run => & $run ,
2020-03-26 04:30:18 -04:00
]);
2015-08-12 09:06:59 -04:00
} else {
2024-10-10 06:40:31 -04:00
\OC_Hook :: emit ( Filesystem :: CLASSNAME , Filesystem :: signal_update , [
Filesystem :: signal_param_path => $hookPath ,
Filesystem :: signal_param_run => & $run ,
2020-03-26 04:30:18 -04:00
]);
2015-08-12 09:06:59 -04:00
}
2024-10-10 06:40:31 -04:00
\OC_Hook :: emit ( Filesystem :: CLASSNAME , Filesystem :: signal_write , [
Filesystem :: signal_param_path => $hookPath ,
Filesystem :: signal_param_run => & $run ,
2020-03-26 04:30:18 -04:00
]);
2015-08-12 10:44:50 -04:00
return $run ;
2015-08-12 09:06:59 -04:00
}
2023-02-28 06:16:37 -05:00
private function emitPostHooks ( bool $exists , ? string $path = null ) : void {
2015-08-12 09:22:33 -04:00
if ( is_null ( $path )) {
$path = $this -> path ;
}
$hookPath = Filesystem :: getView () -> getRelativePath ( $this -> fileView -> getAbsolutePath ( $path ));
2023-02-28 06:16:37 -05:00
if ( $hookPath === null ) {
// We only trigger hooks from inside default view
return ;
}
2015-08-12 09:06:59 -04:00
if ( ! $exists ) {
2024-10-10 06:40:31 -04:00
\OC_Hook :: emit ( Filesystem :: CLASSNAME , Filesystem :: signal_post_create , [
Filesystem :: signal_param_path => $hookPath
2020-03-26 04:30:18 -04:00
]);
2015-08-12 09:06:59 -04:00
} else {
2024-10-10 06:40:31 -04:00
\OC_Hook :: emit ( Filesystem :: CLASSNAME , Filesystem :: signal_post_update , [
Filesystem :: signal_param_path => $hookPath
2020-03-26 04:30:18 -04:00
]);
2015-08-12 09:06:59 -04:00
}
2024-10-10 06:40:31 -04:00
\OC_Hook :: emit ( Filesystem :: CLASSNAME , Filesystem :: signal_post_write , [
Filesystem :: signal_param_path => $hookPath
2020-03-26 04:30:18 -04:00
]);
2015-08-12 09:06:59 -04:00
}
2011-07-20 09:53:34 -04:00
/**
* Returns the data
2015-04-09 08:46:25 -04:00
*
2015-11-20 10:42:34 -05:00
* @ return resource
2015-04-07 08:17:28 -04:00
* @ throws Forbidden
* @ throws ServiceUnavailable
2011-07-20 09:53:34 -04:00
*/
public function get () {
2013-09-24 09:14:42 -04:00
//throw exception if encryption is disabled but files are still encrypted
2015-03-31 05:50:53 -04:00
try {
2017-02-24 05:56:29 -05:00
if ( ! $this -> info -> isReadable ()) {
// do a if the file did not exist
throw new NotFound ();
}
2024-10-18 10:50:34 -04:00
$path = ltrim ( $this -> path , '/' );
2018-04-11 08:45:35 -04:00
try {
2024-10-18 10:50:34 -04:00
$res = $this -> fileView -> fopen ( $path , 'rb' );
2018-04-11 08:45:35 -04:00
} catch ( \Exception $e ) {
$this -> convertToSabreException ( $e );
}
2022-12-01 11:42:07 -05:00
2015-09-16 05:07:13 -04:00
if ( $res === false ) {
2024-10-18 10:50:34 -04:00
if ( $this -> fileView -> file_exists ( $path )) {
throw new ServiceUnavailable ( $this -> l10n -> t ( 'Could not open file: %1$s, file does seem to exist' , [ $path ]));
} else {
throw new ServiceUnavailable ( $this -> l10n -> t ( 'Could not open file: %1$s, file doesn\'t seem to exist' , [ $path ]));
}
2015-09-16 05:07:13 -04:00
}
2022-12-01 11:42:07 -05:00
// comparing current file size with the one in DB
// if different, fix DB and refresh cache.
if ( $this -> getSize () !== $this -> fileView -> filesize ( $this -> getPath ())) {
2025-02-03 09:34:01 -05:00
$logger = Server :: get ( LoggerInterface :: class );
2022-12-01 11:42:07 -05:00
$logger -> warning ( 'fixing cached size of file id=' . $this -> getId ());
$this -> getFileInfo () -> getStorage () -> getUpdater () -> update ( $this -> getFileInfo () -> getInternalPath ());
$this -> refreshInfo ();
}
2015-09-16 05:07:13 -04:00
return $res ;
2015-04-07 08:17:28 -04:00
} catch ( GenericEncryptionException $e ) {
// returning 503 will allow retry of the operation at a later point in time
2022-02-04 14:02:32 -05:00
throw new ServiceUnavailable ( $this -> l10n -> t ( 'Encryption not ready: %1$s' , [ $e -> getMessage ()]));
2015-04-07 08:17:28 -04:00
} catch ( StorageNotAvailableException $e ) {
2022-02-04 14:02:32 -05:00
throw new ServiceUnavailable ( $this -> l10n -> t ( 'Failed to open file: %1$s' , [ $e -> getMessage ()]));
2015-11-13 08:13:16 -05:00
} catch ( ForbiddenException $ex ) {
throw new DAVForbiddenException ( $ex -> getMessage (), $ex -> getRetry ());
2015-05-29 03:59:20 -04:00
} catch ( LockedException $e ) {
throw new FileLocked ( $e -> getMessage (), $e -> getCode (), $e );
2013-08-14 03:44:29 -04:00
}
2011-07-20 09:53:34 -04:00
}
/**
* Delete the current file
2015-04-09 08:46:25 -04:00
*
2015-04-07 08:17:28 -04:00
* @ throws Forbidden
* @ throws ServiceUnavailable
2011-07-20 09:53:34 -04:00
*/
public function delete () {
2014-02-25 10:23:09 -05:00
if ( ! $this -> info -> isDeletable ()) {
2015-04-07 08:17:28 -04:00
throw new Forbidden ();
2013-06-25 11:04:25 -04:00
}
2014-09-22 06:19:34 -04:00
2014-11-06 04:59:36 -05:00
try {
if ( ! $this -> fileView -> unlink ( $this -> path )) {
// assume it wasn't possible to delete due to permissions
2015-04-07 08:17:28 -04:00
throw new Forbidden ();
2014-11-06 04:59:36 -05:00
}
2015-04-07 08:17:28 -04:00
} catch ( StorageNotAvailableException $e ) {
2022-02-04 14:02:32 -05:00
throw new ServiceUnavailable ( $this -> l10n -> t ( 'Failed to unlink: %1$s' , [ $e -> getMessage ()]));
2015-11-13 08:13:16 -05:00
} catch ( ForbiddenException $ex ) {
throw new DAVForbiddenException ( $ex -> getMessage (), $ex -> getRetry ());
2015-05-29 03:59:20 -04:00
} catch ( LockedException $e ) {
throw new FileLocked ( $e -> getMessage (), $e -> getCode (), $e );
2014-09-22 06:19:34 -04:00
}
2015-02-05 14:43:37 -05:00
}
2011-07-20 09:53:34 -04:00
/**
* Returns the mime - type for a file
*
* If null is returned , we ' ll assume application / octet - stream
*
2015-11-20 10:42:34 -05:00
* @ return string
2011-07-20 09:53:34 -04:00
*/
public function getContentType () {
2014-04-15 14:05:43 -04:00
$mimeType = $this -> info -> getMimetype ();
2011-07-20 09:53:34 -04:00
2014-11-11 09:42:50 -05:00
// PROPFIND needs to return the correct mime type, for consistency with the web UI
2023-08-14 10:11:34 -04:00
if ( $this -> request -> getMethod () === 'PROPFIND' ) {
2014-11-11 09:42:50 -05:00
return $mimeType ;
}
2025-02-03 09:34:01 -05:00
return Server :: get ( IMimeTypeDetector :: class ) -> getSecureMimeType ( $mimeType );
2011-07-20 09:53:34 -04:00
}
2013-10-07 11:49:21 -04:00
2015-02-10 07:02:48 -05:00
/**
2020-12-03 05:03:17 -05:00
* @ return array | bool
2015-02-10 07:02:48 -05:00
*/
2014-12-15 09:20:24 -05:00
public function getDirectDownload () {
2024-10-10 06:40:31 -04:00
if ( Server :: get ( IAppManager :: class ) -> isEnabledForUser ( 'encryption' )) {
2014-12-15 09:20:24 -05:00
return [];
}
2021-01-12 04:15:48 -05:00
[ $storage , $internalPath ] = $this -> fileView -> resolvePath ( $this -> path );
2014-12-15 09:20:24 -05:00
if ( is_null ( $storage )) {
return [];
}
return $storage -> getDirectDownload ( $internalPath );
}
2015-06-26 04:38:59 -04:00
/**
* Convert the given exception to a SabreException instance
*
* @ param \Exception $e
*
* @ throws \Sabre\DAV\Exception
*/
private function convertToSabreException ( \Exception $e ) {
if ( $e instanceof \Sabre\DAV\Exception ) {
throw $e ;
}
if ( $e instanceof NotPermittedException ) {
// a more general case - due to whatever reason the content could not be written
throw new Forbidden ( $e -> getMessage (), 0 , $e );
}
2015-11-13 08:13:16 -05:00
if ( $e instanceof ForbiddenException ) {
// the path for the file was forbidden
throw new DAVForbiddenException ( $e -> getMessage (), $e -> getRetry (), $e );
}
2015-06-26 04:38:59 -04:00
if ( $e instanceof EntityTooLargeException ) {
// the file is too big to be stored
throw new EntityTooLarge ( $e -> getMessage (), 0 , $e );
}
if ( $e instanceof InvalidContentException ) {
// the file content is not permitted
throw new UnsupportedMediaType ( $e -> getMessage (), 0 , $e );
}
if ( $e instanceof InvalidPathException ) {
// the path for the file was not valid
// TODO: find proper http status code for this case
throw new Forbidden ( $e -> getMessage (), 0 , $e );
}
if ( $e instanceof LockedException || $e instanceof LockNotAcquiredException ) {
// the file is currently being written to by another process
throw new FileLocked ( $e -> getMessage (), $e -> getCode (), $e );
}
if ( $e instanceof GenericEncryptionException ) {
// returning 503 will allow retry of the operation at a later point in time
2022-02-04 14:02:32 -05:00
throw new ServiceUnavailable ( $this -> l10n -> t ( 'Encryption not ready: %1$s' , [ $e -> getMessage ()]), 0 , $e );
2015-06-26 04:38:59 -04:00
}
if ( $e instanceof StorageNotAvailableException ) {
2022-02-04 14:02:32 -05:00
throw new ServiceUnavailable ( $this -> l10n -> t ( 'Failed to write file contents: %1$s' , [ $e -> getMessage ()]), 0 , $e );
2015-06-26 04:38:59 -04:00
}
2018-11-16 14:21:21 -05:00
if ( $e instanceof NotFoundException ) {
2022-02-04 14:02:32 -05:00
throw new NotFound ( $this -> l10n -> t ( 'File not found: %1$s' , [ $e -> getMessage ()]), 0 , $e );
2018-11-16 14:21:21 -05:00
}
2015-06-26 04:38:59 -04:00
throw new \Sabre\DAV\Exception ( $e -> getMessage (), 0 , $e );
}
2016-01-29 15:50:48 -05:00
/**
* Get the checksum for this file
*
2021-02-24 05:01:32 -05:00
* @ return string | null
2016-01-29 15:50:48 -05:00
*/
public function getChecksum () {
return $this -> info -> getChecksum ();
}
2017-11-27 13:41:48 -05:00
2021-06-04 10:22:39 -04:00
public function setChecksum ( string $checksum ) {
$this -> fileView -> putFileInfo ( $this -> path , [ 'checksum' => $checksum ]);
$this -> refreshInfo ();
}
2017-11-27 13:41:48 -05:00
protected function header ( $string ) {
2020-04-09 06:46:43 -04:00
if ( ! \OC :: $CLI ) {
\header ( $string );
}
2017-11-27 13:41:48 -05:00
}
2021-06-04 10:22:39 -04:00
public function hash ( string $type ) {
return $this -> fileView -> hash ( $type , $this -> path );
}
2022-04-05 09:27:59 -04:00
public function getNode () : \OCP\Files\File {
return $this -> node ;
}
2011-07-20 09:53:34 -04:00
}