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

Parser   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 232
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 88.16%

Importance

Changes 0
Metric Value
wmc 51
lcom 1
cbo 9
dl 0
loc 232
ccs 67
cts 76
cp 0.8816
rs 7.92
c 0
b 0
f 0

9 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 9 3
B parseStat() 0 20 6
A parseDir() 0 21 5
A parseListShares() 0 16 5
D parseACLs() 0 58 18

How to fix   Complexity   

Complex Class

Complex classes like Parser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Parser, and based on these observations, apply Extract Interface, too.

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