Test Failed
Push — master ( cd42b5...841446 )
by
unknown
16:44 queued 06:09
created

Backend::getFormConfig()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 8
rs 10
1
<?php
2
3
namespace Files\Backend\Webdav;
4
5
require_once __DIR__ . "/sabredav/FilesWebDavClient.php";
6
require_once __DIR__ . "/../class.abstract_backend.php";
7
require_once __DIR__ . "/../class.exception.php";
8
9
use Files\Backend\AbstractBackend;
10
use Files\Backend\Exception as BackendException;
11
use Files\Backend\iFeatureQuota;
12
use Files\Backend\iFeatureVersionInfo;
13
use Files\Backend\Webdav\sabredav\FilesWebDavClient;
14
use Sabre\DAV\Exception;
15
use Sabre\HTTP\ClientException;
16
17
/**
18
 * This is a file backend for webdav servers.
19
 *
20
 * @class   Backend
21
 * @extends AbstractBackend
22
 */
23
class Backend extends AbstractBackend implements iFeatureQuota, iFeatureVersionInfo {
24
	/**
25
	 * Error codes
26
	 * see @parseErrorCodeToMessage for description.
27
	 */
28
	public const WD_ERR_UNAUTHORIZED = 401;
29
	public const WD_ERR_FORBIDDEN = 403;
30
	public const WD_ERR_NOTFOUND = 404;
31
	public const WD_ERR_NOTALLOWED = 405;
32
	public const WD_ERR_TIMEOUT = 408;
33
	public const WD_ERR_LOCKED = 423;
34
	public const WD_ERR_FAILED_DEPENDENCY = 423;
35
	public const WD_ERR_INTERNAL = 500;
36
	public const WD_ERR_UNREACHABLE = 800;
37
	public const WD_ERR_TMP = 801;
38
	public const WD_ERR_FEATURES = 802;
39
	public const WD_ERR_NO_CURL = 803;
40
41
	/**
42
	 * Configuration data for the extjs metaform.
43
	 */
44
	protected $formConfig;
45
	protected $formFields;
46
	protected $metaConfig;
47
48
	/**
49
	 * @var bool debugging flag, if true, debugging is enabled
50
	 */
51
	public $debug = false;
52
53
	/**
54
	 * @var int webdav server port
55
	 */
56
	public $port = 80;
57
58
	/**
59
	 * @var string hostname or ip
60
	 */
61
	public $server = "localhost";
62
63
	/**
64
	 * @var string global path prefix for all requests
65
	 */
66
	public $path = "/webdav.php";
67
68
	/**
69
	 * @var bool if true, ssl is used
70
	 */
71
	public $ssl = false;
72
73
	/**
74
	 * @var bool allow self signed certificates
75
	 */
76
	public $allowselfsigned = true;
77
78
	/**
79
	 * @var string the username
80
	 */
81
	public $user = "";
82
83
	/**
84
	 * @var string the password
85
	 */
86
	public $pass = "";
87
88
	/**
89
	 * @var FilesWebDavClient the SabreDAV client object
90
	 */
91
	public $sabre_client;
92
93
	/**
94
	 * @constructor
95
	 */
96
	public function __construct() {
97
		// initialization
98
		$this->debug = PLUGIN_FILESBROWSER_LOGLEVEL === "DEBUG" ? true : false;
0 ignored issues
show
introduced by
The condition Files\Backend\Webdav\PLU...ER_LOGLEVEL === 'DEBUG' is always false.
Loading history...
99
100
		$this->init_form();
101
102
		// set backend description
103
		$this->backendDescription = _("With this backend, you can connect to any WebDAV server.");
104
105
		// set backend display name
106
		$this->backendDisplayName = "Webdav";
107
108
		// set backend version
109
		// TODO: this should be changed on every release
110
		$this->backendVersion = "3.0";
111
112
		// Backend name used in translations
113
		$this->backendTransName = _('Files WebDAV Backend: ');
0 ignored issues
show
Bug Best Practice introduced by
The property backendTransName does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
114
	}
115
116
	/**
117
	 * Initialise form fields.
118
	 */
119
	private function init_form() {
120
		$this->formConfig = [
121
			"labelAlign" => "left",
122
			"columnCount" => 1,
123
			"labelWidth" => 80,
124
			"defaults" => [
125
				"width" => 292,
126
			],
127
		];
128
129
		$this->formFields = [
130
			[
131
				"name" => "server_address",
132
				"fieldLabel" => _('Server address'),
133
				"editor" => [
134
					"allowBlank" => false,
135
				],
136
			],
137
			[
138
				"name" => "server_port",
139
				"fieldLabel" => _('Server port'),
140
				"editor" => [
141
					"ref" => "../../portField",
142
					"allowBlank" => false,
143
				],
144
			],
145
			[
146
				"name" => "server_ssl",
147
				"fieldLabel" => _('Use SSL'),
148
				"editor" => [
149
					"xtype" => "checkbox",
150
					"listeners" => [
151
						"check" => "Zarafa.plugins.files.data.Actions.onCheckSSL", // this javascript function will be called!
152
					],
153
				],
154
			],
155
			[
156
				"name" => "server_path",
157
				"fieldLabel" => _('Webdav base path'),
158
				"editor" => [
159
				],
160
			],
161
			[
162
				"name" => "user",
163
				"fieldLabel" => _('Username'),
164
				"editor" => [
165
					"ref" => "../../usernameField",
166
				],
167
			],
168
			[
169
				"name" => "password",
170
				"fieldLabel" => _('Password'),
171
				"editor" => [
172
					"ref" => "../../passwordField",
173
					"inputType" => "password",
174
				],
175
			],
176
			[
177
				"name" => "use_grommunio_credentials",
178
				"fieldLabel" => _('Use grommunio credentials'),
179
				"editor" => [
180
					"xtype" => "checkbox",
181
					"listeners" => [
182
						"check" => "Zarafa.plugins.files.data.Actions.onCheckCredentials", // this javascript function will be called!
183
					],
184
				],
185
			],
186
		];
187
188
		$this->metaConfig = [
189
			"success" => true,
190
			"metaData" => [
191
				"fields" => $this->formFields,
192
				"formConfig" => $this->formConfig,
193
			],
194
			"data" => [ // here we can specify the default values.
195
				"server_address" => "files.demo.com",
196
				"server_port" => "80",
197
				"server_path" => "/remote.php/webdav",
198
			],
199
		];
200
	}
201
202
	/**
203
	 * Initialize backend from $backend_config array.
204
	 *
205
	 * @param $backend_config
206
	 */
207
	public function init_backend($backend_config) {
208
		$this->set_server($backend_config["server_address"]);
209
		$this->set_port($backend_config["server_port"]);
210
		$this->set_base($backend_config["server_path"]);
211
		$this->set_ssl($backend_config["server_ssl"]);
212
213
		// set user and password
214
		if ($backend_config["use_grommunio_credentials"] === false) {
215
			$this->set_user($backend_config["user"]);
216
			$this->set_pass($backend_config["password"]);
217
		}
218
		else {
219
			// For backward compatibility we will check if the Encryption store exists. If not,
220
			// we will fall back to the old way of retrieving the password from the session.
221
			if (class_exists('EncryptionStore')) {
222
				// Get the username and password from the Encryption store
223
				$encryptionStore = \EncryptionStore::getInstance();
224
				$this->set_user($encryptionStore->get('username'));
225
				$this->set_pass($encryptionStore->get('password'));
226
			}
227
			else {
228
				$this->set_user($GLOBALS['mapisession']->getUserName());
229
				$password = $_SESSION['password'];
230
				if (function_exists('openssl_decrypt')) {
231
					$this->set_pass(openssl_decrypt($password, "des-ede3-cbc", PASSWORD_KEY, 0, PASSWORD_IV));
0 ignored issues
show
Bug introduced by
The constant Files\Backend\Webdav\PASSWORD_IV was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Files\Backend\Webdav\PASSWORD_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
232
				}
233
			}
234
		}
235
	}
236
237
	/**
238
	 * Set webdav server. FQN or IP address.
239
	 *
240
	 * @param string $server hostname or ip of the ftp server
241
	 */
242
	public function set_server($server) {
243
		$this->server = $server;
244
	}
245
246
	/**
247
	 * Set base path.
248
	 *
249
	 * @param string $pp the global path prefix
250
	 */
251
	public function set_base($pp) {
252
		$this->path = $pp;
253
		$this->log('Base path set to ' . $this->path);
254
	}
255
256
	/**
257
	 * Set ssl.
258
	 *
259
	 * @param int /bool $ssl (1 = true, 0 = false)
0 ignored issues
show
Documentation Bug introduced by
The doc comment /bool at position 0 could not be parsed: Unknown type name '/bool' at position 0 in /bool.
Loading history...
260
	 */
261
	public function set_ssl($ssl) {
262
		$this->ssl = $ssl ? true : false;
263
		$this->log('SSL extension was set to ' . $this->ssl);
264
	}
265
266
	/**
267
	 * Allow self signed certificates - unimplemented.
268
	 *
269
	 * @param bool $allowselfsigned Allow self signed certificates. Not yet implemented.
270
	 */
271
	public function set_selfsigned($allowselfsigned) {
272
		$this->allowselfsigned = $allowselfsigned;
273
	}
274
275
	/**
276
	 * Set tcp port of webdav server. Default is 80.
277
	 *
278
	 * @param int $port the port of the ftp server
279
	 */
280
	public function set_port($port) {
281
		$this->port = $port;
282
	}
283
284
	/**
285
	 * set user name for authentication.
286
	 *
287
	 * @param string $user username
288
	 */
289
	public function set_user($user) {
290
		$this->user = $user;
291
	}
292
293
	/**
294
	 * Set password for authentication.
295
	 *
296
	 * @param string $pass password
297
	 */
298
	public function set_pass($pass) {
299
		$this->pass = $pass;
300
	}
301
302
	/**
303
	 * set debug on (1) or off (0).
304
	 * produces a lot of debug messages in webservers error log if set to on (1).
305
	 *
306
	 * @param bool $debug enable or disable debugging
307
	 */
308
	public function set_debug($debug) {
309
		$this->debug = $debug;
310
	}
311
312
	/**
313
	 * Opens the connection to the webdav server.
314
	 *
315
	 * @throws BackendException if connection is not successful
316
	 *
317
	 * @return bool true if action succeeded
318
	 */
319
	public function open() {
320
		// check if curl is available
321
		$serverHasCurl = function_exists('curl_version');
322
		if (!$serverHasCurl) {
323
			$e = new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_NO_CURL), 500);
324
			$e->setTitle($this->backendTransName . _('PHP-CURL is not installed'));
325
326
			throw $e;
327
		}
328
329
		$davsettings = [
330
			'baseUri' => $this->webdavUrl(),
331
			'userName' => $this->user,
332
			'password' => $this->pass,
333
			'authType' => \Sabre\DAV\Client::AUTH_BASIC,
334
		];
335
336
		try {
337
			$this->sabre_client = new FilesWebDavClient($davsettings);
338
			$this->sabre_client->addCurlSetting(CURLOPT_SSL_VERIFYPEER, !$this->allowselfsigned);
339
340
			return true;
341
		}
342
		catch (Exception $e) {
343
			$this->log('Failed to open: ' . $e->getMessage());
344
			if (intval($e->getHTTPCode()) == 401) {
345
				$e = new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_UNAUTHORIZED), $e->getHTTPCode());
346
				$e->setTitle($this->backendTransName . _('Access denied'));
347
348
				throw $e;
349
			}
350
			$e = new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_UNREACHABLE), $e->getHTTPCode());
351
			$e->setTitle($this->backendTransName . _('Connection failed'));
352
353
			throw $e;
354
		}
355
	}
356
357
	/**
358
	 * show content of a directory.
359
	 *
360
	 * @param string $path      directory path
361
	 * @param bool   $hidefirst Optional parameter to hide the root entry. Default true
362
	 * @param mixed  $dir
363
	 *
364
	 * @throws BackendException if request is not successful
365
	 *
366
	 * @return mixed array with directory content
367
	 */
368
	public function ls($dir, $hidefirst = true) {
369
		$time_start = microtime(true);
370
		$dir = $this->removeSlash($dir);
371
		$lsdata = [];
372
		$this->log("[LS] start for dir: {$dir}");
373
374
		try {
375
			$response = $this->sabre_client->propFind($dir, [
376
				'{http://owncloud.org/ns}fileid',
377
				'{DAV:}resourcetype',
378
				'{DAV:}getcontentlength',
379
				'{DAV:}getlastmodified',
380
				'{DAV:}getcontenttype',
381
				'{DAV:}quota-used-bytes',
382
				'{DAV:}quota-available-bytes',
383
			], 1);
384
			$this->log("[LS] backend fetched in: " . (microtime(true) - $time_start) . " seconds.");
385
386
			foreach ($response as $name => $fields) {
387
				if ($hidefirst) {
388
					$hidefirst = false; // skip the first line - its the requested dir itself
389
390
					continue;
391
				}
392
393
				$name = substr($name, strlen($this->path)); // skip the webdav path
394
				$name = urldecode($name);
395
396
				// Always fallback to a file resourceType
397
				$type = "file";
398
				if (isset($fields['{DAV:}resourcetype'])) {
399
					$value = $fields['{DAV:}resourcetype']->getValue();
400
					if (!empty($value) && $value[0] === "{DAV:}collection") {
401
						$type = "collection";
402
					}
403
				}
404
405
				$lsdata[$name] = [
406
					"fileid" => isset($fields["{http://owncloud.org/ns}fileid"]) ? $fields["{http://owncloud.org/ns}fileid"] : '-1',
407
					"resourcetype" => $type,
408
					"getcontentlength" => isset($fields["{DAV:}getcontentlength"]) ? $fields["{DAV:}getcontentlength"] : null,
409
					"getlastmodified" => isset($fields["{DAV:}getlastmodified"]) ? $fields["{DAV:}getlastmodified"] : null,
410
					"getcontenttype" => isset($fields["{DAV:}getcontenttype"]) ? $fields["{DAV:}getcontenttype"] : null,
411
					"quota-used-bytes" => isset($fields["{DAV:}quota-used-bytes"]) ? $fields["{DAV:}quota-used-bytes"] : null,
412
					"quota-available-bytes" => isset($fields["{DAV:}quota-available-bytes"]) ? $fields["{DAV:}quota-available-bytes"] : null,
413
				];
414
			}
415
			$time_end = microtime(true);
416
			$time = $time_end - $time_start;
417
			$this->log("[LS] done in {$time} seconds");
418
419
			return $lsdata;
420
		}
421
		catch (ClientException $e) {
422
			$this->log('ls sabre ClientException: ' . $e->getMessage());
423
			$e = new BackendException($this->parseErrorCodeToMessage($e->getCode()), $e->getCode());
424
			$e->setTitle($this->backendTransName . _('Sabre error'));
425
426
			throw $e;
427
		}
428
		catch (Exception $e) {
429
			$this->log('ls general exception: ' . $e->getMessage() . " [" . $e->getHTTPCode() . "]");
430
			// THIS IS A FIX FOR OWNCLOUD - It does return 500 instead of 401...
431
			$err_code = $e->getHTTPCode();
432
			// check if code is 500 - then we should try to parse the error message
433
			if ($err_code === 500) {
434
				// message example: HTTP-Code: 401
435
				$regx = '/[0-9]{3}/';
436
				if (preg_match($regx, $e->getMessage(), $found)) {
437
					$err_code = $found[0];
438
				}
439
			}
440
			$e = new BackendException($this->parseErrorCodeToMessage($err_code), $err_code);
441
			$e->setTitle($this->backendTransName . _('Connection failed'));
442
443
			throw $e;
444
		}
445
	}
446
447
	/**
448
	 * create a new directory.
449
	 *
450
	 * @param string $dir directory path
451
	 *
452
	 * @throws BackendException if request is not successful
453
	 *
454
	 * @return bool true if action succeeded
455
	 */
456
	public function mkcol($dir) {
457
		$time_start = microtime(true);
458
		$dir = $this->removeSlash($dir);
459
		$this->log("[MKCOL] start for dir: {$dir}");
460
461
		try {
462
			$response = $this->sabre_client->request("MKCOL", $dir, null);
463
			$time_end = microtime(true);
464
			$time = $time_end - $time_start;
465
			$this->log("[MKCOL] done in {$time} seconds: " . $response['statusCode']);
466
467
			return true;
468
		}
469
		catch (ClientException $e) {
470
			$e = new BackendException($this->parseErrorCodeToMessage($e->getCode()), $e->getCode());
471
			$e->setTitle($this->backendTransName . _('Sabre error'));
472
473
			throw $e;
474
		}
475
		catch (Exception $e) {
476
			$this->log('[MKCOL] fatal: ' . $e->getMessage());
477
			$e = new BackendException($this->parseErrorCodeToMessage($e->getHTTPCode()), $e->getHTTPCode());
478
			$e->setTitle($this->backendTransName . _('Directory creation failed'));
479
480
			throw $e;
481
		}
482
	}
483
484
	/**
485
	 * delete a file or directory.
486
	 *
487
	 * @param string $path file/directory path
488
	 *
489
	 * @throws BackendException if request is not successful
490
	 *
491
	 * @return bool true if action succeeded
492
	 */
493
	public function delete($path) {
494
		$time_start = microtime(true);
495
		$path = $this->removeSlash($path);
496
		$this->log("[DELETE] start for dir: {$path}");
497
498
		try {
499
			$response = $this->sabre_client->request("DELETE", $path, null);
500
			$time_end = microtime(true);
501
			$time = $time_end - $time_start;
502
			$this->log("[DELETE] done in {$time} seconds: " . $response['statusCode']);
503
504
			return true;
505
		}
506
		catch (ClientException $e) {
507
			$e = new BackendException($this->parseErrorCodeToMessage($e->getCode()), $e->getCode());
508
			$e->setTitle($this->backendTransName . _('Sabre error'));
509
510
			throw $e;
511
		}
512
		catch (Exception $e) {
513
			$this->log('[DELETE] fatal: ' . $e->getMessage());
514
			$e = new BackendException($this->parseErrorCodeToMessage($e->getHTTPCode()), $e->getHTTPCode());
515
			$e->setTitle($this->backendTransName . _('Deletion failed'));
516
517
			throw $e;
518
		}
519
	}
520
521
	/**
522
	 * Move a file or collection on webdav server (serverside)
523
	 * If you set param overwrite as true, the target will be overwritten.
524
	 *
525
	 * @param string $src_path  Source path
526
	 * @param string $dest_path Destination path
527
	 * @param bool   $overwrite Overwrite file if exists in $dest_path
528
	 * @param mixed  $dst_path
529
	 *
530
	 * @throws BackendException if request is not successful
531
	 *
532
	 * @return bool true if action succeeded
533
	 */
534
	public function move($src_path, $dst_path, $overwrite = false) {
535
		$time_start = microtime(true);
536
		$src_path = $this->removeSlash($src_path);
537
		$dst_path = $this->webdavUrl() . $this->removeSlash($dst_path);
538
		$this->log("[MOVE] start for dir: {$src_path} -> {$dst_path}");
539
		if ($overwrite) {
540
			$overwrite = 'T';
541
		}
542
		else {
543
			$overwrite = 'F';
544
		}
545
546
		try {
547
			$response = $this->sabre_client->request("MOVE", $src_path, null, ["Destination" => $dst_path, 'Overwrite' => $overwrite]);
548
			$time_end = microtime(true);
549
			$time = $time_end - $time_start;
550
			$this->log("[MOVE] done in {$time} seconds: " . $response['statusCode']);
551
552
			return true;
553
		}
554
		catch (ClientException $e) {
555
			$e = new BackendException($this->parseErrorCodeToMessage($e->getCode()), $e->getCode());
556
			$e->setTitle($this->backendTransName . _('Sabre error'));
557
558
			throw $e;
559
		}
560
		catch (Exception $e) {
561
			$this->log('[MOVE] fatal: ' . $e->getMessage());
562
			$e = new BackendException($this->parseErrorCodeToMessage($e->getHTTPCode()), $e->getHTTPCode());
563
			$e->setTitle($this->backendTransName . _('Moving failed'));
564
565
			throw $e;
566
		}
567
	}
568
569
	/**
570
	 * Puts a file into a collection.
571
	 *
572
	 * @param string $path Destination path
573
	 * @param mixed  $data
574
	 *
575
	 * @string mixed $data Any kind of data
576
	 *
577
	 * @throws BackendException if request is not successful
578
	 *
579
	 * @return bool true if action succeeded
580
	 */
581
	public function put($path, $data) {
582
		$time_start = microtime(true);
583
		$path = $this->removeSlash($path);
584
		$this->log("[PUT] start for dir: {$path} strlen: " . strlen($data));
585
586
		try {
587
			$response = $this->sabre_client->request("PUT", $path, $data);
588
			$time_end = microtime(true);
589
			$time = $time_end - $time_start;
590
			$this->log("[PUT] done in {$time} seconds: " . $response['statusCode']);
591
592
			return true;
593
		}
594
		catch (ClientException $e) {
595
			$e = new BackendException($this->parseErrorCodeToMessage($e->getCode()), $e->getCode());
596
			$e->setTitle($this->backendTransName . _('Sabre error'));
597
598
			throw $e;
599
		}
600
		catch (Exception $e) {
601
			$this->log('[PUT] fatal: ' . $e->getMessage());
602
			$e = new BackendException($this->parseErrorCodeToMessage($e->getHTTPCode()), $e->getHTTPCode());
603
			$e->setTitle($this->backendTransName . _('Connection failed'));
604
605
			throw $e;
606
		}
607
	}
608
609
	/**
610
	 * Upload a local file.
611
	 *
612
	 * @param string $path     Destination path on the server
613
	 * @param string $filename Local filename for the file that should be uploaded
614
	 *
615
	 * @throws BackendException if request is not successful
616
	 *
617
	 * @return bool true if action succeeded
618
	 */
619
	public function put_file($path, $filename) {
620
		$buffer = file_get_contents($filename);
621
622
		if ($buffer !== false) {
623
			return $this->put($path, $buffer);
624
		}
625
		$e = new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_TMP), self::WD_ERR_TMP);
626
		$e->setTitle($this->backendTransName . _('Temporary directory problems'));
627
628
		throw $e;
629
	}
630
631
	/**
632
	 * Gets a file from a webdav collection.
633
	 *
634
	 * @param string $path   The source path on the server
635
	 * @param mixed  $buffer Buffer for the received data
636
	 *
637
	 * @throws BackendException if request is not successful
638
	 *
639
	 * @return bool true if action succeeded
640
	 */
641
	public function get($path, &$buffer) {
642
		$tmpfile = tempnam(TMP_PATH, stripslashes(base64_encode($path)));
0 ignored issues
show
Bug introduced by
The constant Files\Backend\Webdav\TMP_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
643
644
		$this->log("[GET] buffer path: {$tmpfile}");
645
		$this->get_file($path, $tmpfile);
646
647
		$buffer = file_get_contents($tmpfile);
648
		unlink($tmpfile);
649
	}
650
651
	/**
652
	 * Gets a file from a collection into local filesystem.
653
	 *
654
	 * @param string $srcpath   Source path on server
655
	 * @param string $localpath Destination path on local filesystem
656
	 *
657
	 * @throws BackendException if request is not successful
658
	 *
659
	 * @return bool true if action succeeded
660
	 */
661
	public function get_file($srcpath, $localpath) {
662
		$time_start = microtime(true);
663
		$path = $this->removeSlash($srcpath);
664
		$this->log("[GET_FILE] start for dir: {$path}");
665
		$this->log("[GET_FILE] local path (" . $localpath . ") writeable: " . is_writable($localpath));
666
667
		try {
668
			$response = $this->sabre_client->getFile($path, $localpath);
669
			$time_end = microtime(true);
670
			$time = $time_end - $time_start;
671
			$this->log("[GET_FILE] done in {$time} seconds: " . $response['statusCode']);
672
		}
673
		catch (ClientException $e) {
674
			$e = new BackendException($this->parseErrorCodeToMessage($e->getCode()), $e->getCode());
675
			$e->setTitle($this->backendTransName . _('Sabre error'));
676
677
			throw $e;
678
		}
679
		catch (Exception $e) {
680
			$this->log('[GET_FILE] - FATAL -' . $e->getMessage());
681
			$e = new BackendException($this->parseErrorCodeToMessage($e->getHTTPCode()), $e->getHTTPCode());
682
			$e->setTitle($this->backendTransName . _('File or folder not found'));
683
684
			throw $e;
685
		}
686
	}
687
688
	/**
689
	 * Public method copy_file.
690
	 *
691
	 * Copy a file on webdav server
692
	 * Duplicates a file on the webdav server (serverside).
693
	 * All work is done on the webdav server. If you set param overwrite as true,
694
	 * the target will be overwritten.
695
	 *
696
	 * @param string $src_path  Source path
697
	 * @param string $dest_path Destination path
698
	 * @param bool   $overwrite Overwrite if file exists in $dst_path
699
	 * @param mixed  $dst_path
700
	 *
701
	 * @throws BackendException if request is not successful
702
	 *
703
	 * @return bool true if action succeeded
704
	 */
705
	public function copy_file($src_path, $dst_path, $overwrite = false) {
706
		return $this->copy($src_path, $dst_path, $overwrite, false);
707
	}
708
709
	/**
710
	 * Public method copy_coll.
711
	 *
712
	 * Copy a collection on webdav server
713
	 * Duplicates a collection on the webdav server (serverside).
714
	 * All work is done on the webdav server. If you set param overwrite as true,
715
	 * the target will be overwritten.
716
	 *
717
	 * @param string $src_path  Source path
718
	 * @param string $dest_path Destination path
719
	 * @param bool   $overwrite Overwrite if collection exists in $dst_path
720
	 * @param mixed  $dst_path
721
	 *
722
	 * @throws BackendException if request is not successful
723
	 *
724
	 * @return bool true if action succeeded
725
	 */
726
	public function copy_coll($src_path, $dst_path, $overwrite = false) {
727
		return $this->copy($src_path, $dst_path, $overwrite, true);
728
	}
729
730
	/**
731
	 * Gets path information from webdav server for one element.
732
	 *
733
	 * @param string $path Path to file or folder
734
	 *
735
	 * @throws BackendException if request is not successful
736
	 *
737
	 * @return array directory info
738
	 */
739
	public function gpi($path) {
740
		$path = $this->removeSlash($path);
741
		$response = $this->sabre_client->propFind($path, [
742
			'{http://owncloud.org/ns}fileid',
743
			'{DAV:}resourcetype',
744
			'{DAV:}getcontentlength',
745
			'{DAV:}getlastmodified',
746
			'{DAV:}getcontenttype',
747
			'{DAV:}quota-used-bytes',
748
			'{DAV:}quota-available-bytes',
749
		]);
750
751
		$type = $response["{DAV:}resourcetype"]->resourceType;
752
		if (is_array($type) && !empty($type)) {
753
			$type = $type[0] == "{DAV:}collection" ? "collection" : "file";
754
		}
755
		else {
756
			$type = "file"; // fall back to file if detection fails... less harmful
757
		}
758
759
		return [
760
			"fileid" => isset($response["{http://owncloud.org/ns}fileid"]) ? $response["{http://owncloud.org/ns}fileid"] : '-1',
761
			"resourcetype" => $type,
762
			"getcontentlength" => isset($response["{DAV:}getcontentlength"]) ? $response["{DAV:}getcontentlength"] : null,
763
			"getlastmodified" => isset($response["{DAV:}getlastmodified"]) ? $response["{DAV:}getlastmodified"] : null,
764
			"getcontenttype" => isset($response["{DAV:}getcontenttype"]) ? $response["{DAV:}getcontenttype"] : null,
765
			"quota-used-bytes" => isset($response["{DAV:}quota-used-bytes"]) ? $response["{DAV:}quota-used-bytes"] : null,
766
			"quota-available-bytes" => isset($response["{DAV:}quota-available-bytes"]) ? $response["{DAV:}quota-available-bytes"] : null,
767
		];
768
	}
769
770
	/**
771
	 * Gets server information.
772
	 *
773
	 * @throws BackendException if request is not successful
774
	 *
775
	 * @return array with all header fields returned from webdav server
776
	 */
777
	public function options() {
778
		$features = $this->sabre_client->options();
779
780
		// be sure it is an array
781
		if (is_array($features)) {
0 ignored issues
show
introduced by
The condition is_array($features) is always true.
Loading history...
782
			return $features;
783
		}
784
785
		$this->log('[OPTIONS] - ERROR - Error getting server features');
786
		$e = new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_FEATURES), self::WD_ERR_FEATURES);
787
		$e->setTitle($this->backendTransName . _('Not implemented'));
788
789
		throw $e;
790
	}
791
792
	/**
793
	 * Gather whether a path points to a file or not.
794
	 *
795
	 * @param string $path Path to file or folder
796
	 *
797
	 * @return bool true if path points to a file, false otherwise
798
	 */
799
	public function is_file($path) {
800
		$item = $this->gpi($path);
801
802
		return $item === false ? false : ($item['resourcetype'] != 'collection');
0 ignored issues
show
introduced by
The condition $item === false is always false.
Loading history...
803
	}
804
805
	/**
806
	 * Gather whether a path points to a directory.
807
	 *
808
	 * @param string $path Path to file or folder
809
	 *
810
	 * @return bool true if path points to a directory, false otherwise
811
	 */
812
	public function is_dir($path) {
813
		$item = $this->gpi($path);
814
815
		return $item === false ? false : ($item['resourcetype'] == 'collection');
0 ignored issues
show
introduced by
The condition $item === false is always false.
Loading history...
816
	}
817
818
	/**
819
	 * check if file/directory exists.
820
	 *
821
	 * @param string $path Path to file or folder
822
	 *
823
	 * @return bool true if path exists, false otherwise
824
	 */
825
	public function exists($path) {
826
		return $this->is_dir($path) || $this->is_file($path);
827
	}
828
829
	/**
830
	 * Copy a collection on webdav server
831
	 * Duplicates a collection on the webdav server (serverside).
832
	 * All work is done on the webdav server. If you set param overwrite as true,
833
	 * the target will be overwritten.
834
	 *
835
	 * @param string $src_path  Source path
836
	 * @param string $dest_path Destination path
837
	 * @param bool   $overwrite Overwrite if collection exists in $dst_path
838
	 * @param bool   $coll      set this to true if you want to copy a folder
839
	 * @param mixed  $dst_path
840
	 *
841
	 * @throws BackendException if request is not successful
842
	 *
843
	 * @return bool true if action succeeded
844
	 */
845
	private function copy($src_path, $dst_path, $overwrite, $coll) {
846
		$time_start = microtime(true);
847
		$src_path = $this->removeSlash($src_path);
848
		$dst_path = $this->webdavUrl() . $this->removeSlash($dst_path);
849
		$this->log("[COPY] start for dir: {$src_path} -> {$dst_path}");
850
		if ($overwrite) {
851
			$overwrite = 'T';
852
		}
853
		else {
854
			$overwrite = 'F';
855
		}
856
857
		["Destination" => $dst_path, 'Overwrite' => $overwrite];
858
		if ($coll) {
859
			$settings = ["Destination" => $dst_path, 'Depth' => 'Infinity'];
860
		}
861
862
		try {
863
			$response = $this->sabre_client->request("COPY", $src_path, null, $settings);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $settings does not seem to be defined for all execution paths leading up to this point.
Loading history...
864
			$time_end = microtime(true);
865
			$time = $time_end - $time_start;
866
			$this->log("[COPY] done in {$time} seconds: " . $response['statusCode']);
867
868
			return true;
869
		}
870
		catch (ClientException $e) {
871
			$e = new BackendException($this->parseErrorCodeToMessage($e->getCode()), $e->getCode());
872
			$e->setTitle($this->backendTransName . _('Sabre error'));
873
874
			throw $e;
875
		}
876
		catch (Exception $e) {
877
			$this->log('[COPY] - FATAL - ' . $e->getMessage());
878
			$e = new BackendException($this->parseErrorCodeToMessage($e->getHTTPCode()), $e->getHTTPCode());
879
			$e->setTitle($this->backendTransName . _('Copying failed'));
880
881
			throw $e;
882
		}
883
	}
884
885
	/**
886
	 * Create the base webdav url.
887
	 *
888
	 * @return string baseURL
889
	 */
890
	protected function webdavUrl() {
891
		if ($this->ssl) {
892
			$url = "https://";
893
		}
894
		else {
895
			$url = "http://";
896
		}
897
898
		// make sure that we do not have any trailing / in our url
899
		$server = rtrim($this->server, '/');
900
		$path = rtrim($this->path, '/');
901
902
		$url .= $server . ":" . $this->port . $path . "/";
903
904
		return $url;
905
	}
906
907
	/**
908
	 * Removes the leading slash from the folder path.
909
	 *
910
	 * @param string $dir directory path
911
	 *
912
	 * @return string trimmed directory path
913
	 */
914
	public function removeSlash($dir) {
915
		if (strpos($dir, '/') === 0) {
916
			$dir = substr($dir, 1);
917
		}
918
919
		// remove all html entities and urlencode the path...
920
		$nohtml = html_entity_decode($dir);
921
922
		return implode("/", array_map("rawurlencode", explode("/", $nohtml)));
923
	}
924
925
	/**
926
	 * This function will return a user friendly error string.
927
	 *
928
	 * @param number $error_code A error code
929
	 *
930
	 * @return string userfriendly error message
931
	 */
932
	private function parseErrorCodeToMessage($error_code) {
933
		$error = intval($error_code);
934
935
		$msg = _('Unknown error');
936
		$contactAdmin = _('Please contact your system administrator');
937
938
		switch ($error) {
939
			case CURLE_BAD_PASSWORD_ENTERED:
940
			case self::WD_ERR_UNAUTHORIZED:
941
				$msg = _('Unauthorized. Wrong username or password.');
942
				break;
943
944
			case CURLE_SSL_CONNECT_ERROR:
945
			case CURLE_COULDNT_RESOLVE_HOST:
946
			case CURLE_COULDNT_CONNECT:
947
			case CURLE_OPERATION_TIMEOUTED:
948
			case self::WD_ERR_UNREACHABLE:
949
				$msg = _('File server is not reachable. Please verify the connection.');
950
				break;
951
952
			case self::WD_ERR_NOTALLOWED:
953
				$msg = _('File server is not reachable. Please verify the file server URL.');
954
				break;
955
956
			case self::WD_ERR_FORBIDDEN:
957
				$msg = _('You don\'t have enough permissions to view this file or folder.');
958
				break;
959
960
			case self::WD_ERR_NOTFOUND:
961
				$msg = _('The file or folder is not available anymore.');
962
				break;
963
964
			case self::WD_ERR_TIMEOUT:
965
				$msg = _('Connection to the file server timed out. Please check again later.');
966
				break;
967
968
			case self::WD_ERR_LOCKED:
969
				$msg = _('This file is locked by another user. Please try again later.');
970
				break;
971
972
			case self::WD_ERR_FAILED_DEPENDENCY:
973
				$msg = _('The request failed.') . ' ' . $contactAdmin;
974
				break;
975
976
			case self::WD_ERR_INTERNAL:
977
				// This is a general error, might be thrown due to a wrong IP, but we don't know.
978
				$msg = _('The file server encountered an internal problem.') . ' ' . $contactAdmin;
979
				break;
980
981
			case self::WD_ERR_TMP:
982
				$msg = _('We could not write to temporary directory.') . ' ' . $contactAdmin;
983
				break;
984
985
			case self::WD_ERR_FEATURES:
986
				$msg = _('We could not retrieve list of server features.') . ' ' . $contactAdmin;
987
				break;
988
989
			case self::WD_ERR_NO_CURL:
990
				$msg = _('PHP-Curl is not available.') . ' ' . $contactAdmin;
991
				break;
992
		}
993
994
		return $msg;
995
	}
996
997
	public function getFormConfig() {
998
		$json = json_encode($this->metaConfig);
999
1000
		if ($json === false) {
1001
			error_log(json_last_error());
1002
		}
1003
1004
		return $json;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $json returns the type string which is incompatible with the return type mandated by Files\Backend\AbstractBackend::getFormConfig() of array.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
1005
	}
1006
1007
	public function getFormConfigWithData() {
1008
		return json_encode($this->metaConfig);
0 ignored issues
show
Bug Best Practice introduced by
The expression return json_encode($this->metaConfig) returns the type string which is incompatible with the return type mandated by Files\Backend\AbstractBa...getFormConfigWithData() of array.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
1009
	}
1010
1011
	/**
1012
	 * a simple php error_log wrapper.
1013
	 *
1014
	 * @param string $err_string error message
1015
	 */
1016
	private function log($err_string) {
1017
		if ($this->debug) {
1018
			error_log("[BACKEND_WEBDAV]: " . $err_string);
1019
		}
1020
	}
1021
1022
	/**
1023
	 * ============================ FEATURE FUNCTIONS ========================.
1024
	 *
1025
	 * @param mixed $dir
1026
	 */
1027
1028
	/**
1029
	 * Returns the bytes that are currently used.
1030
	 *
1031
	 * @param string $dir directory to check
1032
	 *
1033
	 * @return int bytes that are used or -1 on error
1034
	 */
1035
	public function getQuotaBytesUsed($dir) {
1036
		$lsdata = $this->ls($dir, false);
1037
1038
		if (isset($lsdata) && is_array($lsdata)) {
1039
			return $lsdata[$dir]["quota-used-bytes"];
1040
		}
1041
1042
		return -1;
1043
	}
1044
1045
	/**
1046
	 * Returns the bytes that are currently available.
1047
	 *
1048
	 * @param string $dir directory to check
1049
	 *
1050
	 * @return int bytes that are available or -1 on error
1051
	 */
1052
	public function getQuotaBytesAvailable($dir) {
1053
		$lsdata = $this->ls($dir, false);
1054
1055
		if (isset($lsdata) && is_array($lsdata)) {
1056
			return $lsdata[$dir]["quota-available-bytes"];
1057
		}
1058
1059
		return -1;
1060
	}
1061
1062
	/**
1063
	 * Return the version string of the server backend.
1064
	 *
1065
	 * @throws BackendException
1066
	 *
1067
	 * @return string
1068
	 */
1069
	public function getServerVersion() {
1070
		// check if curl is available
1071
		$serverHasCurl = function_exists('curl_version');
1072
		if (!$serverHasCurl) {
1073
			$e = new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_NO_CURL), 500);
1074
			$e->setTitle($this->backendTransName . _('PHP-CURL not installed'));
1075
1076
			throw $e;
1077
		}
1078
1079
		$webdavurl = $this->webdavUrl();
1080
1081
		$url = substr($webdavurl, 0, strlen($webdavurl) - strlen("remote.php/webdav/")) . "status.php";
1082
1083
		// try to get the contents of the owncloud status page
1084
		$ch = curl_init();
1085
		curl_setopt($ch, CURLOPT_AUTOREFERER, true);
1086
		curl_setopt($ch, CURLOPT_TIMEOUT, 3); // timeout of 3 seconds
1087
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1088
		curl_setopt($ch, CURLOPT_URL, $url);
1089
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
1090
		if ($this->allowselfsigned) {
1091
			curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
1092
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
1093
		}
1094
		$versiondata = curl_exec($ch);
1095
		$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
1096
		curl_close($ch);
1097
1098
		if ($httpcode && $httpcode == "200" && $versiondata) {
1099
			$versions = json_decode($versiondata);
0 ignored issues
show
Bug introduced by
It seems like $versiondata can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1099
			$versions = json_decode(/** @scrutinizer ignore-type */ $versiondata);
Loading history...
1100
			$version = $versions->versionstring;
1101
		}
1102
		else {
1103
			$version = "Undetected (no Owncloud?)";
1104
		}
1105
1106
		return $version;
1107
	}
1108
}
1109