Completed
Push — stable8.2 ( 2aed50...8f2759 )
by Thomas
30:52
created

DAV::free_space()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 22
ccs 0
cts 14
cp 0
rs 8.9197
cc 4
eloc 14
nc 5
nop 1
crap 20
1
<?php
2
/**
3
 * @author Bart Visscher <[email protected]>
4
 * @author Björn Schießle <[email protected]>
5
 * @author Carlos Cerrillo <[email protected]>
6
 * @author Felix Moeller <[email protected]>
7
 * @author Jörn Friedrich Dreyer <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Michael Gapczynski <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Philipp Kapfer <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Scrutinizer Auto-Fixer <[email protected]>
14
 * @author Thomas Müller <[email protected]>
15
 * @author Vincent Petry <[email protected]>
16
 *
17
 * @copyright Copyright (c) 2015, ownCloud, Inc.
18
 * @license AGPL-3.0
19
 *
20
 * This code is free software: you can redistribute it and/or modify
21
 * it under the terms of the GNU Affero General Public License, version 3,
22
 * as published by the Free Software Foundation.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
 * GNU Affero General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU Affero General Public License, version 3,
30
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
31
 *
32
 */
33
34
namespace OC\Files\Storage;
35
36
use Exception;
37
use OC\Files\Filesystem;
38
use OC\Files\Stream\Close;
39
use Icewind\Streams\IteratorDirectory;
40
use OC\MemCache\ArrayCache;
41
use OCP\AppFramework\Http;
42
use OCP\Constants;
43
use OCP\Files;
44
use OCP\Files\FileInfo;
45
use OCP\Files\StorageInvalidException;
46
use OCP\Files\StorageNotAvailableException;
47
use OCP\Util;
48
use Sabre\DAV\Client;
49
use Sabre\DAV\Exception\NotFound;
50
use Sabre\HTTP\ClientException;
51
use Sabre\HTTP\ClientHttpException;
52
53
/**
54
 * Class DAV
55
 *
56
 * @package OC\Files\Storage
57
 */
58
class DAV extends Common {
59
	/** @var string */
60
	protected $password;
61
	/** @var string */
62
	protected $user;
63
	/** @var string */
64
	protected $host;
65
	/** @var bool */
66
	protected $secure;
67
	/** @var string */
68
	protected $root;
69
	/** @var string */
70
	protected $certPath;
71
	/** @var bool */
72
	protected $ready;
73
	/** @var Client */
74
	private $client;
75
	/** @var ArrayCache */
76
	private $statCache;
77
	/** @var array */
78
	private static $tempFiles = [];
79
	/** @var \OCP\Http\Client\IClientService */
80
	private $httpClientService;
81
82
	/**
83
	 * @param array $params
84
	 * @throws \Exception
85
	 */
86 7
	public function __construct($params) {
87 7
		$this->statCache = new ArrayCache();
88 7
		$this->httpClientService = \OC::$server->getHTTPClientService();
89 7
		if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
90 7
			$host = $params['host'];
91
			//remove leading http[s], will be generated in createBaseUri()
92 7
			if (substr($host, 0, 8) == "https://") $host = substr($host, 8);
93 7
			else if (substr($host, 0, 7) == "http://") $host = substr($host, 7);
94 7
			$this->host = $host;
95 7
			$this->user = $params['user'];
96 7
			$this->password = $params['password'];
97 7
			if (isset($params['secure'])) {
98 7
				if (is_string($params['secure'])) {
99
					$this->secure = ($params['secure'] === 'true');
100
				} else {
101 7
					$this->secure = (bool)$params['secure'];
102
				}
103 7
			} else {
104
				$this->secure = false;
105
			}
106 7
			if ($this->secure === true) {
107 1
				$certPath = \OC_User::getHome(\OC_User::getUser()) . '/files_external/rootcerts.crt';
108 1
				if (file_exists($certPath)) {
109
					$this->certPath = $certPath;
110
				}
111 1
			}
112 7
			$this->root = isset($params['root']) ? $params['root'] : '/';
113 7 View Code Duplication
			if (!$this->root || $this->root[0] != '/') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
114 4
				$this->root = '/' . $this->root;
115 4
			}
116 7
			if (substr($this->root, -1, 1) != '/') {
117 7
				$this->root .= '/';
118 7
			}
119 7
		} else {
120
			throw new \Exception('Invalid webdav storage configuration');
121
		}
122 7
	}
123
124
	private function init() {
125
		if ($this->ready) {
126
			return;
127
		}
128
		$this->ready = true;
129
130
		$settings = array(
131
			'baseUri' => $this->createBaseUri(),
132
			'userName' => $this->user,
133
			'password' => $this->password,
134
		);
135
136
		$this->client = new Client($settings);
137
		$this->client->setThrowExceptions(true);
138
139
		if ($this->secure === true && $this->certPath) {
140
			$this->client->addTrustedCertificates($this->certPath);
141
		}
142
	}
143
144
	/**
145
	 * Clear the stat cache
146
	 */
147
	public function clearStatCache() {
148
		$this->statCache->clear();
149
	}
150
151
	/** {@inheritdoc} */
152
	public function getId() {
153
		return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
154
	}
155
156
	/** {@inheritdoc} */
157 6
	public function createBaseUri() {
158 6
		$baseUri = 'http';
159 6
		if ($this->secure) {
160 1
			$baseUri .= 's';
161 1
		}
162 6
		$baseUri .= '://' . $this->host . $this->root;
163 6
		return $baseUri;
164
	}
165
166
	/** {@inheritdoc} */
167
	public function mkdir($path) {
168
		$this->init();
169
		$path = $this->cleanPath($path);
170
		$result = $this->simpleResponse('MKCOL', $path, null, 201);
171
		if ($result) {
172
			$this->statCache->set($path, true);
173
		}
174
		return $result;
175
	}
176
177
	/** {@inheritdoc} */
178 View Code Duplication
	public function rmdir($path) {
179
		$this->init();
180
		$path = $this->cleanPath($path);
181
		// FIXME: some WebDAV impl return 403 when trying to DELETE
182
		// a non-empty folder
183
		$result = $this->simpleResponse('DELETE', $path . '/', null, 204);
184
		$this->statCache->clear($path . '/');
185
		$this->statCache->remove($path);
186
		return $result;
187
	}
188
189
	/** {@inheritdoc} */
190
	public function opendir($path) {
191
		$this->init();
192
		$path = $this->cleanPath($path);
193
		try {
194
			$response = $this->client->propfind(
195
				$this->encodePath($path),
196
				array(),
197
				1
198
			);
199
			$id = md5('webdav' . $this->root . $path);
0 ignored issues
show
Unused Code introduced by
$id is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
200
			$content = array();
201
			$files = array_keys($response);
202
			array_shift($files); //the first entry is the current directory
203
204
			if (!$this->statCache->hasKey($path)) {
205
				$this->statCache->set($path, true);
206
			}
207
			foreach ($files as $file) {
208
				$file = urldecode($file);
209
				// do not store the real entry, we might not have all properties
210
				if (!$this->statCache->hasKey($path)) {
211
					$this->statCache->set($file, true);
212
				}
213
				$file = basename($file);
214
				$content[] = $file;
215
			}
216
			return IteratorDirectory::wrap($content);
217
		} catch (ClientHttpException $e) {
0 ignored issues
show
Bug introduced by
The class Sabre\HTTP\ClientHttpException 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...
218 View Code Duplication
			if ($e->getHttpStatus() === 404) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
219
				$this->statCache->clear($path . '/');
220
				$this->statCache->set($path, false);
221
				return false;
222
			}
223
			$this->convertException($e, $path);
224
		} catch (\Exception $e) {
225
			$this->convertException($e, $path);
226
		}
227
		return false;
228
	}
229
230
	/**
231
	 * Propfind call with cache handling.
232
	 *
233
	 * First checks if information is cached.
234
	 * If not, request it from the server then store to cache.
235
	 *
236
	 * @param string $path path to propfind
237
	 *
238
	 * @return array propfind response
239
	 *
240
	 * @throws NotFound
241
	 */
242
	private function propfind($path) {
243
		$path = $this->cleanPath($path);
244
		$cachedResponse = $this->statCache->get($path);
245
		if ($cachedResponse === false) {
246
			// we know it didn't exist
247
			throw new NotFound();
248
		}
249
		// we either don't know it, or we know it exists but need more details
250
		if (is_null($cachedResponse) || $cachedResponse === true) {
251
			$this->init();
252
			try {
253
				$response = $this->client->propfind(
254
					$this->encodePath($path),
255
					array(
256
						'{DAV:}getlastmodified',
257
						'{DAV:}getcontentlength',
258
						'{DAV:}getcontenttype',
259
						'{http://owncloud.org/ns}permissions',
260
						'{DAV:}resourcetype',
261
						'{DAV:}getetag',
262
					)
263
				);
264
				$this->statCache->set($path, $response);
265
			} catch (NotFound $e) {
0 ignored issues
show
Bug introduced by
The class Sabre\DAV\Exception\NotFound 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...
266
				// remember that this path did not exist
267
				$this->statCache->clear($path . '/');
268
				$this->statCache->set($path, false);
269
				throw $e;
270
			}
271
		} else {
272
			$response = $cachedResponse;
273
		}
274
		return $response;
275
	}
276
277
	/** {@inheritdoc} */
278
	public function filetype($path) {
279
		try {
280
			$response = $this->propfind($path);
281
			$responseType = array();
282
			if (isset($response["{DAV:}resourcetype"])) {
283
				$responseType = $response["{DAV:}resourcetype"]->resourceType;
284
			}
285
			return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
286
		} catch (ClientHttpException $e) {
0 ignored issues
show
Bug introduced by
The class Sabre\HTTP\ClientHttpException 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...
287
			if ($e->getHttpStatus() === 404) {
288
				return false;
289
			}
290
			$this->convertException($e, $path);
291
		} catch (\Exception $e) {
292
			$this->convertException($e, $path);
293
		}
294
		return false;
295
	}
296
297
	/** {@inheritdoc} */
298
	public function file_exists($path) {
299
		try {
300
			$path = $this->cleanPath($path);
301
			$cachedState = $this->statCache->get($path);
302
			if ($cachedState === false) {
303
				// we know the file doesn't exist
304
				return false;
305
			} else if (!is_null($cachedState)) {
306
				return true;
307
			}
308
			// need to get from server
309
			$this->propfind($path);
310
			return true; //no 404 exception
311
		} catch (ClientHttpException $e) {
0 ignored issues
show
Bug introduced by
The class Sabre\HTTP\ClientHttpException 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...
312
			if ($e->getHttpStatus() === 404) {
313
				return false;
314
			}
315
			$this->convertException($e, $path);
316
		} catch (\Exception $e) {
317
			$this->convertException($e, $path);
318
		}
319
		return false;
320
	}
321
322
	/** {@inheritdoc} */
323 View Code Duplication
	public function unlink($path) {
324
		$this->init();
325
		$path = $this->cleanPath($path);
326
		$result = $this->simpleResponse('DELETE', $path, null, 204);
327
		$this->statCache->clear($path . '/');
328
		$this->statCache->remove($path);
329
		return $result;
330
	}
331
332
	/** {@inheritdoc} */
333
	public function fopen($path, $mode) {
334
		$this->init();
335
		$path = $this->cleanPath($path);
336
		switch ($mode) {
337
			case 'r':
338
			case 'rb':
339
				if (!$this->file_exists($path)) {
340
					return false;
341
				}
342
				$response = $this->httpClientService
343
					->newClient()
344
					->get($this->createBaseUri() . $this->encodePath($path), [
345
						'auth' => [$this->user, $this->password],
346
						'stream' => true
347
					]);
348
349
				if ($response->getStatusCode() !== Http::STATUS_OK) {
350
					if ($response->getStatusCode() === Http::STATUS_LOCKED) {
351
						throw new \OCP\Lock\LockedException($path);
352
					} else {
353
						Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), Util::ERROR);
354
					}
355
				}
356
357
				return $response->getBody();
0 ignored issues
show
Bug Compatibility introduced by
The expression $response->getBody(); of type string|resource adds the type string to the return on line 357 which is incompatible with the return type declared by the interface OCP\Files\Storage::fopen of type resource|false.
Loading history...
358
			case 'w':
359
			case 'wb':
360
			case 'a':
361
			case 'ab':
362
			case 'r+':
363
			case 'w+':
364
			case 'wb+':
365
			case 'a+':
366
			case 'x':
367
			case 'x+':
368
			case 'c':
369
			case 'c+':
370
				//emulate these
371
				$tempManager = \OC::$server->getTempManager();
372
				if (strrpos($path, '.') !== false) {
373
					$ext = substr($path, strrpos($path, '.'));
374
				} else {
375
					$ext = '';
376
				}
377
				if ($this->file_exists($path)) {
378
					if (!$this->isUpdatable($path)) {
379
						return false;
380
					}
381
					if ($mode === 'w' or $mode === 'w+') {
382
						$tmpFile = $tempManager->getTemporaryFile($ext);
383
					} else {
384
						$tmpFile = $this->getCachedFile($path);
385
					}
386
				} else {
387
					if (!$this->isCreatable(dirname($path))) {
388
						return false;
389
					}
390
					$tmpFile = $tempManager->getTemporaryFile($ext);
391
				}
392
				Close::registerCallback($tmpFile, array($this, 'writeBack'));
393
				self::$tempFiles[$tmpFile] = $path;
394
				return fopen('close://' . $tmpFile, $mode);
395
		}
396
	}
397
398
	/**
399
	 * @param string $tmpFile
400
	 */
401
	public function writeBack($tmpFile) {
402 View Code Duplication
		if (isset(self::$tempFiles[$tmpFile])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
403
			$this->uploadFile($tmpFile, self::$tempFiles[$tmpFile]);
404
			unlink($tmpFile);
405
		}
406
	}
407
408
	/** {@inheritdoc} */
409
	public function free_space($path) {
410
		$this->init();
411
		$path = $this->cleanPath($path);
412
		try {
413
			// TODO: cacheable ?
414
			$response = $this->client->propfind($this->encodePath($path), array('{DAV:}quota-available-bytes'));
415
			if (isset($response['{DAV:}quota-available-bytes'])) {
416
				$freeSpace = (int)$response['{DAV:}quota-available-bytes'];
417
				if ($freeSpace === FileInfo::SPACE_UNLIMITED) {
418
					// most of the code cannot cope with unlimited storage,
419
					// so as a workaround convert to SPACE_UNKNOWN which is a
420
					// value recognized in many places
421
					return FileInfo::SPACE_UNKNOWN;
422
				}
423
				return $freeSpace;
424
			} else {
425
				return FileInfo::SPACE_UNKNOWN;
426
			}
427
		} catch (\Exception $e) {
428
			return FileInfo::SPACE_UNKNOWN;
429
		}
430
	}
431
432
	/** {@inheritdoc} */
433
	public function touch($path, $mtime = null) {
434
		$this->init();
435
		if (is_null($mtime)) {
436
			$mtime = time();
437
		}
438
		$path = $this->cleanPath($path);
439
440
		// if file exists, update the mtime, else create a new empty file
441
		if ($this->file_exists($path)) {
442
			try {
443
				$this->statCache->remove($path);
444
				$this->client->proppatch($this->encodePath($path), array('{DAV:}lastmodified' => $mtime));
445
			} catch (ClientHttpException $e) {
0 ignored issues
show
Bug introduced by
The class Sabre\HTTP\ClientHttpException 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...
446
				if ($e->getHttpStatus() === 501) {
447
					return false;
448
				}
449
				$this->convertException($e, $path);
450
				return false;
451
			} catch (\Exception $e) {
452
				$this->convertException($e, $path);
453
				return false;
454
			}
455
		} else {
456
			$this->file_put_contents($path, '');
457
		}
458
		return true;
459
	}
460
461
	/**
462
	 * @param string $path
463
	 * @param string $data
464
	 * @return int
465
	 */
466
	public function file_put_contents($path, $data) {
467
		$path = $this->cleanPath($path);
468
		$result = parent::file_put_contents($path, $data);
469
		$this->statCache->remove($path);
470
		return $result;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $result; (integer) is incompatible with the return type declared by the interface OCP\Files\Storage::file_put_contents of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
471
	}
472
473
	/**
474
	 * @param string $path
475
	 * @param string $target
476
	 */
477
	protected function uploadFile($path, $target) {
478
		$this->init();
479
480
		// invalidate
481
		$target = $this->cleanPath($target);
482
		$this->statCache->remove($target);
483
		$source = fopen($path, 'r');
484
485
		$this->httpClientService
486
			->newClient()
487
			->put($this->createBaseUri() . $this->encodePath($target), [
488
				'body' => $source,
489
				'auth' => [$this->user, $this->password]
490
			]);
491
492
		$this->removeCachedFile($target);
493
	}
494
495
	/** {@inheritdoc} */
496
	public function rename($path1, $path2) {
497
		$this->init();
498
		$path1 = $this->cleanPath($path1);
499
		$path2 = $this->cleanPath($path2);
500
		try {
501
			$this->client->request(
502
				'MOVE',
503
				$this->encodePath($path1),
504
				null,
505
				array(
506
					'Destination' => $this->createBaseUri() . $this->encodePath($path2)
507
				)
508
			);
509
			$this->statCache->clear($path1 . '/');
510
			$this->statCache->clear($path2 . '/');
511
			$this->statCache->set($path1, false);
512
			$this->statCache->set($path2, true);
513
			$this->removeCachedFile($path1);
514
			$this->removeCachedFile($path2);
515
			return true;
516
		} catch (\Exception $e) {
517
			$this->convertException($e);
518
		}
519
		return false;
520
	}
521
522
	/** {@inheritdoc} */
523
	public function copy($path1, $path2) {
524
		$this->init();
525
		$path1 = $this->encodePath($this->cleanPath($path1));
526
		$path2 = $this->createBaseUri() . $this->encodePath($this->cleanPath($path2));
527
		try {
528
			$this->client->request('COPY', $path1, null, array('Destination' => $path2));
529
			$this->statCache->clear($path2 . '/');
530
			$this->statCache->set($path2, true);
531
			$this->removeCachedFile($path2);
532
			return true;
533
		} catch (\Exception $e) {
534
			$this->convertException($e);
535
		}
536
		return false;
537
	}
538
539
	/** {@inheritdoc} */
540
	public function stat($path) {
541
		try {
542
			$response = $this->propfind($path);
543
			return array(
544
				'mtime' => strtotime($response['{DAV:}getlastmodified']),
545
				'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0,
546
			);
547
		} catch (ClientHttpException $e) {
0 ignored issues
show
Bug introduced by
The class Sabre\HTTP\ClientHttpException 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...
548
			if ($e->getHttpStatus() === 404) {
549
				return array();
550
			}
551
			$this->convertException($e, $path);
552
		} catch (\Exception $e) {
553
			$this->convertException($e, $path);
554
		}
555
		return array();
556
	}
557
558
	/** {@inheritdoc} */
559
	public function getMimeType($path) {
560
		try {
561
			$response = $this->propfind($path);
562
			$responseType = array();
563
			if (isset($response["{DAV:}resourcetype"])) {
564
				$responseType = $response["{DAV:}resourcetype"]->resourceType;
565
			}
566
			$type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
567
			if ($type == 'dir') {
568
				return 'httpd/unix-directory';
569
			} elseif (isset($response['{DAV:}getcontenttype'])) {
570
				return $response['{DAV:}getcontenttype'];
571
			} else {
572
				return false;
573
			}
574
		} catch (ClientHttpException $e) {
0 ignored issues
show
Bug introduced by
The class Sabre\HTTP\ClientHttpException 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...
575
			if ($e->getHttpStatus() === 404) {
576
				return false;
577
			}
578
			$this->convertException($e, $path);
579
		} catch (\Exception $e) {
580
			$this->convertException($e, $path);
581
		}
582
		return false;
583
	}
584
585
	/**
586
	 * @param string $path
587
	 * @return string
588
	 */
589
	public function cleanPath($path) {
590
		if ($path === '') {
591
			return $path;
592
		}
593
		$path = Filesystem::normalizePath($path);
594
		// remove leading slash
595
		return substr($path, 1);
596
	}
597
598
	/**
599
	 * URL encodes the given path but keeps the slashes
600
	 *
601
	 * @param string $path to encode
602
	 * @return string encoded path
603
	 */
604
	private function encodePath($path) {
605
		// slashes need to stay
606
		return str_replace('%2F', '/', rawurlencode($path));
607
	}
608
609
	/**
610
	 * @param string $method
611
	 * @param string $path
612
	 * @param string|resource|null $body
613
	 * @param int $expected
614
	 * @return bool
615
	 * @throws StorageInvalidException
616
	 * @throws StorageNotAvailableException
617
	 */
618
	private function simpleResponse($method, $path, $body, $expected) {
619
		$path = $this->cleanPath($path);
620
		try {
621
			$response = $this->client->request($method, $this->encodePath($path), $body);
622
			return $response['statusCode'] == $expected;
623
		} catch (ClientHttpException $e) {
0 ignored issues
show
Bug introduced by
The class Sabre\HTTP\ClientHttpException 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...
624 View Code Duplication
			if ($e->getHttpStatus() === 404 && $method === 'DELETE') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
625
				$this->statCache->clear($path . '/');
626
				$this->statCache->set($path, false);
627
				return false;
628
			}
629
630
			$this->convertException($e, $path);
631
		} catch (\Exception $e) {
632
			$this->convertException($e, $path);
633
		}
634
		return false;
635
	}
636
637
	/**
638
	 * check if curl is installed
639
	 */
640
	public static function checkDependencies() {
641
		return true;
642
	}
643
644
	/** {@inheritdoc} */
645
	public function isUpdatable($path) {
646
		return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
647
	}
648
649
	/** {@inheritdoc} */
650
	public function isCreatable($path) {
651
		return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
652
	}
653
654
	/** {@inheritdoc} */
655
	public function isSharable($path) {
656
		return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
657
	}
658
659
	/** {@inheritdoc} */
660
	public function isDeletable($path) {
661
		return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
662
	}
663
664
	/** {@inheritdoc} */
665
	public function getPermissions($path) {
666
		$this->init();
667
		$path = $this->cleanPath($path);
668
		$response = $this->propfind($path);
669
		if (isset($response['{http://owncloud.org/ns}permissions'])) {
670
			return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
671
		} else if ($this->is_dir($path)) {
672
			return Constants::PERMISSION_ALL;
673
		} else if ($this->file_exists($path)) {
674
			return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
675
		} else {
676
			return 0;
677
		}
678
	}
679
680
	/** {@inheritdoc} */
681
	public function getETag($path) {
682
		$this->init();
683
		$path = $this->cleanPath($path);
684
		$response = $this->propfind($path);
685
		if (isset($response['{DAV:}getetag'])) {
686
			return trim($response['{DAV:}getetag'], '"');
687
		}
688
		return parent::getEtag($path);
689
	}
690
691
	/**
692
	 * @param string $permissionsString
693
	 * @return int
694
	 */
695
	protected function parsePermissions($permissionsString) {
696
		$permissions = Constants::PERMISSION_READ;
697
		if (strpos($permissionsString, 'R') !== false) {
698
			$permissions |= Constants::PERMISSION_SHARE;
699
		}
700
		if (strpos($permissionsString, 'D') !== false) {
701
			$permissions |= Constants::PERMISSION_DELETE;
702
		}
703
		if (strpos($permissionsString, 'W') !== false) {
704
			$permissions |= Constants::PERMISSION_UPDATE;
705
		}
706
		if (strpos($permissionsString, 'CK') !== false) {
707
			$permissions |= Constants::PERMISSION_CREATE;
708
			$permissions |= Constants::PERMISSION_UPDATE;
709
		}
710
		return $permissions;
711
	}
712
713
	/**
714
	 * check if a file or folder has been updated since $time
715
	 *
716
	 * @param string $path
717
	 * @param int $time
718
	 * @throws \OCP\Files\StorageNotAvailableException
719
	 * @return bool
720
	 */
721
	public function hasUpdated($path, $time) {
722
		$this->init();
723
		$path = $this->cleanPath($path);
724
		try {
725
			// force refresh for $path
726
			$this->statCache->remove($path);
727
			$response = $this->propfind($path);
728
			if (isset($response['{DAV:}getetag'])) {
729
				$cachedData = $this->getCache()->get($path);
730
				$etag = null;
731
				if (isset($response['{DAV:}getetag'])) {
732
					$etag = trim($response['{DAV:}getetag'], '"');
733
				}
734
				if (!empty($etag) && $cachedData['etag'] !== $etag) {
735
					return true;
736
				} else if (isset($response['{http://owncloud.org/ns}permissions'])) {
737
					$permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
738
					return $permissions !== $cachedData['permissions'];
739
				} else {
740
					return false;
741
				}
742
			} else {
743
				$remoteMtime = strtotime($response['{DAV:}getlastmodified']);
744
				return $remoteMtime > $time;
745
			}
746
		} catch (ClientHttpException $e) {
0 ignored issues
show
Bug introduced by
The class Sabre\HTTP\ClientHttpException 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...
747 View Code Duplication
			if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
748
				if ($path === '') {
749
					// if root is gone it means the storage is not available
750
					throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
751
				}
752
				return false;
753
			}
754
			$this->convertException($e, $path);
755
			return false;
756
		} catch (\Exception $e) {
757
			$this->convertException($e, $path);
758
			return false;
759
		}
760
	}
761
762
	/**
763
	 * Interpret the given exception and decide whether it is due to an
764
	 * unavailable storage, invalid storage or other.
765
	 * This will either throw StorageInvalidException, StorageNotAvailableException
766
	 * or do nothing.
767
	 *
768
	 * @param Exception $e sabre exception
769
	 * @param string $path optional path from the operation
770
	 *
771
	 * @throws StorageInvalidException if the storage is invalid, for example
772
	 * when the authentication expired or is invalid
773
	 * @throws StorageNotAvailableException if the storage is not available,
774
	 * which might be temporary
775
	 */
776
	private function convertException(Exception $e, $path = '') {
777
		Util::writeLog('files_external', $e->getMessage(), Util::ERROR);
778
		if ($e instanceof ClientHttpException) {
0 ignored issues
show
Bug introduced by
The class Sabre\HTTP\ClientHttpException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
779
			if ($e->getHttpStatus() === 423) {
780
				throw new \OCP\Lock\LockedException($path);
781
			}
782 View Code Duplication
			if ($e->getHttpStatus() === 401) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
783
				// either password was changed or was invalid all along
784
				throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage());
785
			} else if ($e->getHttpStatus() === 405) {
786
				// ignore exception for MethodNotAllowed, false will be returned
787
				return;
788
			}
789
			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
790
		} else if ($e instanceof ClientException) {
0 ignored issues
show
Bug introduced by
The class Sabre\HTTP\ClientException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
791
			// connection timeout or refused, server could be temporarily down
792
			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
793
		} else if ($e instanceof \InvalidArgumentException) {
794
			// parse error because the server returned HTML instead of XML,
795
			// possibly temporarily down
796
			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
797
		} else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
798
			// rethrow
799
			throw $e;
800
		}
801
802
		// TODO: only log for now, but in the future need to wrap/rethrow exception
803
	}
804
}
805
806