2011-07-20 17:53:34 +04:00
< ? php
2015-02-26 13:37:37 +03:00
/**
2016-07-21 17:49:16 +03:00
* @ copyright Copyright ( c ) 2016 , ownCloud , Inc .
*
2015-03-26 13:44:34 +03:00
* @ author Bart Visscher < bartv @ thisnet . nl >
2016-05-26 20:56:05 +03:00
* @ author Björn Schießle < bjoern @ schiessle . org >
2020-03-31 11:49:10 +03:00
* @ author Christoph Wurst < christoph @ winzerhof - wurst . at >
2019-12-03 21:57:53 +03:00
* @ author Daniel Calviño Sánchez < danxuliu @ gmail . com >
2015-03-26 13:44:34 +03:00
* @ author Jakob Sack < mail @ jakobsack . de >
2019-12-03 21:57:53 +03:00
* @ author Jan - Philipp Litza < jplitza @ users . noreply . github . com >
2016-07-21 17:49:16 +03:00
* @ author Joas Schilling < coding @ schilljs . com >
2015-03-26 13:44:34 +03:00
* @ author Jörn Friedrich Dreyer < jfd @ butonic . de >
2016-05-26 20:56:05 +03:00
* @ author Lukas Reschke < lukas @ statuscode . ch >
2015-03-26 13:44:34 +03:00
* @ author Morris Jobke < hey @ morrisjobke . de >
* @ author Owen Winkler < a_github @ midnightcircus . com >
2016-07-21 19:13:36 +03:00
* @ author Robin Appelman < robin @ icewind . nl >
2016-07-21 17:49:16 +03:00
* @ author Roeland Jago Douma < roeland @ famdouma . nl >
2017-11-06 17:56:42 +03:00
* @ author Semih Serhat Karakaya < karakayasemi @ itu . edu . tr >
* @ author Stefan Schneider < stefan . schneider @ squareweave . com . au >
2015-03-26 13:44:34 +03:00
* @ author Thomas Müller < thomas . mueller @ tmit . eu >
* @ author Vincent Petry < pvince81 @ owncloud . com >
2015-02-26 13:37:37 +03:00
*
2015-03-26 13:44:34 +03:00
* @ license AGPL - 3.0
2015-02-26 13:37:37 +03:00
*
2015-03-26 13:44:34 +03:00
* This code is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , version 3 ,
* as published by the Free Software Foundation .
2015-02-26 13:37:37 +03:00
*
2015-03-26 13:44:34 +03:00
* This program is distributed in the hope that it will be useful ,
2015-02-26 13:37:37 +03:00
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
2015-03-26 13:44:34 +03:00
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
2015-02-26 13:37:37 +03:00
*
2015-03-26 13:44:34 +03:00
* You should have received a copy of the GNU Affero General Public License , version 3 ,
2019-12-03 21:57:53 +03:00
* along with this program . If not , see < http :// www . gnu . org / licenses />
2015-02-26 13:37:37 +03:00
*
*/
2015-08-30 20:13:01 +03:00
namespace OCA\DAV\Connector\Sabre ;
2015-02-12 14:29:01 +03:00
2019-02-14 18:46:30 +03:00
use Icewind\Streams\CallbackWrapper ;
2017-11-27 21:41:34 +03:00
use OC\AppFramework\Http\Request ;
2015-08-12 16:06:59 +03:00
use OC\Files\Filesystem ;
2020-02-07 16:22:12 +03:00
use OC\Files\Stream\HashWrapper ;
2017-11-27 21:41:34 +03:00
use OC\Files\View ;
2015-08-30 20:13:01 +03:00
use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge ;
use OCA\DAV\Connector\Sabre\Exception\FileLocked ;
2015-11-13 16:13:16 +03:00
use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException ;
2015-08-30 20:13:01 +03:00
use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType ;
2015-04-01 17:36:08 +03:00
use OCP\Encryption\Exceptions\GenericEncryptionException ;
2015-04-07 15:17:28 +03:00
use OCP\Files\EntityTooLargeException ;
2017-11-27 21:41:34 +03:00
use OCP\Files\FileInfo ;
2015-11-13 16:13:16 +03:00
use OCP\Files\ForbiddenException ;
2015-04-07 15:17:28 +03:00
use OCP\Files\InvalidContentException ;
use OCP\Files\InvalidPathException ;
use OCP\Files\LockNotAcquiredException ;
2018-11-16 22:21:21 +03:00
use OCP\Files\NotFoundException ;
2015-04-07 15:17:28 +03:00
use OCP\Files\NotPermittedException ;
2018-04-30 13:27:45 +03:00
use OCP\Files\Storage ;
2015-04-07 15:17:28 +03:00
use OCP\Files\StorageNotAvailableException ;
2019-07-26 15:29:13 +03:00
use OCP\ILogger ;
2015-05-27 16:19:46 +03:00
use OCP\Lock\ILockingProvider ;
2015-05-29 10:59:20 +03:00
use OCP\Lock\LockedException ;
2017-11-27 21:41:34 +03:00
use OCP\Share\IManager ;
2015-04-07 15:17:28 +03:00
use Sabre\DAV\Exception ;
use Sabre\DAV\Exception\BadRequest ;
use Sabre\DAV\Exception\Forbidden ;
2019-11-22 22:52:10 +03:00
use Sabre\DAV\Exception\NotFound ;
2015-04-07 15:17:28 +03:00
use Sabre\DAV\Exception\NotImplemented ;
use Sabre\DAV\Exception\ServiceUnavailable ;
use Sabre\DAV\IFile ;
class File extends Node implements IFile {
2017-11-27 21:41:34 +03:00
protected $request ;
/**
* Sets up the node , expects a full path name
*
* @ param \OC\Files\View $view
* @ param \OCP\Files\FileInfo $info
* @ param \OCP\Share\IManager $shareManager
* @ param \OC\AppFramework\Http\Request $request
*/
public function __construct ( View $view , FileInfo $info , IManager $shareManager = null , Request $request = null ) {
parent :: __construct ( $view , $info , $shareManager );
if ( isset ( $request )) {
$this -> request = $request ;
} else {
$this -> request = \OC :: $server -> getRequest ();
}
}
2011-07-20 17:53:34 +04:00
/**
* Updates the data
*
2012-07-21 01:52:47 +04:00
* The data argument is a readable stream resource .
*
2013-09-30 12:46:50 +04:00
* After a successful put operation , you may choose to return an ETag . The
2012-07-21 01: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 .
*
2015-04-14 17:25:52 +03:00
* @ param resource $data
2015-02-12 14:29:01 +03:00
*
2015-04-07 15:17:28 +03:00
* @ throws Forbidden
* @ throws UnsupportedMediaType
* @ throws BadRequest
* @ throws Exception
* @ throws EntityTooLarge
* @ throws ServiceUnavailable
2015-05-29 10:59:20 +03:00
* @ throws FileLocked
2012-07-21 01:52:47 +04:00
* @ return string | null
2011-07-20 17:53:34 +04:00
*/
public function put ( $data ) {
2014-11-06 12:59:36 +03:00
try {
2014-11-06 18:48:20 +03:00
$exists = $this -> fileView -> file_exists ( $this -> path );
if ( $this -> info && $exists && ! $this -> info -> isUpdateable ()) {
2015-04-07 15:17:28 +03:00
throw new Forbidden ();
2014-11-06 12:59:36 +03:00
}
2015-04-07 15:17:28 +03:00
} catch ( StorageNotAvailableException $e ) {
2015-04-09 15:46:25 +03:00
throw new ServiceUnavailable ( " File is not updatable: " . $e -> getMessage ());
2013-06-25 19:04:25 +04:00
}
2015-02-18 19:44:13 +03:00
// verify path of the target
$this -> verifyPath ();
2014-01-13 16:14:05 +04:00
2013-09-24 17:14:42 +04:00
// chunked handling
if ( isset ( $_SERVER [ 'HTTP_OC_CHUNKED' ])) {
2015-06-26 11:38:59 +03:00
try {
return $this -> createFileChunked ( $data );
} catch ( \Exception $e ) {
$this -> convertToSabreException ( $e );
}
2013-09-24 17:14:42 +04:00
}
2018-04-30 13:27:45 +03:00
/** @var Storage $partStorage */
2015-05-07 15:28:31 +03:00
list ( $partStorage ) = $this -> fileView -> resolvePath ( $this -> path );
2018-04-30 13:27:45 +03:00
$needsPartFile = $partStorage -> needsPartFile () && ( strlen ( $this -> path ) > 1 );
2015-01-07 23:21:51 +03:00
2018-10-19 13:19:53 +03:00
$view = \OC\Files\Filesystem :: getView ();
2015-01-07 23:21:51 +03:00
if ( $needsPartFile ) {
// mark file as partial while uploading (ignored by the scanner)
2016-02-26 18:29:42 +03:00
$partFilePath = $this -> getPartFileBasePath ( $this -> path ) . '.ocTransferId' . rand () . '.part' ;
2019-05-28 14:52:32 +03:00
if ( ! $view -> isCreatable ( $partFilePath ) && $view -> isUpdatable ( $this -> path )) {
$needsPartFile = false ;
}
}
if ( ! $needsPartFile ) {
2015-01-07 23:21:51 +03:00
// upload file directly as the final path
$partFilePath = $this -> path ;
2018-03-08 15:02:35 +03:00
2018-10-19 13:19:53 +03:00
if ( $view && ! $this -> emitPreHooks ( $exists )) {
throw new Exception ( 'Could not write to final file, canceled by hook' );
}
2015-01-07 23:21:51 +03:00
}
2013-02-22 20:21:57 +04:00
2015-05-07 15:28:31 +03: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)
/** @var \OC\Files\Storage\Storage $partStorage */
list ( $partStorage , $internalPartPath ) = $this -> fileView -> resolvePath ( $partFilePath );
2014-11-06 18:48:20 +03:00
/** @var \OC\Files\Storage\Storage $storage */
2015-05-07 15:28:31 +03:00
list ( $storage , $internalPath ) = $this -> fileView -> resolvePath ( $this -> path );
2013-09-26 13:50:46 +04:00
try {
2018-05-14 17:22:30 +03:00
if ( ! $needsPartFile ) {
$this -> changeLock ( ILockingProvider :: LOCK_EXCLUSIVE );
}
2020-02-07 16:22:12 +03:00
if ( ! is_resource ( $data )) {
$tmpData = fopen ( 'php://temp' , 'r+' );
if ( $data !== null ) {
fwrite ( $tmpData , $data );
rewind ( $tmpData );
2019-02-18 16:25:57 +03:00
}
2020-02-07 16:22:12 +03:00
$data = $tmpData ;
}
2019-02-18 16:25:57 +03:00
2020-02-07 16:22:12 +03:00
$data = HashWrapper :: wrap ( $data , 'md5' , function ( $hash ) {
$this -> header ( 'X-Hash-MD5: ' . $hash );
});
$data = HashWrapper :: wrap ( $data , 'sha1' , function ( $hash ) {
$this -> header ( 'X-Hash-SHA1: ' . $hash );
});
$data = HashWrapper :: wrap ( $data , 'sha256' , function ( $hash ) {
$this -> header ( 'X-Hash-SHA256: ' . $hash );
});
if ( $partStorage -> instanceOfStorage ( Storage\IWriteStreamStorage :: class )) {
2019-02-14 18:46:30 +03:00
$isEOF = false ;
2019-05-28 14:52:32 +03:00
$wrappedData = CallbackWrapper :: wrap ( $data , null , null , null , null , function ( $stream ) use ( & $isEOF ) {
2019-02-14 18:46:30 +03:00
$isEOF = feof ( $stream );
});
$count = $partStorage -> writeStream ( $internalPartPath , $wrappedData );
2018-10-26 20:15:23 +03:00
$result = $count > 0 ;
2019-02-20 11:49:06 +03:00
2018-11-05 19:00:04 +03:00
if ( $result === false ) {
2019-02-14 18:46:30 +03:00
$result = $isEOF ;
2019-02-20 11:49:06 +03:00
if ( is_resource ( $wrappedData )) {
$result = feof ( $wrappedData );
}
2018-11-05 19:00:04 +03:00
}
2018-10-26 20:15:23 +03:00
} else {
$target = $partStorage -> fopen ( $internalPartPath , 'wb' );
if ( $target === false ) {
\OC :: $server -> getLogger () -> error ( '\OC\Files\Filesystem::fopen() failed' , [ 'app' => 'webdav' ]);
// because we have no clue about the cause we can only throw back a 500/Internal Server Error
throw new Exception ( 'Could not write file contents' );
}
list ( $count , $result ) = \OC_Helper :: streamCopy ( $data , $target );
fclose ( $target );
2013-09-26 13:50:46 +04:00
}
2014-11-06 18:48:20 +03:00
2016-02-26 18:29:42 +03:00
if ( $result === false ) {
2015-12-03 17:27:56 +03:00
$expected = - 1 ;
if ( isset ( $_SERVER [ 'CONTENT_LENGTH' ])) {
$expected = $_SERVER [ 'CONTENT_LENGTH' ];
}
2019-01-28 19:11:14 +03:00
if ( $expected !== " 0 " ) {
throw new Exception ( 'Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )' );
}
2015-12-03 17:27:56 +03:00
}
2014-11-06 18:48:20 +03:00
// if content length is sent by client:
// double check if the file was fully received
// compare expected and actual size
2015-10-28 16:52:45 +03:00
if ( isset ( $_SERVER [ 'CONTENT_LENGTH' ]) && $_SERVER [ 'REQUEST_METHOD' ] === 'PUT' ) {
2018-10-26 20:15:23 +03:00
$expected = ( int ) $_SERVER [ 'CONTENT_LENGTH' ];
2017-05-10 15:03:14 +03:00
if ( $count !== $expected ) {
2019-07-29 16:03:01 +03:00
throw new BadRequest ( 'Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $count . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.' );
2014-11-06 18:48:20 +03:00
}
}
2015-06-26 11:38:59 +03:00
} catch ( \Exception $e ) {
2019-07-26 15:29:13 +03:00
$context = [];
if ( $e instanceof LockedException ) {
$context [ 'level' ] = ILogger :: DEBUG ;
}
\OC :: $server -> getLogger () -> logException ( $e , $context );
2015-07-01 13:52:06 +03:00
if ( $needsPartFile ) {
$partStorage -> unlink ( $internalPartPath );
}
2015-06-26 11:38:59 +03:00
$this -> convertToSabreException ( $e );
2013-09-24 16:25:56 +04:00
}
2013-02-22 20:21:57 +04:00
2014-11-06 12:59:36 +03:00
try {
2018-05-14 17:22:30 +03:00
if ( $needsPartFile ) {
if ( $view && ! $this -> emitPreHooks ( $exists )) {
2015-07-01 13:52:06 +03:00
$partStorage -> unlink ( $internalPartPath );
2018-05-14 17:22:30 +03:00
throw new Exception ( 'Could not rename part file to final file, canceled by hook' );
}
try {
$this -> changeLock ( ILockingProvider :: LOCK_EXCLUSIVE );
} catch ( LockedException $e ) {
2019-11-13 16:54:22 +03: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 19:47:48 +03:00
} catch ( LockedException $ex ) {
2019-11-13 16:54:22 +03:00
if ( $needsPartFile ) {
$partStorage -> unlink ( $internalPartPath );
}
throw new FileLocked ( $e -> getMessage (), $e -> getCode (), $e );
2018-05-14 17:22:30 +03:00
}
2015-07-01 13:52:06 +03:00
}
2015-06-12 19:50:49 +03:00
2015-01-07 23:21:51 +03:00
// rename to correct path
try {
2018-05-14 17:22:30 +03:00
$renameOkay = $storage -> moveFromStorage ( $partStorage , $internalPartPath , $internalPath );
$fileExists = $storage -> file_exists ( $internalPath );
if ( $renameOkay === false || $fileExists === false ) {
2018-10-31 21:41:55 +03:00
\OC :: $server -> getLogger () -> error ( 'renaming part file to final file failed $renameOkay: ' . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')' , [ 'app' => 'webdav' ]);
2015-04-07 15:17:28 +03:00
throw new Exception ( 'Could not rename part file to final file' );
2015-01-07 23:21:51 +03:00
}
2015-11-13 16:13:16 +03:00
} catch ( ForbiddenException $ex ) {
2020-09-03 12:00:24 +03:00
if ( ! $ex -> getRetry ()) {
$partStorage -> unlink ( $internalPartPath );
}
2015-11-13 16:13:16 +03:00
throw new DAVForbiddenException ( $ex -> getMessage (), $ex -> getRetry ());
2015-06-26 11:38:59 +03:00
} catch ( \Exception $e ) {
$partStorage -> unlink ( $internalPartPath );
$this -> convertToSabreException ( $e );
2014-11-06 12:59:36 +03:00
}
2014-05-24 17:18:24 +04:00
}
2013-02-22 20:21:57 +04:00
2016-02-10 18:21:13 +03:00
// since we skipped the view we need to scan and emit the hooks ourselves
$storage -> getUpdater () -> update ( $internalPath );
2015-06-12 19:50:49 +03:00
try {
2015-07-22 16:28:56 +03:00
$this -> changeLock ( ILockingProvider :: LOCK_SHARED );
2015-06-12 19:50:49 +03:00
} catch ( LockedException $e ) {
throw new FileLocked ( $e -> getMessage (), $e -> getCode (), $e );
}
2014-11-06 12:59:36 +03:00
// allow sync clients to send the mtime along in a header
2017-11-27 21:41:34 +03:00
if ( isset ( $this -> request -> server [ 'HTTP_X_OC_MTIME' ])) {
$mtime = $this -> sanitizeMtime ( $this -> request -> server [ 'HTTP_X_OC_MTIME' ]);
2017-03-10 08:45:02 +03:00
if ( $this -> fileView -> touch ( $this -> path , $mtime )) {
2017-11-27 21:41:48 +03:00
$this -> header ( 'X-OC-MTime: accepted' );
2014-11-06 12:59:36 +03:00
}
2013-02-10 14:44:34 +04:00
}
2018-10-26 20:15:23 +03:00
2019-10-30 19:24:55 +03:00
$fileInfoUpdate = [
'upload_time' => time ()
];
// allow sync clients to send the creation time along in a header
if ( isset ( $this -> request -> server [ 'HTTP_X_OC_CTIME' ])) {
$ctime = $this -> sanitizeMtime ( $this -> request -> server [ 'HTTP_X_OC_CTIME' ]);
$fileInfoUpdate [ 'creation_time' ] = $ctime ;
$this -> header ( 'X-OC-CTime: accepted' );
}
$this -> fileView -> putFileInfo ( $this -> path , $fileInfoUpdate );
2016-10-17 13:20:41 +03:00
if ( $view ) {
$this -> emitPostHooks ( $exists );
}
2016-01-29 23:50:48 +03:00
2016-02-29 12:29:48 +03:00
$this -> refreshInfo ();
2017-11-27 21:41:34 +03:00
if ( isset ( $this -> request -> server [ 'HTTP_OC_CHECKSUM' ])) {
$checksum = trim ( $this -> request -> server [ 'HTTP_OC_CHECKSUM' ]);
2016-01-29 23:50:48 +03:00
$this -> fileView -> putFileInfo ( $this -> path , [ 'checksum' => $checksum ]);
2016-03-01 13:24:10 +03:00
$this -> refreshInfo ();
2020-04-10 11:35:09 +03:00
} elseif ( $this -> getChecksum () !== null && $this -> getChecksum () !== '' ) {
2016-02-28 21:41:46 +03:00
$this -> fileView -> putFileInfo ( $this -> path , [ 'checksum' => '' ]);
2016-03-01 13:24:10 +03:00
$this -> refreshInfo ();
2016-01-29 23:50:48 +03:00
}
2015-04-07 15:17:28 +03:00
} catch ( StorageNotAvailableException $e ) {
2019-06-06 17:02:42 +03:00
throw new ServiceUnavailable ( " Failed to check file size: " . $e -> getMessage (), 0 , $e );
2013-02-10 14:05:43 +04:00
}
2011-07-20 17:53:34 +04:00
2014-02-25 19:23:09 +04:00
return '"' . $this -> info -> getEtag () . '"' ;
2011-07-20 17:53:34 +04:00
}
2016-02-26 18:29:42 +03:00
private function getPartFileBasePath ( $path ) {
$partFileInStorage = \OC :: $server -> getConfig () -> getSystemValue ( 'part_file_in_storage' , true );
if ( $partFileInStorage ) {
return $path ;
} else {
return md5 ( $path ); // will place it in the root of the view with a unique name
}
}
2015-11-20 18:42:34 +03:00
/**
* @ param string $path
*/
2015-08-12 16:22:33 +03:00
private function emitPreHooks ( $exists , $path = null ) {
if ( is_null ( $path )) {
$path = $this -> path ;
}
$hookPath = Filesystem :: getView () -> getRelativePath ( $this -> fileView -> getAbsolutePath ( $path ));
2015-08-12 16:06:59 +03:00
$run = true ;
if ( ! $exists ) {
2020-03-26 11:30:18 +03:00
\OC_Hook :: emit ( \OC\Files\Filesystem :: CLASSNAME , \OC\Files\Filesystem :: signal_create , [
2015-08-12 16:06:59 +03:00
\OC\Files\Filesystem :: signal_param_path => $hookPath ,
\OC\Files\Filesystem :: signal_param_run => & $run ,
2020-03-26 11:30:18 +03:00
]);
2015-08-12 16:06:59 +03:00
} else {
2020-03-26 11:30:18 +03:00
\OC_Hook :: emit ( \OC\Files\Filesystem :: CLASSNAME , \OC\Files\Filesystem :: signal_update , [
2015-08-12 16:06:59 +03:00
\OC\Files\Filesystem :: signal_param_path => $hookPath ,
\OC\Files\Filesystem :: signal_param_run => & $run ,
2020-03-26 11:30:18 +03:00
]);
2015-08-12 16:06:59 +03:00
}
2020-03-26 11:30:18 +03:00
\OC_Hook :: emit ( \OC\Files\Filesystem :: CLASSNAME , \OC\Files\Filesystem :: signal_write , [
2015-08-12 16:06:59 +03:00
\OC\Files\Filesystem :: signal_param_path => $hookPath ,
\OC\Files\Filesystem :: signal_param_run => & $run ,
2020-03-26 11:30:18 +03:00
]);
2015-08-12 17:44:50 +03:00
return $run ;
2015-08-12 16:06:59 +03:00
}
2015-11-20 18:42:34 +03:00
/**
* @ param string $path
*/
2015-08-12 16:22:33 +03:00
private function emitPostHooks ( $exists , $path = null ) {
if ( is_null ( $path )) {
$path = $this -> path ;
}
$hookPath = Filesystem :: getView () -> getRelativePath ( $this -> fileView -> getAbsolutePath ( $path ));
2015-08-12 16:06:59 +03:00
if ( ! $exists ) {
2020-03-26 11:30:18 +03:00
\OC_Hook :: emit ( \OC\Files\Filesystem :: CLASSNAME , \OC\Files\Filesystem :: signal_post_create , [
2015-08-12 16:06:59 +03:00
\OC\Files\Filesystem :: signal_param_path => $hookPath
2020-03-26 11:30:18 +03:00
]);
2015-08-12 16:06:59 +03:00
} else {
2020-03-26 11:30:18 +03:00
\OC_Hook :: emit ( \OC\Files\Filesystem :: CLASSNAME , \OC\Files\Filesystem :: signal_post_update , [
2015-08-12 16:06:59 +03:00
\OC\Files\Filesystem :: signal_param_path => $hookPath
2020-03-26 11:30:18 +03:00
]);
2015-08-12 16:06:59 +03:00
}
2020-03-26 11:30:18 +03:00
\OC_Hook :: emit ( \OC\Files\Filesystem :: CLASSNAME , \OC\Files\Filesystem :: signal_post_write , [
2015-08-12 16:06:59 +03:00
\OC\Files\Filesystem :: signal_param_path => $hookPath
2020-03-26 11:30:18 +03:00
]);
2015-08-12 16:06:59 +03:00
}
2011-07-20 17:53:34 +04:00
/**
* Returns the data
2015-04-09 15:46:25 +03:00
*
2015-11-20 18:42:34 +03:00
* @ return resource
2015-04-07 15:17:28 +03:00
* @ throws Forbidden
* @ throws ServiceUnavailable
2011-07-20 17:53:34 +04:00
*/
public function get () {
2013-09-24 17:14:42 +04:00
//throw exception if encryption is disabled but files are still encrypted
2015-03-31 12:50:53 +03:00
try {
2017-02-24 13:56:29 +03:00
if ( ! $this -> info -> isReadable ()) {
// do a if the file did not exist
throw new NotFound ();
}
2018-04-11 15:45:35 +03:00
try {
$res = $this -> fileView -> fopen ( ltrim ( $this -> path , '/' ), 'rb' );
} catch ( \Exception $e ) {
$this -> convertToSabreException ( $e );
}
2015-09-16 12:07:13 +03:00
if ( $res === false ) {
throw new ServiceUnavailable ( " Could not open file " );
}
return $res ;
2015-04-07 15:17:28 +03:00
} catch ( GenericEncryptionException $e ) {
// returning 503 will allow retry of the operation at a later point in time
throw new ServiceUnavailable ( " Encryption not ready: " . $e -> getMessage ());
} catch ( StorageNotAvailableException $e ) {
2015-04-09 15:46:25 +03:00
throw new ServiceUnavailable ( " Failed to open file: " . $e -> getMessage ());
2015-11-13 16:13:16 +03:00
} catch ( ForbiddenException $ex ) {
throw new DAVForbiddenException ( $ex -> getMessage (), $ex -> getRetry ());
2015-05-29 10:59:20 +03:00
} catch ( LockedException $e ) {
throw new FileLocked ( $e -> getMessage (), $e -> getCode (), $e );
2013-08-14 11:44:29 +04:00
}
2011-07-20 17:53:34 +04:00
}
/**
* Delete the current file
2015-04-09 15:46:25 +03:00
*
2015-04-07 15:17:28 +03:00
* @ throws Forbidden
* @ throws ServiceUnavailable
2011-07-20 17:53:34 +04:00
*/
public function delete () {
2014-02-25 19:23:09 +04:00
if ( ! $this -> info -> isDeletable ()) {
2015-04-07 15:17:28 +03:00
throw new Forbidden ();
2013-06-25 19:04:25 +04:00
}
2014-09-22 14:19:34 +04:00
2014-11-06 12:59:36 +03:00
try {
if ( ! $this -> fileView -> unlink ( $this -> path )) {
// assume it wasn't possible to delete due to permissions
2015-04-07 15:17:28 +03:00
throw new Forbidden ();
2014-11-06 12:59:36 +03:00
}
2015-04-07 15:17:28 +03:00
} catch ( StorageNotAvailableException $e ) {
2015-04-09 15:46:25 +03:00
throw new ServiceUnavailable ( " Failed to unlink: " . $e -> getMessage ());
2015-11-13 16:13:16 +03:00
} catch ( ForbiddenException $ex ) {
throw new DAVForbiddenException ( $ex -> getMessage (), $ex -> getRetry ());
2015-05-29 10:59:20 +03:00
} catch ( LockedException $e ) {
throw new FileLocked ( $e -> getMessage (), $e -> getCode (), $e );
2014-09-22 14:19:34 +04:00
}
2015-02-05 22:43:37 +03:00
}
2011-07-20 17:53:34 +04:00
/**
* Returns the mime - type for a file
*
* If null is returned , we ' ll assume application / octet - stream
*
2015-11-20 18:42:34 +03:00
* @ return string
2011-07-20 17:53:34 +04:00
*/
public function getContentType () {
2014-04-15 22:05:43 +04:00
$mimeType = $this -> info -> getMimetype ();
2011-07-20 17:53:34 +04:00
2014-11-11 17:42:50 +03:00
// PROPFIND needs to return the correct mime type, for consistency with the web UI
2015-04-09 15:46:25 +03:00
if ( isset ( $_SERVER [ 'REQUEST_METHOD' ]) && $_SERVER [ 'REQUEST_METHOD' ] === 'PROPFIND' ) {
2014-11-11 17:42:50 +03:00
return $mimeType ;
}
2015-12-18 15:42:59 +03:00
return \OC :: $server -> getMimeTypeDetector () -> getSecureMimeType ( $mimeType );
2011-07-20 17:53:34 +04:00
}
2013-10-07 19:49:21 +04:00
2015-02-10 15:02:48 +03:00
/**
* @ return array | false
*/
2014-12-15 17:20:24 +03:00
public function getDirectDownload () {
if ( \OCP\App :: isEnabled ( 'encryption' )) {
return [];
}
/** @var \OCP\Files\Storage $storage */
list ( $storage , $internalPath ) = $this -> fileView -> resolvePath ( $this -> path );
if ( is_null ( $storage )) {
return [];
}
return $storage -> getDirectDownload ( $internalPath );
}
2014-02-06 19:30:58 +04:00
/**
* @ param resource $data
2014-04-23 17:34:04 +04:00
* @ return null | string
2015-04-07 15:17:28 +03:00
* @ throws Exception
* @ throws BadRequest
* @ throws NotImplemented
* @ throws ServiceUnavailable
2014-02-06 19:30:58 +04:00
*/
2014-11-06 18:48:20 +03:00
private function createFileChunked ( $data ) {
2017-07-20 10:43:23 +03:00
list ( $path , $name ) = \Sabre\Uri\split ( $this -> path );
2013-10-07 19:49:21 +04:00
2015-02-12 14:29:01 +03:00
$info = \OC_FileChunking :: decodeName ( $name );
2013-10-07 19:49:21 +04:00
if ( empty ( $info )) {
2015-08-07 17:04:27 +03:00
throw new NotImplemented ( 'Invalid chunk name' );
2013-10-07 19:49:21 +04:00
}
2015-09-15 18:59:44 +03:00
2015-02-12 14:29:01 +03:00
$chunk_handler = new \OC_FileChunking ( $info );
2013-10-07 19:49:21 +04:00
$bytesWritten = $chunk_handler -> store ( $info [ 'index' ], $data );
//detect aborted upload
2020-04-09 17:05:56 +03:00
if ( isset ( $_SERVER [ 'REQUEST_METHOD' ]) && $_SERVER [ 'REQUEST_METHOD' ] === 'PUT' ) {
2013-10-07 19:49:21 +04:00
if ( isset ( $_SERVER [ 'CONTENT_LENGTH' ])) {
2018-10-26 20:15:23 +03:00
$expected = ( int ) $_SERVER [ 'CONTENT_LENGTH' ];
2017-05-10 15:03:14 +03:00
if ( $bytesWritten !== $expected ) {
2013-10-17 22:20:13 +04:00
$chunk_handler -> remove ( $info [ 'index' ]);
2019-07-29 16:03:01 +03:00
throw new BadRequest ( 'Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $bytesWritten . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.' );
2013-10-07 19:49:21 +04:00
}
}
}
if ( $chunk_handler -> isComplete ()) {
2018-04-30 13:27:45 +03:00
/** @var Storage $storage */
2015-04-09 15:46:25 +03:00
list ( $storage ,) = $this -> fileView -> resolvePath ( $path );
2018-04-30 13:27:45 +03:00
$needsPartFile = $storage -> needsPartFile ();
2015-07-01 10:30:18 +03:00
$partFile = null ;
2013-10-21 15:21:39 +04:00
2015-08-12 16:06:59 +03:00
$targetPath = $path . '/' . $info [ 'name' ];
/** @var \OC\Files\Storage\Storage $targetStorage */
list ( $targetStorage , $targetInternalPath ) = $this -> fileView -> resolvePath ( $targetPath );
$exists = $this -> fileView -> file_exists ( $targetPath );
2014-11-06 12:59:36 +03:00
try {
2015-09-15 18:59:44 +03:00
$this -> fileView -> lockFile ( $targetPath , ILockingProvider :: LOCK_SHARED );
2015-08-12 16:06:59 +03:00
2015-09-15 18:59:44 +03:00
$this -> emitPreHooks ( $exists , $targetPath );
$this -> fileView -> changeLock ( $targetPath , ILockingProvider :: LOCK_EXCLUSIVE );
2015-06-23 14:52:27 +03:00
/** @var \OC\Files\Storage\Storage $targetStorage */
list ( $targetStorage , $targetInternalPath ) = $this -> fileView -> resolvePath ( $targetPath );
2015-08-12 16:06:59 +03:00
2015-01-07 23:21:51 +03:00
if ( $needsPartFile ) {
// we first assembly the target file as a part file
2016-02-26 18:29:42 +03:00
$partFile = $this -> getPartFileBasePath ( $path . '/' . $info [ 'name' ]) . '.ocTransferId' . $info [ 'transferid' ] . '.part' ;
2015-06-23 14:52:27 +03:00
/** @var \OC\Files\Storage\Storage $targetStorage */
2015-08-12 16:06:59 +03:00
list ( $partStorage , $partInternalPath ) = $this -> fileView -> resolvePath ( $partFile );
2016-03-16 12:15:41 +03:00
$chunk_handler -> file_assemble ( $partStorage , $partInternalPath );
2015-01-07 23:21:51 +03:00
// here is the final atomic rename
2015-08-12 16:06:59 +03:00
$renameOkay = $targetStorage -> moveFromStorage ( $partStorage , $partInternalPath , $targetInternalPath );
2015-06-23 14:52:27 +03:00
$fileExists = $targetStorage -> file_exists ( $targetInternalPath );
2015-01-07 23:21:51 +03:00
if ( $renameOkay === false || $fileExists === false ) {
2018-04-20 15:35:37 +03:00
\OC :: $server -> getLogger () -> error ( '\OC\Files\Filesystem::rename() failed' , [ 'app' => 'webdav' ]);
2015-01-07 23:21:51 +03:00
// only delete if an error occurred and the target file was already created
if ( $fileExists ) {
2015-06-26 11:38:59 +03:00
// set to null to avoid double-deletion when handling exception
// stray part file
$partFile = null ;
2015-08-12 16:06:59 +03:00
$targetStorage -> unlink ( $targetInternalPath );
2015-01-07 23:21:51 +03:00
}
2015-09-15 18:59:44 +03:00
$this -> fileView -> changeLock ( $targetPath , ILockingProvider :: LOCK_SHARED );
2015-04-07 15:17:28 +03:00
throw new Exception ( 'Could not rename part file assembled from chunks' );
2014-11-06 12:59:36 +03:00
}
2015-01-07 23:21:51 +03:00
} else {
// assemble directly into the final file
2016-03-16 12:15:41 +03:00
$chunk_handler -> file_assemble ( $targetStorage , $targetInternalPath );
2014-01-08 21:43:20 +04:00
}
2013-10-21 17:00:28 +04:00
2014-11-06 12:59:36 +03:00
// allow sync clients to send the mtime along in a header
2017-11-27 21:41:34 +03:00
if ( isset ( $this -> request -> server [ 'HTTP_X_OC_MTIME' ])) {
$mtime = $this -> sanitizeMtime ( $this -> request -> server [ 'HTTP_X_OC_MTIME' ]);
2017-11-27 21:41:25 +03:00
if ( $targetStorage -> touch ( $targetInternalPath , $mtime )) {
2017-11-27 21:41:48 +03:00
$this -> header ( 'X-OC-MTime: accepted' );
2014-11-06 12:59:36 +03:00
}
2013-10-21 17:00:28 +04:00
}
2015-08-12 16:06:59 +03:00
// since we skipped the view we need to scan and emit the hooks ourselves
2015-11-25 15:53:31 +03:00
$targetStorage -> getUpdater () -> update ( $targetInternalPath );
2015-08-12 16:06:59 +03:00
2016-02-10 18:31:32 +03:00
$this -> fileView -> changeLock ( $targetPath , ILockingProvider :: LOCK_SHARED );
2016-02-29 12:29:48 +03:00
$this -> emitPostHooks ( $exists , $targetPath );
2016-03-01 13:24:10 +03:00
// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
2016-02-29 12:29:48 +03:00
$info = $this -> fileView -> getFileInfo ( $targetPath );
2017-11-27 21:41:34 +03:00
if ( isset ( $this -> request -> server [ 'HTTP_OC_CHECKSUM' ])) {
$checksum = trim ( $this -> request -> server [ 'HTTP_OC_CHECKSUM' ]);
2016-02-28 22:21:43 +03:00
$this -> fileView -> putFileInfo ( $targetPath , [ 'checksum' => $checksum ]);
2020-04-10 11:35:09 +03:00
} elseif ( $info -> getChecksum () !== null && $info -> getChecksum () !== '' ) {
2016-02-28 22:21:43 +03:00
$this -> fileView -> putFileInfo ( $this -> path , [ 'checksum' => '' ]);
}
2015-08-12 16:06:59 +03:00
2015-06-23 14:52:27 +03:00
$this -> fileView -> unlockFile ( $targetPath , ILockingProvider :: LOCK_SHARED );
2014-11-06 12:59:36 +03:00
return $info -> getEtag ();
2015-06-26 11:38:59 +03:00
} catch ( \Exception $e ) {
2015-07-01 10:30:18 +03:00
if ( $partFile !== null ) {
2015-08-12 16:06:59 +03:00
$targetStorage -> unlink ( $targetInternalPath );
2015-06-26 11:38:59 +03:00
}
$this -> convertToSabreException ( $e );
2014-11-06 12:59:36 +03:00
}
2013-10-07 19:49:21 +04:00
}
return null ;
}
2015-01-07 23:21:51 +03:00
2015-06-26 11:38:59 +03: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 16:13:16 +03:00
if ( $e instanceof ForbiddenException ) {
// the path for the file was forbidden
throw new DAVForbiddenException ( $e -> getMessage (), $e -> getRetry (), $e );
}
2015-06-26 11:38:59 +03: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
throw new ServiceUnavailable ( 'Encryption not ready: ' . $e -> getMessage (), 0 , $e );
}
if ( $e instanceof StorageNotAvailableException ) {
throw new ServiceUnavailable ( 'Failed to write file contents: ' . $e -> getMessage (), 0 , $e );
}
2018-11-16 22:21:21 +03:00
if ( $e instanceof NotFoundException ) {
throw new NotFound ( 'File not found: ' . $e -> getMessage (), 0 , $e );
}
2015-06-26 11:38:59 +03:00
throw new \Sabre\DAV\Exception ( $e -> getMessage (), 0 , $e );
}
2016-01-29 23:50:48 +03:00
/**
* Get the checksum for this file
*
* @ return string
*/
public function getChecksum () {
return $this -> info -> getChecksum ();
}
2017-11-27 21:41:48 +03:00
protected function header ( $string ) {
2020-04-09 13:46:43 +03:00
if ( ! \OC :: $CLI ) {
\header ( $string );
}
2017-11-27 21:41:48 +03:00
}
2011-07-20 17:53:34 +04:00
}