2011-07-20 09:53:34 -04:00
< ? php
2015-02-26 05:37:37 -05:00
/**
2016-07-21 10:49:16 -04:00
* @ copyright Copyright ( c ) 2016 , ownCloud , Inc .
*
2015-03-26 06:44:34 -04:00
* @ author Bart Visscher < bartv @ thisnet . nl >
2016-05-26 13:56:05 -04:00
* @ author Björn Schießle < bjoern @ schiessle . org >
2020-03-31 04:49:10 -04:00
* @ author Christoph Wurst < christoph @ winzerhof - wurst . at >
2019-12-03 13:57:53 -05:00
* @ author Daniel Calviño Sánchez < danxuliu @ gmail . com >
2015-03-26 06:44:34 -04:00
* @ author Jakob Sack < mail @ jakobsack . de >
2019-12-03 13:57:53 -05:00
* @ author Jan - Philipp Litza < jplitza @ users . noreply . github . com >
2016-07-21 10:49:16 -04:00
* @ author Joas Schilling < coding @ schilljs . com >
2015-03-26 06:44:34 -04:00
* @ author Jörn Friedrich Dreyer < jfd @ butonic . de >
2016-05-26 13:56:05 -04:00
* @ author Lukas Reschke < lukas @ statuscode . ch >
2015-03-26 06:44:34 -04:00
* @ author Morris Jobke < hey @ morrisjobke . de >
* @ author Owen Winkler < a_github @ midnightcircus . com >
2016-07-21 12:13:36 -04:00
* @ author Robin Appelman < robin @ icewind . nl >
2016-07-21 10:49:16 -04:00
* @ author Roeland Jago Douma < roeland @ famdouma . nl >
2017-11-06 09:56:42 -05:00
* @ author Semih Serhat Karakaya < karakayasemi @ itu . edu . tr >
* @ author Stefan Schneider < stefan . schneider @ squareweave . com . au >
2015-03-26 06:44:34 -04:00
* @ author Thomas Müller < thomas . mueller @ tmit . eu >
* @ author Vincent Petry < pvince81 @ owncloud . com >
2015-02-26 05:37:37 -05:00
*
2015-03-26 06:44:34 -04:00
* @ license AGPL - 3.0
2015-02-26 05:37:37 -05:00
*
2015-03-26 06:44:34 -04: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 05:37:37 -05:00
*
2015-03-26 06:44:34 -04:00
* This program is distributed in the hope that it will be useful ,
2015-02-26 05:37:37 -05:00
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
2015-03-26 06:44:34 -04:00
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
2015-02-26 05:37:37 -05:00
*
2015-03-26 06:44:34 -04:00
* You should have received a copy of the GNU Affero General Public License , version 3 ,
2019-12-03 13:57:53 -05:00
* along with this program . If not , see < http :// www . gnu . org / licenses />
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 ;
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 ;
2015-04-01 10:36:08 -04:00
use OCP\Encryption\Exceptions\GenericEncryptionException ;
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 ;
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 ;
2018-04-30 06:27:45 -04:00
use OCP\Files\Storage ;
2015-04-07 08:17:28 -04:00
use OCP\Files\StorageNotAvailableException ;
2019-07-26 08:29:13 -04:00
use OCP\ILogger ;
2015-05-27 09:19:46 -04:00
use OCP\Lock\ILockingProvider ;
2015-05-29 03:59:20 -04:00
use OCP\Lock\LockedException ;
2017-11-27 13:41:34 -05:00
use OCP\Share\IManager ;
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\NotImplemented ;
use Sabre\DAV\Exception\ServiceUnavailable ;
use Sabre\DAV\IFile ;
class File extends Node implements IFile {
2017-11-27 13:41:34 -05: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 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 .
*
2015-04-14 10:25:52 -04:00
* @ param resource $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 );
if ( $this -> info && $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 ) {
2015-04-09 08:46:25 -04:00
throw new ServiceUnavailable ( " File is not updatable: " . $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
2013-09-24 09:14:42 -04:00
// chunked handling
if ( isset ( $_SERVER [ 'HTTP_OC_CHUNKED' ])) {
2015-06-26 04:38:59 -04:00
try {
return $this -> createFileChunked ( $data );
} catch ( \Exception $e ) {
$this -> convertToSabreException ( $e );
}
2013-09-24 09:14:42 -04:00
}
2018-04-30 06:27:45 -04:00
/** @var Storage $partStorage */
2015-05-07 08:28:31 -04:00
list ( $partStorage ) = $this -> fileView -> resolvePath ( $this -> path );
2018-04-30 06:27:45 -04:00
$needsPartFile = $partStorage -> needsPartFile () && ( strlen ( $this -> path ) > 1 );
2015-01-07 15:21:51 -05:00
2018-10-19 06:19:53 -04:00
$view = \OC\Files\Filesystem :: getView ();
2015-01-07 15:21:51 -05:00
if ( $needsPartFile ) {
// mark file as partial while uploading (ignored by the scanner)
2016-02-26 10:29:42 -05:00
$partFilePath = $this -> getPartFileBasePath ( $this -> path ) . '.ocTransferId' . rand () . '.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 )) {
throw new Exception ( 'Could not write to final file, canceled by hook' );
}
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)
/** @var \OC\Files\Storage\Storage $partStorage */
list ( $partStorage , $internalPartPath ) = $this -> fileView -> resolvePath ( $partFilePath );
2014-11-06 10:48:20 -05:00
/** @var \OC\Files\Storage\Storage $storage */
2015-05-07 08:28:31 -04:00
list ( $storage , $internalPath ) = $this -> fileView -> resolvePath ( $this -> path );
2013-09-26 05:50:46 -04:00
try {
2018-05-14 10:22:30 -04:00
if ( ! $needsPartFile ) {
$this -> changeLock ( ILockingProvider :: LOCK_EXCLUSIVE );
}
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
2020-02-07 08:22:12 -05: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 10:46:30 -05:00
$isEOF = false ;
2019-05-28 07:52:32 -04:00
$wrappedData = CallbackWrapper :: wrap ( $data , null , null , null , null , function ( $stream ) use ( & $isEOF ) {
2019-02-14 10:46:30 -05:00
$isEOF = feof ( $stream );
});
$count = $partStorage -> writeStream ( $internalPartPath , $wrappedData );
2018-10-26 13:15:23 -04:00
$result = $count > 0 ;
2019-02-20 03:49:06 -05:00
2018-11-05 11:00:04 -05:00
if ( $result === false ) {
2019-02-14 10:46:30 -05:00
$result = $isEOF ;
2019-02-20 03:49:06 -05:00
if ( is_resource ( $wrappedData )) {
$result = feof ( $wrappedData );
}
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 ) {
\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 05:50:46 -04:00
}
2014-11-06 10:48:20 -05:00
2016-02-26 10:29:42 -05:00
if ( $result === false ) {
2015-12-03 09:27:56 -05:00
$expected = - 1 ;
if ( isset ( $_SERVER [ 'CONTENT_LENGTH' ])) {
$expected = $_SERVER [ 'CONTENT_LENGTH' ];
}
2019-01-28 11:11:14 -05:00
if ( $expected !== " 0 " ) {
throw new Exception ( 'Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $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
2015-10-28 09:52:45 -04:00
if ( isset ( $_SERVER [ 'CONTENT_LENGTH' ]) && $_SERVER [ 'REQUEST_METHOD' ] === 'PUT' ) {
2018-10-26 13:15:23 -04:00
$expected = ( int ) $_SERVER [ 'CONTENT_LENGTH' ];
2017-05-10 08:03:14 -04:00
if ( $count !== $expected ) {
2019-07-29 09:03:01 -04: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 10:48:20 -05:00
}
}
2015-06-26 04:38:59 -04:00
} catch ( \Exception $e ) {
2019-07-26 08:29:13 -04:00
$context = [];
if ( $e instanceof LockedException ) {
$context [ 'level' ] = ILogger :: DEBUG ;
}
\OC :: $server -> getLogger () -> logException ( $e , $context );
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 );
2018-05-14 10:22:30 -04: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 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 ) {
2018-10-31 14:41:55 -04: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 08:17:28 -04:00
throw new Exception ( '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 ) {
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
2017-11-27 13:41:34 -05:00
if ( isset ( $this -> request -> server [ 'HTTP_X_OC_MTIME' ])) {
$mtime = $this -> sanitizeMtime ( $this -> request -> server [ 'HTTP_X_OC_MTIME' ]);
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
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 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 ();
2017-11-27 13:41:34 -05:00
if ( isset ( $this -> request -> server [ 'HTTP_OC_CHECKSUM' ])) {
$checksum = trim ( $this -> request -> server [ 'HTTP_OC_CHECKSUM' ]);
2016-01-29 15:50:48 -05:00
$this -> fileView -> putFileInfo ( $this -> path , [ 'checksum' => $checksum ]);
2016-03-01 05:24:10 -05:00
$this -> refreshInfo ();
2020-04-10 04:35:09 -04:00
} elseif ( $this -> getChecksum () !== null && $this -> getChecksum () !== '' ) {
2016-02-28 13:41:46 -05:00
$this -> fileView -> putFileInfo ( $this -> path , [ 'checksum' => '' ]);
2016-03-01 05:24:10 -05:00
$this -> refreshInfo ();
2016-01-29 15:50:48 -05:00
}
2015-04-07 08:17:28 -04:00
} catch ( StorageNotAvailableException $e ) {
2019-06-06 10:02:42 -04:00
throw new ServiceUnavailable ( " Failed to check file size: " . $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 ) {
$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 10:42:34 -05:00
/**
* @ param string $path
*/
2015-08-12 09:22:33 -04: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 09:06:59 -04:00
$run = true ;
if ( ! $exists ) {
2020-03-26 04:30:18 -04:00
\OC_Hook :: emit ( \OC\Files\Filesystem :: CLASSNAME , \OC\Files\Filesystem :: signal_create , [
2015-08-12 09:06:59 -04:00
\OC\Files\Filesystem :: signal_param_path => $hookPath ,
\OC\Files\Filesystem :: signal_param_run => & $run ,
2020-03-26 04:30:18 -04:00
]);
2015-08-12 09:06:59 -04:00
} else {
2020-03-26 04:30:18 -04:00
\OC_Hook :: emit ( \OC\Files\Filesystem :: CLASSNAME , \OC\Files\Filesystem :: signal_update , [
2015-08-12 09:06:59 -04:00
\OC\Files\Filesystem :: signal_param_path => $hookPath ,
\OC\Files\Filesystem :: signal_param_run => & $run ,
2020-03-26 04:30:18 -04:00
]);
2015-08-12 09:06:59 -04:00
}
2020-03-26 04:30:18 -04:00
\OC_Hook :: emit ( \OC\Files\Filesystem :: CLASSNAME , \OC\Files\Filesystem :: signal_write , [
2015-08-12 09:06:59 -04:00
\OC\Files\Filesystem :: signal_param_path => $hookPath ,
\OC\Files\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
}
2015-11-20 10:42:34 -05:00
/**
* @ param string $path
*/
2015-08-12 09:22:33 -04: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 09:06:59 -04:00
if ( ! $exists ) {
2020-03-26 04:30:18 -04:00
\OC_Hook :: emit ( \OC\Files\Filesystem :: CLASSNAME , \OC\Files\Filesystem :: signal_post_create , [
2015-08-12 09:06:59 -04:00
\OC\Files\Filesystem :: signal_param_path => $hookPath
2020-03-26 04:30:18 -04:00
]);
2015-08-12 09:06:59 -04:00
} else {
2020-03-26 04:30:18 -04:00
\OC_Hook :: emit ( \OC\Files\Filesystem :: CLASSNAME , \OC\Files\Filesystem :: signal_post_update , [
2015-08-12 09:06:59 -04:00
\OC\Files\Filesystem :: signal_param_path => $hookPath
2020-03-26 04:30:18 -04:00
]);
2015-08-12 09:06:59 -04:00
}
2020-03-26 04:30:18 -04:00
\OC_Hook :: emit ( \OC\Files\Filesystem :: CLASSNAME , \OC\Files\Filesystem :: signal_post_write , [
2015-08-12 09:06:59 -04:00
\OC\Files\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 ();
}
2018-04-11 08:45:35 -04:00
try {
$res = $this -> fileView -> fopen ( ltrim ( $this -> path , '/' ), 'rb' );
} catch ( \Exception $e ) {
$this -> convertToSabreException ( $e );
}
2015-09-16 05:07:13 -04:00
if ( $res === false ) {
throw new ServiceUnavailable ( " Could not open file " );
}
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
throw new ServiceUnavailable ( " Encryption not ready: " . $e -> getMessage ());
} catch ( StorageNotAvailableException $e ) {
2015-04-09 08:46:25 -04:00
throw new ServiceUnavailable ( " Failed to open file: " . $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 ) {
2015-04-09 08:46:25 -04:00
throw new ServiceUnavailable ( " Failed to unlink: " . $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
2015-04-09 08:46:25 -04:00
if ( isset ( $_SERVER [ 'REQUEST_METHOD' ]) && $_SERVER [ 'REQUEST_METHOD' ] === 'PROPFIND' ) {
2014-11-11 09:42:50 -05:00
return $mimeType ;
}
2015-12-18 07:42:59 -05:00
return \OC :: $server -> getMimeTypeDetector () -> 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
/**
* @ return array | false
*/
2014-12-15 09:20:24 -05: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 10:30:58 -05:00
/**
* @ param resource $data
2014-04-23 09:34:04 -04:00
* @ return null | string
2015-04-07 08:17:28 -04:00
* @ throws Exception
* @ throws BadRequest
* @ throws NotImplemented
* @ throws ServiceUnavailable
2014-02-06 10:30:58 -05:00
*/
2014-11-06 10:48:20 -05:00
private function createFileChunked ( $data ) {
2017-07-20 03:43:23 -04:00
list ( $path , $name ) = \Sabre\Uri\split ( $this -> path );
2013-10-07 11:49:21 -04:00
2015-02-12 06:29:01 -05:00
$info = \OC_FileChunking :: decodeName ( $name );
2013-10-07 11:49:21 -04:00
if ( empty ( $info )) {
2015-08-07 10:04:27 -04:00
throw new NotImplemented ( 'Invalid chunk name' );
2013-10-07 11:49:21 -04:00
}
2015-09-15 11:59:44 -04:00
2015-02-12 06:29:01 -05:00
$chunk_handler = new \OC_FileChunking ( $info );
2013-10-07 11:49:21 -04:00
$bytesWritten = $chunk_handler -> store ( $info [ 'index' ], $data );
//detect aborted upload
2020-04-09 10:05:56 -04:00
if ( isset ( $_SERVER [ 'REQUEST_METHOD' ]) && $_SERVER [ 'REQUEST_METHOD' ] === 'PUT' ) {
2013-10-07 11:49:21 -04:00
if ( isset ( $_SERVER [ 'CONTENT_LENGTH' ])) {
2018-10-26 13:15:23 -04:00
$expected = ( int ) $_SERVER [ 'CONTENT_LENGTH' ];
2017-05-10 08:03:14 -04:00
if ( $bytesWritten !== $expected ) {
2013-10-17 14:20:13 -04:00
$chunk_handler -> remove ( $info [ 'index' ]);
2019-07-29 09:03:01 -04: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 11:49:21 -04:00
}
}
}
if ( $chunk_handler -> isComplete ()) {
2018-04-30 06:27:45 -04:00
/** @var Storage $storage */
2015-04-09 08:46:25 -04:00
list ( $storage ,) = $this -> fileView -> resolvePath ( $path );
2018-04-30 06:27:45 -04:00
$needsPartFile = $storage -> needsPartFile ();
2015-07-01 03:30:18 -04:00
$partFile = null ;
2013-10-21 07:21:39 -04:00
2015-08-12 09:06:59 -04: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 04:59:36 -05:00
try {
2015-09-15 11:59:44 -04:00
$this -> fileView -> lockFile ( $targetPath , ILockingProvider :: LOCK_SHARED );
2015-08-12 09:06:59 -04:00
2015-09-15 11:59:44 -04:00
$this -> emitPreHooks ( $exists , $targetPath );
$this -> fileView -> changeLock ( $targetPath , ILockingProvider :: LOCK_EXCLUSIVE );
2015-06-23 07:52:27 -04:00
/** @var \OC\Files\Storage\Storage $targetStorage */
list ( $targetStorage , $targetInternalPath ) = $this -> fileView -> resolvePath ( $targetPath );
2015-08-12 09:06:59 -04:00
2015-01-07 15:21:51 -05:00
if ( $needsPartFile ) {
// we first assembly the target file as a part file
2016-02-26 10:29:42 -05:00
$partFile = $this -> getPartFileBasePath ( $path . '/' . $info [ 'name' ]) . '.ocTransferId' . $info [ 'transferid' ] . '.part' ;
2015-06-23 07:52:27 -04:00
/** @var \OC\Files\Storage\Storage $targetStorage */
2015-08-12 09:06:59 -04:00
list ( $partStorage , $partInternalPath ) = $this -> fileView -> resolvePath ( $partFile );
2016-03-16 05:15:41 -04:00
$chunk_handler -> file_assemble ( $partStorage , $partInternalPath );
2015-01-07 15:21:51 -05:00
// here is the final atomic rename
2015-08-12 09:06:59 -04:00
$renameOkay = $targetStorage -> moveFromStorage ( $partStorage , $partInternalPath , $targetInternalPath );
2015-06-23 07:52:27 -04:00
$fileExists = $targetStorage -> file_exists ( $targetInternalPath );
2015-01-07 15:21:51 -05:00
if ( $renameOkay === false || $fileExists === false ) {
2018-04-20 08:35:37 -04:00
\OC :: $server -> getLogger () -> error ( '\OC\Files\Filesystem::rename() failed' , [ 'app' => 'webdav' ]);
2015-01-07 15:21:51 -05:00
// only delete if an error occurred and the target file was already created
if ( $fileExists ) {
2015-06-26 04:38:59 -04:00
// set to null to avoid double-deletion when handling exception
// stray part file
$partFile = null ;
2015-08-12 09:06:59 -04:00
$targetStorage -> unlink ( $targetInternalPath );
2015-01-07 15:21:51 -05:00
}
2015-09-15 11:59:44 -04:00
$this -> fileView -> changeLock ( $targetPath , ILockingProvider :: LOCK_SHARED );
2015-04-07 08:17:28 -04:00
throw new Exception ( 'Could not rename part file assembled from chunks' );
2014-11-06 04:59:36 -05:00
}
2015-01-07 15:21:51 -05:00
} else {
// assemble directly into the final file
2016-03-16 05:15:41 -04:00
$chunk_handler -> file_assemble ( $targetStorage , $targetInternalPath );
2014-01-08 12:43:20 -05:00
}
2013-10-21 09:00:28 -04:00
2014-11-06 04:59:36 -05:00
// allow sync clients to send the mtime along in a header
2017-11-27 13:41:34 -05:00
if ( isset ( $this -> request -> server [ 'HTTP_X_OC_MTIME' ])) {
$mtime = $this -> sanitizeMtime ( $this -> request -> server [ 'HTTP_X_OC_MTIME' ]);
2017-11-27 13:41:25 -05:00
if ( $targetStorage -> touch ( $targetInternalPath , $mtime )) {
2017-11-27 13:41:48 -05:00
$this -> header ( 'X-OC-MTime: accepted' );
2014-11-06 04:59:36 -05:00
}
2013-10-21 09:00:28 -04:00
}
2015-08-12 09:06:59 -04:00
// since we skipped the view we need to scan and emit the hooks ourselves
2015-11-25 07:53:31 -05:00
$targetStorage -> getUpdater () -> update ( $targetInternalPath );
2015-08-12 09:06:59 -04:00
2016-02-10 10:31:32 -05:00
$this -> fileView -> changeLock ( $targetPath , ILockingProvider :: LOCK_SHARED );
2016-02-29 04:29:48 -05:00
$this -> emitPostHooks ( $exists , $targetPath );
2016-03-01 05:24:10 -05:00
// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
2016-02-29 04:29:48 -05:00
$info = $this -> fileView -> getFileInfo ( $targetPath );
2017-11-27 13:41:34 -05:00
if ( isset ( $this -> request -> server [ 'HTTP_OC_CHECKSUM' ])) {
$checksum = trim ( $this -> request -> server [ 'HTTP_OC_CHECKSUM' ]);
2016-02-28 14:21:43 -05:00
$this -> fileView -> putFileInfo ( $targetPath , [ 'checksum' => $checksum ]);
2020-04-10 04:35:09 -04:00
} elseif ( $info -> getChecksum () !== null && $info -> getChecksum () !== '' ) {
2016-02-28 14:21:43 -05:00
$this -> fileView -> putFileInfo ( $this -> path , [ 'checksum' => '' ]);
}
2015-08-12 09:06:59 -04:00
2015-06-23 07:52:27 -04:00
$this -> fileView -> unlockFile ( $targetPath , ILockingProvider :: LOCK_SHARED );
2014-11-06 04:59:36 -05:00
return $info -> getEtag ();
2015-06-26 04:38:59 -04:00
} catch ( \Exception $e ) {
2015-07-01 03:30:18 -04:00
if ( $partFile !== null ) {
2015-08-12 09:06:59 -04:00
$targetStorage -> unlink ( $targetInternalPath );
2015-06-26 04:38:59 -04:00
}
$this -> convertToSabreException ( $e );
2014-11-06 04:59:36 -05:00
}
2013-10-07 11:49:21 -04:00
}
return null ;
}
2015-01-07 15:21:51 -05:00
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
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 14:21:21 -05:00
if ( $e instanceof NotFoundException ) {
throw new NotFound ( 'File not found: ' . $e -> getMessage (), 0 , $e );
}
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
*
* @ return string
*/
public function getChecksum () {
return $this -> info -> getChecksum ();
}
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
}
2011-07-20 09:53:34 -04:00
}