Passed
Push — master ( 0d22ea...5fe151 )
by Morris
20:23 queued 08:36
created

OC_Util::needUpgrade()   B

Complexity

Conditions 9
Paths 8

Size

Total Lines 37
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 26
nc 8
nop 1
dl 0
loc 37
rs 8.0555
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
 * @author Robert Dailey <[email protected]>
47
 *
48
 * @license AGPL-3.0
49
 *
50
 * This code is free software: you can redistribute it and/or modify
51
 * it under the terms of the GNU Affero General Public License, version 3,
52
 * as published by the Free Software Foundation.
53
 *
54
 * This program is distributed in the hope that it will be useful,
55
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
56
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
57
 * GNU Affero General Public License for more details.
58
 *
59
 * You should have received a copy of the GNU Affero General Public License, version 3,
60
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
61
 *
62
 */
63
64
use OCP\IConfig;
65
use OCP\IGroupManager;
66
use OCP\ILogger;
67
use OCP\IUser;
68
use OC\AppFramework\Http\Request;
69
70
class OC_Util {
71
	public static $scripts = array();
72
	public static $styles = array();
73
	public static $headers = array();
74
	private static $rootMounted = false;
75
	private static $fsSetup = false;
76
77
	/** @var array Local cache of version.php */
78
	private static $versionCache = null;
79
80
	protected static function getAppManager() {
81
		return \OC::$server->getAppManager();
82
	}
83
84
	private static function initLocalStorageRootFS() {
85
		// mount local file backend as root
86
		$configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data");
87
		//first set up the local "root" storage
88
		\OC\Files\Filesystem::initMountManager();
89
		if (!self::$rootMounted) {
90
			\OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => $configDataDirectory), '/');
91
			self::$rootMounted = true;
92
		}
93
	}
94
95
	/**
96
	 * mounting an object storage as the root fs will in essence remove the
97
	 * necessity of a data folder being present.
98
	 * TODO make home storage aware of this and use the object storage instead of local disk access
99
	 *
100
	 * @param array $config containing 'class' and optional 'arguments'
101
	 * @suppress PhanDeprecatedFunction
102
	 */
103
	private static function initObjectStoreRootFS($config) {
104
		// check misconfiguration
105
		if (empty($config['class'])) {
106
			\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

106
			/** @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...
107
		}
108
		if (!isset($config['arguments'])) {
109
			$config['arguments'] = array();
110
		}
111
112
		// instantiate object store implementation
113
		$name = $config['class'];
114
		if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) {
115
			$segments = explode('\\', $name);
116
			OC_App::loadApp(strtolower($segments[1]));
117
		}
118
		$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
119
		// mount with plain / root object store implementation
120
		$config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage';
121
122
		// mount object storage as root
123
		\OC\Files\Filesystem::initMountManager();
124
		if (!self::$rootMounted) {
125
			\OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/');
126
			self::$rootMounted = true;
127
		}
128
	}
129
130
	/**
131
	 * mounting an object storage as the root fs will in essence remove the
132
	 * necessity of a data folder being present.
133
	 *
134
	 * @param array $config containing 'class' and optional 'arguments'
135
	 * @suppress PhanDeprecatedFunction
136
	 */
137
	private static function initObjectStoreMultibucketRootFS($config) {
138
		// check misconfiguration
139
		if (empty($config['class'])) {
140
			\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

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

290
			self::initObjectStoreMultibucketRootFS(/** @scrutinizer ignore-type */ $objectStoreMultibucket);
Loading history...
291
		} elseif (isset($objectStore)) {
292
			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

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

343
			$usersGroups = $groupManager->getUserGroupIds(/** @scrutinizer ignore-type */ $user);
Loading history...
344
			if (!empty($usersGroups)) {
345
				$remainingGroups = array_diff($usersGroups, $excludedGroups);
346
				// if the user is only in groups which are disabled for sharing then
347
				// sharing is also disabled for the user
348
				if (empty($remainingGroups)) {
349
					return true;
350
				}
351
			}
352
		}
353
		return false;
354
	}
355
356
	/**
357
	 * check if share API enforces a default expire date
358
	 *
359
	 * @return boolean
360
	 * @suppress PhanDeprecatedFunction
361
	 */
362
	public static function isDefaultExpireDateEnforced() {
363
		$isDefaultExpireDateEnabled = \OC::$server->getConfig()->getAppValue('core', 'shareapi_default_expire_date', 'no');
364
		$enforceDefaultExpireDate = false;
365
		if ($isDefaultExpireDateEnabled === 'yes') {
366
			$value = \OC::$server->getConfig()->getAppValue('core', 'shareapi_enforce_expire_date', 'no');
367
			$enforceDefaultExpireDate = $value === 'yes';
368
		}
369
370
		return $enforceDefaultExpireDate;
371
	}
372
373
	/**
374
	 * Get the quota of a user
375
	 *
376
	 * @param string $userId
377
	 * @return float Quota bytes
378
	 */
379
	public static function getUserQuota($userId) {
380
		$user = \OC::$server->getUserManager()->get($userId);
381
		if (is_null($user)) {
382
			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
383
		}
384
		$userQuota = $user->getQuota();
385
		if($userQuota === 'none') {
386
			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
387
		}
388
		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...
389
	}
390
391
	/**
392
	 * copies the skeleton to the users /files
393
	 *
394
	 * @param string $userId
395
	 * @param \OCP\Files\Folder $userDirectory
396
	 * @throws \OCP\Files\NotFoundException
397
	 * @throws \OCP\Files\NotPermittedException
398
	 * @suppress PhanDeprecatedFunction
399
	 */
400
	public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
401
402
		$plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
403
		$userLang = \OC::$server->getL10NFactory()->findLanguage();
404
		$skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
405
406
		if (!file_exists($skeletonDirectory)) {
407
			$dialectStart = strpos($userLang, '_');
408
			if ($dialectStart !== false) {
409
				$skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
410
			}
411
			if ($dialectStart === false || !file_exists($skeletonDirectory)) {
412
				$skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
413
			}
414
			if (!file_exists($skeletonDirectory)) {
415
				$skeletonDirectory = '';
416
			}
417
		}
418
419
		$instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
420
421
		if ($instanceId === null) {
422
			throw new \RuntimeException('no instance id!');
423
		}
424
		$appdata = 'appdata_' . $instanceId;
425
		if ($userId === $appdata) {
426
			throw new \RuntimeException('username is reserved name: ' . $appdata);
427
		}
428
429
		if (!empty($skeletonDirectory)) {
430
			\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

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

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

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

735
			/** @scrutinizer ignore-type */ \OC::$server->query(\OCP\Defaults::class),
Loading history...
736
			\OC::$server->getLogger(),
737
			\OC::$server->getSecureRandom(),
738
			\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

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

768
				|| !is_writable(/** @scrutinizer ignore-type */ OC_App::getInstallPath())
Loading history...
769
				|| !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

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

1252
		/** @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...
1253
1254
		/*
1255
		 * If the content is not equal to test content our .htaccess
1256
		 * is working as required
1257
		 */
1258
		return $content !== $testContent;
1259
	}
1260
1261
	/**
1262
	 * Check if the setlocal call does not work. This can happen if the right
1263
	 * local packages are not available on the server.
1264
	 *
1265
	 * @return bool
1266
	 */
1267
	public static function isSetLocaleWorking() {
1268
		\Patchwork\Utf8\Bootup::initLocale();
1269
		if ('' === basename('§')) {
1270
			return false;
1271
		}
1272
		return true;
1273
	}
1274
1275
	/**
1276
	 * Check if it's possible to get the inline annotations
1277
	 *
1278
	 * @return bool
1279
	 */
1280
	public static function isAnnotationsWorking() {
1281
		$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

1281
		$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...
1282
		$docs = $reflection->getDocComment();
1283
1284
		return (is_string($docs) && strlen($docs) > 50);
1285
	}
1286
1287
	/**
1288
	 * Check if the PHP module fileinfo is loaded.
1289
	 *
1290
	 * @return bool
1291
	 */
1292
	public static function fileInfoLoaded() {
1293
		return function_exists('finfo_open');
1294
	}
1295
1296
	/**
1297
	 * clear all levels of output buffering
1298
	 *
1299
	 * @return void
1300
	 */
1301
	public static function obEnd() {
1302
		while (ob_get_level()) {
1303
			ob_end_clean();
1304
		}
1305
	}
1306
1307
	/**
1308
	 * Checks whether the server is running on Mac OS X
1309
	 *
1310
	 * @return bool true if running on Mac OS X, false otherwise
1311
	 */
1312
	public static function runningOnMac() {
1313
		return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
1314
	}
1315
1316
	/**
1317
	 * Handles the case that there may not be a theme, then check if a "default"
1318
	 * theme exists and take that one
1319
	 *
1320
	 * @return string the theme
1321
	 */
1322
	public static function getTheme() {
1323
		$theme = \OC::$server->getSystemConfig()->getValue("theme", '');
1324
1325
		if ($theme === '') {
1326
			if (is_dir(OC::$SERVERROOT . '/themes/default')) {
1327
				$theme = 'default';
1328
			}
1329
		}
1330
1331
		return $theme;
1332
	}
1333
1334
	/**
1335
	 * Normalize a unicode string
1336
	 *
1337
	 * @param string $value a not normalized string
1338
	 * @return bool|string
1339
	 */
1340
	public static function normalizeUnicode($value) {
1341
		if(Normalizer::isNormalized($value)) {
1342
			return $value;
1343
		}
1344
1345
		$normalizedValue = Normalizer::normalize($value);
1346
		if ($normalizedValue === null || $normalizedValue === false) {
1347
			\OC::$server->getLogger()->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
1348
			return $value;
1349
		}
1350
1351
		return $normalizedValue;
1352
	}
1353
1354
	/**
1355
	 * A human readable string is generated based on version and build number
1356
	 *
1357
	 * @return string
1358
	 */
1359
	public static function getHumanVersion() {
1360
		$version = OC_Util::getVersionString();
1361
		$build = OC_Util::getBuild();
1362
		if (!empty($build) and OC_Util::getChannel() === 'daily') {
1363
			$version .= ' Build:' . $build;
1364
		}
1365
		return $version;
1366
	}
1367
1368
	/**
1369
	 * Returns whether the given file name is valid
1370
	 *
1371
	 * @param string $file file name to check
1372
	 * @return bool true if the file name is valid, false otherwise
1373
	 * @deprecated use \OC\Files\View::verifyPath()
1374
	 */
1375
	public static function isValidFileName($file) {
1376
		$trimmed = trim($file);
1377
		if ($trimmed === '') {
1378
			return false;
1379
		}
1380
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1381
			return false;
1382
		}
1383
1384
		// detect part files
1385
		if (preg_match('/' . \OCP\Files\FileInfo::BLACKLIST_FILES_REGEX . '/', $trimmed) !== 0) {
1386
			return false;
1387
		}
1388
1389
		foreach (str_split($trimmed) as $char) {
1390
			if (strpos(\OCP\Constants::FILENAME_INVALID_CHARS, $char) !== false) {
1391
				return false;
1392
			}
1393
		}
1394
		return true;
1395
	}
1396
1397
	/**
1398
	 * Check whether the instance needs to perform an upgrade,
1399
	 * either when the core version is higher or any app requires
1400
	 * an upgrade.
1401
	 *
1402
	 * @param \OC\SystemConfig $config
1403
	 * @return bool whether the core or any app needs an upgrade
1404
	 * @throws \OC\HintException When the upgrade from the given version is not allowed
1405
	 */
1406
	public static function needUpgrade(\OC\SystemConfig $config) {
1407
		if ($config->getValue('installed', false)) {
1408
			$installedVersion = $config->getValue('version', '0.0.0');
1409
			$currentVersion = implode('.', \OCP\Util::getVersion());
1410
			$versionDiff = version_compare($currentVersion, $installedVersion);
1411
			if ($versionDiff > 0) {
1412
				return true;
1413
			} else if ($config->getValue('debug', false) && $versionDiff < 0) {
1414
				// downgrade with debug
1415
				$installedMajor = explode('.', $installedVersion);
1416
				$installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
1417
				$currentMajor = explode('.', $currentVersion);
1418
				$currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
1419
				if ($installedMajor === $currentMajor) {
1420
					// Same major, allow downgrade for developers
1421
					return true;
1422
				} else {
1423
					// downgrade attempt, throw exception
1424
					throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
1425
				}
1426
			} else if ($versionDiff < 0) {
1427
				// downgrade attempt, throw exception
1428
				throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
1429
			}
1430
1431
			// also check for upgrades for apps (independently from the user)
1432
			$apps = \OC_App::getEnabledApps(false, true);
1433
			$shouldUpgrade = false;
1434
			foreach ($apps as $app) {
1435
				if (\OC_App::shouldUpgrade($app)) {
1436
					$shouldUpgrade = true;
1437
					break;
1438
				}
1439
			}
1440
			return $shouldUpgrade;
1441
		} else {
1442
			return false;
1443
		}
1444
	}
1445
1446
	/**
1447
	 * is this Internet explorer ?
1448
	 *
1449
	 * @return boolean
1450
	 */
1451
	public static function isIe() {
1452
		if (!isset($_SERVER['HTTP_USER_AGENT'])) {
1453
			return false;
1454
		}
1455
1456
		return preg_match(Request::USER_AGENT_IE, $_SERVER['HTTP_USER_AGENT']) === 1;
1457
	}
1458
1459
}
1460