Passed
Push — master ( 5cdc85...37718d )
by Morris
38:53 queued 21:57
created

OC_Util::getHumanVersion()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 2
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Adam Williamson <[email protected]>
6
 * @author Andreas Fischer <[email protected]>
7
 * @author Arthur Schiwon <[email protected]>
8
 * @author Bart Visscher <[email protected]>
9
 * @author Bernhard Posselt <[email protected]>
10
 * @author Birk Borkason <[email protected]>
11
 * @author Björn Schießle <[email protected]>
12
 * @author Brice Maron <[email protected]>
13
 * @author Christoph Wurst <[email protected]>
14
 * @author Christopher Schäpers <[email protected]>
15
 * @author Clark Tomlinson <[email protected]>
16
 * @author cmeh <[email protected]>
17
 * @author Felix Epp <[email protected]>
18
 * @author Florin Peter <[email protected]>
19
 * @author Frank Karlitschek <[email protected]>
20
 * @author Georg Ehrke <[email protected]>
21
 * @author helix84 <[email protected]>
22
 * @author Ilja Neumann <[email protected]>
23
 * @author Individual IT Services <[email protected]>
24
 * @author Jakob Sack <[email protected]>
25
 * @author Joas Schilling <[email protected]>
26
 * @author Jörn Friedrich Dreyer <[email protected]>
27
 * @author Kawohl <[email protected]>
28
 * @author Lukas Reschke <[email protected]>
29
 * @author Markus Goetz <[email protected]>
30
 * @author Martin Mattel <[email protected]>
31
 * @author Marvin Thomas Rabe <[email protected]>
32
 * @author Michael Gapczynski <[email protected]>
33
 * @author Morris Jobke <[email protected]>
34
 * @author rakekniven <[email protected]>
35
 * @author Robin Appelman <[email protected]>
36
 * @author Robin McCorkell <[email protected]>
37
 * @author Roeland Jago Douma <[email protected]>
38
 * @author Sebastian Wessalowski <[email protected]>
39
 * @author Stefan Rado <[email protected]>
40
 * @author Stefan Weil <[email protected]>
41
 * @author Thomas Müller <[email protected]>
42
 * @author Thomas Tanghus <[email protected]>
43
 * @author Victor Dubiniuk <[email protected]>
44
 * @author Vincent Petry <[email protected]>
45
 * @author Volkan Gezer <[email protected]>
46
 *
47
 * @license AGPL-3.0
48
 *
49
 * This code is free software: you can redistribute it and/or modify
50
 * it under the terms of the GNU Affero General Public License, version 3,
51
 * as published by the Free Software Foundation.
52
 *
53
 * This program is distributed in the hope that it will be useful,
54
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
55
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
56
 * GNU Affero General Public License for more details.
57
 *
58
 * You should have received a copy of the GNU Affero General Public License, version 3,
59
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
60
 *
61
 */
62
63
use OCP\IConfig;
64
use OCP\IGroupManager;
65
use OCP\ILogger;
66
use OCP\IUser;
67
use OC\AppFramework\Http\Request;
68
69
class OC_Util {
70
	public static $scripts = array();
71
	public static $styles = array();
72
	public static $headers = array();
73
	private static $rootMounted = false;
74
	private static $fsSetup = false;
75
76
	/** @var array Local cache of version.php */
77
	private static $versionCache = null;
78
79
	protected static function getAppManager() {
80
		return \OC::$server->getAppManager();
81
	}
82
83
	private static function initLocalStorageRootFS() {
84
		// mount local file backend as root
85
		$configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data");
86
		//first set up the local "root" storage
87
		\OC\Files\Filesystem::initMountManager();
88
		if (!self::$rootMounted) {
89
			\OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => $configDataDirectory), '/');
90
			self::$rootMounted = true;
91
		}
92
	}
93
94
	/**
95
	 * mounting an object storage as the root fs will in essence remove the
96
	 * necessity of a data folder being present.
97
	 * TODO make home storage aware of this and use the object storage instead of local disk access
98
	 *
99
	 * @param array $config containing 'class' and optional 'arguments'
100
	 * @suppress PhanDeprecatedFunction
101
	 */
102
	private static function initObjectStoreRootFS($config) {
103
		// check misconfiguration
104
		if (empty($config['class'])) {
105
			\OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

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

105
			/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
106
		}
107
		if (!isset($config['arguments'])) {
108
			$config['arguments'] = array();
109
		}
110
111
		// instantiate object store implementation
112
		$name = $config['class'];
113
		if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) {
114
			$segments = explode('\\', $name);
115
			OC_App::loadApp(strtolower($segments[1]));
116
		}
117
		$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
118
		// mount with plain / root object store implementation
119
		$config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage';
120
121
		// mount object storage as root
122
		\OC\Files\Filesystem::initMountManager();
123
		if (!self::$rootMounted) {
124
			\OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/');
125
			self::$rootMounted = true;
126
		}
127
	}
128
129
	/**
130
	 * mounting an object storage as the root fs will in essence remove the
131
	 * necessity of a data folder being present.
132
	 *
133
	 * @param array $config containing 'class' and optional 'arguments'
134
	 * @suppress PhanDeprecatedFunction
135
	 */
136
	private static function initObjectStoreMultibucketRootFS($config) {
137
		// check misconfiguration
138
		if (empty($config['class'])) {
139
			\OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

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

139
			/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
140
		}
141
		if (!isset($config['arguments'])) {
142
			$config['arguments'] = array();
143
		}
144
145
		// instantiate object store implementation
146
		$name = $config['class'];
147
		if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) {
148
			$segments = explode('\\', $name);
149
			OC_App::loadApp(strtolower($segments[1]));
150
		}
151
152
		if (!isset($config['arguments']['bucket'])) {
153
			$config['arguments']['bucket'] = '';
154
		}
155
		// put the root FS always in first bucket for multibucket configuration
156
		$config['arguments']['bucket'] .= '0';
157
158
		$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
159
		// mount with plain / root object store implementation
160
		$config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage';
161
162
		// mount object storage as root
163
		\OC\Files\Filesystem::initMountManager();
164
		if (!self::$rootMounted) {
165
			\OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/');
166
			self::$rootMounted = true;
167
		}
168
	}
169
170
	/**
171
	 * Can be set up
172
	 *
173
	 * @param string $user
174
	 * @return boolean
175
	 * @description configure the initial filesystem based on the configuration
176
	 * @suppress PhanDeprecatedFunction
177
	 * @suppress PhanAccessMethodInternal
178
	 */
179
	public static function setupFS($user = '') {
180
		//setting up the filesystem twice can only lead to trouble
181
		if (self::$fsSetup) {
182
			return false;
183
		}
184
185
		\OC::$server->getEventLogger()->start('setup_fs', 'Setup filesystem');
186
187
		// If we are not forced to load a specific user we load the one that is logged in
188
		if ($user === null) {
0 ignored issues
show
introduced by
The condition $user === null is always false.
Loading history...
189
			$user = '';
190
		} else if ($user == "" && \OC::$server->getUserSession()->isLoggedIn()) {
191
			$user = OC_User::getUser();
192
		}
193
194
		// load all filesystem apps before, so no setup-hook gets lost
195
		OC_App::loadApps(array('filesystem'));
196
197
		// the filesystem will finish when $user is not empty,
198
		// mark fs setup here to avoid doing the setup from loading
199
		// OC_Filesystem
200
		if ($user != '') {
201
			self::$fsSetup = true;
202
		}
203
204
		\OC\Files\Filesystem::initMountManager();
205
206
		\OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(false);
207
		\OC\Files\Filesystem::addStorageWrapper('mount_options', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) {
208
			if ($storage->instanceOfStorage('\OC\Files\Storage\Common')) {
209
				/** @var \OC\Files\Storage\Common $storage */
210
				$storage->setMountOptions($mount->getOptions());
211
			}
212
			return $storage;
213
		});
214
215
		\OC\Files\Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, \OCP\Files\Storage\IStorage $storage, \OCP\Files\Mount\IMountPoint $mount) {
216
			if (!$mount->getOption('enable_sharing', true)) {
217
				return new \OC\Files\Storage\Wrapper\PermissionsMask([
218
					'storage' => $storage,
219
					'mask' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_SHARE
220
				]);
221
			}
222
			return $storage;
223
		});
224
225
		// install storage availability wrapper, before most other wrappers
226
		\OC\Files\Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, \OCP\Files\Storage\IStorage $storage) {
227
			if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
228
				return new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]);
229
			}
230
			return $storage;
231
		});
232
233
		\OC\Files\Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) {
234
			if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
235
				return new \OC\Files\Storage\Wrapper\Encoding(['storage' => $storage]);
236
			}
237
			return $storage;
238
		});
239
240
		\OC\Files\Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) {
241
			// set up quota for home storages, even for other users
242
			// which can happen when using sharing
243
244
			/**
245
			 * @var \OC\Files\Storage\Storage $storage
246
			 */
247
			if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
248
				|| $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
249
			) {
250
				/** @var \OC\Files\Storage\Home $storage */
251
				if (is_object($storage->getUser())) {
252
					$user = $storage->getUser()->getUID();
253
					$quota = OC_Util::getUserQuota($user);
254
					if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
255
						return new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => $quota, 'root' => 'files'));
256
					}
257
				}
258
			}
259
260
			return $storage;
261
		});
262
263
		\OC\Files\Filesystem::addStorageWrapper('readonly', function ($mountPoint, \OCP\Files\Storage\IStorage $storage, \OCP\Files\Mount\IMountPoint $mount) {
264
			/*
265
			 * Do not allow any operations that modify the storage
266
			 */
267
			if ($mount->getOption('readonly', false)) {
268
				return new \OC\Files\Storage\Wrapper\PermissionsMask([
269
					'storage' => $storage,
270
					'mask' => \OCP\Constants::PERMISSION_ALL & ~(
271
						\OCP\Constants::PERMISSION_UPDATE |
272
						\OCP\Constants::PERMISSION_CREATE |
273
						\OCP\Constants::PERMISSION_DELETE
274
					),
275
				]);
276
			}
277
			return $storage;
278
		});
279
280
		OC_Hook::emit('OC_Filesystem', 'preSetup', array('user' => $user));
281
		\OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(true);
282
283
		//check if we are using an object storage
284
		$objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null);
285
		$objectStoreMultibucket = \OC::$server->getSystemConfig()->getValue('objectstore_multibucket', null);
286
287
		// use the same order as in ObjectHomeMountProvider
288
		if (isset($objectStoreMultibucket)) {
289
			self::initObjectStoreMultibucketRootFS($objectStoreMultibucket);
0 ignored issues
show
Bug introduced by
It seems like $objectStoreMultibucket can also be of type string; however, parameter $config of OC_Util::initObjectStoreMultibucketRootFS() does only seem to accept array, 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

289
			self::initObjectStoreMultibucketRootFS(/** @scrutinizer ignore-type */ $objectStoreMultibucket);
Loading history...
290
		} elseif (isset($objectStore)) {
291
			self::initObjectStoreRootFS($objectStore);
0 ignored issues
show
Bug introduced by
It seems like $objectStore can also be of type string; however, parameter $config of OC_Util::initObjectStoreRootFS() does only seem to accept array, 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

291
			self::initObjectStoreRootFS(/** @scrutinizer ignore-type */ $objectStore);
Loading history...
292
		} else {
293
			self::initLocalStorageRootFS();
294
		}
295
296
		if ($user != '' && !\OC::$server->getUserManager()->userExists($user)) {
297
			\OC::$server->getEventLogger()->end('setup_fs');
298
			return false;
299
		}
300
301
		//if we aren't logged in, there is no use to set up the filesystem
302
		if ($user != "") {
303
304
			$userDir = '/' . $user . '/files';
305
306
			//jail the user into his "home" directory
307
			\OC\Files\Filesystem::init($user, $userDir);
308
309
			OC_Hook::emit('OC_Filesystem', 'setup', array('user' => $user, 'user_dir' => $userDir));
310
		}
311
		\OC::$server->getEventLogger()->end('setup_fs');
312
		return true;
313
	}
314
315
	/**
316
	 * check if a password is required for each public link
317
	 *
318
	 * @return boolean
319
	 * @suppress PhanDeprecatedFunction
320
	 */
321
	public static function isPublicLinkPasswordRequired() {
322
		$enforcePassword = \OC::$server->getConfig()->getAppValue('core', 'shareapi_enforce_links_password', 'no');
323
		return $enforcePassword === 'yes';
324
	}
325
326
	/**
327
	 * check if sharing is disabled for the current user
328
	 * @param IConfig $config
329
	 * @param IGroupManager $groupManager
330
	 * @param IUser|null $user
331
	 * @return bool
332
	 */
333
	public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) {
334
		if ($config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
335
			$groupsList = $config->getAppValue('core', 'shareapi_exclude_groups_list', '');
336
			$excludedGroups = json_decode($groupsList);
337
			if (is_null($excludedGroups)) {
338
				$excludedGroups = explode(',', $groupsList);
339
				$newValue = json_encode($excludedGroups);
340
				$config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
341
			}
342
			$usersGroups = $groupManager->getUserGroupIds($user);
0 ignored issues
show
Bug introduced by
It seems like $user can also be of type null; however, parameter $user of OCP\IGroupManager::getUserGroupIds() does only seem to accept OCP\IUser, 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

342
			$usersGroups = $groupManager->getUserGroupIds(/** @scrutinizer ignore-type */ $user);
Loading history...
343
			if (!empty($usersGroups)) {
344
				$remainingGroups = array_diff($usersGroups, $excludedGroups);
345
				// if the user is only in groups which are disabled for sharing then
346
				// sharing is also disabled for the user
347
				if (empty($remainingGroups)) {
348
					return true;
349
				}
350
			}
351
		}
352
		return false;
353
	}
354
355
	/**
356
	 * check if share API enforces a default expire date
357
	 *
358
	 * @return boolean
359
	 * @suppress PhanDeprecatedFunction
360
	 */
361
	public static function isDefaultExpireDateEnforced() {
362
		$isDefaultExpireDateEnabled = \OC::$server->getConfig()->getAppValue('core', 'shareapi_default_expire_date', 'no');
363
		$enforceDefaultExpireDate = false;
364
		if ($isDefaultExpireDateEnabled === 'yes') {
365
			$value = \OC::$server->getConfig()->getAppValue('core', 'shareapi_enforce_expire_date', 'no');
366
			$enforceDefaultExpireDate = $value === 'yes';
367
		}
368
369
		return $enforceDefaultExpireDate;
370
	}
371
372
	/**
373
	 * Get the quota of a user
374
	 *
375
	 * @param string $userId
376
	 * @return float Quota bytes
377
	 */
378
	public static function getUserQuota($userId) {
379
		$user = \OC::$server->getUserManager()->get($userId);
380
		if (is_null($user)) {
381
			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
382
		}
383
		$userQuota = $user->getQuota();
384
		if($userQuota === 'none') {
385
			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
386
		}
387
		return OC_Helper::computerFileSize($userQuota);
0 ignored issues
show
Bug Best Practice introduced by
The expression return OC_Helper::computerFileSize($userQuota) could also return false which is incompatible with the documented return type double. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
388
	}
389
390
	/**
391
	 * copies the skeleton to the users /files
392
	 *
393
	 * @param String $userId
394
	 * @param \OCP\Files\Folder $userDirectory
395
	 * @throws \OCP\Files\NotFoundException
396
	 * @throws \OCP\Files\NotPermittedException
397
	 * @suppress PhanDeprecatedFunction
398
	 */
399
	public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
400
401
		$plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
402
		$userLang = \OC::$server->getL10NFactory()->findLanguage();
403
		$skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
404
405
		if (!file_exists($skeletonDirectory)) {
406
			$dialectStart = strpos($userLang, '_');
407
			if ($dialectStart !== false) {
408
				$skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
409
			}
410
			if ($dialectStart === false || !file_exists($skeletonDirectory)) {
411
				$skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
412
			}
413
			if (!file_exists($skeletonDirectory)) {
414
				$skeletonDirectory = '';
415
			}
416
		}
417
418
		$instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
419
420
		if ($instanceId === null) {
421
			throw new \RuntimeException('no instance id!');
422
		}
423
		$appdata = 'appdata_' . $instanceId;
424
		if ($userId === $appdata) {
425
			throw new \RuntimeException('username is reserved name: ' . $appdata);
426
		}
427
428
		if (!empty($skeletonDirectory)) {
429
			\OCP\Util::writeLog(
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

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

429
			/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
430
				'files_skeleton',
431
				'copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'),
432
				ILogger::DEBUG
433
			);
434
			self::copyr($skeletonDirectory, $userDirectory);
435
			// update the file cache
436
			$userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
437
		}
438
	}
439
440
	/**
441
	 * copies a directory recursively by using streams
442
	 *
443
	 * @param string $source
444
	 * @param \OCP\Files\Folder $target
445
	 * @return void
446
	 */
447
	public static function copyr($source, \OCP\Files\Folder $target) {
448
		$logger = \OC::$server->getLogger();
449
450
		// Verify if folder exists
451
		$dir = opendir($source);
452
		if($dir === false) {
453
			$logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
454
			return;
455
		}
456
457
		// Copy the files
458
		while (false !== ($file = readdir($dir))) {
459
			if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
460
				if (is_dir($source . '/' . $file)) {
461
					$child = $target->newFolder($file);
462
					self::copyr($source . '/' . $file, $child);
463
				} else {
464
					$child = $target->newFile($file);
465
					$sourceStream = fopen($source . '/' . $file, 'r');
466
					if($sourceStream === false) {
467
						$logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
468
						closedir($dir);
469
						return;
470
					}
471
					stream_copy_to_stream($sourceStream, $child->fopen('w'));
472
				}
473
			}
474
		}
475
		closedir($dir);
476
	}
477
478
	/**
479
	 * @return void
480
	 * @suppress PhanUndeclaredMethod
481
	 */
482
	public static function tearDownFS() {
483
		\OC\Files\Filesystem::tearDown();
484
		\OC::$server->getRootFolder()->clearCache();
0 ignored issues
show
Bug introduced by
The method clearCache() does not exist on OCP\Files\IRootFolder. Since it exists in all sub-types, consider adding an abstract or default implementation to OCP\Files\IRootFolder. ( Ignorable by Annotation )

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

484
		\OC::$server->getRootFolder()->/** @scrutinizer ignore-call */ clearCache();
Loading history...
485
		self::$fsSetup = false;
486
		self::$rootMounted = false;
487
	}
488
489
	/**
490
	 * get the current installed version of ownCloud
491
	 *
492
	 * @return array
493
	 */
494
	public static function getVersion() {
495
		OC_Util::loadVersion();
496
		return self::$versionCache['OC_Version'];
497
	}
498
499
	/**
500
	 * get the current installed version string of ownCloud
501
	 *
502
	 * @return string
503
	 */
504
	public static function getVersionString() {
505
		OC_Util::loadVersion();
506
		return self::$versionCache['OC_VersionString'];
507
	}
508
509
	/**
510
	 * @deprecated the value is of no use anymore
511
	 * @return string
512
	 */
513
	public static function getEditionString() {
514
		return '';
515
	}
516
517
	/**
518
	 * @description get the update channel of the current installed of ownCloud.
519
	 * @return string
520
	 */
521
	public static function getChannel() {
522
		OC_Util::loadVersion();
523
		return \OC::$server->getConfig()->getSystemValue('updater.release.channel', self::$versionCache['OC_Channel']);
524
	}
525
526
	/**
527
	 * @description get the build number of the current installed of ownCloud.
528
	 * @return string
529
	 */
530
	public static function getBuild() {
531
		OC_Util::loadVersion();
532
		return self::$versionCache['OC_Build'];
533
	}
534
535
	/**
536
	 * @description load the version.php into the session as cache
537
	 * @suppress PhanUndeclaredVariable
538
	 */
539
	private static function loadVersion() {
540
		if (self::$versionCache !== null) {
0 ignored issues
show
introduced by
The condition self::versionCache !== null is always true.
Loading history...
541
			return;
542
		}
543
544
		$timestamp = filemtime(OC::$SERVERROOT . '/version.php');
545
		require OC::$SERVERROOT . '/version.php';
546
		/** @var $timestamp int */
547
		self::$versionCache['OC_Version_Timestamp'] = $timestamp;
548
		/** @var $OC_Version string */
549
		self::$versionCache['OC_Version'] = $OC_Version;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $OC_Version seems to be never defined.
Loading history...
550
		/** @var $OC_VersionString string */
551
		self::$versionCache['OC_VersionString'] = $OC_VersionString;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $OC_VersionString seems to be never defined.
Loading history...
552
		/** @var $OC_Build string */
553
		self::$versionCache['OC_Build'] = $OC_Build;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $OC_Build seems to be never defined.
Loading history...
554
555
		/** @var $OC_Channel string */
556
		self::$versionCache['OC_Channel'] = $OC_Channel;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $OC_Channel seems to be never defined.
Loading history...
557
	}
558
559
	/**
560
	 * generates a path for JS/CSS files. If no application is provided it will create the path for core.
561
	 *
562
	 * @param string $application application to get the files from
563
	 * @param string $directory directory within this application (css, js, vendor, etc)
564
	 * @param string $file the file inside of the above folder
565
	 * @return string the path
566
	 */
567
	private static function generatePath($application, $directory, $file) {
568
		if (is_null($file)) {
0 ignored issues
show
introduced by
The condition is_null($file) is always false.
Loading history...
569
			$file = $application;
570
			$application = "";
571
		}
572
		if (!empty($application)) {
573
			return "$application/$directory/$file";
574
		} else {
575
			return "$directory/$file";
576
		}
577
	}
578
579
	/**
580
	 * add a javascript file
581
	 *
582
	 * @param string $application application id
583
	 * @param string|null $file filename
584
	 * @param bool $prepend prepend the Script to the beginning of the list
585
	 * @return void
586
	 */
587
	public static function addScript($application, $file = null, $prepend = false) {
588
		$path = OC_Util::generatePath($application, 'js', $file);
589
590
		// core js files need separate handling
591
		if ($application !== 'core' && $file !== null) {
592
			self::addTranslations ( $application );
593
		}
594
		self::addExternalResource($application, $prepend, $path, "script");
595
	}
596
597
	/**
598
	 * add a javascript file from the vendor sub folder
599
	 *
600
	 * @param string $application application id
601
	 * @param string|null $file filename
602
	 * @param bool $prepend prepend the Script to the beginning of the list
603
	 * @return void
604
	 */
605
	public static function addVendorScript($application, $file = null, $prepend = false) {
606
		$path = OC_Util::generatePath($application, 'vendor', $file);
607
		self::addExternalResource($application, $prepend, $path, "script");
608
	}
609
610
	/**
611
	 * add a translation JS file
612
	 *
613
	 * @param string $application application id
614
	 * @param string|null $languageCode language code, defaults to the current language
615
	 * @param bool|null $prepend prepend the Script to the beginning of the list
616
	 */
617
	public static function addTranslations($application, $languageCode = null, $prepend = false) {
618
		if (is_null($languageCode)) {
619
			$languageCode = \OC::$server->getL10NFactory()->findLanguage($application);
620
		}
621
		if (!empty($application)) {
622
			$path = "$application/l10n/$languageCode";
623
		} else {
624
			$path = "l10n/$languageCode";
625
		}
626
		self::addExternalResource($application, $prepend, $path, "script");
627
	}
628
629
	/**
630
	 * add a css file
631
	 *
632
	 * @param string $application application id
633
	 * @param string|null $file filename
634
	 * @param bool $prepend prepend the Style to the beginning of the list
635
	 * @return void
636
	 */
637
	public static function addStyle($application, $file = null, $prepend = false) {
638
		$path = OC_Util::generatePath($application, 'css', $file);
639
		self::addExternalResource($application, $prepend, $path, "style");
640
	}
641
642
	/**
643
	 * add a css file from the vendor sub folder
644
	 *
645
	 * @param string $application application id
646
	 * @param string|null $file filename
647
	 * @param bool $prepend prepend the Style to the beginning of the list
648
	 * @return void
649
	 */
650
	public static function addVendorStyle($application, $file = null, $prepend = false) {
651
		$path = OC_Util::generatePath($application, 'vendor', $file);
652
		self::addExternalResource($application, $prepend, $path, "style");
653
	}
654
655
	/**
656
	 * add an external resource css/js file
657
	 *
658
	 * @param string $application application id
659
	 * @param bool $prepend prepend the file to the beginning of the list
660
	 * @param string $path
661
	 * @param string $type (script or style)
662
	 * @return void
663
	 */
664
	private static function addExternalResource($application, $prepend, $path, $type = "script") {
0 ignored issues
show
Unused Code introduced by
The parameter $application is not used and could be removed. ( Ignorable by Annotation )

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

664
	private static function addExternalResource(/** @scrutinizer ignore-unused */ $application, $prepend, $path, $type = "script") {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
665
666
		if ($type === "style") {
667
			if (!in_array($path, self::$styles)) {
668
				if ($prepend === true) {
669
					array_unshift ( self::$styles, $path );
670
				} else {
671
					self::$styles[] = $path;
672
				}
673
			}
674
		} elseif ($type === "script") {
675
			if (!in_array($path, self::$scripts)) {
676
				if ($prepend === true) {
677
					array_unshift ( self::$scripts, $path );
678
				} else {
679
					self::$scripts [] = $path;
680
				}
681
			}
682
		}
683
	}
684
685
	/**
686
	 * Add a custom element to the header
687
	 * If $text is null then the element will be written as empty element.
688
	 * So use "" to get a closing tag.
689
	 * @param string $tag tag name of the element
690
	 * @param array $attributes array of attributes for the element
691
	 * @param string $text the text content for the element
692
	 * @param bool $prepend prepend the header to the beginning of the list
693
	 */
694
	public static function addHeader($tag, $attributes, $text = null, $prepend = false) {
695
		$header = array(
696
			'tag' => $tag,
697
			'attributes' => $attributes,
698
			'text' => $text
699
		);
700
		if ($prepend === true) {
701
			array_unshift (self::$headers, $header);
702
703
		} else {
704
			self::$headers[] = $header;
705
		}
706
	}
707
708
	/**
709
	 * check if the current server configuration is suitable for ownCloud
710
	 *
711
	 * @param \OC\SystemConfig $config
712
	 * @return array arrays with error messages and hints
713
	 */
714
	public static function checkServer(\OC\SystemConfig $config) {
715
		$l = \OC::$server->getL10N('lib');
716
		$errors = array();
717
		$CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data');
718
719
		if (!self::needUpgrade($config) && $config->getValue('installed', false)) {
720
			// this check needs to be done every time
721
			$errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY);
722
		}
723
724
		// Assume that if checkServer() succeeded before in this session, then all is fine.
725
		if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) {
726
			return $errors;
727
		}
728
729
		$webServerRestart = false;
730
		$setup = new \OC\Setup(
731
			$config,
732
			\OC::$server->getIniWrapper(),
733
			\OC::$server->getL10N('lib'),
734
			\OC::$server->query(\OCP\Defaults::class),
0 ignored issues
show
Bug introduced by
It seems like OC::server->query(OCP\Defaults::class) can also be of type stdClass; however, parameter $defaults of OC\Setup::__construct() does only seem to accept OCP\Defaults, 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

734
			/** @scrutinizer ignore-type */ \OC::$server->query(\OCP\Defaults::class),
Loading history...
735
			\OC::$server->getLogger(),
736
			\OC::$server->getSecureRandom(),
737
			\OC::$server->query(\OC\Installer::class)
0 ignored issues
show
Bug introduced by
It seems like OC::server->query(OC\Installer::class) can also be of type stdClass; however, parameter $installer of OC\Setup::__construct() does only seem to accept OC\Installer, 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

737
			/** @scrutinizer ignore-type */ \OC::$server->query(\OC\Installer::class)
Loading history...
738
		);
739
740
		$urlGenerator = \OC::$server->getURLGenerator();
741
742
		$availableDatabases = $setup->getSupportedDatabases();
743
		if (empty($availableDatabases)) {
744
			$errors[] = array(
745
				'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'),
746
				'hint' => '' //TODO: sane hint
747
			);
748
			$webServerRestart = true;
749
		}
750
751
		// Check if config folder is writable.
752
		if(!OC_Helper::isReadOnlyConfigEnabled()) {
753
			if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) {
754
				$errors[] = array(
755
					'error' => $l->t('Cannot write into "config" directory'),
756
					'hint' => $l->t('This can usually be fixed by giving the webserver write access to the config directory. See %s',
757
						[ $urlGenerator->linkToDocs('admin-dir_permissions') ]) . '. '
758
						. $l->t('Or, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it. See %s',
759
						[ $urlGenerator->linkToDocs('admin-config') ] )
760
				);
761
			}
762
		}
763
764
		// Check if there is a writable install folder.
765
		if ($config->getValue('appstoreenabled', true)) {
766
			if (OC_App::getInstallPath() === null
767
				|| !is_writable(OC_App::getInstallPath())
0 ignored issues
show
Bug introduced by
It seems like OC_App::getInstallPath() can also be of type false; however, parameter $filename of is_writable() 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

767
				|| !is_writable(/** @scrutinizer ignore-type */ OC_App::getInstallPath())
Loading history...
768
				|| !is_readable(OC_App::getInstallPath())
0 ignored issues
show
Bug introduced by
It seems like OC_App::getInstallPath() can also be of type false; however, parameter $filename of is_readable() 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

768
				|| !is_readable(/** @scrutinizer ignore-type */ OC_App::getInstallPath())
Loading history...
769
			) {
770
				$errors[] = array(
771
					'error' => $l->t('Cannot write into "apps" directory'),
772
					'hint' => $l->t('This can usually be fixed by giving the webserver write access to the apps directory'
773
						. ' or disabling the appstore in the config file. See %s',
774
						[$urlGenerator->linkToDocs('admin-dir_permissions')])
775
				);
776
			}
777
		}
778
		// Create root dir.
779
		if ($config->getValue('installed', false)) {
780
			if (!is_dir($CONFIG_DATADIRECTORY)) {
781
				$success = @mkdir($CONFIG_DATADIRECTORY);
782
				if ($success) {
783
					$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
784
				} else {
785
					$errors[] = [
786
						'error' => $l->t('Cannot create "data" directory'),
787
						'hint' => $l->t('This can usually be fixed by giving the webserver write access to the root directory. See %s',
788
							[$urlGenerator->linkToDocs('admin-dir_permissions')])
789
					];
790
				}
791
			} else if (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
792
				//common hint for all file permissions error messages
793
				$permissionsHint = $l->t('Permissions can usually be fixed by giving the webserver write access to the root directory. See %s.',
794
					[$urlGenerator->linkToDocs('admin-dir_permissions')]);
795
				$errors[] = [
796
					'error' => 'Your data directory is not writable',
797
					'hint' => $permissionsHint
798
				];
799
			} else {
800
				$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
801
			}
802
		}
803
804
		if (!OC_Util::isSetLocaleWorking()) {
805
			$errors[] = array(
806
				'error' => $l->t('Setting locale to %s failed',
807
					array('en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/'
808
						. 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8')),
809
				'hint' => $l->t('Please install one of these locales on your system and restart your webserver.')
810
			);
811
		}
812
813
		// Contains the dependencies that should be checked against
814
		// classes = class_exists
815
		// functions = function_exists
816
		// defined = defined
817
		// ini = ini_get
818
		// If the dependency is not found the missing module name is shown to the EndUser
819
		// When adding new checks always verify that they pass on Travis as well
820
		// for ini settings, see https://github.com/owncloud/administration/blob/master/travis-ci/custom.ini
821
		$dependencies = array(
822
			'classes' => array(
823
				'ZipArchive' => 'zip',
824
				'DOMDocument' => 'dom',
825
				'XMLWriter' => 'XMLWriter',
826
				'XMLReader' => 'XMLReader',
827
			),
828
			'functions' => [
829
				'xml_parser_create' => 'libxml',
830
				'mb_strcut' => 'mb multibyte',
831
				'ctype_digit' => 'ctype',
832
				'json_encode' => 'JSON',
833
				'gd_info' => 'GD',
834
				'gzencode' => 'zlib',
835
				'iconv' => 'iconv',
836
				'simplexml_load_string' => 'SimpleXML',
837
				'hash' => 'HASH Message Digest Framework',
838
				'curl_init' => 'cURL',
839
				'openssl_verify' => 'OpenSSL',
840
			],
841
			'defined' => array(
842
				'PDO::ATTR_DRIVER_NAME' => 'PDO'
843
			),
844
			'ini' => [
845
				'default_charset' => 'UTF-8',
846
			],
847
		);
848
		$missingDependencies = array();
849
		$invalidIniSettings = [];
850
		$moduleHint = $l->t('Please ask your server administrator to install the module.');
851
852
		$iniWrapper = \OC::$server->getIniWrapper();
853
		foreach ($dependencies['classes'] as $class => $module) {
854
			if (!class_exists($class)) {
855
				$missingDependencies[] = $module;
856
			}
857
		}
858
		foreach ($dependencies['functions'] as $function => $module) {
859
			if (!function_exists($function)) {
860
				$missingDependencies[] = $module;
861
			}
862
		}
863
		foreach ($dependencies['defined'] as $defined => $module) {
864
			if (!defined($defined)) {
865
				$missingDependencies[] = $module;
866
			}
867
		}
868
		foreach ($dependencies['ini'] as $setting => $expected) {
869
			if (is_bool($expected)) {
870
				if ($iniWrapper->getBool($setting) !== $expected) {
871
					$invalidIniSettings[] = [$setting, $expected];
872
				}
873
			}
874
			if (is_int($expected)) {
875
				if ($iniWrapper->getNumeric($setting) !== $expected) {
876
					$invalidIniSettings[] = [$setting, $expected];
877
				}
878
			}
879
			if (is_string($expected)) {
880
				if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) {
881
					$invalidIniSettings[] = [$setting, $expected];
882
				}
883
			}
884
		}
885
886
		foreach($missingDependencies as $missingDependency) {
887
			$errors[] = array(
888
				'error' => $l->t('PHP module %s not installed.', array($missingDependency)),
889
				'hint' => $moduleHint
890
			);
891
			$webServerRestart = true;
892
		}
893
		foreach($invalidIniSettings as $setting) {
894
			if(is_bool($setting[1])) {
895
				$setting[1] = $setting[1] ? 'on' : 'off';
896
			}
897
			$errors[] = [
898
				'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]),
899
				'hint' =>  $l->t('Adjusting this setting in php.ini will make Nextcloud run again')
900
			];
901
			$webServerRestart = true;
902
		}
903
904
		/**
905
		 * The mbstring.func_overload check can only be performed if the mbstring
906
		 * module is installed as it will return null if the checking setting is
907
		 * not available and thus a check on the boolean value fails.
908
		 *
909
		 * TODO: Should probably be implemented in the above generic dependency
910
		 *       check somehow in the long-term.
911
		 */
912
		if($iniWrapper->getBool('mbstring.func_overload') !== null &&
913
			$iniWrapper->getBool('mbstring.func_overload') === true) {
914
			$errors[] = array(
915
				'error' => $l->t('mbstring.func_overload is set to "%s" instead of the expected value "0"', [$iniWrapper->getString('mbstring.func_overload')]),
916
				'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini')
917
			);
918
		}
919
920
		if(function_exists('xml_parser_create') &&
921
			LIBXML_LOADED_VERSION < 20700 ) {
922
			$version = LIBXML_LOADED_VERSION;
923
			$major = floor($version/10000);
924
			$version -= ($major * 10000);
925
			$minor = floor($version/100);
926
			$version -= ($minor * 100);
927
			$patch = $version;
928
			$errors[] = array(
929
				'error' => $l->t('libxml2 2.7.0 is at least required. Currently %s is installed.', [$major . '.' . $minor . '.' . $patch]),
930
				'hint' => $l->t('To fix this issue update your libxml2 version and restart your web server.')
931
			);
932
		}
933
934
		if (!self::isAnnotationsWorking()) {
935
			$errors[] = array(
936
				'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'),
937
				'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')
938
			);
939
		}
940
941
		if (!\OC::$CLI && $webServerRestart) {
942
			$errors[] = array(
943
				'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'),
944
				'hint' => $l->t('Please ask your server administrator to restart the web server.')
945
			);
946
		}
947
948
		$errors = array_merge($errors, self::checkDatabaseVersion());
949
950
		// Cache the result of this function
951
		\OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0);
952
953
		return $errors;
954
	}
955
956
	/**
957
	 * Check the database version
958
	 *
959
	 * @return array errors array
960
	 */
961
	public static function checkDatabaseVersion() {
962
		$l = \OC::$server->getL10N('lib');
963
		$errors = array();
964
		$dbType = \OC::$server->getSystemConfig()->getValue('dbtype', 'sqlite');
965
		if ($dbType === 'pgsql') {
966
			// check PostgreSQL version
967
			try {
968
				$result = \OC_DB::executeAudited('SHOW SERVER_VERSION');
969
				$data = $result->fetchRow();
970
				if (isset($data['server_version'])) {
971
					$version = $data['server_version'];
972
					if (version_compare($version, '9.0.0', '<')) {
973
						$errors[] = array(
974
							'error' => $l->t('PostgreSQL >= 9 required'),
975
							'hint' => $l->t('Please upgrade your database version')
976
						);
977
					}
978
				}
979
			} catch (\Doctrine\DBAL\DBALException $e) {
980
				$logger = \OC::$server->getLogger();
981
				$logger->warning('Error occurred while checking PostgreSQL version, assuming >= 9');
982
				$logger->logException($e);
983
			}
984
		}
985
		return $errors;
986
	}
987
988
	/**
989
	 * Check for correct file permissions of data directory
990
	 *
991
	 * @param string $dataDirectory
992
	 * @return array arrays with error messages and hints
993
	 */
994
	public static function checkDataDirectoryPermissions($dataDirectory) {
995
		if(\OC::$server->getConfig()->getSystemValue('check_data_directory_permissions', true) === false) {
996
			return  [];
997
		}
998
		$l = \OC::$server->getL10N('lib');
999
		$errors = [];
1000
		$permissionsModHint = $l->t('Please change the permissions to 0770 so that the directory'
1001
			. ' cannot be listed by other users.');
1002
		$perms = substr(decoct(@fileperms($dataDirectory)), -3);
1003
		if (substr($perms, -1) !== '0') {
1004
			chmod($dataDirectory, 0770);
1005
			clearstatcache();
1006
			$perms = substr(decoct(@fileperms($dataDirectory)), -3);
1007
			if ($perms[2] !== '0') {
1008
				$errors[] = [
1009
					'error' => $l->t('Your data directory is readable by other users'),
1010
					'hint' => $permissionsModHint
1011
				];
1012
			}
1013
		}
1014
		return $errors;
1015
	}
1016
1017
	/**
1018
	 * Check that the data directory exists and is valid by
1019
	 * checking the existence of the ".ocdata" file.
1020
	 *
1021
	 * @param string $dataDirectory data directory path
1022
	 * @return array errors found
1023
	 */
1024
	public static function checkDataDirectoryValidity($dataDirectory) {
1025
		$l = \OC::$server->getL10N('lib');
1026
		$errors = [];
1027
		if ($dataDirectory[0] !== '/') {
1028
			$errors[] = [
1029
				'error' => $l->t('Your data directory must be an absolute path'),
1030
				'hint' => $l->t('Check the value of "datadirectory" in your configuration')
1031
			];
1032
		}
1033
		if (!file_exists($dataDirectory . '/.ocdata')) {
1034
			$errors[] = [
1035
				'error' => $l->t('Your data directory is invalid'),
1036
				'hint' => $l->t('Ensure there is a file called ".ocdata"' .
1037
					' in the root of the data directory.')
1038
			];
1039
		}
1040
		return $errors;
1041
	}
1042
1043
	/**
1044
	 * Check if the user is logged in, redirects to home if not. With
1045
	 * redirect URL parameter to the request URI.
1046
	 *
1047
	 * @return void
1048
	 */
1049
	public static function checkLoggedIn() {
1050
		// Check if we are a user
1051
		if (!\OC::$server->getUserSession()->isLoggedIn()) {
1052
			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
1053
						'core.login.showLoginForm',
1054
						[
1055
							'redirect_url' => \OC::$server->getRequest()->getRequestUri(),
1056
						]
1057
					)
1058
			);
1059
			exit();
1060
		}
1061
		// Redirect to 2FA challenge selection if 2FA challenge was not solved yet
1062
		if (\OC::$server->getTwoFactorAuthManager()->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
1063
			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
1064
			exit();
1065
		}
1066
	}
1067
1068
	/**
1069
	 * Check if the user is a admin, redirects to home if not
1070
	 *
1071
	 * @return void
1072
	 */
1073
	public static function checkAdminUser() {
1074
		OC_Util::checkLoggedIn();
1075
		if (!OC_User::isAdminUser(OC_User::getUser())) {
1076
			header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
1077
			exit();
1078
		}
1079
	}
1080
1081
	/**
1082
	 * Returns the URL of the default page
1083
	 * based on the system configuration and
1084
	 * the apps visible for the current user
1085
	 *
1086
	 * @return string URL
1087
	 * @suppress PhanDeprecatedFunction
1088
	 */
1089
	public static function getDefaultPageUrl() {
1090
		$urlGenerator = \OC::$server->getURLGenerator();
1091
		// Deny the redirect if the URL contains a @
1092
		// This prevents unvalidated redirects like ?redirect_url=:[email protected]
1093
		if (isset($_REQUEST['redirect_url']) && strpos($_REQUEST['redirect_url'], '@') === false) {
1094
			$location = $urlGenerator->getAbsoluteURL(urldecode($_REQUEST['redirect_url']));
1095
		} else {
1096
			$defaultPage = \OC::$server->getConfig()->getAppValue('core', 'defaultpage');
1097
			if ($defaultPage) {
1098
				$location = $urlGenerator->getAbsoluteURL($defaultPage);
1099
			} else {
1100
				$appId = 'files';
1101
				$config = \OC::$server->getConfig();
1102
				$defaultApps = explode(',', $config->getSystemValue('defaultapp', 'files'));
1103
				// find the first app that is enabled for the current user
1104
				foreach ($defaultApps as $defaultApp) {
1105
					$defaultApp = OC_App::cleanAppId(strip_tags($defaultApp));
1106
					if (static::getAppManager()->isEnabledForUser($defaultApp)) {
1107
						$appId = $defaultApp;
1108
						break;
1109
					}
1110
				}
1111
1112
				if($config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true') {
1113
					$location = $urlGenerator->getAbsoluteURL('/apps/' . $appId . '/');
1114
				} else {
1115
					$location = $urlGenerator->getAbsoluteURL('/index.php/apps/' . $appId . '/');
1116
				}
1117
			}
1118
		}
1119
		return $location;
1120
	}
1121
1122
	/**
1123
	 * Redirect to the user default page
1124
	 *
1125
	 * @return void
1126
	 */
1127
	public static function redirectToDefaultPage() {
1128
		$location = self::getDefaultPageUrl();
1129
		header('Location: ' . $location);
1130
		exit();
1131
	}
1132
1133
	/**
1134
	 * get an id unique for this instance
1135
	 *
1136
	 * @return string
1137
	 */
1138
	public static function getInstanceId() {
1139
		$id = \OC::$server->getSystemConfig()->getValue('instanceid', null);
1140
		if (is_null($id)) {
1141
			// We need to guarantee at least one letter in instanceid so it can be used as the session_name
1142
			$id = 'oc' . \OC::$server->getSecureRandom()->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_DIGITS);
1143
			\OC::$server->getSystemConfig()->setValue('instanceid', $id);
1144
		}
1145
		return $id;
1146
	}
1147
1148
	/**
1149
	 * Public function to sanitize HTML
1150
	 *
1151
	 * This function is used to sanitize HTML and should be applied on any
1152
	 * string or array of strings before displaying it on a web page.
1153
	 *
1154
	 * @param string|array $value
1155
	 * @return string|array an array of sanitized strings or a single sanitized string, depends on the input parameter.
1156
	 */
1157
	public static function sanitizeHTML($value) {
1158
		if (is_array($value)) {
1159
			$value = array_map(function($value) {
1160
				return self::sanitizeHTML($value);
1161
			}, $value);
1162
		} else {
1163
			// Specify encoding for PHP<5.4
1164
			$value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
1165
		}
1166
		return $value;
1167
	}
1168
1169
	/**
1170
	 * Public function to encode url parameters
1171
	 *
1172
	 * This function is used to encode path to file before output.
1173
	 * Encoding is done according to RFC 3986 with one exception:
1174
	 * Character '/' is preserved as is.
1175
	 *
1176
	 * @param string $component part of URI to encode
1177
	 * @return string
1178
	 */
1179
	public static function encodePath($component) {
1180
		$encoded = rawurlencode($component);
1181
		$encoded = str_replace('%2F', '/', $encoded);
1182
		return $encoded;
1183
	}
1184
1185
1186
	public function createHtaccessTestFile(\OCP\IConfig $config) {
1187
		// php dev server does not support htaccess
1188
		if (php_sapi_name() === 'cli-server') {
1189
			return false;
1190
		}
1191
1192
		// testdata
1193
		$fileName = '/htaccesstest.txt';
1194
		$testContent = 'This is used for testing whether htaccess is properly enabled to disallow access from the outside. This file can be safely removed.';
1195
1196
		// creating a test file
1197
		$testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName;
1198
1199
		if (file_exists($testFile)) {// already running this test, possible recursive call
1200
			return false;
1201
		}
1202
1203
		$fp = @fopen($testFile, 'w');
1204
		if (!$fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1205
			throw new OC\HintException('Can\'t create test file to check for working .htaccess file.',
1206
				'Make sure it is possible for the webserver to write to ' . $testFile);
1207
		}
1208
		fwrite($fp, $testContent);
1209
		fclose($fp);
1210
1211
		return $testContent;
1212
	}
1213
1214
	/**
1215
	 * Check if the .htaccess file is working
1216
	 * @param \OCP\IConfig $config
1217
	 * @return bool
1218
	 * @throws Exception
1219
	 * @throws \OC\HintException If the test file can't get written.
1220
	 */
1221
	public function isHtaccessWorking(\OCP\IConfig $config) {
1222
1223
		if (\OC::$CLI || !$config->getSystemValue('check_for_working_htaccess', true)) {
1224
			return true;
1225
		}
1226
1227
		$testContent = $this->createHtaccessTestFile($config);
1228
		if ($testContent === false) {
0 ignored issues
show
introduced by
The condition $testContent === false is always true.
Loading history...
1229
			return false;
1230
		}
1231
1232
		$fileName = '/htaccesstest.txt';
1233
		$testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName;
1234
1235
		// accessing the file via http
1236
		$url = \OC::$server->getURLGenerator()->getAbsoluteURL(OC::$WEBROOT . '/data' . $fileName);
1237
		try {
1238
			$content = \OC::$server->getHTTPClientService()->newClient()->get($url)->getBody();
1239
		} catch (\Exception $e) {
1240
			$content = false;
1241
		}
1242
1243
		// cleanup
1244
		@unlink($testFile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

1244
		/** @scrutinizer ignore-unhandled */ @unlink($testFile);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1245
1246
		/*
1247
		 * If the content is not equal to test content our .htaccess
1248
		 * is working as required
1249
		 */
1250
		return $content !== $testContent;
1251
	}
1252
1253
	/**
1254
	 * Check if the setlocal call does not work. This can happen if the right
1255
	 * local packages are not available on the server.
1256
	 *
1257
	 * @return bool
1258
	 */
1259
	public static function isSetLocaleWorking() {
1260
		\Patchwork\Utf8\Bootup::initLocale();
1261
		if ('' === basename('§')) {
1262
			return false;
1263
		}
1264
		return true;
1265
	}
1266
1267
	/**
1268
	 * Check if it's possible to get the inline annotations
1269
	 *
1270
	 * @return bool
1271
	 */
1272
	public static function isAnnotationsWorking() {
1273
		$reflection = new \ReflectionMethod(__METHOD__);
0 ignored issues
show
Bug introduced by
The call to ReflectionMethod::__construct() has too few arguments starting with name. ( Ignorable by Annotation )

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

1273
		$reflection = /** @scrutinizer ignore-call */ new \ReflectionMethod(__METHOD__);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1274
		$docs = $reflection->getDocComment();
1275
1276
		return (is_string($docs) && strlen($docs) > 50);
1277
	}
1278
1279
	/**
1280
	 * Check if the PHP module fileinfo is loaded.
1281
	 *
1282
	 * @return bool
1283
	 */
1284
	public static function fileInfoLoaded() {
1285
		return function_exists('finfo_open');
1286
	}
1287
1288
	/**
1289
	 * clear all levels of output buffering
1290
	 *
1291
	 * @return void
1292
	 */
1293
	public static function obEnd() {
1294
		while (ob_get_level()) {
1295
			ob_end_clean();
1296
		}
1297
	}
1298
1299
	/**
1300
	 * Checks whether the server is running on Mac OS X
1301
	 *
1302
	 * @return bool true if running on Mac OS X, false otherwise
1303
	 */
1304
	public static function runningOnMac() {
1305
		return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
1306
	}
1307
1308
	/**
1309
	 * Handles the case that there may not be a theme, then check if a "default"
1310
	 * theme exists and take that one
1311
	 *
1312
	 * @return string the theme
1313
	 */
1314
	public static function getTheme() {
1315
		$theme = \OC::$server->getSystemConfig()->getValue("theme", '');
1316
1317
		if ($theme === '') {
1318
			if (is_dir(OC::$SERVERROOT . '/themes/default')) {
1319
				$theme = 'default';
1320
			}
1321
		}
1322
1323
		return $theme;
1324
	}
1325
1326
	/**
1327
	 * Clear a single file from the opcode cache
1328
	 * This is useful for writing to the config file
1329
	 * in case the opcode cache does not re-validate files
1330
	 * Returns true if successful, false if unsuccessful:
1331
	 * caller should fall back on clearing the entire cache
1332
	 * with clearOpcodeCache() if unsuccessful
1333
	 *
1334
	 * @param string $path the path of the file to clear from the cache
1335
	 * @return bool true if underlying function returns true, otherwise false
1336
	 */
1337
	public static function deleteFromOpcodeCache($path) {
1338
		$ret = false;
1339
		if ($path) {
1340
			// APC >= 3.1.1
1341
			if (function_exists('apc_delete_file')) {
1342
				$ret = @apc_delete_file($path);
1343
			}
1344
			// Zend OpCache >= 7.0.0, PHP >= 5.5.0
1345
			if (function_exists('opcache_invalidate')) {
1346
				$ret = @opcache_invalidate($path);
1347
			}
1348
		}
1349
		return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type string[] which is incompatible with the documented return type boolean.
Loading history...
1350
	}
1351
1352
	/**
1353
	 * Clear the opcode cache if one exists
1354
	 * This is necessary for writing to the config file
1355
	 * in case the opcode cache does not re-validate files
1356
	 *
1357
	 * @return void
1358
	 * @suppress PhanDeprecatedFunction
1359
	 * @suppress PhanUndeclaredConstant
1360
	 */
1361
	public static function clearOpcodeCache() {
1362
		// APC
1363
		if (function_exists('apc_clear_cache')) {
1364
			apc_clear_cache();
1365
		}
1366
		// Zend Opcache
1367
		if (function_exists('accelerator_reset')) {
1368
			accelerator_reset();
1369
		}
1370
		// Opcache (PHP >= 5.5)
1371
		if (function_exists('opcache_reset')) {
1372
			@opcache_reset();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for opcache_reset(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

1372
			/** @scrutinizer ignore-unhandled */ @opcache_reset();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1373
		}
1374
	}
1375
1376
	/**
1377
	 * Normalize a unicode string
1378
	 *
1379
	 * @param string $value a not normalized string
1380
	 * @return bool|string
1381
	 */
1382
	public static function normalizeUnicode($value) {
1383
		if(Normalizer::isNormalized($value)) {
1384
			return $value;
1385
		}
1386
1387
		$normalizedValue = Normalizer::normalize($value);
1388
		if ($normalizedValue === null || $normalizedValue === false) {
1389
			\OC::$server->getLogger()->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
1390
			return $value;
1391
		}
1392
1393
		return $normalizedValue;
1394
	}
1395
1396
	/**
1397
	 * A human readable string is generated based on version and build number
1398
	 *
1399
	 * @return string
1400
	 */
1401
	public static function getHumanVersion() {
1402
		$version = OC_Util::getVersionString();
1403
		$build = OC_Util::getBuild();
1404
		if (!empty($build) and OC_Util::getChannel() === 'daily') {
1405
			$version .= ' Build:' . $build;
1406
		}
1407
		return $version;
1408
	}
1409
1410
	/**
1411
	 * Returns whether the given file name is valid
1412
	 *
1413
	 * @param string $file file name to check
1414
	 * @return bool true if the file name is valid, false otherwise
1415
	 * @deprecated use \OC\Files\View::verifyPath()
1416
	 */
1417
	public static function isValidFileName($file) {
1418
		$trimmed = trim($file);
1419
		if ($trimmed === '') {
1420
			return false;
1421
		}
1422
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1423
			return false;
1424
		}
1425
1426
		// detect part files
1427
		if (preg_match('/' . \OCP\Files\FileInfo::BLACKLIST_FILES_REGEX . '/', $trimmed) !== 0) {
1428
			return false;
1429
		}
1430
1431
		foreach (str_split($trimmed) as $char) {
1432
			if (strpos(\OCP\Constants::FILENAME_INVALID_CHARS, $char) !== false) {
1433
				return false;
1434
			}
1435
		}
1436
		return true;
1437
	}
1438
1439
	/**
1440
	 * Check whether the instance needs to perform an upgrade,
1441
	 * either when the core version is higher or any app requires
1442
	 * an upgrade.
1443
	 *
1444
	 * @param \OC\SystemConfig $config
1445
	 * @return bool whether the core or any app needs an upgrade
1446
	 * @throws \OC\HintException When the upgrade from the given version is not allowed
1447
	 */
1448
	public static function needUpgrade(\OC\SystemConfig $config) {
1449
		if ($config->getValue('installed', false)) {
1450
			$installedVersion = $config->getValue('version', '0.0.0');
1451
			$currentVersion = implode('.', \OCP\Util::getVersion());
1452
			$versionDiff = version_compare($currentVersion, $installedVersion);
1453
			if ($versionDiff > 0) {
1454
				return true;
1455
			} else if ($config->getValue('debug', false) && $versionDiff < 0) {
1456
				// downgrade with debug
1457
				$installedMajor = explode('.', $installedVersion);
1458
				$installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
1459
				$currentMajor = explode('.', $currentVersion);
1460
				$currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
1461
				if ($installedMajor === $currentMajor) {
1462
					// Same major, allow downgrade for developers
1463
					return true;
1464
				} else {
1465
					// downgrade attempt, throw exception
1466
					throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
1467
				}
1468
			} else if ($versionDiff < 0) {
1469
				// downgrade attempt, throw exception
1470
				throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
1471
			}
1472
1473
			// also check for upgrades for apps (independently from the user)
1474
			$apps = \OC_App::getEnabledApps(false, true);
1475
			$shouldUpgrade = false;
1476
			foreach ($apps as $app) {
1477
				if (\OC_App::shouldUpgrade($app)) {
1478
					$shouldUpgrade = true;
1479
					break;
1480
				}
1481
			}
1482
			return $shouldUpgrade;
1483
		} else {
1484
			return false;
1485
		}
1486
	}
1487
1488
	/**
1489
	 * is this Internet explorer ?
1490
	 *
1491
	 * @return boolean
1492
	 */
1493
	public static function isIe() {
1494
		if (!isset($_SERVER['HTTP_USER_AGENT'])) {
1495
			return false;
1496
		}
1497
1498
		return preg_match(Request::USER_AGENT_IE, $_SERVER['HTTP_USER_AGENT']) === 1;
1499
	}
1500
1501
}
1502