Completed
Pull Request — master (#83)
by Bram Van der
02:46
created

Parser   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 167
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 89.04%

Importance

Changes 0
Metric Value
wmc 33
lcom 1
cbo 8
dl 0
loc 167
ccs 65
cts 73
cp 0.8904
rs 9.76
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
B checkConnectionError() 0 21 7
A checkForError() 0 13 3
A getErrorCode() 0 9 3
A parseMode() 0 9 3
B parseStat() 0 20 6
A parseDir() 0 18 5
A parseListShares() 0 16 5
1
<?php
2
/**
3
 * Copyright (c) 2014 Robin Appelman <[email protected]>
4
 * This file is licensed under the Licensed under the MIT license:
5
 * http://opensource.org/licenses/MIT
6
 */
7
8
namespace Icewind\SMB\Wrapped;
9
10
use Icewind\SMB\Exception\AccessDeniedException;
11
use Icewind\SMB\Exception\AlreadyExistsException;
12
use Icewind\SMB\Exception\AuthenticationException;
13
use Icewind\SMB\Exception\Exception;
14
use Icewind\SMB\Exception\FileInUseException;
15
use Icewind\SMB\Exception\InvalidHostException;
16
use Icewind\SMB\Exception\InvalidParameterException;
17
use Icewind\SMB\Exception\InvalidResourceException;
18
use Icewind\SMB\Exception\InvalidTypeException;
19
use Icewind\SMB\Exception\NoLoginServerException;
20
use Icewind\SMB\Exception\NotEmptyException;
21
use Icewind\SMB\Exception\NotFoundException;
22
23
class Parser {
24
	const MSG_NOT_FOUND = 'Error opening local file ';
25
26
	/**
27
	 * @var string
28
	 */
29
	protected $timeZone;
30
31
	/**
32
	 * @var string
33
	 */
34
	private $host;
35
36
	// todo replace with static once <5.6 support is dropped
37
	// see error.h
38
	const EXCEPTION_MAP = [
39
		ErrorCodes::LogonFailure      => AuthenticationException::class,
40
		ErrorCodes::PathNotFound      => NotFoundException::class,
41
		ErrorCodes::ObjectNotFound    => NotFoundException::class,
42
		ErrorCodes::NoSuchFile        => NotFoundException::class,
43
		ErrorCodes::NameCollision     => AlreadyExistsException::class,
44
		ErrorCodes::AccessDenied      => AccessDeniedException::class,
45
		ErrorCodes::DirectoryNotEmpty => NotEmptyException::class,
46
		ErrorCodes::FileIsADirectory  => InvalidTypeException::class,
47
		ErrorCodes::NotADirectory     => InvalidTypeException::class,
48
		ErrorCodes::SharingViolation  => FileInUseException::class,
49
		ErrorCodes::InvalidParameter  => InvalidParameterException::class
50
	];
51
52
	const MODE_STRINGS = [
53
		'R' => FileInfo::MODE_READONLY,
54
		'H' => FileInfo::MODE_HIDDEN,
55
		'S' => FileInfo::MODE_SYSTEM,
56
		'D' => FileInfo::MODE_DIRECTORY,
57
		'A' => FileInfo::MODE_ARCHIVE,
58
		'N' => FileInfo::MODE_NORMAL
59
	];
60
61
	/**
62
	 * @param string $timeZone
63
	 */
64 1088
	public function __construct($timeZone) {
65 1088
		$this->timeZone = $timeZone;
66 1088
	}
67
68 80
	private function getErrorCode($line) {
69 80
		$parts = explode(' ', $line);
70 80
		foreach ($parts as $part) {
71 80
			if (substr($part, 0, 9) === 'NT_STATUS') {
72 80
				return $part;
73
			}
74
		}
75 8
		return false;
76
	}
77
78 80
	public function checkForError($output, $path) {
79 80
		if (strpos($output[0], 'does not exist')) {
80
			throw new NotFoundException($path);
81
		}
82 80
		$error = $this->getErrorCode($output[0]);
83
84 80
		if (substr($output[0], 0, strlen(self::MSG_NOT_FOUND)) === self::MSG_NOT_FOUND) {
85 4
			$localPath = substr($output[0], strlen(self::MSG_NOT_FOUND));
86 4
			throw new InvalidResourceException('Failed opening local file "' . $localPath . '" for writing');
87
		}
88
89 80
		throw Exception::fromMap(self::EXCEPTION_MAP, $error, $path);
90
	}
91
92
	/**
93
	 * check if the first line holds a connection failure
94
	 *
95
	 * @param $line
96
	 * @throws AuthenticationException
97
	 * @throws InvalidHostException
98
	 * @throws NoLoginServerException
99
	 * @throws AccessDeniedException
100
	 */
101 1040
	public function checkConnectionError($line) {
102 1040
		$line = rtrim($line, ')');
103 1040
		if (substr($line, -23) === ErrorCodes::LogonFailure) {
104 4
			throw new AuthenticationException('Invalid login');
105
		}
106 1036
		if (substr($line, -26) === ErrorCodes::BadHostName) {
107
			throw new InvalidHostException('Invalid hostname');
108
		}
109 1036
		if (substr($line, -22) === ErrorCodes::Unsuccessful) {
110 12
			throw new InvalidHostException('Connection unsuccessful');
111
		}
112 1028
		if (substr($line, -28) === ErrorCodes::ConnectionRefused) {
113
			throw new InvalidHostException('Connection refused');
114
		}
115 1028
		if (substr($line, -26) === ErrorCodes::NoLogonServers) {
116
			throw new NoLoginServerException('No login server');
117
		}
118 1028
		if (substr($line, -23) === ErrorCodes::AccessDenied) {
119
			throw new AccessDeniedException('Access denied');
120
		}
121 1028
	}
122
123 460
	public function parseMode($mode) {
124 460
		$result = 0;
125 460
		foreach (self::MODE_STRINGS as $char => $val) {
126 460
			if (strpos($mode, $char) !== false) {
127 460
				$result |= $val;
128
			}
129
		}
130 460
		return $result;
131
	}
132
133 16
	public function parseStat($output) {
134 16
		$data = [];
135 16
		foreach ($output as $line) {
136
			// A line = explode statement may not fill all array elements
137
			// properly. May happen when accessing non Windows Fileservers
138 16
			$words = explode(':', $line, 2);
139 16
			$name = isset($words[0]) ? $words[0] : '';
140 16
			$value = isset($words[1]) ? $words[1] : '';
141 16
			$value = trim($value);
142
143 16
			if (!isset($data[$name])) {
144 16
				$data[$name] = $value;
145
			}
146
		}
147
		return [
148 16
			'mtime' => strtotime($data['write_time']),
149 16
			'mode'  => hexdec(substr($data['attributes'], strpos($data['attributes'], '('), -1)),
150 16
			'size'  => isset($data['stream']) ? (int)(explode(' ', $data['stream'])[1]) : 0
151
		];
152
	}
153
154 1008
	public function parseDir($output, $basePath) {
155
		//last line is used space
156 1008
		array_pop($output);
157 1008
		$regex = '/^\s*(.*?)\s\s\s\s+(?:([NDHARS]*)\s+)?([0-9]+)\s+(.*)$/';
158
		//2 spaces, filename, optional type, size, date
159 1008
		$content = [];
160 1008
		foreach ($output as $line) {
161 1008
			if (preg_match($regex, $line, $matches)) {
162 1008
				list(, $name, $mode, $size, $time) = $matches;
163 1008
				if ($name !== '.' and $name !== '..') {
164 428
					$mode = $this->parseMode($mode);
165 428
					$time = strtotime($time . ' ' . $this->timeZone);
166 718
					$content[] = new FileInfo($basePath . '/' . $name, $name, $size, $time, $mode);
167
				}
168
			}
169
		}
170 1008
		return $content;
171
	}
172
173 8
	public function parseListShares($output) {
174 8
		$shareNames = [];
175 8
		foreach ($output as $line) {
176 8
			if (strpos($line, '|')) {
177
				list($type, $name, $description) = explode('|', $line);
178
				if (strtolower($type) === 'disk') {
179
					$shareNames[$name] = $description;
180
				}
181 8
			} elseif (strpos($line, 'Disk')) {
182
				// new output format
183 8
				list($name, $description) = explode('Disk', $line);
184 8
				$shareNames[trim($name)] = trim($description);
185
			}
186
		}
187 8
		return $shareNames;
188
	}
189
}
190