2012-03-22 14:56:19 -04:00
< ? php
/**
2021-10-07 11:36:17 -04:00
* @ copyright Copyright ( c ) 2021 Robin Appelman < robin @ icewind . nl >
*
2016-07-21 12:13:36 -04:00
* @ author Robin Appelman < robin @ icewind . nl >
2015-03-26 06:44:34 -04:00
*
* @ license AGPL - 3.0
*
* 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 .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License , version 3 ,
2021-05-27 16:18:59 -04:00
* along with this program . If not , see < http :// www . gnu . org / licenses />
2015-03-26 06:44:34 -04:00
*
2012-03-22 14:56:19 -04:00
*/
2016-04-13 18:18:07 -04:00
namespace OCA\Files_External\Lib\Storage ;
2012-09-07 12:30:48 -04:00
2017-01-03 11:26:44 -05:00
use Icewind\Streams\CallbackWrapper ;
2021-05-27 16:18:59 -04:00
use Icewind\Streams\CountWrapper ;
2020-07-10 08:18:40 -04:00
use Icewind\Streams\IteratorDirectory ;
2021-05-27 16:18:59 -04:00
use OC\Files\Storage\Common ;
use OC\Files\Storage\PolyFill\CopyDirectory ;
use OCP\Constants ;
use OCP\Files\FileInfo ;
use OCP\Files\StorageNotAvailableException ;
2023-07-20 02:42:15 -04:00
use Psr\Log\LoggerInterface ;
2016-03-21 10:10:24 -04:00
2021-05-27 16:18:59 -04:00
class FTP extends Common {
use CopyDirectory ;
private $root ;
2012-03-22 14:56:19 -04:00
private $host ;
2021-05-27 16:18:59 -04:00
private $password ;
private $username ;
2012-03-22 14:56:19 -04:00
private $secure ;
2021-05-27 16:18:59 -04:00
private $port ;
private $utf8Mode ;
2021-05-31 09:24:29 -04:00
/** @var FtpConnection|null */
2021-05-27 16:18:59 -04:00
private $connection ;
2012-03-22 14:56:19 -04:00
2012-09-07 09:22:01 -04:00
public function __construct ( $params ) {
2012-12-24 13:45:52 -05:00
if ( isset ( $params [ 'host' ]) && isset ( $params [ 'user' ]) && isset ( $params [ 'password' ])) {
2020-10-05 09:12:57 -04:00
$this -> host = $params [ 'host' ];
2021-05-27 16:18:59 -04:00
$this -> username = $params [ 'user' ];
2020-10-05 09:12:57 -04:00
$this -> password = $params [ 'password' ];
2012-12-24 13:45:52 -05:00
if ( isset ( $params [ 'secure' ])) {
2021-05-27 16:18:59 -04:00
if ( is_string ( $params [ 'secure' ])) {
$this -> secure = ( $params [ 'secure' ] === 'true' );
} else {
$this -> secure = ( bool ) $params [ 'secure' ];
}
2012-11-30 10:27:11 -05:00
} else {
2012-12-24 13:45:52 -05:00
$this -> secure = false ;
}
2021-05-27 16:18:59 -04:00
$this -> root = isset ( $params [ 'root' ]) ? '/' . ltrim ( $params [ 'root' ]) : '/' ;
$this -> port = $params [ 'port' ] ? ? 21 ;
$this -> utf8Mode = isset ( $params [ 'utf8' ]) && $params [ 'utf8' ];
} else {
throw new \Exception ( 'Creating ' . self :: class . ' storage failed, required parameters not set' );
}
}
2021-05-31 09:24:29 -04:00
public function __destruct () {
$this -> connection = null ;
}
2021-05-27 16:18:59 -04:00
protected function getConnection () : FtpConnection {
if ( ! $this -> connection ) {
try {
$this -> connection = new FtpConnection (
$this -> secure ,
$this -> host ,
$this -> port ,
$this -> username ,
$this -> password
);
} catch ( \Exception $e ) {
throw new StorageNotAvailableException ( " Failed to create ftp connection " , 0 , $e );
2012-12-24 13:45:52 -05:00
}
2021-05-27 16:18:59 -04:00
if ( $this -> utf8Mode ) {
if ( ! $this -> connection -> setUtf8Mode ()) {
throw new StorageNotAvailableException ( " Could not set UTF-8 mode " );
}
2013-11-28 05:45:26 -05:00
}
2012-07-27 12:32:03 -04:00
}
2021-05-27 16:18:59 -04:00
return $this -> connection ;
2012-03-22 14:56:19 -04:00
}
2020-04-09 07:53:40 -04:00
public function getId () {
2021-05-27 16:18:59 -04:00
return 'ftp::' . $this -> username . '@' . $this -> host . '/' . $this -> root ;
2012-10-11 17:06:57 -04:00
}
2021-05-27 16:18:59 -04:00
protected function buildPath ( $path ) {
return rtrim ( $this -> root . '/' . $path , '/' );
}
public static function checkDependencies () {
if ( function_exists ( 'ftp_login' )) {
2021-10-07 12:47:13 -04:00
return true ;
2021-05-27 16:18:59 -04:00
} else {
return [ 'ftp' ];
}
}
public function filemtime ( $path ) {
$result = $this -> getConnection () -> mdtm ( $this -> buildPath ( $path ));
if ( $result === - 1 ) {
if ( $this -> is_dir ( $path )) {
$list = $this -> getConnection () -> mlsd ( $this -> buildPath ( $path ));
if ( ! $list ) {
2023-07-20 02:42:15 -04:00
\OC :: $server -> get ( LoggerInterface :: class ) -> warning ( " Unable to get last modified date for ftp folder ( $path ), failed to list folder contents " );
2021-05-27 16:18:59 -04:00
return time ();
}
$currentDir = current ( array_filter ( $list , function ( $item ) {
return $item [ 'type' ] === 'cdir' ;
}));
if ( $currentDir ) {
2023-02-28 11:15:14 -05:00
[ $modify ] = explode ( '.' , $currentDir [ 'modify' ] ? ? '' , 2 );
$time = \DateTime :: createFromFormat ( 'YmdHis' , $modify );
2021-05-27 16:18:59 -04:00
if ( $time === false ) {
throw new \Exception ( " Invalid date format for directory: $currentDir " );
}
return $time -> getTimestamp ();
} else {
2023-07-20 02:42:15 -04:00
\OC :: $server -> get ( LoggerInterface :: class ) -> warning ( " Unable to get last modified date for ftp folder ( $path ), folder contents doesn't include current folder " );
2021-05-27 16:18:59 -04:00
return time ();
}
} else {
return false ;
}
} else {
return $result ;
}
}
2023-02-06 16:05:34 -05:00
public function filesize ( $path ) : false | int | float {
2021-05-27 16:18:59 -04:00
$result = $this -> getConnection () -> size ( $this -> buildPath ( $path ));
if ( $result === - 1 ) {
return false ;
} else {
return $result ;
}
}
public function rmdir ( $path ) {
if ( $this -> is_dir ( $path )) {
$result = $this -> getConnection () -> rmdir ( $this -> buildPath ( $path ));
// recursive rmdir support depends on the ftp server
if ( $result ) {
return $result ;
} else {
return $this -> recursiveRmDir ( $path );
}
} elseif ( $this -> is_file ( $path )) {
return $this -> unlink ( $path );
} else {
return false ;
2012-03-22 14:56:19 -04:00
}
}
2013-11-29 06:58:57 -05:00
/**
2014-05-15 08:19:32 -04:00
* @ param string $path
2021-05-27 16:18:59 -04:00
* @ return bool
2013-11-29 06:58:57 -05:00
*/
2021-05-27 16:18:59 -04:00
private function recursiveRmDir ( $path ) : bool {
$contents = $this -> getDirectoryContent ( $path );
$result = true ;
foreach ( $contents as $content ) {
if ( $content [ 'mimetype' ] === FileInfo :: MIMETYPE_FOLDER ) {
$result = $result && $this -> recursiveRmDir ( $path . '/' . $content [ 'name' ]);
} else {
$result = $result && $this -> getConnection () -> delete ( $this -> buildPath ( $path . '/' . $content [ 'name' ]));
}
}
$result = $result && $this -> getConnection () -> rmdir ( $this -> buildPath ( $path ));
return $result ;
}
public function test () {
try {
return $this -> getConnection () -> systype () !== false ;
} catch ( \Exception $e ) {
return false ;
}
}
public function stat ( $path ) {
if ( ! $this -> file_exists ( $path )) {
return false ;
}
return [
'mtime' => $this -> filemtime ( $path ),
'size' => $this -> filesize ( $path ),
];
}
public function file_exists ( $path ) {
if ( $path === '' || $path === '.' || $path === '/' ) {
return true ;
}
return $this -> filetype ( $path ) !== false ;
}
2013-11-29 06:58:57 -05:00
public function unlink ( $path ) {
2021-05-27 16:18:59 -04:00
switch ( $this -> filetype ( $path )) {
case 'dir' :
return $this -> rmdir ( $path );
case 'file' :
return $this -> getConnection () -> delete ( $this -> buildPath ( $path ));
default :
return false ;
}
}
public function opendir ( $path ) {
$files = $this -> getConnection () -> nlist ( $this -> buildPath ( $path ));
return IteratorDirectory :: wrap ( $files );
}
public function mkdir ( $path ) {
if ( $this -> is_dir ( $path )) {
return false ;
}
return $this -> getConnection () -> mkdir ( $this -> buildPath ( $path )) !== false ;
}
public function is_dir ( $path ) {
if ( $path === " " ) {
return true ;
}
if ( $this -> getConnection () -> chdir ( $this -> buildPath ( $path )) === true ) {
$this -> getConnection () -> chdir ( '/' );
return true ;
} else {
return false ;
}
}
public function is_file ( $path ) {
return $this -> filesize ( $path ) !== false ;
}
public function filetype ( $path ) {
2013-11-29 06:58:57 -05:00
if ( $this -> is_dir ( $path )) {
2021-05-27 16:18:59 -04:00
return 'dir' ;
} elseif ( $this -> is_file ( $path )) {
return 'file' ;
2020-04-10 08:19:56 -04:00
} else {
2021-05-27 16:18:59 -04:00
return false ;
2013-11-29 06:58:57 -05:00
}
}
2021-05-27 16:18:59 -04:00
public function fopen ( $path , $mode ) {
$useExisting = true ;
2020-04-10 08:19:56 -04:00
switch ( $mode ) {
2012-03-22 14:56:19 -04:00
case 'r' :
case 'rb' :
2021-05-27 16:18:59 -04:00
return $this -> readStream ( $path );
2012-03-22 14:56:19 -04:00
case 'w' :
2021-05-27 16:18:59 -04:00
case 'w+' :
2012-03-22 14:56:19 -04:00
case 'wb' :
2021-05-27 16:18:59 -04:00
case 'wb+' :
$useExisting = false ;
2022-11-14 10:23:50 -05:00
// no break
2012-03-22 14:56:19 -04:00
case 'a' :
case 'ab' :
case 'r+' :
case 'a+' :
case 'x' :
case 'x+' :
case 'c' :
case 'c+' :
//emulate these
2021-05-27 16:18:59 -04:00
if ( $useExisting and $this -> file_exists ( $path )) {
if ( ! $this -> isUpdatable ( $path )) {
return false ;
}
$tmpFile = $this -> getCachedFile ( $path );
2012-11-30 10:27:11 -05:00
} else {
2021-05-27 16:18:59 -04:00
if ( ! $this -> isCreatable ( dirname ( $path ))) {
return false ;
}
$tmpFile = \OC :: $server -> getTempManager () -> getTemporaryFile ();
2012-03-22 14:56:19 -04:00
}
2021-05-27 16:18:59 -04:00
$source = fopen ( $tmpFile , $mode );
return CallbackWrapper :: wrap ( $source , null , null , function () use ( $tmpFile , $path ) {
$this -> writeStream ( $path , fopen ( $tmpFile , 'r' ));
unlink ( $tmpFile );
2017-01-03 11:26:44 -05:00
});
2012-03-22 14:56:19 -04:00
}
2012-10-11 17:17:59 -04:00
return false ;
2012-03-22 14:56:19 -04:00
}
2021-05-27 16:18:59 -04:00
public function writeStream ( string $path , $stream , int $size = null ) : int {
if ( $size === null ) {
$stream = CountWrapper :: wrap ( $stream , function ( $writtenSize ) use ( & $size ) {
$size = $writtenSize ;
});
2020-07-10 08:18:40 -04:00
}
2021-05-27 16:18:59 -04:00
$this -> getConnection () -> fput ( $this -> buildPath ( $path ), $stream );
fclose ( $stream );
return $size ;
2020-07-10 08:18:40 -04:00
}
2021-05-27 16:18:59 -04:00
public function readStream ( string $path ) {
$stream = fopen ( 'php://temp' , 'w+' );
$result = $this -> getConnection () -> fget ( $stream , $this -> buildPath ( $path ));
rewind ( $stream );
2020-07-10 08:18:40 -04:00
2021-05-27 16:18:59 -04:00
if ( ! $result ) {
fclose ( $stream );
return false ;
}
return $stream ;
2012-03-22 14:56:19 -04:00
}
2013-08-02 09:44:56 -04:00
2021-05-27 16:18:59 -04:00
public function touch ( $path , $mtime = null ) {
if ( $this -> file_exists ( $path )) {
return false ;
2013-08-02 09:44:56 -04:00
} else {
2021-05-27 16:18:59 -04:00
$this -> file_put_contents ( $path , '' );
return true ;
}
}
2022-10-18 06:49:34 -04:00
public function rename ( $source , $target ) {
$this -> unlink ( $target );
return $this -> getConnection () -> rename ( $this -> buildPath ( $source ), $this -> buildPath ( $target ));
2021-05-27 16:18:59 -04:00
}
public function getDirectoryContent ( $directory ) : \Traversable {
$files = $this -> getConnection () -> mlsd ( $this -> buildPath ( $directory ));
$mimeTypeDetector = \OC :: $server -> getMimeTypeDetector ();
foreach ( $files as $file ) {
$name = $file [ 'name' ];
if ( $file [ 'type' ] === 'cdir' || $file [ 'type' ] === 'pdir' ) {
continue ;
}
$permissions = Constants :: PERMISSION_ALL - Constants :: PERMISSION_CREATE ;
$isDir = $file [ 'type' ] === 'dir' ;
if ( $isDir ) {
$permissions += Constants :: PERMISSION_CREATE ;
}
$data = [];
$data [ 'mimetype' ] = $isDir ? FileInfo :: MIMETYPE_FOLDER : $mimeTypeDetector -> detectPath ( $name );
2023-02-28 11:15:14 -05:00
// strip fractional seconds
[ $modify ] = explode ( '.' , $file [ 'modify' ], 2 );
$mtime = \DateTime :: createFromFormat ( 'YmdGis' , $modify );
$data [ 'mtime' ] = $mtime === false ? time () : $mtime -> getTimestamp ();
2021-05-27 16:18:59 -04:00
if ( $isDir ) {
$data [ 'size' ] = - 1 ; //unknown
} elseif ( isset ( $file [ 'size' ])) {
$data [ 'size' ] = $file [ 'size' ];
} else {
$data [ 'size' ] = $this -> filesize ( $directory . '/' . $name );
}
$data [ 'etag' ] = uniqid ();
$data [ 'storage_mtime' ] = $data [ 'mtime' ];
$data [ 'permissions' ] = $permissions ;
$data [ 'name' ] = $name ;
yield $data ;
2013-08-02 09:44:56 -04:00
}
}
2012-03-22 14:56:19 -04:00
}