mirror of
https://codeberg.org/icewind/SMB.git
synced 2026-06-03 17:24:07 +02:00
Split out parsing of smbclient output
This commit is contained in:
parent
7278910ddc
commit
e4cb609342
3 changed files with 231 additions and 103 deletions
123
src/Parser.php
Normal file
123
src/Parser.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/Share.php
124
src/Share.php
|
|
@ -32,6 +32,11 @@ class Share implements IShare {
|
||||||
*/
|
*/
|
||||||
public $connection;
|
public $connection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Icewind\SMB\Parser
|
||||||
|
*/
|
||||||
|
protected $parser;
|
||||||
|
|
||||||
private $serverTimezone;
|
private $serverTimezone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,10 +46,11 @@ class Share implements IShare {
|
||||||
public function __construct($server, $name) {
|
public function __construct($server, $name) {
|
||||||
$this->server = $server;
|
$this->server = $server;
|
||||||
$this->name = $name;
|
$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\AuthenticationException
|
||||||
* @throws \Icewind\SMB\Exception\InvalidHostException
|
* @throws \Icewind\SMB\Exception\InvalidHostException
|
||||||
*/
|
*/
|
||||||
|
|
@ -80,13 +86,6 @@ class Share implements IShare {
|
||||||
return $this->parseOutput($output);
|
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
|
* List the content of a remote folder
|
||||||
*
|
*
|
||||||
|
|
@ -104,22 +103,7 @@ class Share implements IShare {
|
||||||
$output = $this->execute('dir');
|
$output = $this->execute('dir');
|
||||||
$this->execute('cd /');
|
$this->execute('cd /');
|
||||||
|
|
||||||
//last line is used space
|
return $this->parser->parseDir($output, $path);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -132,22 +116,8 @@ class Share implements IShare {
|
||||||
if (count($output) < 3) {
|
if (count($output) < 3) {
|
||||||
$this->parseOutput($output);
|
$this->parseOutput($output);
|
||||||
}
|
}
|
||||||
$mtime = 0;
|
$stat = $this->parser->parseStat($output);
|
||||||
$mode = 0;
|
return new FileInfo($path, basename($path), $stat['size'], $stat['mtime'], $stat['mode']);
|
||||||
$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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -322,17 +292,16 @@ class Share implements IShare {
|
||||||
*/
|
*/
|
||||||
public function setMode($path, $mode) {
|
public function setMode($path, $mode) {
|
||||||
$modeString = '';
|
$modeString = '';
|
||||||
if ($mode & FileInfo::MODE_READONLY) {
|
$modeMap = array(
|
||||||
$modeString .= 'r';
|
FileInfo::MODE_READONLY => 'r',
|
||||||
}
|
FileInfo::MODE_HIDDEN => 'h',
|
||||||
if ($mode & FileInfo::MODE_HIDDEN) {
|
FileInfo::MODE_ARCHIVE => 'a',
|
||||||
$modeString .= 'h';
|
FileInfo::MODE_SYSTEM => 's'
|
||||||
}
|
);
|
||||||
if ($mode & FileInfo::MODE_ARCHIVE) {
|
foreach ($modeMap as $modeByte => $string) {
|
||||||
$modeString .= 'a';
|
if ($mode & $modeByte) {
|
||||||
}
|
$modeString .= $string;
|
||||||
if ($mode & FileInfo::MODE_SYSTEM) {
|
}
|
||||||
$modeString .= 's';
|
|
||||||
}
|
}
|
||||||
$path = $this->escapePath($path);
|
$path = $this->escapePath($path);
|
||||||
|
|
||||||
|
|
@ -347,27 +316,6 @@ class Share implements IShare {
|
||||||
return $this->parseOutput($output);
|
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
|
* @param string $command
|
||||||
* @return array
|
* @return array
|
||||||
|
|
@ -393,37 +341,7 @@ class Share implements IShare {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function parseOutput($lines) {
|
protected function parseOutput($lines) {
|
||||||
if (count($lines) === 0) {
|
$this->parser->checkForError($lines);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
87
tests/Parser.php
Normal file
87
tests/Parser.php
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
<?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\Test;
|
||||||
|
|
||||||
|
|
||||||
|
use Icewind\SMB\FileInfo;
|
||||||
|
|
||||||
|
class Parser extends \PHPUnit_Framework_TestCase {
|
||||||
|
public function modeProvider() {
|
||||||
|
return array(
|
||||||
|
array('D', FileInfo::MODE_DIRECTORY),
|
||||||
|
array('A', FileInfo::MODE_ARCHIVE),
|
||||||
|
array('S', FileInfo::MODE_SYSTEM),
|
||||||
|
array('H', FileInfo::MODE_HIDDEN),
|
||||||
|
array('R', FileInfo::MODE_READONLY),
|
||||||
|
array('N', FileInfo::MODE_NORMAL),
|
||||||
|
array('RA', FileInfo::MODE_READONLY | FileInfo::MODE_ARCHIVE),
|
||||||
|
array('RAH', FileInfo::MODE_READONLY | FileInfo::MODE_ARCHIVE | FileInfo::MODE_HIDDEN)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider modeProvider
|
||||||
|
*/
|
||||||
|
public function testParseMode($string, $mode) {
|
||||||
|
$parser = new \Icewind\SMB\Parser('UTC');
|
||||||
|
$this->assertEquals($mode, $parser->parseMode($string), 'Failed parsing ' . $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function statProvider() {
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'altname: test.txt',
|
||||||
|
'create_time: Sat Oct 12 07:05:58 PM 2013 CEST',
|
||||||
|
'access_time: Tue Oct 15 02:58:48 PM 2013 CEST',
|
||||||
|
'write_time: Sat Oct 12 07:05:58 PM 2013 CEST',
|
||||||
|
'change_time: Sat Oct 12 07:05:58 PM 2013 CEST',
|
||||||
|
'attributes: (80)',
|
||||||
|
'stream: [::$DATA], 29634 bytes'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'mtime' => strtotime('12 Oct 2013 19:05:58 CEST'),
|
||||||
|
'mode' => FileInfo::MODE_NORMAL,
|
||||||
|
'size' => 29634
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider statProvider
|
||||||
|
*/
|
||||||
|
public function testStat($output, $stat) {
|
||||||
|
$parser = new \Icewind\SMB\Parser('UTC');
|
||||||
|
$this->assertEquals($stat, $parser->parseStat($output));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dirProvider() {
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
' . D 0 Tue Aug 26 19:11:56 2014',
|
||||||
|
' .. DR 0 Sun Oct 28 15:24:02 2012',
|
||||||
|
' c.pdf N 29634 Sat Oct 12 19:05:58 2013',
|
||||||
|
'',
|
||||||
|
' 62536 blocks of size 8388608. 57113 blocks available'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
new FileInfo('/c.pdf', 'c.pdf', 29634, strtotime('12 Oct 2013 19:05:58 CEST'), FileInfo::MODE_NORMAL)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider dirProvider
|
||||||
|
*/
|
||||||
|
public function testDir($output, $dir) {
|
||||||
|
$parser = new \Icewind\SMB\Parser('CEST');
|
||||||
|
$this->assertEquals($dir, $parser->parseDir($output, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue