Issues (1798)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

apps/files_external/lib/Lib/Storage/SMB.php (8 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Jesús Macias <[email protected]>
5
 * @author Jörn Friedrich Dreyer <[email protected]>
6
 * @author Juan Pablo Villafañez <[email protected]>
7
 * @author Michael Gapczynski <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Philipp Kapfer <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Robin McCorkell <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 * @author Vincent Petry <[email protected]>
14
 *
15
 * @copyright Copyright (c) 2018, ownCloud GmbH
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OCA\Files_External\Lib\Storage;
33
34
use Icewind\SMB\Exception\AlreadyExistsException;
35
use Icewind\SMB\Exception\ConnectException;
36
use Icewind\SMB\Exception\Exception;
37
use Icewind\SMB\Exception\ForbiddenException;
38
use Icewind\SMB\Exception\NotFoundException;
39
use Icewind\SMB\BasicAuth;
40
use Icewind\SMB\IFileInfo;
41
use Icewind\SMB\Native\NativeServer;
42
use Icewind\SMB\Wrapped\FileInfo;
43
use Icewind\SMB\ServerFactory;
44
use Icewind\SMB\System;
45
use Icewind\SMB\IShare;
46
use Icewind\Streams\CallbackWrapper;
47
use Icewind\Streams\IteratorDirectory;
48
use OC\Cache\CappedMemoryCache;
49
use OC\Files\Filesystem;
50
use OCP\Files\Cache\ICache;
51
use OCP\Files\StorageNotAvailableException;
52
use OCP\Util;
53
54
class SMB extends \OCP\Files\Storage\StorageAdapter {
55
	/**
56
	 * @var \Icewind\SMB\IServer
57
	 */
58
	protected $server;
59
60
	/**
61
	 * @var \Icewind\SMB\IShare
62
	 */
63
	protected $share;
64
65
	/**
66
	 * @var string
67
	 */
68
	protected $root;
69
70
	/**
71
	 * @var ICache
72
	 */
73
	protected $statCache;
74
75
	public function __construct($params) {
76
		$loggedParams = $params;
77
		// remove password from log if it is set
78
		if (!empty($loggedParams['password'])) {
79
			$loggedParams['password'] = '***removed***';
80
		}
81
		$this->log('enter: '.__FUNCTION__.'('.\json_encode($loggedParams).')');
82
83
		if (isset($params['host'], $params['user'], $params['password'], $params['share'])) {
84
			$auth = new BasicAuth($params['user'], '', $params['password']);
85
			$serverFactory = new ServerFactory();
86
			$this->server = $serverFactory->createServer($params['host'], $auth);
87
			$this->share = $this->server->getShare(\trim($params['share'], '/'));
88
89
			$shareClass = \get_class($this->share);
90
			$this->log("using $shareClass for the connection");
91
92
			$this->root = isset($params['root']) ? $params['root'] : '/';
93 View Code Duplication
			if (!$this->root || $this->root[0] != '/') {
94
				$this->root = '/' . $this->root;
95
			}
96
			if (\substr($this->root, -1, 1) != '/') {
97
				$this->root .= '/';
98
			}
99
		} else {
100
			$ex = new \Exception('Invalid configuration');
101
			$this->leave(__FUNCTION__, $ex);
102
			throw $ex;
103
		}
104
		$this->statCache = new CappedMemoryCache();
105
		$this->log('leave: '.__FUNCTION__.', getId:'.$this->getId());
106
	}
107
108
	/**
109
	 * @return string
110
	 */
111
	public function getId() {
112
		// FIXME: double slash to keep compatible with the old storage ids,
113
		// failure to do so will lead to creation of a new storage id and
114
		// loss of shares from the storage
115
		return 'smb::' . $this->server->getAuth()->getUsername() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
116
	}
117
118
	/**
119
	 * @param string $path
120
	 * @return string
121
	 */
122
	protected function buildPath($path) {
123
		$this->log('enter: '.__FUNCTION__."($path)");
124
		$result = Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
125
		return $this->leave(__FUNCTION__, $result);
126
	}
127
128
	/**
129
	 * @param string $path
130
	 * @return \Icewind\SMB\IFileInfo
131
	 * @throws StorageNotAvailableException
132
	 * @throws ForbiddenException
133
	 * @throws NotFoundException
134
	 */
135
	protected function getFileInfo($path) {
136
		$this->log('enter: '.__FUNCTION__."($path)");
137
		$path = $this->buildPath($path);
138
		if (!isset($this->statCache[$path])) {
139
			try {
140
				$this->log("stat fetching '$path'");
141
				try {
142
					$this->statCache[$path] = $this->share->stat($path);
143
				} catch (NotFoundException $e) {
144
					if ($this->share instanceof IShare) {
145
						// smbclient may have problems with the allinfo cmd
146
						$this->log("stat for '$path' failed, trying to read parent dir");
147
						$infos = $this->share->dir(\dirname($path));
148
						foreach ($infos as $fileInfo) {
149
							if ($fileInfo->getName() === \basename($path)) {
150
								$this->statCache[$path] = $fileInfo;
151
								break;
152
							}
153
						}
154
						if (empty($this->statCache[$path])) {
155
							$this->leave(__FUNCTION__, $e);
156
							throw $e;
157
						}
158
					} else {
159
						// trust the results of libsmb
160
						$this->leave(__FUNCTION__, $e);
161
						throw $e;
162
					}
163
				}
164
				if ($this->isRootDir($path) && $this->statCache[$path]->isHidden()) {
165
					$this->log("unhiding stat for '$path'");
166
					// make root never hidden, may happen when accessing a shared drive (mode is 22, archived and readonly - neither is true ... whatever)
167
					if ($this->statCache[$path]->isReadOnly()) {
168
						$mode = IFileInfo::MODE_DIRECTORY & IFileInfo::MODE_READONLY;
169
					} else {
170
						$mode = IFileInfo::MODE_DIRECTORY;
171
					}
172
					$this->statCache[$path] = new FileInfo($path, '', 0, $this->statCache[$path]->getMTime(), $mode);
173
				}
174
			} catch (ConnectException $e) {
175
				$ex = new StorageNotAvailableException(
176
						$e->getMessage(), $e->getCode(), $e);
177
				$this->leave(__FUNCTION__, $ex);
178
				throw $ex;
179
			} catch (ForbiddenException $e) {
180
				if ($this->remoteIsShare() && $this->isRootDir($path)) { //mtime may not work for share root
181
					$this->log("faking stat for forbidden '$path'");
182
					$this->statCache[$path] = new FileInfo($path, '', 0, $this->shareMTime(), IFileInfo::MODE_DIRECTORY);
183
				} else {
184
					$this->leave(__FUNCTION__, $e);
185
					throw $e;
186
				}
187
			}
188
		} else {
189
			$this->log("stat cache hit for '$path'");
190
		}
191
		$result = $this->statCache[$path];
192
		return $this->leave(__FUNCTION__, $result);
193
	}
194
195
	/**
196
	 * @param string $path
197
	 * @return \Icewind\SMB\IFileInfo[]
198
	 * @throws StorageNotAvailableException
199
	 */
200
	protected function getFolderContents($path) {
201
		$this->log('enter: '.__FUNCTION__."($path)");
202
		try {
203
			$path = $this->buildPath($path);
204
			$result = [];
205
			$children = $this->share->dir($path);
206
			foreach ($children as $fileInfo) {
207
				// check if the file is readable before adding it to the list
208
				// can't use "isReadable" function here, use smb internals instead
209
				try {
210
					if ($fileInfo->isHidden()) {
211
						$this->log("{$fileInfo->getName()} isn't readable, skipping", Util::DEBUG);
212
					} else {
213
						$result[] = $fileInfo;
214
						//remember entry so we can answer file_exists and filetype without a full stat
215
						$this->statCache[$path . '/' . $fileInfo->getName()] = $fileInfo;
216
					}
217
				} catch (NotFoundException $e) {
218
					$this->swallow(__FUNCTION__, $e);
219
				}
220
			}
221
		} catch (ConnectException $e) {
222
			$ex = new StorageNotAvailableException(
223
				$e->getMessage(), $e->getCode(), $e);
224
			$this->leave(__FUNCTION__, $ex);
225
			throw $ex;
226
		}
227
		return $this->leave(__FUNCTION__, $result);
228
	}
229
230
	/**
231
	 * @param \Icewind\SMB\IFileInfo $info
232
	 * @return array
233
	 */
234
	protected function formatInfo($info) {
235
		$result = [
236
			'size' => $info->getSize(),
237
			'mtime' => $info->getMTime(),
238
		];
239
		if ($info->isDirectory()) {
240
			$result['type'] = 'dir';
241
		} else {
242
			$result['type'] = 'file';
243
		}
244
		return $result;
245
	}
246
247
	/**
248
	 * Rename the files. If the source or the target is the root, the rename won't happen.
249
	 *
250
	 * @param string $source the old name of the path
251
	 * @param string $target the new name of the path
252
	 * @return bool true if the rename is successful, false otherwise
253
	 */
254
	public function rename($source, $target) {
255
		$this->log("enter: rename('$source', '$target')", Util::DEBUG);
256
257
		if ($this->isRootDir($source) || $this->isRootDir($target)) {
258
			$this->log("refusing to rename \"$source\" to \"$target\"");
259
			return $this->leave(__FUNCTION__, false);
260
		}
261
262
		try {
263
			$result = $this->share->rename($this->root . $source, $this->root . $target);
264
			if ($result) {
265
				$this->removeFromCache($this->root . $source);
266
				$this->removeFromCache($this->root . $target);
267
			}
268
		} catch (AlreadyExistsException $e) {
269
			$this->swallow(__FUNCTION__, $e);
270 View Code Duplication
			if ($this->unlink($target)) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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