Completed
Push — master ( 0d9341...91d173 )
by Robin
01:19
created

Parser::checkConnectionError()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 8.1426

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 10
cts 14
cp 0.7143
rs 8.6506
c 0
b 0
f 0
cc 7
nc 7
nop 1
crap 8.1426
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\ACL;
11
use Icewind\SMB\Exception\AccessDeniedException;
12
use Icewind\SMB\Exception\AlreadyExistsException;
13
use Icewind\SMB\Exception\AuthenticationException;
14
use Icewind\SMB\Exception\Exception;
15
use Icewind\SMB\Exception\FileInUseException;
16
use Icewind\SMB\Exception\InvalidHostException;
17
use Icewind\SMB\Exception\InvalidParameterException;
18
use Icewind\SMB\Exception\InvalidResourceException;
19
use Icewind\SMB\Exception\InvalidTypeException;
20
use Icewind\SMB\Exception\NoLoginServerException;
21
use Icewind\SMB\Exception\NotEmptyException;
22
use Icewind\SMB\Exception\NotFoundException;
23
24
class Parser {
25
	const MSG_NOT_FOUND = 'Error opening local file ';
26
27
	/**
28
	 * @var string
29
	 */
30
	protected $timeZone;
31
32
	/**
33
	 * @var string
34
	 */
35
	private $host;
36
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 542
	 */
64 542
	public function __construct(string $timeZone) {
65 542
		$this->timeZone = $timeZone;
66
	}
67 38
68 38
	private function getErrorCode($line) {
69 38
		$parts = explode(' ', $line);
70 38
		foreach ($parts as $part) {
71 38
			if (substr($part, 0, 9) === 'NT_STATUS') {
72
				return $part;
73
			}
74 2
		}
75
		return false;
76
	}
77 38
78 38
	public function checkForError($output, $path) {
79
		if (strpos($output[0], 'does not exist')) {
80
			throw new NotFoundException($path);
81 38
		}
82
		$error = $this->getErrorCode($output[0]);
83 38
84 2
		if (substr($output[0], 0, strlen(self::MSG_NOT_FOUND)) === self::MSG_NOT_FOUND) {
85 2
			$localPath = substr($output[0], strlen(self::MSG_NOT_FOUND));
86
			throw new InvalidResourceException('Failed opening local file "' . $localPath . '" for writing');
87
		}
88 38
89
		throw Exception::fromMap(self::EXCEPTION_MAP, $error, $path);
90
	}
91
92
	/**
93
	 * check if the first line holds a connection failure
94
	 *
95
	 * @param string $line
96
	 * @throws AuthenticationException
97
	 * @throws InvalidHostException
98
	 * @throws NoLoginServerException
99
	 * @throws AccessDeniedException
100 494
	 */
101 494
	public function checkConnectionError(string $line) {
102 494
		$line = rtrim($line, ')');
103 2
		if (substr($line, -23) === ErrorCodes::LogonFailure) {
104
			throw new AuthenticationException('Invalid login');
105 492
		}
106
		if (substr($line, -26) === ErrorCodes::BadHostName) {
107
			throw new InvalidHostException('Invalid hostname');
108 492
		}
109 6
		if (substr($line, -22) === ErrorCodes::Unsuccessful) {
110
			throw new InvalidHostException('Connection unsuccessful');
111 488
		}
112
		if (substr($line, -28) === ErrorCodes::ConnectionRefused) {
113
			throw new InvalidHostException('Connection refused');
114 488
		}
115
		if (substr($line, -26) === ErrorCodes::NoLogonServers) {
116
			throw new NoLoginServerException('No login server');
117 488
		}
118
		if (substr($line, -23) === ErrorCodes::AccessDenied) {
119
			throw new AccessDeniedException('Access denied');
120 488
		}
121
	}
122 230
123 230
	public function parseMode($mode) {
124 230
		$result = 0;
125 230
		foreach (self::MODE_STRINGS as $char => $val) {
126 230
			if (strpos($mode, $char) !== false) {
127
				$result |= $val;
128
			}
129 230
		}
130
		return $result;
131
	}
132 14
133 14
	public function parseStat($output) {
134 14
		$data = [];
135
		foreach ($output as $line) {
136
			// A line = explode statement may not fill all array elements
137 14
			// properly. May happen when accessing non Windows Fileservers
138 14
			$words = explode(':', $line, 2);
139 14
			$name = isset($words[0]) ? $words[0] : '';
140 14
			$value = isset($words[1]) ? $words[1] : '';
141
			$value = trim($value);
142 14
143 14
			if (!isset($data[$name])) {
144
				$data[$name] = $value;
145
			}
146
		}
147 14
		return [
148 14
			'mtime' => strtotime($data['write_time']),
149 14
			'mode'  => hexdec(substr($data['attributes'], strpos($data['attributes'], '(') + 1, -1)),
150
			'size'  => isset($data['stream']) ? (int)(explode(' ', $data['stream'])[1]) : 0
151
		];
152
	}
153 488
154
	public function parseDir($output, $basePath, callable $aclCallback) {
155 488
		//last line is used space
156 488
		array_pop($output);
157
		$regex = '/^\s*(.*?)\s\s\s\s+(?:([NDHARS]*)\s+)?([0-9]+)\s+(.*)$/';
158 488
		//2 spaces, filename, optional type, size, date
159 488
		$content = [];
160 488
		foreach ($output as $line) {
161 488
			if (preg_match($regex, $line, $matches)) {
162 488
				list(, $name, $mode, $size, $time) = $matches;
163 198
				if ($name !== '.' and $name !== '..') {
164 198
					$mode = $this->parseMode($mode);
165 198
					$time = strtotime($time . ' ' . $this->timeZone);
166 198
					$path = $basePath . '/' . $name;
167
					$content[] = new FileInfo($path, $name, $size, $time, $mode, function () use ($aclCallback, $path) {
168 198
						return $aclCallback($path);
169
					});
170
				}
171
			}
172 488
		}
173
		return $content;
174
	}
175 4
176 4
	public function parseListShares($output) {
177 4
		$shareNames = [];
178 4
		foreach ($output as $line) {
179
			if (strpos($line, '|')) {
180
				list($type, $name, $description) = explode('|', $line);
181
				if (strtolower($type) === 'disk') {
182
					$shareNames[$name] = $description;
183 4
				}
184
			} elseif (strpos($line, 'Disk')) {
185 4
				// new output format
186 4
				list($name, $description) = explode('Disk', $line);
187
				$shareNames[trim($name)] = trim($description);
188
			}
189 4
		}
190
		return $shareNames;
191
	}
192
193
	/**
194
	 * @param string[] $rawAcls
195
	 * @return ACL[]
196
	 */
197
	public function parseACLs(array $rawAcls): array {
198
		$acls = [];
199
		foreach ($rawAcls as $acl) {
200
			if (strpos($acl, ':') === false) {
201
				continue;
202
			}
203
			[$type, $acl] = explode(':', $acl, 2);
0 ignored issues
show
Bug introduced by
The variable $type does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
204
			if ($type !== 'ACL') {
205
				continue;
206
			}
207
			[$user, $permissions] = explode(':', $acl, 2);
0 ignored issues
show
Bug introduced by
The variable $user does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $permissions does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
208
			[$type, $flags, $mask] = explode('/', $permissions);
0 ignored issues
show
Bug introduced by
The variable $flags does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $mask does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
209
210
			$type = $type === 'ALLOWED' ? ACL::TYPE_ALLOW : ACL::TYPE_DENY;
211
212
			$flagsInt = 0;
213
			foreach (explode('|', $flags) as $flagString) {
214
				if ($flagString === 'OI') {
215
					$flagsInt += ACL::FLAG_OBJECT_INHERIT;
216
				} elseif ($flagString === 'CI') {
217
					$flagsInt += ACL::FLAG_CONTAINER_INHERIT;
218
				}
219
			}
220
221
			if (substr($mask, 0, 2) === '0x') {
222
				$maskInt = hexdec($mask);
223
			} else {
224
				$maskInt = 0;
225
				foreach (explode('|', $mask) as $maskString) {
226
					if ($maskString === 'R') {
227
						$maskInt += ACL::MASK_READ;
228
					} elseif ($maskString === 'W') {
229
						$maskInt += ACL::MASK_WRITE;
230
					} elseif ($maskString === 'X') {
231
						$maskInt += ACL::MASK_EXECUTE;
232
					} elseif ($maskString === 'D') {
233
						$maskInt += ACL::MASK_DELETE;
234
					} elseif ($maskString === 'READ') {
235
						$maskInt += ACL::MASK_READ + ACL::MASK_EXECUTE;
236
					} elseif ($maskString === 'CHANGE') {
237
						$maskInt += ACL::MASK_READ + ACL::MASK_EXECUTE + ACL::MASK_WRITE + ACL::MASK_DELETE;
238
					} elseif ($maskString === 'FULL') {
239
						$maskInt += ACL::MASK_READ + ACL::MASK_EXECUTE + ACL::MASK_WRITE + ACL::MASK_DELETE;
240
					}
241
				}
242
			}
243
244
			if (isset($acls[$user])) {
245
				$existing = $acls[$user];
246
				$maskInt += $existing->getMask();
247
			}
248
			$acls[$user] = new ACL($type, $flagsInt, $maskInt);
249
		}
250
251
		ksort($acls);
252
253
		return $acls;
254
	}
255
}
256