Completed
Push — master ( e1438a...669135 )
by Robin
03:24
created

Parser::checkForError()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0175

Importance

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