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