Passed
Push — master ( 8c5bb2...4a8871 )
by Blizzz
11:11 queued 11s
created
apps/files_external/lib/Lib/Backend/SMB.php 1 patch
Indentation   +52 added lines, -52 removed lines patch added patch discarded remove patch
@@ -38,59 +38,59 @@
 block discarded – undo
38 38
 use OCP\IUser;
39 39
 
40 40
 class SMB extends Backend {
41
-	use LegacyDependencyCheckPolyfill;
41
+    use LegacyDependencyCheckPolyfill;
42 42
 
43
-	public function __construct(IL10N $l, Password $legacyAuth) {
44
-		$this
45
-			->setIdentifier('smb')
46
-			->addIdentifierAlias('\OC\Files\Storage\SMB')// legacy compat
47
-			->setStorageClass('\OCA\Files_External\Lib\Storage\SMB')
48
-			->setText($l->t('SMB / CIFS'))
49
-			->addParameters([
50
-				new DefinitionParameter('host', $l->t('Host')),
51
-				new DefinitionParameter('share', $l->t('Share')),
52
-				(new DefinitionParameter('root', $l->t('Remote subfolder')))
53
-					->setFlag(DefinitionParameter::FLAG_OPTIONAL),
54
-				(new DefinitionParameter('domain', $l->t('Domain')))
55
-					->setFlag(DefinitionParameter::FLAG_OPTIONAL),
56
-				(new DefinitionParameter('show_hidden', $l->t('Show hidden files')))
57
-					->setType(DefinitionParameter::VALUE_BOOLEAN)
58
-					->setFlag(DefinitionParameter::FLAG_OPTIONAL),
59
-				(new DefinitionParameter('check_acl', $l->t('Verify ACL access when listing files')))
60
-					->setType(DefinitionParameter::VALUE_BOOLEAN)
61
-					->setFlag(DefinitionParameter::FLAG_OPTIONAL)
62
-					->setTooltip($l->t("Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty")),
63
-				(new DefinitionParameter('timeout', $l->t('Timeout')))
64
-					->setType(DefinitionParameter::VALUE_HIDDEN)
65
-					->setFlag(DefinitionParameter::FLAG_OPTIONAL),
66
-			])
67
-			->addAuthScheme(AuthMechanism::SCHEME_PASSWORD)
68
-			->addAuthScheme(AuthMechanism::SCHEME_SMB)
69
-			->setLegacyAuthMechanism($legacyAuth);
70
-	}
43
+    public function __construct(IL10N $l, Password $legacyAuth) {
44
+        $this
45
+            ->setIdentifier('smb')
46
+            ->addIdentifierAlias('\OC\Files\Storage\SMB')// legacy compat
47
+            ->setStorageClass('\OCA\Files_External\Lib\Storage\SMB')
48
+            ->setText($l->t('SMB / CIFS'))
49
+            ->addParameters([
50
+                new DefinitionParameter('host', $l->t('Host')),
51
+                new DefinitionParameter('share', $l->t('Share')),
52
+                (new DefinitionParameter('root', $l->t('Remote subfolder')))
53
+                    ->setFlag(DefinitionParameter::FLAG_OPTIONAL),
54
+                (new DefinitionParameter('domain', $l->t('Domain')))
55
+                    ->setFlag(DefinitionParameter::FLAG_OPTIONAL),
56
+                (new DefinitionParameter('show_hidden', $l->t('Show hidden files')))
57
+                    ->setType(DefinitionParameter::VALUE_BOOLEAN)
58
+                    ->setFlag(DefinitionParameter::FLAG_OPTIONAL),
59
+                (new DefinitionParameter('check_acl', $l->t('Verify ACL access when listing files')))
60
+                    ->setType(DefinitionParameter::VALUE_BOOLEAN)
61
+                    ->setFlag(DefinitionParameter::FLAG_OPTIONAL)
62
+                    ->setTooltip($l->t("Check the ACL's of each file or folder inside a directory to filter out items where the user has no read permissions, comes with a performance penalty")),
63
+                (new DefinitionParameter('timeout', $l->t('Timeout')))
64
+                    ->setType(DefinitionParameter::VALUE_HIDDEN)
65
+                    ->setFlag(DefinitionParameter::FLAG_OPTIONAL),
66
+            ])
67
+            ->addAuthScheme(AuthMechanism::SCHEME_PASSWORD)
68
+            ->addAuthScheme(AuthMechanism::SCHEME_SMB)
69
+            ->setLegacyAuthMechanism($legacyAuth);
70
+    }
71 71
 
72
-	/**
73
-	 * @param StorageConfig $storage
74
-	 * @param IUser $user
75
-	 */
76
-	public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) {
77
-		$auth = $storage->getAuthMechanism();
78
-		if ($auth->getScheme() === AuthMechanism::SCHEME_PASSWORD) {
79
-			$smbAuth = new BasicAuth(
80
-				$storage->getBackendOption('user'),
81
-				$storage->getBackendOption('domain'),
82
-				$storage->getBackendOption('password')
83
-			);
84
-		} else {
85
-			switch ($auth->getIdentifier()) {
86
-				case 'smb::kerberos':
87
-					$smbAuth = new KerberosAuth();
88
-					break;
89
-				default:
90
-					throw new \InvalidArgumentException('unknown authentication backend');
91
-			}
92
-		}
72
+    /**
73
+     * @param StorageConfig $storage
74
+     * @param IUser $user
75
+     */
76
+    public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) {
77
+        $auth = $storage->getAuthMechanism();
78
+        if ($auth->getScheme() === AuthMechanism::SCHEME_PASSWORD) {
79
+            $smbAuth = new BasicAuth(
80
+                $storage->getBackendOption('user'),
81
+                $storage->getBackendOption('domain'),
82
+                $storage->getBackendOption('password')
83
+            );
84
+        } else {
85
+            switch ($auth->getIdentifier()) {
86
+                case 'smb::kerberos':
87
+                    $smbAuth = new KerberosAuth();
88
+                    break;
89
+                default:
90
+                    throw new \InvalidArgumentException('unknown authentication backend');
91
+            }
92
+        }
93 93
 
94
-		$storage->setBackendOption('auth', $smbAuth);
95
-	}
94
+        $storage->setBackendOption('auth', $smbAuth);
95
+    }
96 96
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Lib/Storage/SMB.php 2 patches
Indentation   +651 added lines, -651 removed lines patch added patch discarded remove patch
@@ -65,655 +65,655 @@
 block discarded – undo
65 65
 use OCP\ILogger;
66 66
 
67 67
 class SMB extends Common implements INotifyStorage {
68
-	/**
69
-	 * @var \Icewind\SMB\IServer
70
-	 */
71
-	protected $server;
72
-
73
-	/**
74
-	 * @var \Icewind\SMB\IShare
75
-	 */
76
-	protected $share;
77
-
78
-	/**
79
-	 * @var string
80
-	 */
81
-	protected $root;
82
-
83
-	/**
84
-	 * @var \Icewind\SMB\IFileInfo[]
85
-	 */
86
-	protected $statCache;
87
-
88
-	/** @var ILogger */
89
-	protected $logger;
90
-
91
-	/** @var bool */
92
-	protected $showHidden;
93
-
94
-	/** @var bool */
95
-	protected $checkAcl;
96
-
97
-	public function __construct($params) {
98
-		if (!isset($params['host'])) {
99
-			throw new \Exception('Invalid configuration, no host provided');
100
-		}
101
-
102
-		if (isset($params['auth'])) {
103
-			$auth = $params['auth'];
104
-		} elseif (isset($params['user']) && isset($params['password']) && isset($params['share'])) {
105
-			[$workgroup, $user] = $this->splitUser($params['user']);
106
-			$auth = new BasicAuth($user, $workgroup, $params['password']);
107
-		} else {
108
-			throw new \Exception('Invalid configuration, no credentials provided');
109
-		}
110
-
111
-		if (isset($params['logger'])) {
112
-			$this->logger = $params['logger'];
113
-		} else {
114
-			$this->logger = \OC::$server->getLogger();
115
-		}
116
-
117
-		$options = new Options();
118
-		if (isset($params['timeout'])) {
119
-			$timeout = (int)$params['timeout'];
120
-			if ($timeout > 0) {
121
-				$options->setTimeout($timeout);
122
-			}
123
-		}
124
-		$serverFactory = new ServerFactory($options);
125
-		$this->server = $serverFactory->createServer($params['host'], $auth);
126
-		$this->share = $this->server->getShare(trim($params['share'], '/'));
127
-
128
-		$this->root = $params['root'] ?? '/';
129
-		$this->root = '/' . ltrim($this->root, '/');
130
-		$this->root = rtrim($this->root, '/') . '/';
131
-
132
-		$this->showHidden = isset($params['show_hidden']) && $params['show_hidden'];
133
-		$this->checkAcl = isset($params['check_acl']) && $params['check_acl'];
134
-
135
-		$this->statCache = new CappedMemoryCache();
136
-		parent::__construct($params);
137
-	}
138
-
139
-	private function splitUser($user) {
140
-		if (strpos($user, '/')) {
141
-			return explode('/', $user, 2);
142
-		} elseif (strpos($user, '\\')) {
143
-			return explode('\\', $user);
144
-		} else {
145
-			return [null, $user];
146
-		}
147
-	}
148
-
149
-	/**
150
-	 * @return string
151
-	 */
152
-	public function getId() {
153
-		// FIXME: double slash to keep compatible with the old storage ids,
154
-		// failure to do so will lead to creation of a new storage id and
155
-		// loss of shares from the storage
156
-		return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
157
-	}
158
-
159
-	/**
160
-	 * @param string $path
161
-	 * @return string
162
-	 */
163
-	protected function buildPath($path) {
164
-		return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
165
-	}
166
-
167
-	protected function relativePath($fullPath) {
168
-		if ($fullPath === $this->root) {
169
-			return '';
170
-		} elseif (substr($fullPath, 0, strlen($this->root)) === $this->root) {
171
-			return substr($fullPath, strlen($this->root));
172
-		} else {
173
-			return null;
174
-		}
175
-	}
176
-
177
-	/**
178
-	 * @param string $path
179
-	 * @return \Icewind\SMB\IFileInfo
180
-	 * @throws StorageAuthException
181
-	 */
182
-	protected function getFileInfo($path) {
183
-		try {
184
-			$path = $this->buildPath($path);
185
-			if (!isset($this->statCache[$path])) {
186
-				$this->statCache[$path] = $this->share->stat($path);
187
-			}
188
-			return $this->statCache[$path];
189
-		} catch (ConnectException $e) {
190
-			$this->throwUnavailable($e);
191
-		} catch (ForbiddenException $e) {
192
-			// with php-smbclient, this exceptions is thrown when the provided password is invalid.
193
-			// Possible is also ForbiddenException with a different error code, so we check it.
194
-			if ($e->getCode() === 1) {
195
-				$this->throwUnavailable($e);
196
-			}
197
-			throw $e;
198
-		}
199
-	}
200
-
201
-	/**
202
-	 * @param \Exception $e
203
-	 * @throws StorageAuthException
204
-	 */
205
-	protected function throwUnavailable(\Exception $e) {
206
-		$this->logger->logException($e, ['message' => 'Error while getting file info']);
207
-		throw new StorageAuthException($e->getMessage(), $e);
208
-	}
209
-
210
-	/**
211
-	 * get the acl from fileinfo that is relevant for the configured user
212
-	 *
213
-	 * @param IFileInfo $file
214
-	 * @return ACL|null
215
-	 */
216
-	private function getACL(IFileInfo $file): ?ACL {
217
-		$acls = $file->getAcls();
218
-		foreach ($acls as $user => $acl) {
219
-			[, $user] = explode('\\', $user); // strip domain
220
-			if ($user === $this->server->getAuth()->getUsername()) {
221
-				return $acl;
222
-			}
223
-		}
224
-
225
-		return null;
226
-	}
227
-
228
-	/**
229
-	 * @param string $path
230
-	 * @return \Icewind\SMB\IFileInfo[]
231
-	 * @throws StorageNotAvailableException
232
-	 */
233
-	protected function getFolderContents($path): iterable {
234
-		try {
235
-			$path = ltrim($this->buildPath($path), '/');
236
-			$files = $this->share->dir($path);
237
-			foreach ($files as $file) {
238
-				$this->statCache[$path . '/' . $file->getName()] = $file;
239
-			}
240
-
241
-			foreach ($files as $file) {
242
-				try {
243
-					// the isHidden check is done before checking the config boolean to ensure that the metadata is always fetch
244
-					// so we trigger the below exceptions where applicable
245
-					$hide = $file->isHidden() && !$this->showHidden;
246
-
247
-					if ($this->checkAcl && $acl = $this->getACL($file)) {
248
-						// if there is no explicit deny, we assume it's allowed
249
-						// this doesn't take inheritance fully into account but if read permissions is denied for a parent we wouldn't be in this folder
250
-						// additionally, it's better to have false negatives here then false positives
251
-						if ($acl->denies(ACL::MASK_READ) || $acl->denies(ACL::MASK_EXECUTE)) {
252
-							$this->logger->debug('Hiding non readable entry ' . $file->getName());
253
-							return false;
254
-						}
255
-					}
256
-
257
-					if ($hide) {
258
-						$this->logger->debug('hiding hidden file ' . $file->getName());
259
-					}
260
-					if (!$hide) {
261
-						yield $file;
262
-					}
263
-				} catch (ForbiddenException $e) {
264
-					$this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding forbidden entry ' . $file->getName()]);
265
-				} catch (NotFoundException $e) {
266
-					$this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding not found entry ' . $file->getName()]);
267
-				}
268
-			}
269
-		} catch (ConnectException $e) {
270
-			$this->logger->logException($e, ['message' => 'Error while getting folder content']);
271
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
272
-		}
273
-	}
274
-
275
-	/**
276
-	 * @param \Icewind\SMB\IFileInfo $info
277
-	 * @return array
278
-	 */
279
-	protected function formatInfo($info) {
280
-		$result = [
281
-			'size' => $info->getSize(),
282
-			'mtime' => $info->getMTime(),
283
-		];
284
-		if ($info->isDirectory()) {
285
-			$result['type'] = 'dir';
286
-		} else {
287
-			$result['type'] = 'file';
288
-		}
289
-		return $result;
290
-	}
291
-
292
-	/**
293
-	 * Rename the files. If the source or the target is the root, the rename won't happen.
294
-	 *
295
-	 * @param string $source the old name of the path
296
-	 * @param string $target the new name of the path
297
-	 * @return bool true if the rename is successful, false otherwise
298
-	 */
299
-	public function rename($source, $target, $retry = true) {
300
-		if ($this->isRootDir($source) || $this->isRootDir($target)) {
301
-			return false;
302
-		}
303
-
304
-		$absoluteSource = $this->buildPath($source);
305
-		$absoluteTarget = $this->buildPath($target);
306
-		try {
307
-			$result = $this->share->rename($absoluteSource, $absoluteTarget);
308
-		} catch (AlreadyExistsException $e) {
309
-			if ($retry) {
310
-				$this->remove($target);
311
-				$result = $this->share->rename($absoluteSource, $absoluteTarget, false);
312
-			} else {
313
-				$this->logger->logException($e, ['level' => ILogger::WARN]);
314
-				return false;
315
-			}
316
-		} catch (InvalidArgumentException $e) {
317
-			if ($retry) {
318
-				$this->remove($target);
319
-				$result = $this->share->rename($absoluteSource, $absoluteTarget, false);
320
-			} else {
321
-				$this->logger->logException($e, ['level' => ILogger::WARN]);
322
-				return false;
323
-			}
324
-		} catch (\Exception $e) {
325
-			$this->logger->logException($e, ['level' => ILogger::WARN]);
326
-			return false;
327
-		}
328
-		unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
329
-		return $result;
330
-	}
331
-
332
-	public function stat($path, $retry = true) {
333
-		try {
334
-			$result = $this->formatInfo($this->getFileInfo($path));
335
-		} catch (ForbiddenException $e) {
336
-			return false;
337
-		} catch (NotFoundException $e) {
338
-			return false;
339
-		} catch (TimedOutException $e) {
340
-			if ($retry) {
341
-				return $this->stat($path, false);
342
-			} else {
343
-				throw $e;
344
-			}
345
-		}
346
-		if ($this->remoteIsShare() && $this->isRootDir($path)) {
347
-			$result['mtime'] = $this->shareMTime();
348
-		}
349
-		return $result;
350
-	}
351
-
352
-	/**
353
-	 * get the best guess for the modification time of the share
354
-	 *
355
-	 * @return int
356
-	 */
357
-	private function shareMTime() {
358
-		$highestMTime = 0;
359
-		$files = $this->share->dir($this->root);
360
-		foreach ($files as $fileInfo) {
361
-			try {
362
-				if ($fileInfo->getMTime() > $highestMTime) {
363
-					$highestMTime = $fileInfo->getMTime();
364
-				}
365
-			} catch (NotFoundException $e) {
366
-				// Ignore this, can happen on unavailable DFS shares
367
-			} catch (ForbiddenException $e) {
368
-				// Ignore this too - it's a symlink
369
-			}
370
-		}
371
-		return $highestMTime;
372
-	}
373
-
374
-	/**
375
-	 * Check if the path is our root dir (not the smb one)
376
-	 *
377
-	 * @param string $path the path
378
-	 * @return bool
379
-	 */
380
-	private function isRootDir($path) {
381
-		return $path === '' || $path === '/' || $path === '.';
382
-	}
383
-
384
-	/**
385
-	 * Check if our root points to a smb share
386
-	 *
387
-	 * @return bool true if our root points to a share false otherwise
388
-	 */
389
-	private function remoteIsShare() {
390
-		return $this->share->getName() && (!$this->root || $this->root === '/');
391
-	}
392
-
393
-	/**
394
-	 * @param string $path
395
-	 * @return bool
396
-	 */
397
-	public function unlink($path) {
398
-		if ($this->isRootDir($path)) {
399
-			return false;
400
-		}
401
-
402
-		try {
403
-			if ($this->is_dir($path)) {
404
-				return $this->rmdir($path);
405
-			} else {
406
-				$path = $this->buildPath($path);
407
-				unset($this->statCache[$path]);
408
-				$this->share->del($path);
409
-				return true;
410
-			}
411
-		} catch (NotFoundException $e) {
412
-			return false;
413
-		} catch (ForbiddenException $e) {
414
-			return false;
415
-		} catch (ConnectException $e) {
416
-			$this->logger->logException($e, ['message' => 'Error while deleting file']);
417
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
418
-		}
419
-	}
420
-
421
-	/**
422
-	 * check if a file or folder has been updated since $time
423
-	 *
424
-	 * @param string $path
425
-	 * @param int $time
426
-	 * @return bool
427
-	 */
428
-	public function hasUpdated($path, $time) {
429
-		if (!$path and $this->root === '/') {
430
-			// mtime doesn't work for shares, but giving the nature of the backend,
431
-			// doing a full update is still just fast enough
432
-			return true;
433
-		} else {
434
-			$actualTime = $this->filemtime($path);
435
-			return $actualTime > $time;
436
-		}
437
-	}
438
-
439
-	/**
440
-	 * @param string $path
441
-	 * @param string $mode
442
-	 * @return resource|false
443
-	 */
444
-	public function fopen($path, $mode) {
445
-		$fullPath = $this->buildPath($path);
446
-		try {
447
-			switch ($mode) {
448
-				case 'r':
449
-				case 'rb':
450
-					if (!$this->file_exists($path)) {
451
-						return false;
452
-					}
453
-					return $this->share->read($fullPath);
454
-				case 'w':
455
-				case 'wb':
456
-					$source = $this->share->write($fullPath);
457
-					return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
458
-						unset($this->statCache[$fullPath]);
459
-					});
460
-				case 'a':
461
-				case 'ab':
462
-				case 'r+':
463
-				case 'w+':
464
-				case 'wb+':
465
-				case 'a+':
466
-				case 'x':
467
-				case 'x+':
468
-				case 'c':
469
-				case 'c+':
470
-					//emulate these
471
-					if (strrpos($path, '.') !== false) {
472
-						$ext = substr($path, strrpos($path, '.'));
473
-					} else {
474
-						$ext = '';
475
-					}
476
-					if ($this->file_exists($path)) {
477
-						if (!$this->isUpdatable($path)) {
478
-							return false;
479
-						}
480
-						$tmpFile = $this->getCachedFile($path);
481
-					} else {
482
-						if (!$this->isCreatable(dirname($path))) {
483
-							return false;
484
-						}
485
-						$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
486
-					}
487
-					$source = fopen($tmpFile, $mode);
488
-					$share = $this->share;
489
-					return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
490
-						unset($this->statCache[$fullPath]);
491
-						$share->put($tmpFile, $fullPath);
492
-						unlink($tmpFile);
493
-					});
494
-			}
495
-			return false;
496
-		} catch (NotFoundException $e) {
497
-			return false;
498
-		} catch (ForbiddenException $e) {
499
-			return false;
500
-		} catch (ConnectException $e) {
501
-			$this->logger->logException($e, ['message' => 'Error while opening file']);
502
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
503
-		}
504
-	}
505
-
506
-	public function rmdir($path) {
507
-		if ($this->isRootDir($path)) {
508
-			return false;
509
-		}
510
-
511
-		try {
512
-			$this->statCache = [];
513
-			$content = $this->share->dir($this->buildPath($path));
514
-			foreach ($content as $file) {
515
-				if ($file->isDirectory()) {
516
-					$this->rmdir($path . '/' . $file->getName());
517
-				} else {
518
-					$this->share->del($file->getPath());
519
-				}
520
-			}
521
-			$this->share->rmdir($this->buildPath($path));
522
-			return true;
523
-		} catch (NotFoundException $e) {
524
-			return false;
525
-		} catch (ForbiddenException $e) {
526
-			return false;
527
-		} catch (ConnectException $e) {
528
-			$this->logger->logException($e, ['message' => 'Error while removing folder']);
529
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
530
-		}
531
-	}
532
-
533
-	public function touch($path, $time = null) {
534
-		try {
535
-			if (!$this->file_exists($path)) {
536
-				$fh = $this->share->write($this->buildPath($path));
537
-				fclose($fh);
538
-				return true;
539
-			}
540
-			return false;
541
-		} catch (ConnectException $e) {
542
-			$this->logger->logException($e, ['message' => 'Error while creating file']);
543
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
544
-		}
545
-	}
546
-
547
-	public function getMetaData($path) {
548
-		$fileInfo = $this->getFileInfo($path);
549
-		if (!$fileInfo) {
550
-			return null;
551
-		}
552
-
553
-		return $this->getMetaDataFromFileInfo($fileInfo);
554
-	}
555
-
556
-	private function getMetaDataFromFileInfo(IFileInfo $fileInfo) {
557
-		$permissions = Constants::PERMISSION_READ + Constants::PERMISSION_SHARE;
558
-
559
-		if (!$fileInfo->isReadOnly()) {
560
-			$permissions += Constants::PERMISSION_DELETE;
561
-			$permissions += Constants::PERMISSION_UPDATE;
562
-			if ($fileInfo->isDirectory()) {
563
-				$permissions += Constants::PERMISSION_CREATE;
564
-			}
565
-		}
566
-
567
-		$data = [];
568
-		if ($fileInfo->isDirectory()) {
569
-			$data['mimetype'] = 'httpd/unix-directory';
570
-		} else {
571
-			$data['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($fileInfo->getPath());
572
-		}
573
-		$data['mtime'] = $fileInfo->getMTime();
574
-		if ($fileInfo->isDirectory()) {
575
-			$data['size'] = -1; //unknown
576
-		} else {
577
-			$data['size'] = $fileInfo->getSize();
578
-		}
579
-		$data['etag'] = $this->getETag($fileInfo->getPath());
580
-		$data['storage_mtime'] = $data['mtime'];
581
-		$data['permissions'] = $permissions;
582
-		$data['name'] = $fileInfo->getName();
583
-
584
-		return $data;
585
-	}
586
-
587
-	public function opendir($path) {
588
-		try {
589
-			$files = $this->getFolderContents($path);
590
-		} catch (NotFoundException $e) {
591
-			return false;
592
-		} catch (ForbiddenException $e) {
593
-			return false;
594
-		}
595
-		$names = array_map(function ($info) {
596
-			/** @var \Icewind\SMB\IFileInfo $info */
597
-			return $info->getName();
598
-		}, iterator_to_array($files));
599
-		return IteratorDirectory::wrap($names);
600
-	}
601
-
602
-	public function getDirectoryContent($directory): \Traversable {
603
-		$files = $this->getFolderContents($directory);
604
-		foreach ($files as $file) {
605
-			yield $this->getMetaDataFromFileInfo($file);
606
-		}
607
-	}
608
-
609
-	public function filetype($path) {
610
-		try {
611
-			return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
612
-		} catch (NotFoundException $e) {
613
-			return false;
614
-		} catch (ForbiddenException $e) {
615
-			return false;
616
-		}
617
-	}
618
-
619
-	public function mkdir($path) {
620
-		$path = $this->buildPath($path);
621
-		try {
622
-			$this->share->mkdir($path);
623
-			return true;
624
-		} catch (ConnectException $e) {
625
-			$this->logger->logException($e, ['message' => 'Error while creating folder']);
626
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
627
-		} catch (Exception $e) {
628
-			return false;
629
-		}
630
-	}
631
-
632
-	public function file_exists($path) {
633
-		try {
634
-			$this->getFileInfo($path);
635
-			return true;
636
-		} catch (NotFoundException $e) {
637
-			return false;
638
-		} catch (ForbiddenException $e) {
639
-			return false;
640
-		} catch (ConnectException $e) {
641
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
642
-		}
643
-	}
644
-
645
-	public function isReadable($path) {
646
-		try {
647
-			$info = $this->getFileInfo($path);
648
-			return $this->showHidden || !$info->isHidden();
649
-		} catch (NotFoundException $e) {
650
-			return false;
651
-		} catch (ForbiddenException $e) {
652
-			return false;
653
-		}
654
-	}
655
-
656
-	public function isUpdatable($path) {
657
-		try {
658
-			$info = $this->getFileInfo($path);
659
-			// following windows behaviour for read-only folders: they can be written into
660
-			// (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
661
-			return ($this->showHidden || !$info->isHidden()) && (!$info->isReadOnly() || $this->is_dir($path));
662
-		} catch (NotFoundException $e) {
663
-			return false;
664
-		} catch (ForbiddenException $e) {
665
-			return false;
666
-		}
667
-	}
668
-
669
-	public function isDeletable($path) {
670
-		try {
671
-			$info = $this->getFileInfo($path);
672
-			return ($this->showHidden || !$info->isHidden()) && !$info->isReadOnly();
673
-		} catch (NotFoundException $e) {
674
-			return false;
675
-		} catch (ForbiddenException $e) {
676
-			return false;
677
-		}
678
-	}
679
-
680
-	/**
681
-	 * check if smbclient is installed
682
-	 */
683
-	public static function checkDependencies() {
684
-		return (
685
-			(bool)\OC_Helper::findBinaryPath('smbclient')
686
-			|| NativeServer::available(new System())
687
-		) ? true : ['smbclient'];
688
-	}
689
-
690
-	/**
691
-	 * Test a storage for availability
692
-	 *
693
-	 * @return bool
694
-	 */
695
-	public function test() {
696
-		try {
697
-			return parent::test();
698
-		} catch (Exception $e) {
699
-			$this->logger->logException($e);
700
-			return false;
701
-		}
702
-	}
703
-
704
-	public function listen($path, callable $callback) {
705
-		$this->notify($path)->listen(function (IChange $change) use ($callback) {
706
-			if ($change instanceof IRenameChange) {
707
-				return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
708
-			} else {
709
-				return $callback($change->getType(), $change->getPath());
710
-			}
711
-		});
712
-	}
713
-
714
-	public function notify($path) {
715
-		$path = '/' . ltrim($path, '/');
716
-		$shareNotifyHandler = $this->share->notify($this->buildPath($path));
717
-		return new SMBNotifyHandler($shareNotifyHandler, $this->root);
718
-	}
68
+    /**
69
+     * @var \Icewind\SMB\IServer
70
+     */
71
+    protected $server;
72
+
73
+    /**
74
+     * @var \Icewind\SMB\IShare
75
+     */
76
+    protected $share;
77
+
78
+    /**
79
+     * @var string
80
+     */
81
+    protected $root;
82
+
83
+    /**
84
+     * @var \Icewind\SMB\IFileInfo[]
85
+     */
86
+    protected $statCache;
87
+
88
+    /** @var ILogger */
89
+    protected $logger;
90
+
91
+    /** @var bool */
92
+    protected $showHidden;
93
+
94
+    /** @var bool */
95
+    protected $checkAcl;
96
+
97
+    public function __construct($params) {
98
+        if (!isset($params['host'])) {
99
+            throw new \Exception('Invalid configuration, no host provided');
100
+        }
101
+
102
+        if (isset($params['auth'])) {
103
+            $auth = $params['auth'];
104
+        } elseif (isset($params['user']) && isset($params['password']) && isset($params['share'])) {
105
+            [$workgroup, $user] = $this->splitUser($params['user']);
106
+            $auth = new BasicAuth($user, $workgroup, $params['password']);
107
+        } else {
108
+            throw new \Exception('Invalid configuration, no credentials provided');
109
+        }
110
+
111
+        if (isset($params['logger'])) {
112
+            $this->logger = $params['logger'];
113
+        } else {
114
+            $this->logger = \OC::$server->getLogger();
115
+        }
116
+
117
+        $options = new Options();
118
+        if (isset($params['timeout'])) {
119
+            $timeout = (int)$params['timeout'];
120
+            if ($timeout > 0) {
121
+                $options->setTimeout($timeout);
122
+            }
123
+        }
124
+        $serverFactory = new ServerFactory($options);
125
+        $this->server = $serverFactory->createServer($params['host'], $auth);
126
+        $this->share = $this->server->getShare(trim($params['share'], '/'));
127
+
128
+        $this->root = $params['root'] ?? '/';
129
+        $this->root = '/' . ltrim($this->root, '/');
130
+        $this->root = rtrim($this->root, '/') . '/';
131
+
132
+        $this->showHidden = isset($params['show_hidden']) && $params['show_hidden'];
133
+        $this->checkAcl = isset($params['check_acl']) && $params['check_acl'];
134
+
135
+        $this->statCache = new CappedMemoryCache();
136
+        parent::__construct($params);
137
+    }
138
+
139
+    private function splitUser($user) {
140
+        if (strpos($user, '/')) {
141
+            return explode('/', $user, 2);
142
+        } elseif (strpos($user, '\\')) {
143
+            return explode('\\', $user);
144
+        } else {
145
+            return [null, $user];
146
+        }
147
+    }
148
+
149
+    /**
150
+     * @return string
151
+     */
152
+    public function getId() {
153
+        // FIXME: double slash to keep compatible with the old storage ids,
154
+        // failure to do so will lead to creation of a new storage id and
155
+        // loss of shares from the storage
156
+        return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
157
+    }
158
+
159
+    /**
160
+     * @param string $path
161
+     * @return string
162
+     */
163
+    protected function buildPath($path) {
164
+        return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
165
+    }
166
+
167
+    protected function relativePath($fullPath) {
168
+        if ($fullPath === $this->root) {
169
+            return '';
170
+        } elseif (substr($fullPath, 0, strlen($this->root)) === $this->root) {
171
+            return substr($fullPath, strlen($this->root));
172
+        } else {
173
+            return null;
174
+        }
175
+    }
176
+
177
+    /**
178
+     * @param string $path
179
+     * @return \Icewind\SMB\IFileInfo
180
+     * @throws StorageAuthException
181
+     */
182
+    protected function getFileInfo($path) {
183
+        try {
184
+            $path = $this->buildPath($path);
185
+            if (!isset($this->statCache[$path])) {
186
+                $this->statCache[$path] = $this->share->stat($path);
187
+            }
188
+            return $this->statCache[$path];
189
+        } catch (ConnectException $e) {
190
+            $this->throwUnavailable($e);
191
+        } catch (ForbiddenException $e) {
192
+            // with php-smbclient, this exceptions is thrown when the provided password is invalid.
193
+            // Possible is also ForbiddenException with a different error code, so we check it.
194
+            if ($e->getCode() === 1) {
195
+                $this->throwUnavailable($e);
196
+            }
197
+            throw $e;
198
+        }
199
+    }
200
+
201
+    /**
202
+     * @param \Exception $e
203
+     * @throws StorageAuthException
204
+     */
205
+    protected function throwUnavailable(\Exception $e) {
206
+        $this->logger->logException($e, ['message' => 'Error while getting file info']);
207
+        throw new StorageAuthException($e->getMessage(), $e);
208
+    }
209
+
210
+    /**
211
+     * get the acl from fileinfo that is relevant for the configured user
212
+     *
213
+     * @param IFileInfo $file
214
+     * @return ACL|null
215
+     */
216
+    private function getACL(IFileInfo $file): ?ACL {
217
+        $acls = $file->getAcls();
218
+        foreach ($acls as $user => $acl) {
219
+            [, $user] = explode('\\', $user); // strip domain
220
+            if ($user === $this->server->getAuth()->getUsername()) {
221
+                return $acl;
222
+            }
223
+        }
224
+
225
+        return null;
226
+    }
227
+
228
+    /**
229
+     * @param string $path
230
+     * @return \Icewind\SMB\IFileInfo[]
231
+     * @throws StorageNotAvailableException
232
+     */
233
+    protected function getFolderContents($path): iterable {
234
+        try {
235
+            $path = ltrim($this->buildPath($path), '/');
236
+            $files = $this->share->dir($path);
237
+            foreach ($files as $file) {
238
+                $this->statCache[$path . '/' . $file->getName()] = $file;
239
+            }
240
+
241
+            foreach ($files as $file) {
242
+                try {
243
+                    // the isHidden check is done before checking the config boolean to ensure that the metadata is always fetch
244
+                    // so we trigger the below exceptions where applicable
245
+                    $hide = $file->isHidden() && !$this->showHidden;
246
+
247
+                    if ($this->checkAcl && $acl = $this->getACL($file)) {
248
+                        // if there is no explicit deny, we assume it's allowed
249
+                        // this doesn't take inheritance fully into account but if read permissions is denied for a parent we wouldn't be in this folder
250
+                        // additionally, it's better to have false negatives here then false positives
251
+                        if ($acl->denies(ACL::MASK_READ) || $acl->denies(ACL::MASK_EXECUTE)) {
252
+                            $this->logger->debug('Hiding non readable entry ' . $file->getName());
253
+                            return false;
254
+                        }
255
+                    }
256
+
257
+                    if ($hide) {
258
+                        $this->logger->debug('hiding hidden file ' . $file->getName());
259
+                    }
260
+                    if (!$hide) {
261
+                        yield $file;
262
+                    }
263
+                } catch (ForbiddenException $e) {
264
+                    $this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding forbidden entry ' . $file->getName()]);
265
+                } catch (NotFoundException $e) {
266
+                    $this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding not found entry ' . $file->getName()]);
267
+                }
268
+            }
269
+        } catch (ConnectException $e) {
270
+            $this->logger->logException($e, ['message' => 'Error while getting folder content']);
271
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
272
+        }
273
+    }
274
+
275
+    /**
276
+     * @param \Icewind\SMB\IFileInfo $info
277
+     * @return array
278
+     */
279
+    protected function formatInfo($info) {
280
+        $result = [
281
+            'size' => $info->getSize(),
282
+            'mtime' => $info->getMTime(),
283
+        ];
284
+        if ($info->isDirectory()) {
285
+            $result['type'] = 'dir';
286
+        } else {
287
+            $result['type'] = 'file';
288
+        }
289
+        return $result;
290
+    }
291
+
292
+    /**
293
+     * Rename the files. If the source or the target is the root, the rename won't happen.
294
+     *
295
+     * @param string $source the old name of the path
296
+     * @param string $target the new name of the path
297
+     * @return bool true if the rename is successful, false otherwise
298
+     */
299
+    public function rename($source, $target, $retry = true) {
300
+        if ($this->isRootDir($source) || $this->isRootDir($target)) {
301
+            return false;
302
+        }
303
+
304
+        $absoluteSource = $this->buildPath($source);
305
+        $absoluteTarget = $this->buildPath($target);
306
+        try {
307
+            $result = $this->share->rename($absoluteSource, $absoluteTarget);
308
+        } catch (AlreadyExistsException $e) {
309
+            if ($retry) {
310
+                $this->remove($target);
311
+                $result = $this->share->rename($absoluteSource, $absoluteTarget, false);
312
+            } else {
313
+                $this->logger->logException($e, ['level' => ILogger::WARN]);
314
+                return false;
315
+            }
316
+        } catch (InvalidArgumentException $e) {
317
+            if ($retry) {
318
+                $this->remove($target);
319
+                $result = $this->share->rename($absoluteSource, $absoluteTarget, false);
320
+            } else {
321
+                $this->logger->logException($e, ['level' => ILogger::WARN]);
322
+                return false;
323
+            }
324
+        } catch (\Exception $e) {
325
+            $this->logger->logException($e, ['level' => ILogger::WARN]);
326
+            return false;
327
+        }
328
+        unset($this->statCache[$absoluteSource], $this->statCache[$absoluteTarget]);
329
+        return $result;
330
+    }
331
+
332
+    public function stat($path, $retry = true) {
333
+        try {
334
+            $result = $this->formatInfo($this->getFileInfo($path));
335
+        } catch (ForbiddenException $e) {
336
+            return false;
337
+        } catch (NotFoundException $e) {
338
+            return false;
339
+        } catch (TimedOutException $e) {
340
+            if ($retry) {
341
+                return $this->stat($path, false);
342
+            } else {
343
+                throw $e;
344
+            }
345
+        }
346
+        if ($this->remoteIsShare() && $this->isRootDir($path)) {
347
+            $result['mtime'] = $this->shareMTime();
348
+        }
349
+        return $result;
350
+    }
351
+
352
+    /**
353
+     * get the best guess for the modification time of the share
354
+     *
355
+     * @return int
356
+     */
357
+    private function shareMTime() {
358
+        $highestMTime = 0;
359
+        $files = $this->share->dir($this->root);
360
+        foreach ($files as $fileInfo) {
361
+            try {
362
+                if ($fileInfo->getMTime() > $highestMTime) {
363
+                    $highestMTime = $fileInfo->getMTime();
364
+                }
365
+            } catch (NotFoundException $e) {
366
+                // Ignore this, can happen on unavailable DFS shares
367
+            } catch (ForbiddenException $e) {
368
+                // Ignore this too - it's a symlink
369
+            }
370
+        }
371
+        return $highestMTime;
372
+    }
373
+
374
+    /**
375
+     * Check if the path is our root dir (not the smb one)
376
+     *
377
+     * @param string $path the path
378
+     * @return bool
379
+     */
380
+    private function isRootDir($path) {
381
+        return $path === '' || $path === '/' || $path === '.';
382
+    }
383
+
384
+    /**
385
+     * Check if our root points to a smb share
386
+     *
387
+     * @return bool true if our root points to a share false otherwise
388
+     */
389
+    private function remoteIsShare() {
390
+        return $this->share->getName() && (!$this->root || $this->root === '/');
391
+    }
392
+
393
+    /**
394
+     * @param string $path
395
+     * @return bool
396
+     */
397
+    public function unlink($path) {
398
+        if ($this->isRootDir($path)) {
399
+            return false;
400
+        }
401
+
402
+        try {
403
+            if ($this->is_dir($path)) {
404
+                return $this->rmdir($path);
405
+            } else {
406
+                $path = $this->buildPath($path);
407
+                unset($this->statCache[$path]);
408
+                $this->share->del($path);
409
+                return true;
410
+            }
411
+        } catch (NotFoundException $e) {
412
+            return false;
413
+        } catch (ForbiddenException $e) {
414
+            return false;
415
+        } catch (ConnectException $e) {
416
+            $this->logger->logException($e, ['message' => 'Error while deleting file']);
417
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
418
+        }
419
+    }
420
+
421
+    /**
422
+     * check if a file or folder has been updated since $time
423
+     *
424
+     * @param string $path
425
+     * @param int $time
426
+     * @return bool
427
+     */
428
+    public function hasUpdated($path, $time) {
429
+        if (!$path and $this->root === '/') {
430
+            // mtime doesn't work for shares, but giving the nature of the backend,
431
+            // doing a full update is still just fast enough
432
+            return true;
433
+        } else {
434
+            $actualTime = $this->filemtime($path);
435
+            return $actualTime > $time;
436
+        }
437
+    }
438
+
439
+    /**
440
+     * @param string $path
441
+     * @param string $mode
442
+     * @return resource|false
443
+     */
444
+    public function fopen($path, $mode) {
445
+        $fullPath = $this->buildPath($path);
446
+        try {
447
+            switch ($mode) {
448
+                case 'r':
449
+                case 'rb':
450
+                    if (!$this->file_exists($path)) {
451
+                        return false;
452
+                    }
453
+                    return $this->share->read($fullPath);
454
+                case 'w':
455
+                case 'wb':
456
+                    $source = $this->share->write($fullPath);
457
+                    return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
458
+                        unset($this->statCache[$fullPath]);
459
+                    });
460
+                case 'a':
461
+                case 'ab':
462
+                case 'r+':
463
+                case 'w+':
464
+                case 'wb+':
465
+                case 'a+':
466
+                case 'x':
467
+                case 'x+':
468
+                case 'c':
469
+                case 'c+':
470
+                    //emulate these
471
+                    if (strrpos($path, '.') !== false) {
472
+                        $ext = substr($path, strrpos($path, '.'));
473
+                    } else {
474
+                        $ext = '';
475
+                    }
476
+                    if ($this->file_exists($path)) {
477
+                        if (!$this->isUpdatable($path)) {
478
+                            return false;
479
+                        }
480
+                        $tmpFile = $this->getCachedFile($path);
481
+                    } else {
482
+                        if (!$this->isCreatable(dirname($path))) {
483
+                            return false;
484
+                        }
485
+                        $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
486
+                    }
487
+                    $source = fopen($tmpFile, $mode);
488
+                    $share = $this->share;
489
+                    return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
490
+                        unset($this->statCache[$fullPath]);
491
+                        $share->put($tmpFile, $fullPath);
492
+                        unlink($tmpFile);
493
+                    });
494
+            }
495
+            return false;
496
+        } catch (NotFoundException $e) {
497
+            return false;
498
+        } catch (ForbiddenException $e) {
499
+            return false;
500
+        } catch (ConnectException $e) {
501
+            $this->logger->logException($e, ['message' => 'Error while opening file']);
502
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
503
+        }
504
+    }
505
+
506
+    public function rmdir($path) {
507
+        if ($this->isRootDir($path)) {
508
+            return false;
509
+        }
510
+
511
+        try {
512
+            $this->statCache = [];
513
+            $content = $this->share->dir($this->buildPath($path));
514
+            foreach ($content as $file) {
515
+                if ($file->isDirectory()) {
516
+                    $this->rmdir($path . '/' . $file->getName());
517
+                } else {
518
+                    $this->share->del($file->getPath());
519
+                }
520
+            }
521
+            $this->share->rmdir($this->buildPath($path));
522
+            return true;
523
+        } catch (NotFoundException $e) {
524
+            return false;
525
+        } catch (ForbiddenException $e) {
526
+            return false;
527
+        } catch (ConnectException $e) {
528
+            $this->logger->logException($e, ['message' => 'Error while removing folder']);
529
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
530
+        }
531
+    }
532
+
533
+    public function touch($path, $time = null) {
534
+        try {
535
+            if (!$this->file_exists($path)) {
536
+                $fh = $this->share->write($this->buildPath($path));
537
+                fclose($fh);
538
+                return true;
539
+            }
540
+            return false;
541
+        } catch (ConnectException $e) {
542
+            $this->logger->logException($e, ['message' => 'Error while creating file']);
543
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
544
+        }
545
+    }
546
+
547
+    public function getMetaData($path) {
548
+        $fileInfo = $this->getFileInfo($path);
549
+        if (!$fileInfo) {
550
+            return null;
551
+        }
552
+
553
+        return $this->getMetaDataFromFileInfo($fileInfo);
554
+    }
555
+
556
+    private function getMetaDataFromFileInfo(IFileInfo $fileInfo) {
557
+        $permissions = Constants::PERMISSION_READ + Constants::PERMISSION_SHARE;
558
+
559
+        if (!$fileInfo->isReadOnly()) {
560
+            $permissions += Constants::PERMISSION_DELETE;
561
+            $permissions += Constants::PERMISSION_UPDATE;
562
+            if ($fileInfo->isDirectory()) {
563
+                $permissions += Constants::PERMISSION_CREATE;
564
+            }
565
+        }
566
+
567
+        $data = [];
568
+        if ($fileInfo->isDirectory()) {
569
+            $data['mimetype'] = 'httpd/unix-directory';
570
+        } else {
571
+            $data['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($fileInfo->getPath());
572
+        }
573
+        $data['mtime'] = $fileInfo->getMTime();
574
+        if ($fileInfo->isDirectory()) {
575
+            $data['size'] = -1; //unknown
576
+        } else {
577
+            $data['size'] = $fileInfo->getSize();
578
+        }
579
+        $data['etag'] = $this->getETag($fileInfo->getPath());
580
+        $data['storage_mtime'] = $data['mtime'];
581
+        $data['permissions'] = $permissions;
582
+        $data['name'] = $fileInfo->getName();
583
+
584
+        return $data;
585
+    }
586
+
587
+    public function opendir($path) {
588
+        try {
589
+            $files = $this->getFolderContents($path);
590
+        } catch (NotFoundException $e) {
591
+            return false;
592
+        } catch (ForbiddenException $e) {
593
+            return false;
594
+        }
595
+        $names = array_map(function ($info) {
596
+            /** @var \Icewind\SMB\IFileInfo $info */
597
+            return $info->getName();
598
+        }, iterator_to_array($files));
599
+        return IteratorDirectory::wrap($names);
600
+    }
601
+
602
+    public function getDirectoryContent($directory): \Traversable {
603
+        $files = $this->getFolderContents($directory);
604
+        foreach ($files as $file) {
605
+            yield $this->getMetaDataFromFileInfo($file);
606
+        }
607
+    }
608
+
609
+    public function filetype($path) {
610
+        try {
611
+            return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
612
+        } catch (NotFoundException $e) {
613
+            return false;
614
+        } catch (ForbiddenException $e) {
615
+            return false;
616
+        }
617
+    }
618
+
619
+    public function mkdir($path) {
620
+        $path = $this->buildPath($path);
621
+        try {
622
+            $this->share->mkdir($path);
623
+            return true;
624
+        } catch (ConnectException $e) {
625
+            $this->logger->logException($e, ['message' => 'Error while creating folder']);
626
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
627
+        } catch (Exception $e) {
628
+            return false;
629
+        }
630
+    }
631
+
632
+    public function file_exists($path) {
633
+        try {
634
+            $this->getFileInfo($path);
635
+            return true;
636
+        } catch (NotFoundException $e) {
637
+            return false;
638
+        } catch (ForbiddenException $e) {
639
+            return false;
640
+        } catch (ConnectException $e) {
641
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
642
+        }
643
+    }
644
+
645
+    public function isReadable($path) {
646
+        try {
647
+            $info = $this->getFileInfo($path);
648
+            return $this->showHidden || !$info->isHidden();
649
+        } catch (NotFoundException $e) {
650
+            return false;
651
+        } catch (ForbiddenException $e) {
652
+            return false;
653
+        }
654
+    }
655
+
656
+    public function isUpdatable($path) {
657
+        try {
658
+            $info = $this->getFileInfo($path);
659
+            // following windows behaviour for read-only folders: they can be written into
660
+            // (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
661
+            return ($this->showHidden || !$info->isHidden()) && (!$info->isReadOnly() || $this->is_dir($path));
662
+        } catch (NotFoundException $e) {
663
+            return false;
664
+        } catch (ForbiddenException $e) {
665
+            return false;
666
+        }
667
+    }
668
+
669
+    public function isDeletable($path) {
670
+        try {
671
+            $info = $this->getFileInfo($path);
672
+            return ($this->showHidden || !$info->isHidden()) && !$info->isReadOnly();
673
+        } catch (NotFoundException $e) {
674
+            return false;
675
+        } catch (ForbiddenException $e) {
676
+            return false;
677
+        }
678
+    }
679
+
680
+    /**
681
+     * check if smbclient is installed
682
+     */
683
+    public static function checkDependencies() {
684
+        return (
685
+            (bool)\OC_Helper::findBinaryPath('smbclient')
686
+            || NativeServer::available(new System())
687
+        ) ? true : ['smbclient'];
688
+    }
689
+
690
+    /**
691
+     * Test a storage for availability
692
+     *
693
+     * @return bool
694
+     */
695
+    public function test() {
696
+        try {
697
+            return parent::test();
698
+        } catch (Exception $e) {
699
+            $this->logger->logException($e);
700
+            return false;
701
+        }
702
+    }
703
+
704
+    public function listen($path, callable $callback) {
705
+        $this->notify($path)->listen(function (IChange $change) use ($callback) {
706
+            if ($change instanceof IRenameChange) {
707
+                return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
708
+            } else {
709
+                return $callback($change->getType(), $change->getPath());
710
+            }
711
+        });
712
+    }
713
+
714
+    public function notify($path) {
715
+        $path = '/' . ltrim($path, '/');
716
+        $shareNotifyHandler = $this->share->notify($this->buildPath($path));
717
+        return new SMBNotifyHandler($shareNotifyHandler, $this->root);
718
+    }
719 719
 }
Please login to merge, or discard this patch.
Spacing   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -116,7 +116,7 @@  discard block
 block discarded – undo
116 116
 
117 117
 		$options = new Options();
118 118
 		if (isset($params['timeout'])) {
119
-			$timeout = (int)$params['timeout'];
119
+			$timeout = (int) $params['timeout'];
120 120
 			if ($timeout > 0) {
121 121
 				$options->setTimeout($timeout);
122 122
 			}
@@ -126,8 +126,8 @@  discard block
 block discarded – undo
126 126
 		$this->share = $this->server->getShare(trim($params['share'], '/'));
127 127
 
128 128
 		$this->root = $params['root'] ?? '/';
129
-		$this->root = '/' . ltrim($this->root, '/');
130
-		$this->root = rtrim($this->root, '/') . '/';
129
+		$this->root = '/'.ltrim($this->root, '/');
130
+		$this->root = rtrim($this->root, '/').'/';
131 131
 
132 132
 		$this->showHidden = isset($params['show_hidden']) && $params['show_hidden'];
133 133
 		$this->checkAcl = isset($params['check_acl']) && $params['check_acl'];
@@ -153,7 +153,7 @@  discard block
 block discarded – undo
153 153
 		// FIXME: double slash to keep compatible with the old storage ids,
154 154
 		// failure to do so will lead to creation of a new storage id and
155 155
 		// loss of shares from the storage
156
-		return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
156
+		return 'smb::'.$this->server->getAuth()->getUsername().'@'.$this->server->getHost().'//'.$this->share->getName().'/'.$this->root;
157 157
 	}
158 158
 
159 159
 	/**
@@ -161,7 +161,7 @@  discard block
 block discarded – undo
161 161
 	 * @return string
162 162
 	 */
163 163
 	protected function buildPath($path) {
164
-		return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
164
+		return Filesystem::normalizePath($this->root.'/'.$path, true, false, true);
165 165
 	}
166 166
 
167 167
 	protected function relativePath($fullPath) {
@@ -235,7 +235,7 @@  discard block
 block discarded – undo
235 235
 			$path = ltrim($this->buildPath($path), '/');
236 236
 			$files = $this->share->dir($path);
237 237
 			foreach ($files as $file) {
238
-				$this->statCache[$path . '/' . $file->getName()] = $file;
238
+				$this->statCache[$path.'/'.$file->getName()] = $file;
239 239
 			}
240 240
 
241 241
 			foreach ($files as $file) {
@@ -249,21 +249,21 @@  discard block
 block discarded – undo
249 249
 						// this doesn't take inheritance fully into account but if read permissions is denied for a parent we wouldn't be in this folder
250 250
 						// additionally, it's better to have false negatives here then false positives
251 251
 						if ($acl->denies(ACL::MASK_READ) || $acl->denies(ACL::MASK_EXECUTE)) {
252
-							$this->logger->debug('Hiding non readable entry ' . $file->getName());
252
+							$this->logger->debug('Hiding non readable entry '.$file->getName());
253 253
 							return false;
254 254
 						}
255 255
 					}
256 256
 
257 257
 					if ($hide) {
258
-						$this->logger->debug('hiding hidden file ' . $file->getName());
258
+						$this->logger->debug('hiding hidden file '.$file->getName());
259 259
 					}
260 260
 					if (!$hide) {
261 261
 						yield $file;
262 262
 					}
263 263
 				} catch (ForbiddenException $e) {
264
-					$this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding forbidden entry ' . $file->getName()]);
264
+					$this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding forbidden entry '.$file->getName()]);
265 265
 				} catch (NotFoundException $e) {
266
-					$this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding not found entry ' . $file->getName()]);
266
+					$this->logger->logException($e, ['level' => ILogger::DEBUG, 'message' => 'Hiding not found entry '.$file->getName()]);
267 267
 				}
268 268
 			}
269 269
 		} catch (ConnectException $e) {
@@ -454,7 +454,7 @@  discard block
 block discarded – undo
454 454
 				case 'w':
455 455
 				case 'wb':
456 456
 					$source = $this->share->write($fullPath);
457
-					return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
457
+					return CallBackWrapper::wrap($source, null, null, function() use ($fullPath) {
458 458
 						unset($this->statCache[$fullPath]);
459 459
 					});
460 460
 				case 'a':
@@ -486,7 +486,7 @@  discard block
 block discarded – undo
486 486
 					}
487 487
 					$source = fopen($tmpFile, $mode);
488 488
 					$share = $this->share;
489
-					return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
489
+					return CallbackWrapper::wrap($source, null, null, function() use ($tmpFile, $fullPath, $share) {
490 490
 						unset($this->statCache[$fullPath]);
491 491
 						$share->put($tmpFile, $fullPath);
492 492
 						unlink($tmpFile);
@@ -513,7 +513,7 @@  discard block
 block discarded – undo
513 513
 			$content = $this->share->dir($this->buildPath($path));
514 514
 			foreach ($content as $file) {
515 515
 				if ($file->isDirectory()) {
516
-					$this->rmdir($path . '/' . $file->getName());
516
+					$this->rmdir($path.'/'.$file->getName());
517 517
 				} else {
518 518
 					$this->share->del($file->getPath());
519 519
 				}
@@ -592,7 +592,7 @@  discard block
 block discarded – undo
592 592
 		} catch (ForbiddenException $e) {
593 593
 			return false;
594 594
 		}
595
-		$names = array_map(function ($info) {
595
+		$names = array_map(function($info) {
596 596
 			/** @var \Icewind\SMB\IFileInfo $info */
597 597
 			return $info->getName();
598 598
 		}, iterator_to_array($files));
@@ -682,7 +682,7 @@  discard block
 block discarded – undo
682 682
 	 */
683 683
 	public static function checkDependencies() {
684 684
 		return (
685
-			(bool)\OC_Helper::findBinaryPath('smbclient')
685
+			(bool) \OC_Helper::findBinaryPath('smbclient')
686 686
 			|| NativeServer::available(new System())
687 687
 		) ? true : ['smbclient'];
688 688
 	}
@@ -702,7 +702,7 @@  discard block
 block discarded – undo
702 702
 	}
703 703
 
704 704
 	public function listen($path, callable $callback) {
705
-		$this->notify($path)->listen(function (IChange $change) use ($callback) {
705
+		$this->notify($path)->listen(function(IChange $change) use ($callback) {
706 706
 			if ($change instanceof IRenameChange) {
707 707
 				return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
708 708
 			} else {
@@ -712,7 +712,7 @@  discard block
 block discarded – undo
712 712
 	}
713 713
 
714 714
 	public function notify($path) {
715
-		$path = '/' . ltrim($path, '/');
715
+		$path = '/'.ltrim($path, '/');
716 716
 		$shareNotifyHandler = $this->share->notify($this->buildPath($path));
717 717
 		return new SMBNotifyHandler($shareNotifyHandler, $this->root);
718 718
 	}
Please login to merge, or discard this patch.