Completed
Pull Request — master (#30092)
by
unknown
16:32
created

SMB::getFileInfo()   C

Complexity

Conditions 14
Paths 124

Size

Total Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
nc 124
nop 1
dl 0
loc 59
rs 6.0666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Jesús Macias <[email protected]>
5
 * @author Jörn Friedrich Dreyer <[email protected]>
6
 * @author Juan Pablo Villafañez <[email protected]>
7
 * @author Michael Gapczynski <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Philipp Kapfer <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Robin McCorkell <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 * @author Vincent Petry <[email protected]>
14
 *
15
 * @copyright Copyright (c) 2018, ownCloud GmbH
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OCA\Files_External\Lib\Storage;
33
34
use Icewind\SMB\Exception\AlreadyExistsException;
35
use Icewind\SMB\Exception\ConnectException;
36
use Icewind\SMB\Exception\Exception;
37
use Icewind\SMB\Exception\ForbiddenException;
38
use Icewind\SMB\Exception\NotFoundException;
39
use Icewind\SMB\BasicAuth;
40
use Icewind\SMB\IFileInfo;
41
use Icewind\SMB\Native\NativeServer;
42
use Icewind\SMB\Wrapped\FileInfo;
43
use Icewind\SMB\ServerFactory;
44
use Icewind\SMB\System;
45
use Icewind\SMB\IShare;
46
use Icewind\Streams\CallbackWrapper;
47
use Icewind\Streams\IteratorDirectory;
48
use OC\Cache\CappedMemoryCache;
49
use OC\Files\Filesystem;
50
use OCP\Files\Cache\ICache;
51
use OCP\Files\StorageNotAvailableException;
52
use OCP\Util;
53
54
class SMB extends \OCP\Files\Storage\StorageAdapter {
55
	/**
56
	 * @var \Icewind\SMB\IServer
57
	 */
58
	protected $server;
59
60
	/**
61
	 * @var \Icewind\SMB\IShare
62
	 */
63
	protected $share;
64
65
	/**
66
	 * @var string
67
	 */
68
	protected $root;
69
70
	/**
71
	 * @var ICache
72
	 */
73
	protected $statCache;
74
75
	public function __construct($params) {
76
		$loggedParams = $params;
77
		// remove password from log if it is set
78
		if (!empty($loggedParams['password'])) {
79
			$loggedParams['password'] = '***removed***';
80
		}
81
		$this->log('enter: '.__FUNCTION__.'('.\json_encode($loggedParams).')');
82
83
		if (isset($params['host'], $params['user'], $params['password'], $params['share'])) {
84
			$auth = new BasicAuth($params['user'], '', $params['password']);
85
			$serverFactory = new ServerFactory();
86
			$this->server = $serverFactory->createServer($params['host'], $auth);
87
			$this->share = $this->server->getShare(\trim($params['share'], '/'));
88
89
			$shareClass = \get_class($this->share);
90
			$this->log("using $shareClass for the connection");
91
92
			$this->root = isset($params['root']) ? $params['root'] : '/';
93 View Code Duplication
			if (!$this->root || $this->root[0] != '/') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
94
				$this->root = '/' . $this->root;
95
			}
96
			if (\substr($this->root, -1, 1) != '/') {
97
				$this->root .= '/';
98
			}
99
		} else {
100
			$ex = new \Exception('Invalid configuration');
101
			$this->leave(__FUNCTION__, $ex);
102
			throw $ex;
103
		}
104
		$this->statCache = new CappedMemoryCache();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \OC\Cache\CappedMemoryCache() of type object<OC\Cache\CappedMemoryCache> is incompatible with the declared type object<OCP\Files\Cache\ICache> of property $statCache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
105
		$this->log('leave: '.__FUNCTION__.', getId:'.$this->getId());
106
	}
107
108
	/**
109
	 * @return string
110
	 */
111
	public function getId() {
112
		// FIXME: double slash to keep compatible with the old storage ids,
113
		// failure to do so will lead to creation of a new storage id and
114
		// loss of shares from the storage
115
		return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
116
	}
117
118
	/**
119
	 * @param string $path
120
	 * @return string
121
	 */
122
	protected function buildPath($path) {
123
		$this->log('enter: '.__FUNCTION__."($path)");
124
		$result = Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
125
		return $this->leave(__FUNCTION__, $result);
126
	}
127
128
	/**
129
	 * @param string $path
130
	 * @return \Icewind\SMB\IFileInfo
131
	 * @throws StorageNotAvailableException
132
	 * @throws ForbiddenException
133
	 * @throws NotFoundException
134
	 */
135
	protected function getFileInfo($path) {
136
		$this->log('enter: '.__FUNCTION__."($path)");
137
		$path = $this->buildPath($path);
138
		if (!isset($this->statCache[$path])) {
139
			try {
140
				$this->log("stat fetching '$path'");
141
				try {
142
					$this->statCache[$path] = $this->share->stat($path);
143
				} catch (NotFoundException $e) {
144
					if ($this->share instanceof IShare) {
145
						// smbclient may have problems with the allinfo cmd
146
						$this->log("stat for '$path' failed, trying to read parent dir");
147
						$infos = $this->share->dir(\dirname($path));
148
						foreach ($infos as $fileInfo) {
149
							if ($fileInfo->getName() === \basename($path)) {
150
								$this->statCache[$path] = $fileInfo;
151
								break;
152
							}
153
						}
154
						if (empty($this->statCache[$path])) {
155
							$this->leave(__FUNCTION__, $e);
156
							throw $e;
157
						}
158
					} else {
159
						// trust the results of libsmb
160
						$this->leave(__FUNCTION__, $e);
161
						throw $e;
162
					}
163
				}
164
				if ($this->isRootDir($path) && $this->statCache[$path]->isHidden()) {
165
					$this->log("unhiding stat for '$path'");
166
					// make root never hidden, may happen when accessing a shared drive (mode is 22, archived and readonly - neither is true ... whatever)
167
					if ($this->statCache[$path]->isReadOnly()) {
168
						$mode = IFileInfo::MODE_DIRECTORY & IFileInfo::MODE_READONLY;
169
					} else {
170
						$mode = IFileInfo::MODE_DIRECTORY;
171
					}
172
					$this->statCache[$path] = new FileInfo($path, '', 0, $this->statCache[$path]->getMTime(), $mode);
173
				}
174
			} catch (ConnectException $e) {
175
				$ex = new StorageNotAvailableException(
176
						$e->getMessage(), $e->getCode(), $e);
177
				$this->leave(__FUNCTION__, $ex);
178
				throw $ex;
179
			} catch (ForbiddenException $e) {
180
				if ($this->remoteIsShare() && $this->isRootDir($path)) { //mtime may not work for share root
181
					$this->log("faking stat for forbidden '$path'");
182
					$this->statCache[$path] = new FileInfo($path, '', 0, $this->shareMTime(), IFileInfo::MODE_DIRECTORY);
183
				} else {
184
					$this->leave(__FUNCTION__, $e);
185
					throw $e;
186
				}
187
			}
188
		} else {
189
			$this->log("stat cache hit for '$path'");
190
		}
191
		$result = $this->statCache[$path];
192
		return $this->leave(__FUNCTION__, $result);
193
	}
194
195
	/**
196
	 * @param string $path
197
	 * @return \Icewind\SMB\IFileInfo[]
198
	 * @throws StorageNotAvailableException
199
	 */
200
	protected function getFolderContents($path) {
201
		$this->log('enter: '.__FUNCTION__."($path)");
202
		try {
203
			$path = $this->buildPath($path);
204
			$result = [];
205
			$children = $this->share->dir($path);
206
			foreach ($children as $fileInfo) {
207
				// check if the file is readable before adding it to the list
208
				// can't use "isReadable" function here, use smb internals instead
209
				try {
210
					if ($fileInfo->isHidden()) {
211
						$this->log("{$fileInfo->getName()} isn't readable, skipping", Util::DEBUG);
212
					} else {
213
						$result[] = $fileInfo;
214
						//remember entry so we can answer file_exists and filetype without a full stat
215
						$this->statCache[$path . '/' . $fileInfo->getName()] = $fileInfo;
216
					}
217
				} catch (NotFoundException $e) {
218
					$this->swallow(__FUNCTION__, $e);
219
				}
220
			}
221
		} catch (ConnectException $e) {
222
			$ex = new StorageNotAvailableException(
223
				$e->getMessage(), $e->getCode(), $e);
224
			$this->leave(__FUNCTION__, $ex);
225
			throw $ex;
226
		}
227
		return $this->leave(__FUNCTION__, $result);
228
	}
229
230
	/**
231
	 * @param \Icewind\SMB\IFileInfo $info
232
	 * @return array
233
	 */
234
	protected function formatInfo($info) {
235
		$result = [
236
			'size' => $info->getSize(),
237
			'mtime' => $info->getMTime(),
238
		];
239
		if ($info->isDirectory()) {
240
			$result['type'] = 'dir';
241
		} else {
242
			$result['type'] = 'file';
243
		}
244
		return $result;
245
	}
246
247
	/**
248
	 * Rename the files. If the source or the target is the root, the rename won't happen.
249
	 *
250
	 * @param string $source the old name of the path
251
	 * @param string $target the new name of the path
252
	 * @return bool true if the rename is successful, false otherwise
253
	 */
254
	public function rename($source, $target) {
255
		$this->log("enter: rename('$source', '$target')", Util::DEBUG);
256
257
		if ($this->isRootDir($source) || $this->isRootDir($target)) {
258
			$this->log("refusing to rename \"$source\" to \"$target\"");
259
			return $this->leave(__FUNCTION__, false);
260
		}
261
262
		try {
263
			$result = $this->share->rename($this->root . $source, $this->root . $target);
264
			if ($result) {
265
				$this->removeFromCache($this->root . $source);
266
				$this->removeFromCache($this->root . $target);
267
			}
268
		} catch (AlreadyExistsException $e) {
0 ignored issues
show
Bug introduced by
The class Icewind\SMB\Exception\AlreadyExistsException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
269
			$this->swallow(__FUNCTION__, $e);
270 View Code Duplication
			if ($this->unlink($target)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
271
				$result = $this->share->rename($this->root . $source, $this->root . $target);
272
				if ($result) {
273
					$this->removeFromCache($this->root . $source);
274
					$this->removeFromCache($this->root . $target);
275
				}
276
			} else {
277
				$result = false;
278
			}
279
		} catch (Exception $e) {
280
			$this->swallow(__FUNCTION__, $e);
281
			// Icewind\SMB\Exception\Exception, not a plain exception
282
			if ($e->getCode() === 22) {
283 View Code Duplication
				if ($this->unlink($target)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
284
					$result = $this->share->rename($this->root . $source, $this->root . $target);
285
					if ($result) {
286
						$this->removeFromCache($this->root . $source);
287
						$this->removeFromCache($this->root . $target);
288
					}
289
				} else {
290
					$result = false;
291
				}
292 View Code Duplication
			} else if ($e->getCode() === 16) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
293
				$this->swallow(__FUNCTION__, $e);
294
				$result = false;
295
			} else {
296
				$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
297
				$this->leave(__FUNCTION__, $ex);
298
				throw $ex;
299
			}
300
		} catch (\Exception $e) {
301
			$this->swallow(__FUNCTION__, $e);
302
			$result = false;
303
		}
304
		return $this->leave(__FUNCTION__, $result);
305
	}
306
307
	private function removeFromCache($path) {
308
		$path = \trim($path, '/');
309
		// TODO The CappedCache does not really clear by prefix. It just clears all.
310
		//$this->dirCache->clear($path);
311
		$this->statCache->clear($path);
312
		//$this->xattrCache->clear($path);
313
	}
314
	/**
315
	 * @param string $path
316
	 * @return array
317
	 */
318
	public function stat($path) {
319
		$this->log('enter: '.__FUNCTION__."($path)");
320
		try {
321
			$result = $this->formatInfo($this->getFileInfo($path));
322
		} catch (NotFoundException $e) {
323
			$this->swallow(__FUNCTION__, $e);
324
			$result = false;
325
		} catch (Exception $e) {
326
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
327
			$this->leave(__FUNCTION__, $ex);
328
			throw $ex;
329
		}
330
		return $this->leave(__FUNCTION__, $result);
331
	}
332
333
	/**
334
	 * get the best guess for the modification time of the share
335
	 * NOTE: modification times do not bubble up the directory tree, basically
336
	 * we are just guessing a time
337
	 *
338
	 * @return int the calculated mtime for the folder
339
	 */
340
	private function shareMTime() {
341
		$this->log('enter: '.__FUNCTION__, Util::DEBUG);
342
		$files = $this->share->dir($this->root);
343
		$result = 0;
344
		foreach ($files as $fileInfo) {
345
			if ($fileInfo->getMTime() > $result) {
346
				$result = $fileInfo->getMTime();
347
			}
348
		}
349
		return $this->leave(__FUNCTION__, $result);
350
	}
351
	/**
352
	 * Check if the path is our root dir (not the smb one)
353
	 *
354
	 * @param string $path the path
355
	 * @return bool true if it's root, false if not
356
	 */
357
	private function isRootDir($path) {
358
		$this->log('enter: '.__FUNCTION__."($path)", Util::DEBUG);
359
		$result = $path === '' || $path === '/' || $path === '.';
360
		return $this->leave(__FUNCTION__, $result);
361
	}
362
	/**
363
	 * Check if our root points to a smb share
364
	 *
365
	 * @return bool true if our root points to a share false otherwise
366
	 */
367
	private function remoteIsShare() {
368
		$this->log('enter: '.__FUNCTION__, Util::DEBUG);
369
		$result = $this->share->getName() && (!$this->root || $this->root === '/');
370
		return $this->leave(__FUNCTION__, $result);
371
	}
372
	/**
373
	 * @param string $path
374
	 * @return bool
375
	 * @throws StorageNotAvailableException
376
	 */
377
	public function unlink($path) {
378
		$this->log('enter: '.__FUNCTION__."($path)");
379
380
		if ($this->isRootDir($path)) {
381
			$this->log("refusing to unlink \"$path\"");
382
			return $this->leave(__FUNCTION__, false);
383
		}
384
385
		$result = false;
386
		try {
387
			if ($this->is_dir($path)) {
388
				$result = $this->rmdir($path);
389
			} else {
390
				$path = $this->buildPath($path);
391
				$this->share->del($path);
392
				unset($this->statCache[$path]);
393
				$result = true;
394
			}
395
		} catch (NotFoundException $e) {
396
			$this->swallow(__FUNCTION__, $e);
397
		} catch (ForbiddenException $e) {
398
			$this->swallow(__FUNCTION__, $e);
399
		} catch (Exception $e) {
400 View Code Duplication
			if ($e->getCode() === 16) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
401
				$this->swallow(__FUNCTION__, $e);
402
			} else {
403
				$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
404
				$this->leave(__FUNCTION__, $ex);
405
				throw $ex;
406
			}
407
		}
408
		return $this->leave(__FUNCTION__, $result);
409
	}
410
411
	/**
412
	 * check if a file or folder has been updated since $time
413
	 *
414
	 * @param string $path
415
	 * @param int $time
416
	 * @return bool
417
	 */
418
	public function hasUpdated($path, $time) {
419
		$this->log('enter: '.__FUNCTION__."($path, $time)");
420
		$actualTime = $this->filemtime($path);
421
		$result = $actualTime > $time;
422
		return $this->leave(__FUNCTION__, $result);
423
	}
424
425
	/**
426
	 * @param string $path
427
	 * @param string $mode
428
	 * @return resource
429
	 * @throws StorageNotAvailableException
430
	 */
431
	public function fopen($path, $mode) {
432
		$this->log('enter: '.__FUNCTION__."($path, $mode)");
433
		$fullPath = $this->buildPath($path);
434
		$result = false;
435
		try {
436
			switch ($mode) {
437
				case 'r':
438
				case 'rb':
439
					if ($this->file_exists($path)) {
440
						$result = $this->share->read($fullPath);
441
					}
442
					break;
443
				case 'w':
444
				case 'wb':
445
					$source = $this->share->write($fullPath);
446
					$result = CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
447
						unset($this->statCache[$fullPath]);
448
					});
449
					break;
450
				case 'a':
451
				case 'ab':
452
				case 'r+':
453
				case 'w+':
454
				case 'wb+':
455
				case 'a+':
456
				case 'x':
457
				case 'x+':
458
				case 'c':
459
				case 'c+':
460
					//emulate these
461
					if (\strrpos($path, '.') !== false) {
462
						$ext = \substr($path, \strrpos($path, '.'));
463
					} else {
464
						$ext = '';
465
					}
466
					if ($this->file_exists($path)) {
467
						if (!$this->isUpdatable($path)) {
468
							break;
469
						}
470
						$tmpFile = $this->getCachedFile($path);
471
					} else {
472
						if (!$this->isCreatable(\dirname($path))) {
473
							break;
474
						}
475
						$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
476
					}
477
					$source = \fopen($tmpFile, $mode);
478
					$share = $this->share;
479
					$result = CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
480
						unset($this->statCache[$fullPath]);
481
						$share->put($tmpFile, $fullPath);
482
						\unlink($tmpFile);
483
					});
484
			}
485
		} catch (NotFoundException $e) {
486
			$this->swallow(__FUNCTION__, $e);
487
		} catch (ForbiddenException $e) {
488
			$this->swallow(__FUNCTION__, $e);
489
		} catch (Exception $e) {
490 View Code Duplication
			if ($e->getCode() === 16) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
491
				$this->swallow(__FUNCTION__, $e);
492
			} else {
493
				$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
494
				$this->leave(__FUNCTION__, $ex);
495
				throw $ex;
496
			}
497
		}
498
		return $this->leave(__FUNCTION__, $result);
499
	}
500
501
	public function rmdir($path) {
502
		$this->log('enter: '.__FUNCTION__."($path)");
503
504
		if ($this->isRootDir($path)) {
505
			$this->log("refusing to delete \"$path\"");
506
			return $this->leave(__FUNCTION__, false);
507
		}
508
509
		$result = false;
510
		try {
511
			$this->removeFromCache($path);
512
			$content = $this->share->dir($this->buildPath($path));
513
			foreach ($content as $file) {
514
				if ($file->isDirectory()) {
515
					$this->rmdir($path . '/' . $file->getName());
516
				} else {
517
					$this->share->del($file->getPath());
518
				}
519
			}
520
			$this->share->rmdir($this->buildPath($path));
521
			$result = true;
522
		} catch (NotFoundException $e) {
523
			$this->swallow(__FUNCTION__, $e);
524
		} catch (ForbiddenException $e) {
525
			$this->swallow(__FUNCTION__, $e);
526
		} catch (Exception $e) {
527 View Code Duplication
			if ($e->getCode() === 16) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
528
				$this->swallow(__FUNCTION__, $e);
529
			} else {
530
				$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
531
				$this->leave(__FUNCTION__, $ex);
532
				throw $ex;
533
			}
534
		}
535
		return $this->leave(__FUNCTION__, $result);
536
	}
537
538
	public function touch($path, $time = null) {
539
		$this->log('enter: '.__FUNCTION__."($path, $time)");
540
		$result = false;
541
		try {
542
			if (!$this->file_exists($path)) {
543
				$fh = $this->share->write($this->buildPath($path));
544
				\fclose($fh);
545
				$result = true;
546
			}		} catch (Exception $e) {
547 View Code Duplication
			if ($e->getCode() === 16) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
548
				$this->swallow(__FUNCTION__, $e);
549
			} else {
550
				$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
551
				$this->leave(__FUNCTION__, $ex);
552
				throw $ex;
553
			}
554
		}
555
		return $this->leave(__FUNCTION__, $result);
556
	}
557
558
	public function opendir($path) {
559
		$this->log('enter: '.__FUNCTION__."($path)");
560
		$result = false;
561
		try {
562
			$files = $this->getFolderContents($path);
563
			$names = \array_map(function ($info) {
564
				/** @var \Icewind\SMB\IFileInfo $info */
565
				return $info->getName();
566
			}, $files);
567
			$result = IteratorDirectory::wrap($names);
568
		} catch (NotFoundException $e) {
569
			$this->swallow(__FUNCTION__, $e);
570
		} catch (ForbiddenException $e) {
571
			$this->swallow(__FUNCTION__, $e);
572
		} catch (Exception $e) {
573
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
574
			$this->leave(__FUNCTION__, $ex);
575
			throw $ex;
576
		}
577
		return $this->leave(__FUNCTION__, $result);
578
	}
579
580
	public function filetype($path) {
581
		$this->log('enter: '.__FUNCTION__."($path)");
582
		$result = false;
583
		try {
584
			$result = $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
585
		} catch (NotFoundException $e) {
586
			$this->swallow(__FUNCTION__, $e);
587
		} catch (ForbiddenException $e) {
588
			$this->swallow(__FUNCTION__, $e);
589
		} catch (Exception $e) {
590
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
591
			$this->leave(__FUNCTION__, $ex);
592
			throw $ex;
593
		}
594
		return $this->leave(__FUNCTION__, $result);
595
	}
596
597
	public function mkdir($path) {
598
		$this->log('enter: '.__FUNCTION__."($path)");
599
		$result = false;
600
		$path = $this->buildPath($path);
601
		try {
602
			$result = $this->share->mkdir($path);
603
		} catch (ForbiddenException $e) {
604
			$this->swallow(__FUNCTION__, $e);
605
		} catch (AlreadyExistsException $e) {
0 ignored issues
show
Bug introduced by
The class Icewind\SMB\Exception\AlreadyExistsException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
606
			$this->swallow(__FUNCTION__, $e);
607
		} catch (Exception $e) {
608 View Code Duplication
			if ($e->getCode() === 16) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
609
				$this->swallow(__FUNCTION__, $e);
610
			} else {
611
				$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
612
				$this->leave(__FUNCTION__, $ex);
613
				throw $ex;
614
			}
615
		}
616
		return $this->leave(__FUNCTION__, $result);
617
	}
618
619
	public function file_exists($path) {
620
		$this->log('enter: '.__FUNCTION__."($path)");
621
		$result = false;
622
		try {
623
			$this->getFileInfo($path);
624
			$result = true;
625
		} catch (NotFoundException $e) {
626
			$this->swallow(__FUNCTION__, $e);
627
		} catch (ForbiddenException $e) {
628
			$this->swallow(__FUNCTION__, $e);
629
		} catch (Exception $e) {
630
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
631
			$this->leave(__FUNCTION__, $ex);
632
			throw $ex;
633
		}
634
		return $this->leave(__FUNCTION__, $result);
635
	}
636
637
	public function isReadable($path) {
638
		$this->log('enter: '.__FUNCTION__."($path)");
639
		$result = false;
640
		try {
641
			$info = $this->getFileInfo($path);
642
			$result = !$info->isHidden();
643
		} catch (NotFoundException $e) {
644
			$this->swallow(__FUNCTION__, $e);
645
		} catch (ForbiddenException $e) {
646
			$this->swallow(__FUNCTION__, $e);
647
		} catch (Exception $e) {
648
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
649
			$this->leave(__FUNCTION__, $ex);
650
			throw $ex;
651
		}
652
		return $this->leave(__FUNCTION__, $result);
653
	}
654
655 View Code Duplication
	public function isUpdatable($path) {
656
		$this->log('enter: '.__FUNCTION__."($path)");
657
		$result = false;
658
		try {
659
			$info = $this->getFileInfo($path);
660
			// following windows behaviour for read-only folders: they can be written into
661
			// (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
662
			$result = !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path));
663
		} catch (NotFoundException $e) {
664
			$this->swallow(__FUNCTION__, $e);
665
		} catch (ForbiddenException $e) {
666
			$this->swallow(__FUNCTION__, $e);
667
		} catch (Exception $e) {
668
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
669
			$this->leave(__FUNCTION__, $ex);
670
			throw $ex;
671
		}
672
		return $this->leave(__FUNCTION__, $result);
673
	}
674
675 View Code Duplication
	public function isDeletable($path) {
676
		$this->log('enter: '.__FUNCTION__."($path)");
677
		$result = false;
678
		try {
679
			$info = $this->getFileInfo($path);
680
			$result = !$info->isHidden() && !$info->isReadOnly();
681
		} catch (NotFoundException $e) {
682
			$this->swallow(__FUNCTION__, $e);
683
		} catch (ForbiddenException $e) {
684
			$this->swallow(__FUNCTION__, $e);
685
		} catch (Exception $e) {
686
			$ex = new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
687
			$this->leave(__FUNCTION__, $ex);
688
			throw $ex;
689
		}
690
		return $this->leave(__FUNCTION__, $result);
691
	}
692
693
	/**
694
	 * check if smbclient is installed
695
	 */
696
	public static function checkDependencies() {
697
		return (
698
			(bool)\OC_Helper::findBinaryPath('smbclient')
699
			|| NativeServer::available(new System())
700
		) ? true : ['smbclient'];
701
	}
702
703
	/**
704
	 * Test a storage for availability
705
	 *
706
	 * @return bool
707
	 */
708
	public function test() {
709
		$this->log('enter: '.__FUNCTION__."()");
710
		$result = false;
711
		try {
712
			$result = parent::test();
713
		} catch (Exception $e) {
714
			$this->swallow(__FUNCTION__, $e);
715
		}
716
		return $this->leave(__FUNCTION__, $result);
717
	}
718
719
	/**
720
	 * @param string $message
721
	 * @param int $level
722
	 * @param string $from
723
	 */
724
	private function log($message, $level = Util::DEBUG, $from = 'smb') {
725
		if (\OC::$server->getConfig()->getSystemValue('smb.logging.enable', false) === true) {
726
			Util::writeLog($from, $message, $level);
727
		}
728
	}
729
730
	/**
731
	 * if smb.logging.enable is set to true in the config will log a leave line
732
	 * with the given function, the return value or the exception
733
	 *
734
	 * @param $function
735
	 * @param mixed $result an exception will be logged and then returned
736
	 * @return mixed
737
	 */
738
	private function leave($function, $result) {
739
		if (\OC::$server->getConfig()->getSystemValue('smb.logging.enable', false) === false) {
740
			//don't bother building log strings
741
			return $result;
742
		} elseif ($result === true) {
743
			Util::writeLog('smb', "leave: $function, return true", Util::DEBUG);
744
		} elseif ($result === false) {
745
			Util::writeLog('smb', "leave: $function, return false", Util::DEBUG);
746
		} elseif (\is_string($result)) {
747
			Util::writeLog('smb', "leave: $function, return '$result'", Util::DEBUG);
748
		} elseif (\is_resource($result)) {
749
			Util::writeLog('smb', "leave: $function, return resource", Util::DEBUG);
750
		} elseif ($result instanceof \Exception) {
751
			Util::writeLog('smb', "leave: $function, throw ".\get_class($result)
752
				.' - code: '.$result->getCode()
753
				.' message: '.$result->getMessage()
754
				.' trace: '.$result->getTraceAsString(), Util::DEBUG);
755
		} else {
756
			Util::writeLog('smb', "leave: $function, return ".\json_encode($result, true), Util::DEBUG);
757
		}
758
		return $result;
759
	}
760
761
	private function swallow($function, \Exception $exception) {
762
		if (\OC::$server->getConfig()->getSystemValue('smb.logging.enable', false) === true) {
763
			Util::writeLog('smb', "$function swallowing ".\get_class($exception)
764
				.' - code: '.$exception->getCode()
765
				.' message: '.$exception->getMessage()
766
				.' trace: '.$exception->getTraceAsString(), Util::DEBUG);
767
		}
768
	}
769
770
	/**
771
	 * immediately close / free connection
772
	 */
773
	public function __destruct() {
774
		unset($this->share);
775
	}
776
}
777