Completed
Push — stable2 ( 4ad10a...b888dd )
by Robin
03:39
created

Parser   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 154
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 9

Test Coverage

Coverage 94.19%

Importance

Changes 0
Metric Value
wmc 31
c 0
b 0
f 0
lcom 2
cbo 9
dl 0
loc 154
ccs 81
cts 86
cp 0.9419
rs 9.8

8 Methods

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