Completed
Pull Request — master (#66)
by
unknown
05:06
created

Parser::parseListShares()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.583

Importance

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