Test Failed
Push — master ( 647c72...cd42b5 )
by
unknown
10:25
created

Backend::mkcol()   A

Complexity

Conditions 3
Paths 7

Size

Total Lines 21
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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

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