diff options
author | Robin Appelman <icewind@owncloud.com> | 2014-08-26 18:46:07 +0200 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2015-02-16 13:52:11 +0100 |
commit | d2255a1d304aa5e537e8aa097add2a51aa0e5d89 (patch) | |
tree | 79801d54149e6c8037e717a1d5d6ac43327eb6db /apps/files_external/3rdparty/icewind/smb/src/Share.php | |
parent | 78febb2ee594bac5d483f7c8534ed5eb33c2c528 (diff) | |
download | nextcloud-server-d2255a1d304aa5e537e8aa097add2a51aa0e5d89.tar.gz nextcloud-server-d2255a1d304aa5e537e8aa097add2a51aa0e5d89.zip |
New SMB storage backend
Diffstat (limited to 'apps/files_external/3rdparty/icewind/smb/src/Share.php')
-rw-r--r-- | apps/files_external/3rdparty/icewind/smb/src/Share.php | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/apps/files_external/3rdparty/icewind/smb/src/Share.php b/apps/files_external/3rdparty/icewind/smb/src/Share.php new file mode 100644 index 00000000000..5c48b1702fa --- /dev/null +++ b/apps/files_external/3rdparty/icewind/smb/src/Share.php @@ -0,0 +1,394 @@ +<?php +/** + * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Licensed under the MIT license: + * http://opensource.org/licenses/MIT + */ + +namespace Icewind\SMB; + +use Icewind\SMB\Exception\AccessDeniedException; +use Icewind\SMB\Exception\AlreadyExistsException; +use Icewind\SMB\Exception\ConnectionException; +use Icewind\SMB\Exception\Exception; +use Icewind\SMB\Exception\FileInUseException; +use Icewind\SMB\Exception\InvalidTypeException; +use Icewind\SMB\Exception\NotEmptyException; +use Icewind\SMB\Exception\NotFoundException; +use Icewind\Streams\CallbackWrapper; + +class Share implements IShare { + /** + * @var Server $server + */ + private $server; + + /** + * @var string $name + */ + private $name; + + /** + * @var Connection $connection + */ + public $connection; + + /** + * @var \Icewind\SMB\Parser + */ + protected $parser; + + private $serverTimezone; + + /** + * @param Server $server + * @param string $name + */ + public function __construct($server, $name) { + $this->server = $server; + $this->name = $name; + $this->parser = new Parser($this->server->getTimeZone()); + } + + /** + * @throws \Icewind\SMB\Exception\ConnectionException + * @throws \Icewind\SMB\Exception\AuthenticationException + * @throws \Icewind\SMB\Exception\InvalidHostException + */ + protected function connect() { + if ($this->connection and $this->connection->isValid()) { + return; + } + $command = sprintf('%s --authentication-file=/proc/self/fd/3 //%s/%s', + Server::CLIENT, + $this->server->getHost(), + $this->name + ); + $this->connection = new Connection($command); + $this->connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); + if (!$this->connection->isValid()) { + throw new ConnectionException(); + } + } + + protected function reconnect() { + $this->connection->reconnect(); + $this->connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); + if (!$this->connection->isValid()) { + throw new ConnectionException(); + } + } + + /** + * Get the name of the share + * + * @return string + */ + public function getName() { + return $this->name; + } + + protected function simpleCommand($command, $path) { + $path = $this->escapePath($path); + $cmd = $command . ' ' . $path; + $output = $this->execute($cmd); + return $this->parseOutput($output, $path); + } + + /** + * List the content of a remote folder + * + * @param $path + * @return \Icewind\SMB\IFileInfo[] + * + * @throws \Icewind\SMB\Exception\NotFoundException + * @throws \Icewind\SMB\Exception\InvalidTypeException + */ + public function dir($path) { + $escapedPath = $this->escapePath($path); + $output = $this->execute('cd ' . $escapedPath); + //check output for errors + $this->parseOutput($output, $path); + $output = $this->execute('dir'); + $this->execute('cd /'); + + return $this->parser->parseDir($output, $path); + } + + /** + * @param string $path + * @return \Icewind\SMB\IFileInfo[] + */ + public function stat($path) { + $escapedPath = $this->escapePath($path); + $output = $this->execute('allinfo ' . $escapedPath); + if (count($output) < 3) { + $this->parseOutput($output, $path); + } + $stat = $this->parser->parseStat($output); + return new FileInfo($path, basename($path), $stat['size'], $stat['mtime'], $stat['mode']); + } + + /** + * Create a folder on the share + * + * @param string $path + * @return bool + * + * @throws \Icewind\SMB\Exception\NotFoundException + * @throws \Icewind\SMB\Exception\AlreadyExistsException + */ + public function mkdir($path) { + return $this->simpleCommand('mkdir', $path); + } + + /** + * Remove a folder on the share + * + * @param string $path + * @return bool + * + * @throws \Icewind\SMB\Exception\NotFoundException + * @throws \Icewind\SMB\Exception\InvalidTypeException + */ + public function rmdir($path) { + return $this->simpleCommand('rmdir', $path); + } + + /** + * Delete a file on the share + * + * @param string $path + * @param bool $secondTry + * @return bool + * @throws InvalidTypeException + * @throws NotFoundException + * @throws \Exception + */ + public function del($path, $secondTry = false) { + //del return a file not found error when trying to delete a folder + //we catch it so we can check if $path doesn't exist or is of invalid type + try { + return $this->simpleCommand('del', $path); + } catch (NotFoundException $e) { + //no need to do anything with the result, we just check if this throws the not found error + try { + $this->simpleCommand('ls', $path); + } catch (NotFoundException $e2) { + throw $e; + } catch (\Exception $e2) { + throw new InvalidTypeException($path); + } + throw $e; + } catch (FileInUseException $e) { + if ($secondTry) { + throw $e; + } + $this->reconnect(); + return $this->del($path, true); + } + } + + /** + * Rename a remote file + * + * @param string $from + * @param string $to + * @return bool + * + * @throws \Icewind\SMB\Exception\NotFoundException + * @throws \Icewind\SMB\Exception\AlreadyExistsException + */ + public function rename($from, $to) { + $path1 = $this->escapePath($from); + $path2 = $this->escapePath($to); + $cmd = 'rename ' . $path1 . ' ' . $path2; + $output = $this->execute($cmd); + return $this->parseOutput($output, $to); + } + + /** + * Upload a local file + * + * @param string $source local file + * @param string $target remove file + * @return bool + * + * @throws \Icewind\SMB\Exception\NotFoundException + * @throws \Icewind\SMB\Exception\InvalidTypeException + */ + public function put($source, $target) { + $path1 = $this->escapeLocalPath($source); //first path is local, needs different escaping + $path2 = $this->escapePath($target); + $output = $this->execute('put ' . $path1 . ' ' . $path2); + return $this->parseOutput($output, $target); + } + + /** + * Download a remote file + * + * @param string $source remove file + * @param string $target local file + * @return bool + * + * @throws \Icewind\SMB\Exception\NotFoundException + * @throws \Icewind\SMB\Exception\InvalidTypeException + */ + public function get($source, $target) { + $path1 = $this->escapePath($source); + $path2 = $this->escapeLocalPath($target); //second path is local, needs different escaping + $output = $this->execute('get ' . $path1 . ' ' . $path2); + return $this->parseOutput($output, $source); + } + + /** + * Open a readable stream to a remote file + * + * @param string $source + * @return resource a read only stream with the contents of the remote file + * + * @throws \Icewind\SMB\Exception\NotFoundException + * @throws \Icewind\SMB\Exception\InvalidTypeException + */ + public function read($source) { + $source = $this->escapePath($source); + // close the single quote, open a double quote where we put the single quote... + $source = str_replace('\'', '\'"\'"\'', $source); + // since returned stream is closed by the caller we need to create a new instance + // since we can't re-use the same file descriptor over multiple calls + $command = sprintf('%s --authentication-file=/proc/self/fd/3 //%s/%s -c \'get %s /proc/self/fd/5\'', + Server::CLIENT, + $this->server->getHost(), + $this->name, + $source + ); + $connection = new Connection($command); + $connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); + $fh = $connection->getFileOutputStream(); + stream_context_set_option($fh, 'file', 'connection', $connection); + return $fh; + } + + /** + * Open a writable stream to a remote file + * + * @param string $target + * @return resource a write only stream to upload a remote file + * + * @throws \Icewind\SMB\Exception\NotFoundException + * @throws \Icewind\SMB\Exception\InvalidTypeException + */ + public function write($target) { + $target = $this->escapePath($target); + // close the single quote, open a double quote where we put the single quote... + $target = str_replace('\'', '\'"\'"\'', $target); + // since returned stream is closed by the caller we need to create a new instance + // since we can't re-use the same file descriptor over multiple calls + $command = sprintf('%s --authentication-file=/proc/self/fd/3 //%s/%s -c \'put /proc/self/fd/4 %s\'', + Server::CLIENT, + $this->server->getHost(), + $this->name, + $target + ); + $connection = new RawConnection($command); + $connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); + $fh = $connection->getFileInputStream(); + + // use a close callback to ensure the upload is finished before continuing + // this also serves as a way to keep the connection in scope + return CallbackWrapper::wrap($fh, null, null, function () use ($connection) { + $connection->close(false); // dont terminate, give the upload some time + }); + } + + /** + * @param string $path + * @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL + * @return mixed + */ + public function setMode($path, $mode) { + $modeString = ''; + $modeMap = array( + FileInfo::MODE_READONLY => 'r', + FileInfo::MODE_HIDDEN => 'h', + FileInfo::MODE_ARCHIVE => 'a', + FileInfo::MODE_SYSTEM => 's' + ); + foreach ($modeMap as $modeByte => $string) { + if ($mode & $modeByte) { + $modeString .= $string; + } + } + $path = $this->escapePath($path); + + // first reset the mode to normal + $cmd = 'setmode ' . $path . ' -rsha'; + $output = $this->execute($cmd); + $this->parseOutput($output, $path); + + // then set the modes we want + $cmd = 'setmode ' . $path . ' ' . $modeString; + $output = $this->execute($cmd); + return $this->parseOutput($output, $path); + } + + /** + * @param string $command + * @return array + */ + protected function execute($command) { + $this->connect(); + $this->connection->write($command . PHP_EOL); + $output = $this->connection->read(); + return $output; + } + + /** + * check output for errors + * + * @param string[] $lines + * @param string $path + * + * @throws NotFoundException + * @throws \Icewind\SMB\Exception\AlreadyExistsException + * @throws \Icewind\SMB\Exception\AccessDeniedException + * @throws \Icewind\SMB\Exception\NotEmptyException + * @throws \Icewind\SMB\Exception\InvalidTypeException + * @throws \Icewind\SMB\Exception\Exception + * @return bool + */ + protected function parseOutput($lines, $path = '') { + $this->parser->checkForError($lines, $path); + } + + /** + * @param string $string + * @return string + */ + protected function escape($string) { + return escapeshellarg($string); + } + + /** + * @param string $path + * @return string + */ + protected function escapePath($path) { + $path = str_replace('/', '\\', $path); + $path = str_replace('"', '^"', $path); + return '"' . $path . '"'; + } + + /** + * @param string $path + * @return string + */ + protected function escapeLocalPath($path) { + $path = str_replace('"', '\"', $path); + return '"' . $path . '"'; + } + + public function __destruct() { + unset($this->connection); + } +} |