Completed
Push — stable8.1 ( 6cfe33...8c8ab4 )
by Thomas
57:36
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 12
cp 0
rs 8.9197
cc 4
eloc 14
nc 5
nop 1
crap 20
1
<?php
2
/**
3
 * @author Alexander Bogdanov <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Björn Schießle <[email protected]>
6
 * @author Carlos Cerrillo <[email protected]>
7
 * @author Felix Moeller <[email protected]>
8
 * @author Jörn Friedrich Dreyer <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Michael Gapczynski <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Philippe Kueck <[email protected]>
13
 * @author Philipp Kapfer <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Scrutinizer Auto-Fixer <[email protected]>
16
 * @author Thomas Müller <[email protected]>
17
 * @author Vincent Petry <[email protected]>
18
 *
19
 * @copyright Copyright (c) 2015, ownCloud, Inc.
20
 * @license AGPL-3.0
21
 *
22
 * This code is free software: you can redistribute it and/or modify
23
 * it under the terms of the GNU Affero General Public License, version 3,
24
 * as published by the Free Software Foundation.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29
 * GNU Affero General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU Affero General Public License, version 3,
32
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
33
 *
34
 */
35
36
namespace OC\Files\Storage;
37
38
use Exception;
39
use OC\Files\Filesystem;
40
use OC\Files\Stream\Close;
41
use OC\Files\Stream\Dir;
42
use OC\MemCache\ArrayCache;
43
use OCP\Constants;
44
use OCP\Files;
45
use OCP\Files\FileInfo;
46
use OCP\Files\StorageInvalidException;
47
use OCP\Files\StorageNotAvailableException;
48
use OCP\Util;
49
use Sabre\DAV\Client;
50
use Sabre\DAV\Exception\NotFound;
51
use Sabre\HTTP\ClientException;
52
use Sabre\HTTP\ClientHttpException;
53
54
/**
55
 * Class DAV
56
 *
57
 * @package OC\Files\Storage
58
 */
59
class DAV extends Common {
60
	/** @var string */
61
	protected $password;
62
	/** @var string */
63
	protected $user;
64
	/** @var string */
65
	protected $host;
66
	/** @var bool */
67
	protected $secure;
68
	/** @var string */
69
	protected $root;
70
	/** @var string */
71
	protected $certPath;
72
	/** @var bool */
73
	protected $ready;
74
	/** @var Client */
75
	private $client;
76
	/** @var ArrayCache */
77
	private $statCache;
78
	/** @var array */
79
	private static $tempFiles = [];
80
81
	/**
82
	 * @param array $params
83
	 * @throws \Exception
84
	 */
85 7
	public function __construct($params) {
86 7
		$this->statCache = new ArrayCache();
87 7
		if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
88 7
			$host = $params['host'];
89
			//remove leading http[s], will be generated in createBaseUri()
90 7
			if (substr($host, 0, 8) == "https://") $host = substr($host, 8);
91 7
			else if (substr($host, 0, 7) == "http://") $host = substr($host, 7);
92 7
			$this->host = $host;
93 7
			$this->user = $params['user'];
94 7
			$this->password = $params['password'];
95 7 View Code Duplication
			if (isset($params['secure'])) {
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...
96 7
				if (is_string($params['secure'])) {
97
					$this->secure = ($params['secure'] === 'true');
98
				} else {
99 7
					$this->secure = (bool)$params['secure'];
100
				}
101 7
			} else {
102
				$this->secure = false;
103
			}
104 7
			if ($this->secure === true) {
105 1
				$certPath = \OC_User::getHome(\OC_User::getUser()) . '/files_external/rootcerts.crt';
106 1
				if (file_exists($certPath)) {
107
					$this->certPath = $certPath;
108
				}
109 1
			}
110 7
			$this->root = isset($params['root']) ? $params['root'] : '/';
111 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...
112 4
				$this->root = '/' . $this->root;
113 4
			}
114 7
			if (substr($this->root, -1, 1) != '/') {
115 7
				$this->root .= '/';
116 7
			}
117 7
		} else {
118
			throw new \Exception('Invalid webdav storage configuration');
119
		}
120 7
	}
121
122
	private function init() {
123
		if ($this->ready) {
124
			return;
125
		}
126
		$this->ready = true;
127
128
		$settings = array(
129
			'baseUri' => $this->createBaseUri(),
130
			'userName' => $this->user,
131
			'password' => $this->password,
132
		);
133
134
		$this->client = new Client($settings);
135
		$this->client->setThrowExceptions(true);
136
137
		if ($this->secure === true && $this->certPath) {
138
			$this->client->addTrustedCertificates($this->certPath);
139
		}
140
	}
141
142
	/**
143
	 * Clear the stat cache
144
	 */
145
	public function clearStatCache() {
146
		$this->statCache->clear();
147
	}
148
149
	/** {@inheritdoc} */
150
	public function getId() {
151
		return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
152
	}
153
154
	/** {@inheritdoc} */
155 6
	public function createBaseUri() {
156 6
		$baseUri = 'http';
157 6
		if ($this->secure) {
158 1
			$baseUri .= 's';
159 1
		}
160 6
		$baseUri .= '://' . $this->host . $this->root;
161 6
		return $baseUri;
162
	}
163
164
	/** {@inheritdoc} */
165
	public function mkdir($path) {
166
		$this->init();
167
		$path = $this->cleanPath($path);
168
		$result = $this->simpleResponse('MKCOL', $path, null, 201);
169
		if ($result) {
170
			$this->statCache->set($path, true);
171
		}
172
		return $result;
173
	}
174
175
	/** {@inheritdoc} */
176 View Code Duplication
	public function rmdir($path) {
177
		$this->init();
178
		$path = $this->cleanPath($path);
179
		// FIXME: some WebDAV impl return 403 when trying to DELETE
180
		// a non-empty folder
181
		$result = $this->simpleResponse('DELETE', $path . '/', null, 204);
182
		$this->statCache->clear($path . '/');
183
		$this->statCache->remove($path);
184
		return $result;
185
	}
186
187
	/** {@inheritdoc} */
188
	public function opendir($path) {
189
		$this->init();
190
		$path = $this->cleanPath($path);
191
		try {
192
			$response = $this->client->propfind(
193
				$this->encodePath($path),
194
				array(),
195
				1
196
			);
197
			$id = md5('webdav' . $this->root . $path);
198
			$content = array();
199
			$files = array_keys($response);
200
			array_shift($files); //the first entry is the current directory
201
202
			if (!$this->statCache->hasKey($path)) {
203
				$this->statCache->set($path, true);
204
			}
205
			foreach ($files as $file) {
206
				$file = urldecode($file);
207
				// do not store the real entry, we might not have all properties
208
				if (!$this->statCache->hasKey($path)) {
209
					$this->statCache->set($file, true);
210
				}
211
				$file = basename($file);
212
				$content[] = $file;
213
			}
214
			Dir::register($id, $content);
215
			return opendir('fakedir://' . $id);
216
		} 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...
217 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...
218
				$this->statCache->clear($path . '/');
219
				$this->statCache->set($path, false);
220
				return false;
221
			}
222
			$this->convertException($e, $path);
223
		} catch (\Exception $e) {
224
			$this->convertException($e, $path);
225
		}
226
		return false;
227
	}
228
229
	/**
230
	 * Propfind call with cache handling.
231
	 *
232
	 * First checks if information is cached.
233
	 * If not, request it from the server then store to cache.
234
	 *
235
	 * @param string $path path to propfind
236
	 * 
237
	 * @return array propfind response
238
	 *
239
	 * @throws NotFound
240
	 */
241
	private function propfind($path) {
242
		$path = $this->cleanPath($path);
243
		$cachedResponse = $this->statCache->get($path);
244
		if ($cachedResponse === false) {
245
			// we know it didn't exist
246
			throw new NotFound();
247
		}
248
		// we either don't know it, or we know it exists but need more details
249
		if (is_null($cachedResponse) || $cachedResponse === true) {
250
			$this->init();
251
			try {
252
				$response = $this->client->propfind(
253
					$this->encodePath($path),
254
					array(
255
						'{DAV:}getlastmodified',
256
						'{DAV:}getcontentlength',
257
						'{DAV:}getcontenttype',
258
						'{http://owncloud.org/ns}permissions',
259
						'{DAV:}resourcetype',
260
						'{DAV:}getetag',
261
					)
262
				);
263
				$this->statCache->set($path, $response);
264
			} 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...
265
				// remember that this path did not exist
266
				$this->statCache->clear($path . '/');
267
				$this->statCache->set($path, false);
268
				throw $e;
269
			}
270
		} else {
271
			$response = $cachedResponse;
272
		}
273
		return $response;
274
	}
275
276
	/** {@inheritdoc} */
277
	public function filetype($path) {
278
		try {
279
			$response = $this->propfind($path);
280
			$responseType = array();
281
			if (isset($response["{DAV:}resourcetype"])) {
282
				$responseType = $response["{DAV:}resourcetype"]->resourceType;
283
			}
284
			return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
285
		} 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...
286
			if ($e->getHttpStatus() === 404) {
287
				return false;
288
			}
289
			$this->convertException($e, $path);
290
		} catch (\Exception $e) {
291
			$this->convertException($e, $path);
292
		}
293
		return false;
294
	}
295
296
	/** {@inheritdoc} */
297
	public function file_exists($path) {
298
		try {
299
			$path = $this->cleanPath($path);
300
			$cachedState = $this->statCache->get($path);
301
			if ($cachedState === false) {
302
				// we know the file doesn't exist
303
				return false;
304
			} else if (!is_null($cachedState)) {
305
				return true;
306
			}
307
			// need to get from server
308
			$this->propfind($path);
309
			return true; //no 404 exception
310
		} 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...
311
			if ($e->getHttpStatus() === 404) {
312
				return false;
313
			}
314
			$this->convertException($e, $path);
315
		} catch (\Exception $e) {
316
			$this->convertException($e, $path);
317
		}
318
		return false;
319
	}
320
321
	/** {@inheritdoc} */
322 View Code Duplication
	public function unlink($path) {
323
		$this->init();
324
		$path = $this->cleanPath($path);
325
		$result = $this->simpleResponse('DELETE', $path, null, 204);
326
		$this->statCache->clear($path . '/');
327
		$this->statCache->remove($path);
328
		return $result;
329
	}
330
331
	/** {@inheritdoc} */
332
	public function fopen($path, $mode) {
333
		$this->init();
334
		$path = $this->cleanPath($path);
335
		switch ($mode) {
336
			case 'r':
337
			case 'rb':
338
				if (!$this->file_exists($path)) {
339
					return false;
340
				}
341
				//straight up curl instead of sabredav here, sabredav put's the entire get result in memory
342
				$curl = curl_init();
343
				$fp = fopen('php://temp', 'r+');
344
				curl_setopt($curl, CURLOPT_USERPWD, $this->user . ':' . $this->password);
345
				curl_setopt($curl, CURLOPT_URL, $this->createBaseUri() . $this->encodePath($path));
346
				curl_setopt($curl, CURLOPT_FILE, $fp);
347
				curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
348
				if(defined('CURLOPT_PROTOCOLS')) {
349
					curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
350
				}
351
				if(defined('CURLOPT_REDIR_PROTOCOLS')) {
352
					curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
353
				}
354 View Code Duplication
				if ($this->secure === true) {
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...
355
					curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
356
					curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
357
					if ($this->certPath) {
358
						curl_setopt($curl, CURLOPT_CAINFO, $this->certPath);
359
					}
360
				}
361
362
				curl_exec($curl);
363
				$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
364 View Code Duplication
				if ($statusCode !== 200) {
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...
365
					Util::writeLog("webdav client", 'curl GET ' . curl_getinfo($curl, CURLINFO_EFFECTIVE_URL) . ' returned status code ' . $statusCode, Util::ERROR);
366
					if ($statusCode === 423) {
367
						throw new \OCP\Lock\LockedException($path);
368
					}
369
				}
370
				curl_close($curl);
371
				rewind($fp);
372
				return $fp;
373
			case 'w':
374
			case 'wb':
375
			case 'a':
376
			case 'ab':
377
			case 'r+':
378
			case 'w+':
379
			case 'wb+':
380
			case 'a+':
381
			case 'x':
382
			case 'x+':
383
			case 'c':
384
			case 'c+':
385
				//emulate these
386
				if (strrpos($path, '.') !== false) {
387
					$ext = substr($path, strrpos($path, '.'));
388
				} else {
389
					$ext = '';
390
				}
391 View Code Duplication
				if ($this->file_exists($path)) {
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...
392
					if (!$this->isUpdatable($path)) {
393
						return false;
394
					}
395
					$tmpFile = $this->getCachedFile($path);
396
				} else {
397
					if (!$this->isCreatable(dirname($path))) {
398
						return false;
399
					}
400
					$tmpFile = Files::tmpFile($ext);
401
				}
402
				Close::registerCallback($tmpFile, array($this, 'writeBack'));
403
				self::$tempFiles[$tmpFile] = $path;
404
				return fopen('close://' . $tmpFile, $mode);
405
		}
406
	}
407
408
	/**
409
	 * @param string $tmpFile
410
	 */
411
	public function writeBack($tmpFile) {
412 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...
413
			$this->uploadFile($tmpFile, self::$tempFiles[$tmpFile]);
414
			unlink($tmpFile);
415
		}
416
	}
417
418
	/** {@inheritdoc} */
419
	public function free_space($path) {
420
		$this->init();
421
		$path = $this->cleanPath($path);
422
		try {
423
			// TODO: cacheable ?
424
			$response = $this->client->propfind($this->encodePath($path), array('{DAV:}quota-available-bytes'));
425
			if (isset($response['{DAV:}quota-available-bytes'])) {
426
				$freeSpace = (int)$response['{DAV:}quota-available-bytes'];
427
				if ($freeSpace === FileInfo::SPACE_UNLIMITED) {
428
					// most of the code cannot cope with unlimited storage,
429
					// so as a workaround convert to SPACE_UNKNOWN which is a
430
					// value recognized in many places
431
					return FileInfo::SPACE_UNKNOWN;
432
				}
433
				return $freeSpace;
434
			} else {
435
				return FileInfo::SPACE_UNKNOWN;
436
			}
437
		} catch (\Exception $e) {
438
			return FileInfo::SPACE_UNKNOWN;
439
		}
440
	}
441
442
	/** {@inheritdoc} */
443
	public function touch($path, $mtime = null) {
444
		$this->init();
445
		if (is_null($mtime)) {
446
			$mtime = time();
447
		}
448
		$path = $this->cleanPath($path);
449
450
		// if file exists, update the mtime, else create a new empty file
451
		if ($this->file_exists($path)) {
452
			try {
453
				$this->statCache->remove($path);
454
				$this->client->proppatch($this->encodePath($path), array('{DAV:}lastmodified' => $mtime));
455
			} 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...
456
				if ($e->getHttpStatus() === 501) {
457
					return false;
458
				}
459
				$this->convertException($e, $path);
460
				return false;
461
			} catch (\Exception $e) {
462
				$this->convertException($e, $path);
463
				return false;
464
			}
465
		} else {
466
			$this->file_put_contents($path, '');
467
		}
468
		return true;
469
	}
470
471
	/**
472
	 * @param string $path
473
	 * @param string $data
474
	 * @return int
475
	 */
476
	public function file_put_contents($path, $data) {
477
		$path = $this->cleanPath($path);
478
		$result = parent::file_put_contents($path, $data);
479
		$this->statCache->remove($path);
480
		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...
481
	}
482
483
	/**
484
	 * @param string $path
485
	 * @param string $target
486
	 */
487
	protected function uploadFile($path, $target) {
488
		$this->init();
489
		// invalidate
490
		$target = $this->cleanPath($target);
491
		$this->statCache->remove($target);
492
		$source = fopen($path, 'r');
493
494
		$curl = curl_init();
495
		curl_setopt($curl, CURLOPT_USERPWD, $this->user . ':' . $this->password);
496
		curl_setopt($curl, CURLOPT_URL, $this->createBaseUri() . $this->encodePath($target));
497
		curl_setopt($curl, CURLOPT_BINARYTRANSFER, true);
498
		curl_setopt($curl, CURLOPT_INFILE, $source); // file pointer
499
		curl_setopt($curl, CURLOPT_INFILESIZE, filesize($path));
500
		curl_setopt($curl, CURLOPT_PUT, true);
501
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
502
		curl_setopt($curl, CURLOPT_PROTOCOLS,  CURLPROTO_HTTP | CURLPROTO_HTTPS);
503
		curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS,  CURLPROTO_HTTP | CURLPROTO_HTTPS);
504 View Code Duplication
		if ($this->secure === true) {
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...
505
			curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
506
			curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
507
			if ($this->certPath) {
508
				curl_setopt($curl, CURLOPT_CAINFO, $this->certPath);
509
			}
510
		}
511
		curl_exec($curl);
512
		$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
513 View Code Duplication
		if ($statusCode !== 200) {
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...
514
			Util::writeLog("webdav client", 'curl GET ' . curl_getinfo($curl, CURLINFO_EFFECTIVE_URL) . ' returned status code ' . $statusCode, Util::ERROR);
515
			if ($statusCode === 423) {
516
				throw new \OCP\Lock\LockedException($path);
517
			}
518
		}
519
		curl_close($curl);
520
		fclose($source);
521
		$this->removeCachedFile($target);
522
	}
523
524
	/** {@inheritdoc} */
525
	public function rename($path1, $path2) {
526
		$this->init();
527
		$path1 = $this->cleanPath($path1);
528
		$path2 = $this->cleanPath($path2);
529
		try {
530
			$this->client->request(
531
				'MOVE',
532
				$this->encodePath($path1),
533
				null,
534
				array(
535
					'Destination' => $this->createBaseUri() . $this->encodePath($path2)
536
				)
537
			);
538
			$this->statCache->clear($path1 . '/');
539
			$this->statCache->clear($path2 . '/');
540
			$this->statCache->set($path1, false);
541
			$this->statCache->set($path2, true);
542
			$this->removeCachedFile($path1);
543
			$this->removeCachedFile($path2);
544
			return true;
545
		} catch (\Exception $e) {
546
			$this->convertException($e);
547
		}
548
		return false;
549
	}
550
551
	/** {@inheritdoc} */
552
	public function copy($path1, $path2) {
553
		$this->init();
554
		$path1 = $this->encodePath($this->cleanPath($path1));
555
		$path2 = $this->createBaseUri() . $this->encodePath($this->cleanPath($path2));
556
		try {
557
			$this->client->request('COPY', $path1, null, array('Destination' => $path2));
558
			$this->statCache->clear($path2 . '/');
559
			$this->statCache->set($path2, true);
560
			$this->removeCachedFile($path2);
561
			return true;
562
		} catch (\Exception $e) {
563
			$this->convertException($e);
564
		}
565
		return false;
566
	}
567
568
	/** {@inheritdoc} */
569
	public function stat($path) {
570
		try {
571
			$response = $this->propfind($path);
572
			return array(
573
				'mtime' => strtotime($response['{DAV:}getlastmodified']),
574
				'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0,
575
			);
576
		} 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...
577
			if ($e->getHttpStatus() === 404) {
578
				return array();
579
			}
580
			$this->convertException($e, $path);
581
		} catch (\Exception $e) {
582
			$this->convertException($e, $path);
583
		}
584
		return array();
585
	}
586
587
	/** {@inheritdoc} */
588
	public function getMimeType($path) {
589
		try {
590
			$response = $this->propfind($path);
591
			$responseType = array();
592
			if (isset($response["{DAV:}resourcetype"])) {
593
				$responseType = $response["{DAV:}resourcetype"]->resourceType;
594
			}
595
			$type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
596
			if ($type == 'dir') {
597
				return 'httpd/unix-directory';
598
			} elseif (isset($response['{DAV:}getcontenttype'])) {
599
				return $response['{DAV:}getcontenttype'];
600
			} else {
601
				return false;
602
			}
603
		} 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...
604
			if ($e->getHttpStatus() === 404) {
605
				return false;
606
			}
607
			$this->convertException($e, $path);
608
		} catch (\Exception $e) {
609
			$this->convertException($e, $path);
610
		}
611
		return false;
612
	}
613
614
	/**
615
	 * @param string $path
616
	 * @return string
617
	 */
618
	public function cleanPath($path) {
619
		if ($path === '') {
620
			return $path;
621
		}
622
		$path = Filesystem::normalizePath($path);
623
		// remove leading slash
624
		return substr($path, 1);
625
	}
626
627
	/**
628
	 * URL encodes the given path but keeps the slashes
629
	 *
630
	 * @param string $path to encode
631
	 * @return string encoded path
632
	 */
633
	private function encodePath($path) {
634
		// slashes need to stay
635
		return str_replace('%2F', '/', rawurlencode($path));
636
	}
637
638
	/**
639
	 * @param string $method
640
	 * @param string $path
641
	 * @param string|resource|null $body
642
	 * @param int $expected
643
	 * @return bool
644
	 * @throws StorageInvalidException
645
	 * @throws StorageNotAvailableException
646
	 */
647
	private function simpleResponse($method, $path, $body, $expected) {
648
		$path = $this->cleanPath($path);
649
		try {
650
			$response = $this->client->request($method, $this->encodePath($path), $body);
651
			return $response['statusCode'] == $expected;
652
		} 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...
653 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...
654
				$this->statCache->clear($path . '/');
655
				$this->statCache->set($path, false);
656
				return false;
657
			}
658
659
			$this->convertException($e, $path);
660
		} catch (\Exception $e) {
661
			$this->convertException($e, $path);
662
		}
663
		return false;
664
	}
665
666
	/**
667
	 * check if curl is installed
668
	 */
669
	public static function checkDependencies() {
670
		return true;
671
	}
672
673
	/** {@inheritdoc} */
674
	public function isUpdatable($path) {
675
		return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
676
	}
677
678
	/** {@inheritdoc} */
679
	public function isCreatable($path) {
680
		return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
681
	}
682
683
	/** {@inheritdoc} */
684
	public function isSharable($path) {
685
		return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
686
	}
687
688
	/** {@inheritdoc} */
689
	public function isDeletable($path) {
690
		return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
691
	}
692
693
	/** {@inheritdoc} */
694
	public function getPermissions($path) {
695
		$this->init();
696
		$path = $this->cleanPath($path);
697
		$response = $this->propfind($path);
698
		if (isset($response['{http://owncloud.org/ns}permissions'])) {
699
			return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
700
		} else if ($this->is_dir($path)) {
701
			return Constants::PERMISSION_ALL;
702
		} else if ($this->file_exists($path)) {
703
			return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
704
		} else {
705
			return 0;
706
		}
707
	}
708
709
	/** {@inheritdoc} */
710
	public function getETag($path) {
711
		$this->init();
712
		$path = $this->cleanPath($path);
713
		$response = $this->propfind($path);
714
		if (isset($response['{DAV:}getetag'])) {
715
			return trim($response['{DAV:}getetag'], '"');
716
		}
717
		return parent::getEtag($path);
718
	}
719
720
	/**
721
	 * @param string $permissionsString
722
	 * @return int
723
	 */
724
	protected function parsePermissions($permissionsString) {
725
		$permissions = Constants::PERMISSION_READ;
726
		if (strpos($permissionsString, 'R') !== false) {
727
			$permissions |= Constants::PERMISSION_SHARE;
728
		}
729
		if (strpos($permissionsString, 'D') !== false) {
730
			$permissions |= Constants::PERMISSION_DELETE;
731
		}
732
		if (strpos($permissionsString, 'W') !== false) {
733
			$permissions |= Constants::PERMISSION_UPDATE;
734
		}
735
		if (strpos($permissionsString, 'CK') !== false) {
736
			$permissions |= Constants::PERMISSION_CREATE;
737
			$permissions |= Constants::PERMISSION_UPDATE;
738
		}
739
		return $permissions;
740
	}
741
742
	/**
743
	 * check if a file or folder has been updated since $time
744
	 *
745
	 * @param string $path
746
	 * @param int $time
747
	 * @throws \OCP\Files\StorageNotAvailableException
748
	 * @return bool
749
	 */
750
	public function hasUpdated($path, $time) {
751
		$this->init();
752
		$path = $this->cleanPath($path);
753
		try {
754
			// force refresh for $path
755
			$this->statCache->remove($path);
756
			$response = $this->propfind($path);
757
			if (isset($response['{DAV:}getetag'])) {
758
				$cachedData = $this->getCache()->get($path);
759
				$etag = null;
760
				if (isset($response['{DAV:}getetag'])) {
761
					$etag = trim($response['{DAV:}getetag'], '"');
762
				}
763
				if (!empty($etag) && $cachedData['etag'] !== $etag) {
764
					return true;
765
				} else if (isset($response['{http://owncloud.org/ns}permissions'])) {
766
					$permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
767
					return $permissions !== $cachedData['permissions'];
768
				} else {
769
					return false;
770
				}
771
			} else {
772
				$remoteMtime = strtotime($response['{DAV:}getlastmodified']);
773
				return $remoteMtime > $time;
774
			}
775
		} 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...
776 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...
777
				if ($path === '') {
778
					// if root is gone it means the storage is not available
779
					throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
780
				}
781
				return false;
782
			}
783
			$this->convertException($e, $path);
784
			return false;
785
		} catch (\Exception $e) {
786
			$this->convertException($e, $path);
787
			return false;
788
		}
789
	}
790
791
	/**
792
	 * Interpret the given exception and decide whether it is due to an
793
	 * unavailable storage, invalid storage or other.
794
	 * This will either throw StorageInvalidException, StorageNotAvailableException
795
	 * or do nothing.
796
	 *
797
	 * @param Exception $e sabre exception
798
	 * @param string $path optional path from the operation
799
	 *
800
	 * @throws StorageInvalidException if the storage is invalid, for example
801
	 * when the authentication expired or is invalid
802
	 * @throws StorageNotAvailableException if the storage is not available,
803
	 * which might be temporary
804
	 */
805
	private function convertException(Exception $e, $path = '') {
806
		Util::writeLog('files_external', $e->getMessage(), Util::ERROR);
807
		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...
808
			if ($e->getHttpStatus() === 423) {
809
				throw new \OCP\Lock\LockedException($path);
810
			}
811 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...
812
				// either password was changed or was invalid all along
813
				throw new StorageInvalidException(get_class($e).': '.$e->getMessage());
814
			} else if ($e->getHttpStatus() === 405) {
815
				// ignore exception for MethodNotAllowed, false will be returned
816
				return;
817
			}
818
			throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
819
		} 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...
820
			// connection timeout or refused, server could be temporarily down
821
			throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
822
		} else if ($e instanceof \InvalidArgumentException) {
823
			// parse error because the server returned HTML instead of XML,
824
			// possibly temporarily down
825
			throw new StorageNotAvailableException(get_class($e).': '.$e->getMessage());
826
		} else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
827
			// rethrow
828
			throw $e;
829
		}
830
831
		// TODO: only log for now, but in the future need to wrap/rethrow exception
832
	}
833
}
834
835