Split out parsing of smbclient output

This commit is contained in:
Robin Appelman 2014-08-27 11:49:06 +02:00
commit e4cb609342
3 changed files with 231 additions and 103 deletions

123
src/Parser.php Normal file
View file

@ -0,0 +1,123 @@
<?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\Exception;
use Icewind\SMB\Exception\InvalidTypeException;
use Icewind\SMB\Exception\NotEmptyException;
use Icewind\SMB\Exception\NotFoundException;
class Parser {
/**
* @var string
*/
protected $timeZone;
/**
* @param string $timeZone
*/
public function __construct($timeZone) {
$this->timeZone = $timeZone;
}
public function checkForError($output) {
if (count($output) === 0) {
return true;
} else {
if (strpos($output[0], 'does not exist')) {
throw new NotFoundException();
}
$parts = explode(' ', $output[0]);
$error = false;
foreach ($parts as $part) {
if (substr($part, 0, 9) === 'NT_STATUS') {
$error = $part;
}
}
switch ($error) {
case ErrorCodes::PathNotFound:
case ErrorCodes::ObjectNotFound:
case ErrorCodes::NoSuchFile:
throw new NotFoundException();
case ErrorCodes::NameCollision:
throw new AlreadyExistsException();
case ErrorCodes::AccessDenied:
throw new AccessDeniedException();
case ErrorCodes::DirectoryNotEmpty:
throw new NotEmptyException();
case ErrorCodes::FileIsADirectory:
case ErrorCodes::NotADirectory:
throw new InvalidTypeException();
default:
throw new Exception();
}
}
}
public function parseMode($mode) {
$result = 0;
$modeStrings = array(
'R' => FileInfo::MODE_READONLY,
'H' => FileInfo::MODE_HIDDEN,
'S' => FileInfo::MODE_SYSTEM,
'D' => FileInfo::MODE_DIRECTORY,
'A' => FileInfo::MODE_ARCHIVE,
'N' => FileInfo::MODE_NORMAL
);
foreach ($modeStrings as $char => $val) {
if (strpos($mode, $char) !== false) {
$result |= $val;
}
}
return $result;
}
public function parseStat($output) {
$mtime = 0;
$mode = 0;
$size = 0;
foreach ($output as $line) {
list($name, $value) = explode(':', $line, 2);
$value = trim($value);
if ($name === 'write_time') {
$mtime = strtotime($value);
} else if ($name === 'attributes') {
$mode = hexdec(substr($value, 1, -1));
} else if ($name === 'stream') {
list(, $size,) = explode(' ', $value);
$size = intval($size);
}
}
return array(
'mtime' => $mtime,
'mode' => $mode,
'size' => $size
);
}
public function parseDir($output, $basePath) {
//last line is used space
array_pop($output);
$regex = '/^\s*(.*?)\s\s\s\s+(?:([NDHARS]*)\s+)?([0-9]+)\s+(.*)$/';
//2 spaces, filename, optional type, size, date
$content = array();
foreach ($output as $line) {
if (preg_match($regex, $line, $matches)) {
list(, $name, $mode, $size, $time) = $matches;
if ($name !== '.' and $name !== '..') {
$mode = $this->parseMode($mode);
$time = strtotime($time . ' ' . $this->timeZone);
$content[] = new FileInfo($basePath . '/' . $name, $name, $size, $time, $mode);
}
}
}
return $content;
}
}

View file

@ -32,6 +32,11 @@ class Share implements IShare {
*/
public $connection;
/**
* @var \Icewind\SMB\Parser
*/
protected $parser;
private $serverTimezone;
/**
@ -41,10 +46,11 @@ class Share implements IShare {
public function __construct($server, $name) {
$this->server = $server;
$this->name = $name;
$this->parser = new Parser($this->server->getTimeZone());
}
/**
* @throws \Icewind\SMB\Exception\ConnectionError
* @throws \Icewind\SMB\Exception\ConnectionException
* @throws \Icewind\SMB\Exception\AuthenticationException
* @throws \Icewind\SMB\Exception\InvalidHostException
*/
@ -80,13 +86,6 @@ class Share implements IShare {
return $this->parseOutput($output);
}
private function getServerTimeZone() {
if (!$this->serverTimezone) {
$this->serverTimezone = $this->server->getTimeZone();
}
return $this->serverTimezone;
}
/**
* List the content of a remote folder
*
@ -104,22 +103,7 @@ class Share implements IShare {
$output = $this->execute('dir');
$this->execute('cd /');
//last line is used space
array_pop($output);
$regex = '/^\s*(.*?)\s\s\s\s+(?:([NDHARS]*)\s+)?([0-9]+)\s+(.*)$/';
//2 spaces, filename, optional type, size, date
$content = array();
foreach ($output as $line) {
if (preg_match($regex, $line, $matches)) {
list(, $name, $mode, $size, $time) = $matches;
if ($name !== '.' and $name !== '..') {
$mode = $this->parseMode($mode);
$time = strtotime($time . ' ' . $this->getServerTimeZone());
$content[] = new FileInfo($path . '/' . $name, $name, $size, $time, $mode);
}
}
}
return $content;
return $this->parser->parseDir($output, $path);
}
/**
@ -132,22 +116,8 @@ class Share implements IShare {
if (count($output) < 3) {
$this->parseOutput($output);
}
$mtime = 0;
$mode = 0;
$size = 0;
foreach ($output as $line) {
list($name, $value) = explode(':', $line, 2);
$value = trim($value);
if ($name === 'write_time') {
$mtime = $value;
} else if ($name === 'attributes') {
$mode = $this->parseMode($value);
} else if ($name === 'stream') {
list(, $size,) = explode(' ', $value);
$size = intval($size);
}
}
return new FileInfo($path, basename($path), $size, $mtime, $mode);
$stat = $this->parser->parseStat($output);
return new FileInfo($path, basename($path), $stat['size'], $stat['mtime'], $stat['mode']);
}
/**
@ -322,17 +292,16 @@ class Share implements IShare {
*/
public function setMode($path, $mode) {
$modeString = '';
if ($mode & FileInfo::MODE_READONLY) {
$modeString .= 'r';
}
if ($mode & FileInfo::MODE_HIDDEN) {
$modeString .= 'h';
}
if ($mode & FileInfo::MODE_ARCHIVE) {
$modeString .= 'a';
}
if ($mode & FileInfo::MODE_SYSTEM) {
$modeString .= 's';
$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);
@ -347,27 +316,6 @@ class Share implements IShare {
return $this->parseOutput($output);
}
/**
* @param string $mode
* @return string
*/
protected function parseMode($mode) {
$result = 0;
$modeStrings = array(
'R' => FileInfo::MODE_READONLY,
'H' => FileInfo::MODE_HIDDEN,
'S' => FileInfo::MODE_SYSTEM,
'D' => FileInfo::MODE_DIRECTORY,
'A' => FileInfo::MODE_ARCHIVE
);
foreach ($modeStrings as $char => $val) {
if (strpos($mode, $char) !== false) {
$result |= $val;
}
}
return $result;
}
/**
* @param string $command
* @return array
@ -393,37 +341,7 @@ class Share implements IShare {
* @return bool
*/
protected function parseOutput($lines) {
if (count($lines) === 0) {
return true;
} else {
if (strpos($lines[0], 'does not exist')) {
throw new NotFoundException();
}
$parts = explode(' ', $lines[0]);
$error = false;
foreach ($parts as $part) {
if (substr($part, 0, 9) === 'NT_STATUS') {
$error = $part;
}
}
switch ($error) {
case ErrorCodes::PathNotFound:
case ErrorCodes::ObjectNotFound:
case ErrorCodes::NoSuchFile:
throw new NotFoundException();
case ErrorCodes::NameCollision:
throw new AlreadyExistsException();
case ErrorCodes::AccessDenied:
throw new AccessDeniedException();
case ErrorCodes::DirectoryNotEmpty:
throw new NotEmptyException();
case ErrorCodes::FileIsADirectory:
case ErrorCodes::NotADirectory:
throw new InvalidTypeException();
default:
throw new Exception();
}
}
$this->parser->checkForError($lines);
}
/**