Completed
Push — master ( f4d8bc...afc829 )
by Robin
03:09
created

Parser   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 158
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 90%

Importance

Changes 0
Metric Value
wmc 32
lcom 1
cbo 9
dl 0
loc 158
ccs 81
cts 90
cp 0.9
rs 9.6
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A getErrorCode() 0 9 3
A checkForError() 0 13 3
A parseMode() 0 17 3
B parseStat() 0 17 5
B parseDir() 0 18 5
B checkConnectionError() 0 21 7
B 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
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
	/**
49
	 * @param TimeZoneProvider $timeZoneProvider
50
	 */
51 825
	public function __construct(TimeZoneProvider $timeZoneProvider) {
52 825
		$this->timeZoneProvider = $timeZoneProvider;
53 825
	}
54
55 60
	private function getErrorCode($line) {
56 60
		$parts = explode(' ', $line);
57 60
		foreach ($parts as $part) {
58 60
			if (substr($part, 0, 9) === 'NT_STATUS') {
59 60
				return $part;
60
			}
61 3
		}
62 6
		return false;
63
	}
64
65 60
	public function checkForError($output, $path) {
66 60
		if (strpos($output[0], 'does not exist')) {
67
			throw new NotFoundException($path);
68
		}
69 60
		$error = $this->getErrorCode($output[0]);
70
71 60
		if (substr($output[0], 0, strlen(self::MSG_NOT_FOUND)) === self::MSG_NOT_FOUND) {
72 3
			$localPath = substr($output[0], strlen(self::MSG_NOT_FOUND));
73 3
			throw new InvalidResourceException('Failed opening local file "' . $localPath . '" for writing');
74
		}
75
76 60
		throw Exception::fromMap(self::EXCEPTION_MAP, $error, $path);
77
	}
78
79
	/**
80
	 * check if the first line holds a connection failure
81
	 *
82
	 * @param $line
83
	 * @throws AuthenticationException
84
	 * @throws InvalidHostException
85
	 * @throws NoLoginServerException
86
	 * @throws AccessDeniedException
87
	 */
88 777
	public function checkConnectionError($line) {
89 777
		$line = rtrim($line, ')');
90 777
		if (substr($line, -23) === ErrorCodes::LogonFailure) {
91 3
			throw new AuthenticationException('Invalid login');
92
		}
93 774
		if (substr($line, -26) === ErrorCodes::BadHostName) {
94
			throw new InvalidHostException('Invalid hostname');
95
		}
96 774
		if (substr($line, -22) === ErrorCodes::Unsuccessful) {
97 9
			throw new InvalidHostException('Connection unsuccessful');
98
		}
99 768
		if (substr($line, -28) === ErrorCodes::ConnectionRefused) {
100
			throw new InvalidHostException('Connection refused');
101
		}
102 768
		if (substr($line, -26) === ErrorCodes::NoLogonServers) {
103
			throw new NoLoginServerException('No login server');
104
		}
105 768
		if (substr($line, -23) === ErrorCodes::AccessDenied) {
106
			throw new AccessDeniedException('Access denied');
107
		}
108 768
	}
109
110 324
	public function parseMode($mode) {
111 324
		$result = 0;
112
		$modeStrings = array(
113 324
			'R' => FileInfo::MODE_READONLY,
114 219
			'H' => FileInfo::MODE_HIDDEN,
115 219
			'S' => FileInfo::MODE_SYSTEM,
116 219
			'D' => FileInfo::MODE_DIRECTORY,
117 219
			'A' => FileInfo::MODE_ARCHIVE,
118 105
			'N' => FileInfo::MODE_NORMAL
119 114
		);
120 324
		foreach ($modeStrings as $char => $val) {
121 324
			if (strpos($mode, $char) !== false) {
122 324
				$result |= $val;
123 114
			}
124 114
		}
125 324
		return $result;
126
	}
127
128 81
	public function parseStat($output) {
129 81
		$data = [];
130 81
		foreach ($output as $line) {
131
			// A line = explode statement may not fill all array elements
132
			// properly. May happen when accessing non Windows Fileservers
133 81
			$words = explode(':', $line, 2);
134 81
			$name = isset($words[0]) ? $words[0] : '';
135 81
			$value = isset($words[1]) ? $words[1] : '';
136 81
			$value = trim($value);
137 81
			$data[$name] = $value;
138 29
		}
139
		return [
140 81
			'mtime' => strtotime($data['write_time']),
141 81
			'mode'  => hexdec(substr($data['attributes'], strpos($data['attributes'], '('), -1)),
142 81
			'size'  => isset($data['stream']) ? intval(explode(' ', $data['stream'])[1]) : 0
143 29
		];
144
	}
145
146 754
	public function parseDir($output, $basePath) {
147
		//last line is used space
148 754
		array_pop($output);
149 754
		$regex = '/^\s*(.*?)\s\s\s\s+(?:([NDHARS]*)\s+)?([0-9]+)\s+(.*)$/';
150
		//2 spaces, filename, optional type, size, date
151 754
		$content = array();
152 754
		foreach ($output as $line) {
153 754
			if (preg_match($regex, $line, $matches)) {
154 754
				list(, $name, $mode, $size, $time) = $matches;
155 754
				if ($name !== '.' and $name !== '..') {
156 292
					$mode = $this->parseMode($mode);
157 292
					$time = strtotime($time . ' ' . $this->timeZoneProvider->get());
158 600
					$content[] = new FileInfo($basePath . '/' . $name, $name, $size, $time, $mode);
159 98
				}
160 252
			}
161 252
		}
162 754
		return $content;
163
	}
164
165 6
	public function parseListShares($output) {
166 6
		$shareNames = array();
167 6
		foreach ($output as $line) {
168 6
			if (strpos($line, '|')) {
169
				list($type, $name, $description) = explode('|', $line);
170
				if (strtolower($type) === 'disk') {
171
					$shareNames[$name] = $description;
172
				}
173 6
			} else if (strpos($line, 'Disk')) {
174
				// new output format
175 6
				list($name, $description) = explode('Disk', $line);
176 6
				$shareNames[trim($name)] = trim($description);
177 2
			}
178 2
		}
179 6
		return $shareNames;
180
	}
181
}
182