2011-07-27 19:52:24 +02:00
< ? php
2024-05-23 09:26:56 +02:00
2012-09-07 18:30:48 +02:00
/**
2024-05-23 09:26:56 +02:00
* SPDX - FileCopyrightText : 2016 - 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX - FileCopyrightText : 2016 ownCloud , Inc .
* SPDX - License - Identifier : AGPL - 3.0 - only
2012-09-07 18:30:48 +02:00
*/
namespace OC\Files\Storage ;
2016-05-30 15:44:19 +02:00
2019-06-27 11:10:08 +02:00
use OC\Files\Filesystem ;
2022-12-27 15:32:59 +01:00
use OC\Files\Storage\Wrapper\Encryption ;
2017-04-12 14:56:51 +02:00
use OC\Files\Storage\Wrapper\Jail ;
2020-02-14 22:07:09 +01:00
use OCP\Constants ;
2016-05-30 15:44:19 +02:00
use OCP\Files\ForbiddenException ;
2020-07-01 15:37:47 +02:00
use OCP\Files\GenericFileException ;
2022-03-17 16:05:52 +01:00
use OCP\Files\IMimeTypeDetector ;
2017-07-19 19:44:10 +02:00
use OCP\Files\Storage\IStorage ;
2023-08-03 23:09:17 +02:00
use OCP\Files\StorageNotAvailableException ;
2022-03-17 16:05:52 +01:00
use OCP\IConfig ;
2024-11-21 08:38:50 +01:00
use OCP\Server ;
2023-05-11 12:46:16 +02:00
use OCP\Util ;
2022-03-31 10:57:10 +02:00
use Psr\Log\LoggerInterface ;
2016-05-30 15:44:19 +02:00
2015-07-03 15:41:29 +02:00
/**
* for local filestore , we only have to map the paths
*/
class Local extends \OC\Files\Storage\Common {
protected $datadir ;
2013-04-26 17:30:55 +02:00
2016-05-30 15:44:19 +02:00
protected $dataDirLength ;
protected $realDataDir ;
2022-03-17 16:05:52 +01:00
private IConfig $config ;
private IMimeTypeDetector $mimeTypeDetector ;
2022-06-04 00:24:35 +02:00
private $defUMask ;
2022-03-31 10:38:25 +02:00
protected bool $unlinkOnTruncate ;
2023-10-16 17:39:23 +02:00
protected bool $caseInsensitive = false ;
2024-10-08 15:13:16 +02:00
public function __construct ( array $parameters ) {
if ( ! isset ( $parameters [ 'datadir' ]) || ! is_string ( $parameters [ 'datadir' ])) {
2016-04-25 14:36:53 +02:00
throw new \InvalidArgumentException ( 'No data directory set for local storage' );
}
2024-10-08 15:13:16 +02:00
$this -> datadir = str_replace ( '//' , '/' , $parameters [ 'datadir' ]);
2016-09-08 12:11:53 +02:00
// some crazy code uses a local storage on root...
if ( $this -> datadir === '/' ) {
$this -> realDataDir = $this -> datadir ;
} else {
2018-01-14 23:44:59 +01:00
$realPath = realpath ( $this -> datadir ) ? : $this -> datadir ;
$this -> realDataDir = rtrim ( $realPath , '/' ) . '/' ;
2016-09-08 12:11:53 +02:00
}
2023-07-07 04:54:20 +03:30
if ( ! str_ends_with ( $this -> datadir , '/' )) {
2015-07-03 15:41:29 +02:00
$this -> datadir .= '/' ;
2011-07-27 19:52:24 +02:00
}
2016-05-30 15:44:19 +02:00
$this -> dataDirLength = strlen ( $this -> realDataDir );
2024-11-21 08:38:50 +01:00
$this -> config = Server :: get ( IConfig :: class );
$this -> mimeTypeDetector = Server :: get ( IMimeTypeDetector :: class );
2022-06-04 00:24:35 +02:00
$this -> defUMask = $this -> config -> getSystemValue ( 'localstorage.umask' , 0022 );
2023-10-16 17:39:23 +02:00
$this -> caseInsensitive = $this -> config -> getSystemValueBool ( 'localstorage.case_insensitive' , false );
2022-03-31 10:38:25 +02:00
// support Write-Once-Read-Many file systems
2023-04-05 12:50:08 +02:00
$this -> unlinkOnTruncate = $this -> config -> getSystemValueBool ( 'localstorage.unlink_on_truncate' , false );
2023-08-03 23:09:17 +02:00
2024-10-08 15:13:16 +02:00
if ( isset ( $parameters [ 'isExternal' ]) && $parameters [ 'isExternal' ] && ! $this -> stat ( '' )) {
2023-08-03 23:09:17 +02:00
// data dir not accessible or available, can happen when using an external storage of type Local
// on an unmounted system mount point
throw new StorageNotAvailableException ( 'Local storage path does not exist "' . $this -> getSourcePath ( '' ) . '"' );
}
2015-07-03 15:41:29 +02:00
}
2013-04-26 17:30:55 +02:00
2015-07-03 15:41:29 +02:00
public function __destruct () {
}
2013-01-03 00:35:57 +01:00
2024-09-19 18:19:34 +02:00
public function getId () : string {
2015-07-03 15:41:29 +02:00
return 'local::' . $this -> datadir ;
}
2013-04-26 17:30:55 +02:00
2024-10-01 16:12:30 +02:00
public function mkdir ( string $path ) : bool {
2021-01-20 17:15:57 +01:00
$sourcePath = $this -> getSourcePath ( $path );
2022-06-04 00:24:35 +02:00
$oldMask = umask ( $this -> defUMask );
2021-01-20 17:15:57 +01:00
$result = @ mkdir ( $sourcePath , 0777 , true );
2021-01-22 15:44:24 +01:00
umask ( $oldMask );
2021-01-20 17:15:57 +01:00
return $result ;
2015-07-03 15:41:29 +02:00
}
2013-04-26 17:30:55 +02:00
2024-10-01 16:12:30 +02:00
public function rmdir ( string $path ) : bool {
2015-07-03 15:41:29 +02:00
if ( ! $this -> isDeletable ( $path )) {
return false ;
}
try {
$it = new \RecursiveIteratorIterator (
new \RecursiveDirectoryIterator ( $this -> getSourcePath ( $path )),
\RecursiveIteratorIterator :: CHILD_FIRST
);
/**
* RecursiveDirectoryIterator on an NFS path isn ' t iterable with foreach
* This bug is fixed in PHP 5.5 . 9 or before
* See #8376
*/
$it -> rewind ();
while ( $it -> valid ()) {
2014-04-28 19:32:25 +02:00
/**
2015-07-03 15:41:29 +02:00
* @ var \SplFileInfo $file
2014-04-28 19:32:25 +02:00
*/
2015-07-03 15:41:29 +02:00
$file = $it -> current ();
2024-09-15 15:24:22 +02:00
clearstatcache ( true , $file -> getRealPath ());
2020-03-26 09:30:18 +01:00
if ( in_array ( $file -> getBasename (), [ '.' , '..' ])) {
2014-04-28 10:20:24 +02:00
$it -> next ();
2015-07-03 15:41:29 +02:00
continue ;
2020-04-10 10:35:09 +02:00
} elseif ( $file -> isFile () || $file -> isLink ()) {
2015-07-03 15:41:29 +02:00
unlink ( $file -> getPathname ());
2023-05-24 08:22:50 +02:00
} elseif ( $file -> isDir ()) {
rmdir ( $file -> getPathname ());
2013-06-06 20:47:20 +02:00
}
2015-07-03 15:41:29 +02:00
$it -> next ();
2013-06-06 20:47:20 +02:00
}
2024-06-20 23:53:38 +02:00
unset ( $it ); // Release iterator and thereby its potential directory lock (e.g. in case of VirtualBox shared folders)
2020-07-03 22:42:27 +02:00
clearstatcache ( true , $this -> getSourcePath ( $path ));
2015-07-03 15:41:29 +02:00
return rmdir ( $this -> getSourcePath ( $path ));
} catch ( \UnexpectedValueException $e ) {
return false ;
2013-04-26 17:30:55 +02:00
}
2015-07-03 15:41:29 +02:00
}
2013-04-26 17:30:55 +02:00
2024-10-01 16:12:30 +02:00
public function opendir ( string $path ) {
2015-07-03 15:41:29 +02:00
return opendir ( $this -> getSourcePath ( $path ));
}
2013-01-04 23:03:26 +01:00
2024-10-01 16:12:30 +02:00
public function is_dir ( string $path ) : bool {
2023-10-16 17:39:23 +02:00
if ( $this -> caseInsensitive && ! $this -> file_exists ( $path )) {
return false ;
}
2023-07-07 04:54:20 +03:30
if ( str_ends_with ( $path , '/' )) {
2015-07-03 15:41:29 +02:00
$path = substr ( $path , 0 , - 1 );
2011-07-27 19:52:24 +02:00
}
2015-07-03 15:41:29 +02:00
return is_dir ( $this -> getSourcePath ( $path ));
}
2013-04-26 17:30:55 +02:00
2024-10-01 16:12:30 +02:00
public function is_file ( string $path ) : bool {
2023-10-16 17:39:23 +02:00
if ( $this -> caseInsensitive && ! $this -> file_exists ( $path )) {
return false ;
}
2015-07-03 15:41:29 +02:00
return is_file ( $this -> getSourcePath ( $path ));
}
2013-04-26 17:30:55 +02:00
2024-10-01 16:12:30 +02:00
public function stat ( string $path ) : array | false {
2015-07-03 15:41:29 +02:00
$fullPath = $this -> getSourcePath ( $path );
2020-07-03 22:44:31 +02:00
clearstatcache ( true , $fullPath );
2022-08-16 17:17:51 +02:00
if ( ! file_exists ( $fullPath )) {
return false ;
}
2020-10-07 13:50:29 +02:00
$statResult = @ stat ( $fullPath );
2023-01-12 16:05:03 +01:00
if ( PHP_INT_SIZE === 4 && $statResult && ! $this -> is_dir ( $path )) {
$filesize = $this -> filesize ( $path );
$statResult [ 'size' ] = $filesize ;
$statResult [ 7 ] = $filesize ;
}
2022-03-17 16:05:52 +01:00
if ( is_array ( $statResult )) {
$statResult [ 'full_path' ] = $fullPath ;
}
2015-07-03 15:41:29 +02:00
return $statResult ;
}
2013-04-26 17:30:55 +02:00
2024-10-01 16:12:30 +02:00
public function getMetaData ( string $path ) : ? array {
2022-04-20 13:01:16 +02:00
try {
$stat = $this -> stat ( $path );
} catch ( ForbiddenException $e ) {
return null ;
}
2020-02-14 22:07:09 +01:00
if ( ! $stat ) {
return null ;
}
$permissions = Constants :: PERMISSION_SHARE ;
$statPermissions = $stat [ 'mode' ];
2021-06-17 11:44:44 +01:00
$isDir = ( $statPermissions & 0x4000 ) === 0x4000 && ! ( $statPermissions & 0x8000 );
2020-02-14 22:07:09 +01:00
if ( $statPermissions & 0x0100 ) {
$permissions += Constants :: PERMISSION_READ ;
}
if ( $statPermissions & 0x0080 ) {
$permissions += Constants :: PERMISSION_UPDATE ;
if ( $isDir ) {
$permissions += Constants :: PERMISSION_CREATE ;
}
}
if ( ! ( $path === '' || $path === '/' )) { // deletable depends on the parents unix permissions
2022-03-17 16:05:52 +01:00
$parent = dirname ( $stat [ 'full_path' ]);
2020-02-14 22:07:09 +01:00
if ( is_writable ( $parent )) {
$permissions += Constants :: PERMISSION_DELETE ;
}
}
$data = [];
2022-03-17 16:05:52 +01:00
$data [ 'mimetype' ] = $isDir ? 'httpd/unix-directory' : $this -> mimeTypeDetector -> detectPath ( $path );
2020-02-14 22:07:09 +01:00
$data [ 'mtime' ] = $stat [ 'mtime' ];
if ( $data [ 'mtime' ] === false ) {
$data [ 'mtime' ] = time ();
}
if ( $isDir ) {
$data [ 'size' ] = - 1 ; //unknown
} else {
$data [ 'size' ] = $stat [ 'size' ];
}
$data [ 'etag' ] = $this -> calculateEtag ( $path , $stat );
$data [ 'storage_mtime' ] = $data [ 'mtime' ];
$data [ 'permissions' ] = $permissions ;
2020-03-27 17:47:20 +01:00
$data [ 'name' ] = basename ( $path );
2020-02-14 22:07:09 +01:00
return $data ;
}
2024-10-01 16:12:30 +02:00
public function filetype ( string $path ) : string | false {
2015-07-03 15:41:29 +02:00
$filetype = filetype ( $this -> getSourcePath ( $path ));
if ( $filetype == 'link' ) {
$filetype = filetype ( realpath ( $this -> getSourcePath ( $path )));
2012-02-14 09:59:54 +01:00
}
2015-07-03 15:41:29 +02:00
return $filetype ;
}
2012-08-29 08:38:33 +02:00
2024-10-01 16:12:30 +02:00
public function filesize ( string $path ) : int | float | false {
2021-10-04 17:21:37 +02:00
if ( ! $this -> is_file ( $path )) {
2015-07-03 15:41:29 +02:00
return 0 ;
2012-04-01 13:30:41 -04:00
}
2015-07-03 15:41:29 +02:00
$fullPath = $this -> getSourcePath ( $path );
2023-01-12 16:05:03 +01:00
if ( PHP_INT_SIZE === 4 ) {
$helper = new \OC\LargeFileHelper ;
2023-03-30 11:18:31 +02:00
return $helper -> getFileSize ( $fullPath );
2023-01-12 16:05:03 +01:00
}
2015-07-03 15:41:29 +02:00
return filesize ( $fullPath );
}
2011-11-01 22:35:13 +01:00
2024-10-01 16:12:30 +02:00
public function isReadable ( string $path ) : bool {
2015-07-03 15:41:29 +02:00
return is_readable ( $this -> getSourcePath ( $path ));
}
2013-04-26 17:30:55 +02:00
2024-10-01 16:12:30 +02:00
public function isUpdatable ( string $path ) : bool {
2015-07-03 15:41:29 +02:00
return is_writable ( $this -> getSourcePath ( $path ));
}
2013-04-26 17:30:55 +02:00
2024-10-01 16:12:30 +02:00
public function file_exists ( string $path ) : bool {
2023-10-16 17:39:23 +02:00
if ( $this -> caseInsensitive ) {
$fullPath = $this -> getSourcePath ( $path );
2024-03-14 16:26:52 +01:00
$parentPath = dirname ( $fullPath );
if ( ! is_dir ( $parentPath )) {
return false ;
}
$content = scandir ( $parentPath , SCANDIR_SORT_NONE );
2023-10-16 17:39:23 +02:00
return is_array ( $content ) && array_search ( basename ( $fullPath ), $content ) !== false ;
} else {
return file_exists ( $this -> getSourcePath ( $path ));
}
2015-07-03 15:41:29 +02:00
}
2013-04-26 17:30:55 +02:00
2024-10-01 16:12:30 +02:00
public function filemtime ( string $path ) : int | false {
2016-09-29 10:10:35 +01:00
$fullPath = $this -> getSourcePath ( $path );
2018-01-12 13:59:58 +01:00
clearstatcache ( true , $fullPath );
2016-09-29 10:10:35 +01:00
if ( ! $this -> file_exists ( $path )) {
return false ;
}
2023-01-12 16:05:03 +01:00
if ( PHP_INT_SIZE === 4 ) {
$helper = new \OC\LargeFileHelper ();
return $helper -> getFileMtime ( $fullPath );
}
2016-09-29 10:10:35 +01:00
return filemtime ( $fullPath );
2015-07-03 15:41:29 +02:00
}
2013-04-26 17:30:55 +02:00
2024-10-01 16:12:30 +02:00
public function touch ( string $path , ? int $mtime = null ) : bool {
2015-07-03 15:41:29 +02:00
// sets the modification time of the file to the given value.
// If mtime is nil the current time is set.
// note that the access time of the file always changes to the current time.
2024-11-21 08:38:50 +01:00
if ( $this -> file_exists ( $path ) && ! $this -> isUpdatable ( $path )) {
2015-07-03 15:41:29 +02:00
return false ;
2013-04-26 17:30:55 +02:00
}
2022-06-04 00:24:35 +02:00
$oldMask = umask ( $this -> defUMask );
2015-07-03 15:41:29 +02:00
if ( ! is_null ( $mtime )) {
2019-07-26 17:26:59 +02:00
$result = @ touch ( $this -> getSourcePath ( $path ), $mtime );
2015-07-03 15:41:29 +02:00
} else {
2019-07-26 17:26:59 +02:00
$result = @ touch ( $this -> getSourcePath ( $path ));
2013-04-26 17:30:55 +02:00
}
2021-01-22 15:44:24 +01:00
umask ( $oldMask );
2015-07-03 15:41:29 +02:00
if ( $result ) {
clearstatcache ( true , $this -> getSourcePath ( $path ));
2013-04-26 17:30:55 +02:00
}
2015-07-03 15:41:29 +02:00
return $result ;
}
2014-07-10 10:54:26 +02:00
2024-10-01 16:12:30 +02:00
public function file_get_contents ( string $path ) : string | false {
2017-01-12 16:07:41 +01:00
return file_get_contents ( $this -> getSourcePath ( $path ));
2015-07-03 15:41:29 +02:00
}
2014-07-10 10:54:26 +02:00
2024-10-01 16:12:30 +02:00
public function file_put_contents ( string $path , mixed $data ) : int | float | false {
2022-06-04 00:24:35 +02:00
$oldMask = umask ( $this -> defUMask );
2022-03-31 10:38:25 +02:00
if ( $this -> unlinkOnTruncate ) {
$this -> unlink ( $path );
}
2021-01-22 15:44:24 +01:00
$result = file_put_contents ( $this -> getSourcePath ( $path ), $data );
umask ( $oldMask );
return $result ;
2015-07-03 15:41:29 +02:00
}
2014-07-10 10:54:26 +02:00
2024-10-01 16:12:30 +02:00
public function unlink ( string $path ) : bool {
2015-07-03 15:41:29 +02:00
if ( $this -> is_dir ( $path )) {
return $this -> rmdir ( $path );
2020-04-10 10:35:09 +02:00
} elseif ( $this -> is_file ( $path )) {
2015-07-03 15:41:29 +02:00
return unlink ( $this -> getSourcePath ( $path ));
} else {
return false ;
}
}
2013-07-01 17:45:01 +02:00
2024-09-19 18:19:34 +02:00
private function checkTreeForForbiddenItems ( string $path ) : void {
2019-06-27 11:10:08 +02:00
$iterator = new \RecursiveIteratorIterator ( new \RecursiveDirectoryIterator ( $path ));
foreach ( $iterator as $file ) {
/** @var \SplFileInfo $file */
if ( Filesystem :: isFileBlacklisted ( $file -> getBasename ())) {
2020-06-19 14:57:58 +02:00
throw new ForbiddenException ( 'Invalid path: ' . $file -> getPathname (), false );
2019-06-27 11:10:08 +02:00
}
}
}
2024-10-01 16:12:30 +02:00
public function rename ( string $source , string $target ) : bool {
2022-10-18 12:49:34 +02:00
$srcParent = dirname ( $source );
$dstParent = dirname ( $target );
2015-03-31 13:47:06 +02:00
2015-07-03 15:41:29 +02:00
if ( ! $this -> isUpdatable ( $srcParent )) {
2024-11-21 08:38:50 +01:00
Server :: get ( LoggerInterface :: class ) -> error ( 'unable to rename, source directory is not writable : ' . $srcParent , [ 'app' => 'core' ]);
2015-07-03 15:41:29 +02:00
return false ;
2011-07-27 19:52:24 +02:00
}
2015-07-03 15:41:29 +02:00
if ( ! $this -> isUpdatable ( $dstParent )) {
2024-11-21 08:38:50 +01:00
Server :: get ( LoggerInterface :: class ) -> error ( 'unable to rename, destination directory is not writable : ' . $dstParent , [ 'app' => 'core' ]);
2015-07-03 15:41:29 +02:00
return false ;
2011-07-27 19:52:24 +02:00
}
2022-10-18 12:49:34 +02:00
if ( ! $this -> file_exists ( $source )) {
2024-11-21 08:38:50 +01:00
Server :: get ( LoggerInterface :: class ) -> error ( 'unable to rename, file does not exists : ' . $source , [ 'app' => 'core' ]);
2015-07-03 15:41:29 +02:00
return false ;
2011-07-27 19:52:24 +02:00
}
2013-04-26 17:30:55 +02:00
2024-11-15 19:50:16 +01:00
if ( $this -> file_exists ( $target )) {
if ( $this -> is_dir ( $target )) {
$this -> rmdir ( $target );
} elseif ( $this -> is_file ( $target )) {
$this -> unlink ( $target );
}
2013-04-26 17:30:55 +02:00
}
2013-01-04 23:03:26 +01:00
2022-10-18 12:49:34 +02:00
if ( $this -> is_dir ( $source )) {
$this -> checkTreeForForbiddenItems ( $this -> getSourcePath ( $source ));
2013-04-26 17:30:55 +02:00
}
2011-07-27 19:52:24 +02:00
2023-06-02 22:26:20 +02:00
if ( @ rename ( $this -> getSourcePath ( $source ), $this -> getSourcePath ( $target ))) {
2023-10-16 17:39:23 +02:00
if ( $this -> caseInsensitive ) {
if ( mb_strtolower ( $target ) === mb_strtolower ( $source ) && ! $this -> file_exists ( $target )) {
return false ;
}
}
2023-06-02 22:26:20 +02:00
return true ;
}
2023-07-31 21:41:25 +02:00
return $this -> copy ( $source , $target ) && $this -> unlink ( $source );
2015-07-03 15:41:29 +02:00
}
2011-07-27 19:52:24 +02:00
2024-10-01 16:12:30 +02:00
public function copy ( string $source , string $target ) : bool {
2022-10-18 12:49:34 +02:00
if ( $this -> is_dir ( $source )) {
return parent :: copy ( $source , $target );
2015-07-03 15:41:29 +02:00
} else {
2022-06-04 00:24:35 +02:00
$oldMask = umask ( $this -> defUMask );
2022-03-31 10:38:25 +02:00
if ( $this -> unlinkOnTruncate ) {
2022-10-18 12:49:34 +02:00
$this -> unlink ( $target );
2022-03-31 10:38:25 +02:00
}
2022-10-18 12:49:34 +02:00
$result = copy ( $this -> getSourcePath ( $source ), $this -> getSourcePath ( $target ));
2021-01-22 15:44:24 +01:00
umask ( $oldMask );
2023-10-16 17:39:23 +02:00
if ( $this -> caseInsensitive ) {
if ( mb_strtolower ( $target ) === mb_strtolower ( $source ) && ! $this -> file_exists ( $target )) {
return false ;
}
}
2021-01-22 15:44:24 +01:00
return $result ;
2013-04-26 17:30:55 +02:00
}
2015-07-03 15:41:29 +02:00
}
2011-07-27 19:52:24 +02:00
2024-10-01 16:12:30 +02:00
public function fopen ( string $path , string $mode ) {
2022-08-16 17:17:51 +02:00
$sourcePath = $this -> getSourcePath ( $path );
if ( ! file_exists ( $sourcePath ) && $mode === 'r' ) {
return false ;
}
2022-06-04 00:24:35 +02:00
$oldMask = umask ( $this -> defUMask );
2022-03-31 10:38:25 +02:00
if (( $mode === 'w' || $mode === 'w+' ) && $this -> unlinkOnTruncate ) {
2020-12-07 18:24:37 +01:00
$this -> unlink ( $path );
}
2022-08-16 17:17:51 +02:00
$result = @ fopen ( $sourcePath , $mode );
2021-01-22 15:44:24 +01:00
umask ( $oldMask );
return $result ;
2015-07-03 15:41:29 +02:00
}
2013-04-26 17:30:55 +02:00
2024-10-01 16:12:30 +02:00
public function hash ( string $type , string $path , bool $raw = false ) : string | false {
2024-09-16 11:32:29 +02:00
return hash_file ( $type , $this -> getSourcePath ( $path ), $raw );
2015-07-03 15:41:29 +02:00
}
2014-11-26 16:58:25 +01:00
2024-10-01 16:12:30 +02:00
public function free_space ( string $path ) : int | float | false {
2016-03-07 10:58:24 +01:00
$sourcePath = $this -> getSourcePath ( $path );
// using !is_dir because $sourcePath might be a part file or
// non-existing file, so we'd still want to use the parent dir
// in such cases
if ( ! is_dir ( $sourcePath )) {
// disk_free_space doesn't work on files
$sourcePath = dirname ( $sourcePath );
}
2023-03-07 17:09:24 +01:00
$space = ( function_exists ( 'disk_free_space' ) && is_dir ( $sourcePath )) ? disk_free_space ( $sourcePath ) : false ;
2015-07-03 15:41:29 +02:00
if ( $space === false || is_null ( $space )) {
return \OCP\Files\FileInfo :: SPACE_UNKNOWN ;
2011-07-27 19:52:24 +02:00
}
2023-05-11 12:46:16 +02:00
return Util :: numericToNumber ( $space );
2015-07-03 15:41:29 +02:00
}
2011-07-27 19:52:24 +02:00
2024-10-01 16:12:30 +02:00
public function search ( string $query ) : array {
2015-07-03 15:41:29 +02:00
return $this -> searchInDir ( $query );
}
2024-10-01 16:12:30 +02:00
public function getLocalFile ( string $path ) : string | false {
2015-07-03 15:41:29 +02:00
return $this -> getSourcePath ( $path );
}
2024-10-01 16:12:30 +02:00
protected function searchInDir ( string $query , string $dir = '' ) : array {
2020-03-26 09:30:18 +01:00
$files = [];
2015-07-03 15:41:29 +02:00
$physicalDir = $this -> getSourcePath ( $dir );
foreach ( scandir ( $physicalDir ) as $item ) {
2015-09-21 14:09:28 +02:00
if ( \OC\Files\Filesystem :: isIgnoredDir ( $item )) {
2015-07-03 15:41:29 +02:00
continue ;
2020-04-10 14:19:56 +02:00
}
2015-07-03 15:41:29 +02:00
$physicalItem = $physicalDir . '/' . $item ;
if ( strstr ( strtolower ( $item ), strtolower ( $query )) !== false ) {
$files [] = $dir . '/' . $item ;
}
if ( is_dir ( $physicalItem )) {
$files = array_merge ( $files , $this -> searchInDir ( $query , $dir . '/' . $item ));
}
2014-11-26 16:58:25 +01:00
}
2015-07-03 15:41:29 +02:00
return $files ;
}
2014-11-26 16:58:25 +01:00
2024-10-01 16:12:30 +02:00
public function hasUpdated ( string $path , int $time ) : bool {
2015-07-03 15:41:29 +02:00
if ( $this -> file_exists ( $path )) {
return $this -> filemtime ( $path ) > $time ;
} else {
2014-02-04 19:58:49 +01:00
return true ;
}
2015-07-03 15:41:29 +02:00
}
2014-10-24 16:07:45 +02:00
2015-07-03 15:41:29 +02:00
/**
* Get the source path ( on disk ) of a given path
*
2016-05-30 15:44:19 +02:00
* @ throws ForbiddenException
2015-07-03 15:41:29 +02:00
*/
2024-10-01 16:12:30 +02:00
public function getSourcePath ( string $path ) : string {
2019-06-27 11:10:08 +02:00
if ( Filesystem :: isFileBlacklisted ( $path )) {
2020-06-19 14:57:58 +02:00
throw new ForbiddenException ( 'Invalid path: ' . $path , false );
2019-06-27 11:10:08 +02:00
}
2015-07-03 15:41:29 +02:00
$fullPath = $this -> datadir . $path ;
2018-01-14 23:44:59 +01:00
$currentPath = $path ;
2023-04-05 12:50:08 +02:00
$allowSymlinks = $this -> config -> getSystemValueBool ( 'localstorage.allowsymlinks' , false );
2020-12-08 15:12:04 +01:00
if ( $allowSymlinks || $currentPath === '' ) {
2016-05-30 15:44:19 +02:00
return $fullPath ;
}
$pathToResolve = $fullPath ;
$realPath = realpath ( $pathToResolve );
while ( $realPath === false ) { // for non existing files check the parent directory
2018-01-14 23:44:59 +01:00
$currentPath = dirname ( $currentPath );
2024-04-22 18:11:32 +02:00
/** @psalm-suppress TypeDoesNotContainType Let's be extra cautious and still check for empty string */
2018-01-14 23:44:59 +01:00
if ( $currentPath === '' || $currentPath === '.' ) {
return $fullPath ;
}
$realPath = realpath ( $this -> datadir . $currentPath );
2016-05-30 15:44:19 +02:00
}
if ( $realPath ) {
$realPath = $realPath . '/' ;
}
if ( substr ( $realPath , 0 , $this -> dataDirLength ) === $this -> realDataDir ) {
return $fullPath ;
}
2017-03-29 00:34:33 +02:00
2024-11-21 08:38:50 +01:00
Server :: get ( LoggerInterface :: class ) -> error ( " Following symlinks is not allowed (' $fullPath ' -> ' $realPath ' not inside ' { $this -> realDataDir } ') " , [ 'app' => 'core' ]);
2017-03-29 00:34:33 +02:00
throw new ForbiddenException ( 'Following symlinks is not allowed' , false );
2015-07-03 15:41:29 +02:00
}
2024-09-19 18:19:34 +02:00
public function isLocal () : bool {
2015-07-03 15:41:29 +02:00
return true ;
}
2024-10-01 16:12:30 +02:00
public function getETag ( string $path ) : string | false {
2020-02-14 22:07:09 +01:00
return $this -> calculateEtag ( $path , $this -> stat ( $path ));
}
2019-11-23 14:14:41 +01:00
2024-09-09 10:11:07 +02:00
private function calculateEtag ( string $path , array $stat ) : string | false {
2021-06-17 11:44:44 +01:00
if ( $stat [ 'mode' ] & 0x4000 && ! ( $stat [ 'mode' ] & 0x8000 )) { // is_dir & not socket
2020-02-14 22:07:09 +01:00
return parent :: getETag ( $path );
} else {
2019-11-23 14:14:41 +01:00
if ( $stat === false ) {
return md5 ( '' );
}
$toHash = '' ;
if ( isset ( $stat [ 'mtime' ])) {
$toHash .= $stat [ 'mtime' ];
}
if ( isset ( $stat [ 'ino' ])) {
$toHash .= $stat [ 'ino' ];
}
if ( isset ( $stat [ 'dev' ])) {
$toHash .= $stat [ 'dev' ];
}
if ( isset ( $stat [ 'size' ])) {
$toHash .= $stat [ 'size' ];
}
return md5 ( $toHash );
2014-10-24 16:07:45 +02:00
}
2015-07-03 15:41:29 +02:00
}
2015-01-16 13:33:17 +01:00
2024-09-19 18:19:34 +02:00
private function canDoCrossStorageMove ( IStorage $sourceStorage ) : bool {
2024-10-01 16:12:30 +02:00
/** @psalm-suppress UndefinedClass,InvalidArgument */
2022-12-27 15:32:59 +01:00
return $sourceStorage -> instanceOfStorage ( Local :: class )
// Don't treat ACLStorageWrapper like local storage where copy can be done directly.
// Instead, use the slower recursive copying in php from Common::copyFromStorage with
// more permissions checks.
&& ! $sourceStorage -> instanceOfStorage ( 'OCA\GroupFolders\ACL\ACLStorageWrapper' )
2024-04-17 16:29:07 +02:00
// Same for access control
&& ! $sourceStorage -> instanceOfStorage ( \OCA\FilesAccessControl\StorageWrapper :: class )
2022-12-27 15:32:59 +01:00
// when moving encrypted files we have to handle keys and the target might not be encrypted
&& ! $sourceStorage -> instanceOfStorage ( Encryption :: class );
}
2024-10-01 16:12:30 +02:00
public function copyFromStorage ( IStorage $sourceStorage , string $sourceInternalPath , string $targetInternalPath , bool $preserveMtime = false ) : bool {
2022-12-27 15:32:59 +01:00
if ( $this -> canDoCrossStorageMove ( $sourceStorage )) {
2024-09-09 13:07:14 +02:00
// resolve any jailed paths
while ( $sourceStorage -> instanceOfStorage ( Jail :: class )) {
2017-08-09 11:56:31 +02:00
/**
* @ var \OC\Files\Storage\Wrapper\Jail $sourceStorage
*/
$sourceInternalPath = $sourceStorage -> getUnjailedPath ( $sourceInternalPath );
2024-09-09 13:07:14 +02:00
$sourceStorage = $sourceStorage -> getUnjailedStorage ();
2017-08-09 11:56:31 +02:00
}
2015-07-03 15:41:29 +02:00
/**
* @ var \OC\Files\Storage\Local $sourceStorage
*/
$rootStorage = new Local ([ 'datadir' => '/' ]);
return $rootStorage -> copy ( $sourceStorage -> getSourcePath ( $sourceInternalPath ), $this -> getSourcePath ( $targetInternalPath ));
} else {
return parent :: copyFromStorage ( $sourceStorage , $sourceInternalPath , $targetInternalPath );
2015-01-16 13:33:17 +01:00
}
2015-07-03 15:41:29 +02:00
}
2015-01-16 13:33:17 +01:00
2024-10-01 16:12:30 +02:00
public function moveFromStorage ( IStorage $sourceStorage , string $sourceInternalPath , string $targetInternalPath ) : bool {
2022-12-27 15:32:59 +01:00
if ( $this -> canDoCrossStorageMove ( $sourceStorage )) {
2024-09-09 13:07:14 +02:00
// resolve any jailed paths
while ( $sourceStorage -> instanceOfStorage ( Jail :: class )) {
2017-04-12 14:56:51 +02:00
/**
* @ var \OC\Files\Storage\Wrapper\Jail $sourceStorage
*/
$sourceInternalPath = $sourceStorage -> getUnjailedPath ( $sourceInternalPath );
2024-09-09 13:07:14 +02:00
$sourceStorage = $sourceStorage -> getUnjailedStorage ();
2017-04-12 14:56:51 +02:00
}
2015-07-03 15:41:29 +02:00
/**
* @ var \OC\Files\Storage\Local $sourceStorage
*/
$rootStorage = new Local ([ 'datadir' => '/' ]);
return $rootStorage -> rename ( $sourceStorage -> getSourcePath ( $sourceInternalPath ), $this -> getSourcePath ( $targetInternalPath ));
} else {
return parent :: moveFromStorage ( $sourceStorage , $sourceInternalPath , $targetInternalPath );
2015-01-16 13:33:17 +01:00
}
2012-06-15 16:43:24 +02:00
}
2018-10-26 19:15:23 +02:00
2024-03-28 16:13:19 +01:00
public function writeStream ( string $path , $stream , ? int $size = null ) : int {
2022-12-19 14:13:11 +01:00
/** @var int|false $result We consider here that returned size will never be a float because we write less than 4GB */
2020-12-07 17:57:07 +01:00
$result = $this -> file_put_contents ( $path , $stream );
2021-09-20 19:20:51 +02:00
if ( is_resource ( $stream )) {
fclose ( $stream );
}
2020-07-01 15:37:47 +02:00
if ( $result === false ) {
2021-09-20 19:20:51 +02:00
throw new GenericFileException ( " Failed write stream to $path " );
2020-07-01 15:37:47 +02:00
} else {
return $result ;
}
2018-10-26 19:15:23 +02:00
}
2011-07-27 19:52:24 +02:00
}