Completed
Push — master ( ffc627...007fba )
by Morris
30:41 queued 14:15
created

OC_Util::normalizeUnicode()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 3
nop 1
dl 0
loc 13
rs 9.2
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\IUser;
66
67
class OC_Util {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
68
	public static $scripts = array();
69
	public static $styles = array();
70
	public static $headers = array();
71
	private static $rootMounted = false;
72
	private static $fsSetup = false;
73
74
	/** @var array Local cache of version.php */
75
	private static $versionCache = null;
76
77
	protected static function getAppManager() {
78
		return \OC::$server->getAppManager();
79
	}
80
81
	private static function initLocalStorageRootFS() {
82
		// mount local file backend as root
83
		$configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data");
84
		//first set up the local "root" storage
85
		\OC\Files\Filesystem::initMountManager();
86
		if (!self::$rootMounted) {
87
			\OC\Files\Filesystem::mount('\OC\Files\Storage\Local', array('datadir' => $configDataDirectory), '/');
88
			self::$rootMounted = true;
89
		}
90
	}
91
92
	/**
93
	 * mounting an object storage as the root fs will in essence remove the
94
	 * necessity of a data folder being present.
95
	 * TODO make home storage aware of this and use the object storage instead of local disk access
96
	 *
97
	 * @param array $config containing 'class' and optional 'arguments'
98
	 * @suppress PhanDeprecatedFunction
99
	 */
100
	private static function initObjectStoreRootFS($config) {
101
		// check misconfiguration
102
		if (empty($config['class'])) {
103
			\OCP\Util::writeLog('files', 'No class given for objectstore', \OCP\Util::ERROR);
104
		}
105
		if (!isset($config['arguments'])) {
106
			$config['arguments'] = array();
107
		}
108
109
		// instantiate object store implementation
110
		$name = $config['class'];
111 View Code Duplication
		if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) {
112
			$segments = explode('\\', $name);
113
			OC_App::loadApp(strtolower($segments[1]));
114
		}
115
		$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
116
		// mount with plain / root object store implementation
117
		$config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage';
118
119
		// mount object storage as root
120
		\OC\Files\Filesystem::initMountManager();
121 View Code Duplication
		if (!self::$rootMounted) {
122
			\OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/');
123
			self::$rootMounted = true;
124
		}
125
	}
126
127
	/**
128
	 * mounting an object storage as the root fs will in essence remove the
129
	 * necessity of a data folder being present.
130
	 *
131
	 * @param array $config containing 'class' and optional 'arguments'
132
	 * @suppress PhanDeprecatedFunction
133
	 */
134
	private static function initObjectStoreMultibucketRootFS($config) {
135
		// check misconfiguration
136
		if (empty($config['class'])) {
137
			\OCP\Util::writeLog('files', 'No class given for objectstore', \OCP\Util::ERROR);
138
		}
139
		if (!isset($config['arguments'])) {
140
			$config['arguments'] = array();
141
		}
142
143
		// instantiate object store implementation
144
		$name = $config['class'];
145 View Code Duplication
		if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) {
146
			$segments = explode('\\', $name);
147
			OC_App::loadApp(strtolower($segments[1]));
148
		}
149
150
		if (!isset($config['arguments']['bucket'])) {
151
			$config['arguments']['bucket'] = '';
152
		}
153
		// put the root FS always in first bucket for multibucket configuration
154
		$config['arguments']['bucket'] .= '0';
155
156
		$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
157
		// mount with plain / root object store implementation
158
		$config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage';
159
160
		// mount object storage as root
161
		\OC\Files\Filesystem::initMountManager();
162 View Code Duplication
		if (!self::$rootMounted) {
163
			\OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/');
164
			self::$rootMounted = true;
165
		}
166
	}
167
168
	/**
169
	 * Can be set up
170
	 *
171
	 * @param string $user
172
	 * @return boolean
173
	 * @description configure the initial filesystem based on the configuration
174
	 * @suppress PhanDeprecatedFunction
175
	 * @suppress PhanAccessMethodInternal
176
	 */
177
	public static function setupFS($user = '') {
178
		//setting up the filesystem twice can only lead to trouble
179
		if (self::$fsSetup) {
180
			return false;
181
		}
182
183
		\OC::$server->getEventLogger()->start('setup_fs', 'Setup filesystem');
184
185
		// If we are not forced to load a specific user we load the one that is logged in
186
		if ($user === null) {
187
			$user = '';
188
		} else if ($user == "" && \OC::$server->getUserSession()->isLoggedIn()) {
189
			$user = OC_User::getUser();
190
		}
191
192
		// load all filesystem apps before, so no setup-hook gets lost
193
		OC_App::loadApps(array('filesystem'));
194
195
		// the filesystem will finish when $user is not empty,
196
		// mark fs setup here to avoid doing the setup from loading
197
		// OC_Filesystem
198
		if ($user != '') {
199
			self::$fsSetup = true;
200
		}
201
202
		\OC\Files\Filesystem::initMountManager();
203
204
		\OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(false);
205
		\OC\Files\Filesystem::addStorageWrapper('mount_options', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) {
206
			if ($storage->instanceOfStorage('\OC\Files\Storage\Common')) {
207
				/** @var \OC\Files\Storage\Common $storage */
208
				$storage->setMountOptions($mount->getOptions());
209
			}
210
			return $storage;
211
		});
212
213
		\OC\Files\Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, \OCP\Files\Storage\IStorage $storage, \OCP\Files\Mount\IMountPoint $mount) {
214
			if (!$mount->getOption('enable_sharing', true)) {
215
				return new \OC\Files\Storage\Wrapper\PermissionsMask([
216
					'storage' => $storage,
217
					'mask' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_SHARE
218
				]);
219
			}
220
			return $storage;
221
		});
222
223
		// install storage availability wrapper, before most other wrappers
224
		\OC\Files\Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, \OCP\Files\Storage\IStorage $storage) {
225
			if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
226
				return new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]);
227
			}
228
			return $storage;
229
		});
230
231
		\OC\Files\Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) {
232
			if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
233
				return new \OC\Files\Storage\Wrapper\Encoding(['storage' => $storage]);
234
			}
235
			return $storage;
236
		});
237
238
		\OC\Files\Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) {
239
			// set up quota for home storages, even for other users
240
			// which can happen when using sharing
241
242
			/**
243
			 * @var \OC\Files\Storage\Storage $storage
244
			 */
245
			if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
246
				|| $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
247
			) {
248
				/** @var \OC\Files\Storage\Home $storage */
249
				if (is_object($storage->getUser())) {
250
					$user = $storage->getUser()->getUID();
251
					$quota = OC_Util::getUserQuota($user);
252
					if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
253
						return new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => $quota, 'root' => 'files'));
254
					}
255
				}
256
			}
257
258
			return $storage;
259
		});
260
261
		OC_Hook::emit('OC_Filesystem', 'preSetup', array('user' => $user));
262
		\OC\Files\Filesystem::logWarningWhenAddingStorageWrapper(true);
263
264
		//check if we are using an object storage
265
		$objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null);
266
		$objectStoreMultibucket = \OC::$server->getSystemConfig()->getValue('objectstore_multibucket', null);
267
268
		// use the same order as in ObjectHomeMountProvider
269
		if (isset($objectStoreMultibucket)) {
270
			self::initObjectStoreMultibucketRootFS($objectStoreMultibucket);
271
		} elseif (isset($objectStore)) {
272
			self::initObjectStoreRootFS($objectStore);
273
		} else {
274
			self::initLocalStorageRootFS();
275
		}
276
277
		if ($user != '' && !OCP\User::userExists($user)) {
278
			\OC::$server->getEventLogger()->end('setup_fs');
279
			return false;
280
		}
281
282
		//if we aren't logged in, there is no use to set up the filesystem
283
		if ($user != "") {
284
285
			$userDir = '/' . $user . '/files';
286
287
			//jail the user into his "home" directory
288
			\OC\Files\Filesystem::init($user, $userDir);
289
290
			OC_Hook::emit('OC_Filesystem', 'setup', array('user' => $user, 'user_dir' => $userDir));
291
		}
292
		\OC::$server->getEventLogger()->end('setup_fs');
293
		return true;
294
	}
295
296
	/**
297
	 * check if a password is required for each public link
298
	 *
299
	 * @return boolean
300
	 * @suppress PhanDeprecatedFunction
301
	 */
302
	public static function isPublicLinkPasswordRequired() {
303
		$appConfig = \OC::$server->getAppConfig();
304
		$enforcePassword = $appConfig->getValue('core', 'shareapi_enforce_links_password', 'no');
305
		return ($enforcePassword === 'yes') ? true : false;
306
	}
307
308
	/**
309
	 * check if sharing is disabled for the current user
310
	 * @param IConfig $config
311
	 * @param IGroupManager $groupManager
312
	 * @param IUser|null $user
313
	 * @return bool
314
	 */
315
	public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) {
316
		if ($config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
317
			$groupsList = $config->getAppValue('core', 'shareapi_exclude_groups_list', '');
318
			$excludedGroups = json_decode($groupsList);
319 View Code Duplication
			if (is_null($excludedGroups)) {
320
				$excludedGroups = explode(',', $groupsList);
321
				$newValue = json_encode($excludedGroups);
322
				$config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
323
			}
324
			$usersGroups = $groupManager->getUserGroupIds($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by parameter $user on line 315 can be null; however, OCP\IGroupManager::getUserGroupIds() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
325 View Code Duplication
			if (!empty($usersGroups)) {
326
				$remainingGroups = array_diff($usersGroups, $excludedGroups);
327
				// if the user is only in groups which are disabled for sharing then
328
				// sharing is also disabled for the user
329
				if (empty($remainingGroups)) {
330
					return true;
331
				}
332
			}
333
		}
334
		return false;
335
	}
336
337
	/**
338
	 * check if share API enforces a default expire date
339
	 *
340
	 * @return boolean
341
	 * @suppress PhanDeprecatedFunction
342
	 */
343
	public static function isDefaultExpireDateEnforced() {
344
		$isDefaultExpireDateEnabled = \OCP\Config::getAppValue('core', 'shareapi_default_expire_date', 'no');
345
		$enforceDefaultExpireDate = false;
346
		if ($isDefaultExpireDateEnabled === 'yes') {
347
			$value = \OCP\Config::getAppValue('core', 'shareapi_enforce_expire_date', 'no');
348
			$enforceDefaultExpireDate = ($value === 'yes') ? true : false;
349
		}
350
351
		return $enforceDefaultExpireDate;
352
	}
353
354
	/**
355
	 * Get the quota of a user
356
	 *
357
	 * @param string $userId
358
	 * @return float Quota bytes
359
	 */
360
	public static function getUserQuota($userId) {
361
		$user = \OC::$server->getUserManager()->get($userId);
362
		if (is_null($user)) {
363
			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
364
		}
365
		$userQuota = $user->getQuota();
366
		if($userQuota === 'none') {
367
			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
368
		}
369
		return OC_Helper::computerFileSize($userQuota);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression \OC_Helper::computerFileSize($userQuota); of type double|false adds false to the return on line 369 which is incompatible with the return type documented by OC_Util::getUserQuota of type double. It seems like you forgot to handle an error condition.
Loading history...
370
	}
371
372
	/**
373
	 * copies the skeleton to the users /files
374
	 *
375
	 * @param String $userId
376
	 * @param \OCP\Files\Folder $userDirectory
377
	 * @throws \RuntimeException
378
	 * @suppress PhanDeprecatedFunction
379
	 */
380
	public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
381
382
		$skeletonDirectory = \OC::$server->getConfig()->getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
383
		$instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
384
385
		if ($instanceId === null) {
386
			throw new \RuntimeException('no instance id!');
387
		}
388
		$appdata = 'appdata_' . $instanceId;
389
		if ($userId === $appdata) {
390
			throw new \RuntimeException('username is reserved name: ' . $appdata);
391
		}
392
393
		if (!empty($skeletonDirectory)) {
394
			\OCP\Util::writeLog(
395
				'files_skeleton',
396
				'copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'),
397
				\OCP\Util::DEBUG
398
			);
399
			self::copyr($skeletonDirectory, $userDirectory);
400
			// update the file cache
401
			$userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
402
		}
403
	}
404
405
	/**
406
	 * copies a directory recursively by using streams
407
	 *
408
	 * @param string $source
409
	 * @param \OCP\Files\Folder $target
410
	 * @return void
411
	 */
412
	public static function copyr($source, \OCP\Files\Folder $target) {
413
		$logger = \OC::$server->getLogger();
414
415
		// Verify if folder exists
416
		$dir = opendir($source);
417
		if($dir === false) {
418
			$logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
419
			return;
420
		}
421
422
		// Copy the files
423
		while (false !== ($file = readdir($dir))) {
424
			if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
425
				if (is_dir($source . '/' . $file)) {
426
					$child = $target->newFolder($file);
427
					self::copyr($source . '/' . $file, $child);
428
				} else {
429
					$child = $target->newFile($file);
430
					$sourceStream = fopen($source . '/' . $file, 'r');
431
					if($sourceStream === false) {
432
						$logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
433
						closedir($dir);
434
						return;
435
					}
436
					stream_copy_to_stream($sourceStream, $child->fopen('w'));
437
				}
438
			}
439
		}
440
		closedir($dir);
441
	}
442
443
	/**
444
	 * @return void
445
	 * @suppress PhanUndeclaredMethod
446
	 */
447
	public static function tearDownFS() {
448
		\OC\Files\Filesystem::tearDown();
449
		\OC::$server->getRootFolder()->clearCache();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\Files\IRootFolder as the method clearCache() does only exist in the following implementations of said interface: OC\Files\Node\Root.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
450
		self::$fsSetup = false;
451
		self::$rootMounted = false;
452
	}
453
454
	/**
455
	 * get the current installed version of ownCloud
456
	 *
457
	 * @return array
458
	 */
459
	public static function getVersion() {
460
		OC_Util::loadVersion();
461
		return self::$versionCache['OC_Version'];
462
	}
463
464
	/**
465
	 * get the current installed version string of ownCloud
466
	 *
467
	 * @return string
468
	 */
469
	public static function getVersionString() {
470
		OC_Util::loadVersion();
471
		return self::$versionCache['OC_VersionString'];
472
	}
473
474
	/**
475
	 * @deprecated the value is of no use anymore
476
	 * @return string
477
	 */
478
	public static function getEditionString() {
479
		return '';
480
	}
481
482
	/**
483
	 * @description get the update channel of the current installed of ownCloud.
484
	 * @return string
485
	 */
486
	public static function getChannel() {
487
		OC_Util::loadVersion();
488
		return \OC::$server->getConfig()->getSystemValue('updater.release.channel', self::$versionCache['OC_Channel']);
489
	}
490
491
	/**
492
	 * @description get the build number of the current installed of ownCloud.
493
	 * @return string
494
	 */
495
	public static function getBuild() {
496
		OC_Util::loadVersion();
497
		return self::$versionCache['OC_Build'];
498
	}
499
500
	/**
501
	 * @description load the version.php into the session as cache
502
	 * @suppress PhanUndeclaredVariable
503
	 */
504
	private static function loadVersion() {
505
		if (self::$versionCache !== null) {
506
			return;
507
		}
508
509
		$timestamp = filemtime(OC::$SERVERROOT . '/version.php');
510
		require OC::$SERVERROOT . '/version.php';
511
		/** @var $timestamp int */
512
		self::$versionCache['OC_Version_Timestamp'] = $timestamp;
513
		/** @var $OC_Version string */
514
		self::$versionCache['OC_Version'] = $OC_Version;
0 ignored issues
show
Bug introduced by
The variable $OC_Version does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
515
		/** @var $OC_VersionString string */
516
		self::$versionCache['OC_VersionString'] = $OC_VersionString;
0 ignored issues
show
Bug introduced by
The variable $OC_VersionString does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
517
		/** @var $OC_Build string */
518
		self::$versionCache['OC_Build'] = $OC_Build;
0 ignored issues
show
Bug introduced by
The variable $OC_Build does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
519
520
		/** @var $OC_Channel string */
521
		self::$versionCache['OC_Channel'] = $OC_Channel;
0 ignored issues
show
Bug introduced by
The variable $OC_Channel does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
522
	}
523
524
	/**
525
	 * generates a path for JS/CSS files. If no application is provided it will create the path for core.
526
	 *
527
	 * @param string $application application to get the files from
528
	 * @param string $directory directory within this application (css, js, vendor, etc)
529
	 * @param string $file the file inside of the above folder
530
	 * @return string the path
531
	 */
532
	private static function generatePath($application, $directory, $file) {
533
		if (is_null($file)) {
534
			$file = $application;
535
			$application = "";
536
		}
537
		if (!empty($application)) {
538
			return "$application/$directory/$file";
539
		} else {
540
			return "$directory/$file";
541
		}
542
	}
543
544
	/**
545
	 * add a javascript file
546
	 *
547
	 * @param string $application application id
548
	 * @param string|null $file filename
549
	 * @param bool $prepend prepend the Script to the beginning of the list
550
	 * @return void
551
	 */
552
	public static function addScript($application, $file = null, $prepend = false) {
553
		$path = OC_Util::generatePath($application, 'js', $file);
554
555
		// core js files need separate handling
556
		if ($application !== 'core' && $file !== null) {
557
			self::addTranslations ( $application );
558
		}
559
		self::addExternalResource($application, $prepend, $path, "script");
560
	}
561
562
	/**
563
	 * add a javascript file from the vendor sub folder
564
	 *
565
	 * @param string $application application id
566
	 * @param string|null $file filename
567
	 * @param bool $prepend prepend the Script to the beginning of the list
568
	 * @return void
569
	 */
570
	public static function addVendorScript($application, $file = null, $prepend = false) {
571
		$path = OC_Util::generatePath($application, 'vendor', $file);
572
		self::addExternalResource($application, $prepend, $path, "script");
573
	}
574
575
	/**
576
	 * add a translation JS file
577
	 *
578
	 * @param string $application application id
579
	 * @param string|null $languageCode language code, defaults to the current language
580
	 * @param bool|null $prepend prepend the Script to the beginning of the list
581
	 */
582
	public static function addTranslations($application, $languageCode = null, $prepend = false) {
583
		if (is_null($languageCode)) {
584
			$languageCode = \OC::$server->getL10NFactory()->findLanguage($application);
585
		}
586
		if (!empty($application)) {
587
			$path = "$application/l10n/$languageCode";
588
		} else {
589
			$path = "l10n/$languageCode";
590
		}
591
		self::addExternalResource($application, $prepend, $path, "script");
0 ignored issues
show
Bug introduced by
It seems like $prepend defined by parameter $prepend on line 582 can also be of type null; however, OC_Util::addExternalResource() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
592
	}
593
594
	/**
595
	 * add a css file
596
	 *
597
	 * @param string $application application id
598
	 * @param string|null $file filename
599
	 * @param bool $prepend prepend the Style to the beginning of the list
600
	 * @return void
601
	 */
602
	public static function addStyle($application, $file = null, $prepend = false) {
603
		$path = OC_Util::generatePath($application, 'css', $file);
604
		self::addExternalResource($application, $prepend, $path, "style");
605
	}
606
607
	/**
608
	 * add a css file from the vendor sub folder
609
	 *
610
	 * @param string $application application id
611
	 * @param string|null $file filename
612
	 * @param bool $prepend prepend the Style to the beginning of the list
613
	 * @return void
614
	 */
615
	public static function addVendorStyle($application, $file = null, $prepend = false) {
616
		$path = OC_Util::generatePath($application, 'vendor', $file);
617
		self::addExternalResource($application, $prepend, $path, "style");
618
	}
619
620
	/**
621
	 * add an external resource css/js file
622
	 *
623
	 * @param string $application application id
624
	 * @param bool $prepend prepend the file to the beginning of the list
625
	 * @param string $path
626
	 * @param string $type (script or style)
627
	 * @return void
628
	 */
629
	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.

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

Loading history...
630
631
		if ($type === "style") {
632 View Code Duplication
			if (!in_array($path, self::$styles)) {
633
				if ($prepend === true) {
634
					array_unshift ( self::$styles, $path );
635
				} else {
636
					self::$styles[] = $path;
637
				}
638
			}
639 View Code Duplication
		} elseif ($type === "script") {
640
			if (!in_array($path, self::$scripts)) {
641
				if ($prepend === true) {
642
					array_unshift ( self::$scripts, $path );
643
				} else {
644
					self::$scripts [] = $path;
645
				}
646
			}
647
		}
648
	}
649
650
	/**
651
	 * Add a custom element to the header
652
	 * If $text is null then the element will be written as empty element.
653
	 * So use "" to get a closing tag.
654
	 * @param string $tag tag name of the element
655
	 * @param array $attributes array of attributes for the element
656
	 * @param string $text the text content for the element
657
	 */
658 View Code Duplication
	public static function addHeader($tag, $attributes, $text=null) {
659
		self::$headers[] = array(
660
			'tag' => $tag,
661
			'attributes' => $attributes,
662
			'text' => $text
663
		);
664
	}
665
666
	/**
667
	 * formats a timestamp in the "right" way
668
	 *
669
	 * @param int $timestamp
670
	 * @param bool $dateOnly option to omit time from the result
671
	 * @param DateTimeZone|string $timeZone where the given timestamp shall be converted to
672
	 * @return string timestamp
673
	 *
674
	 * @deprecated Use \OC::$server->query('DateTimeFormatter') instead
675
	 */
676
	public static function formatDate($timestamp, $dateOnly = false, $timeZone = null) {
677
		if ($timeZone !== null && !$timeZone instanceof \DateTimeZone) {
678
			$timeZone = new \DateTimeZone($timeZone);
679
		}
680
681
		/** @var \OC\DateTimeFormatter $formatter */
682
		$formatter = \OC::$server->query('DateTimeFormatter');
683
		if ($dateOnly) {
684
			return $formatter->formatDate($timestamp, 'long', $timeZone);
685
		}
686
		return $formatter->formatDateTime($timestamp, 'long', 'long', $timeZone);
687
	}
688
689
	/**
690
	 * check if the current server configuration is suitable for ownCloud
691
	 *
692
	 * @param \OC\SystemConfig $config
693
	 * @return array arrays with error messages and hints
694
	 */
695
	public static function checkServer(\OC\SystemConfig $config) {
696
		$l = \OC::$server->getL10N('lib');
697
		$errors = array();
698
		$CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data');
699
700
		if (!self::needUpgrade($config) && $config->getValue('installed', false)) {
701
			// this check needs to be done every time
702
			$errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY);
703
		}
704
705
		// Assume that if checkServer() succeeded before in this session, then all is fine.
706
		if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) {
707
			return $errors;
708
		}
709
710
		$webServerRestart = false;
711
		$setup = new \OC\Setup(
712
			$config,
713
			\OC::$server->getIniWrapper(),
714
			\OC::$server->getL10N('lib'),
715
			\OC::$server->query(\OCP\Defaults::class),
716
			\OC::$server->getLogger(),
717
			\OC::$server->getSecureRandom(),
718
			\OC::$server->query(\OC\Installer::class)
719
		);
720
721
		$urlGenerator = \OC::$server->getURLGenerator();
722
723
		$availableDatabases = $setup->getSupportedDatabases();
724
		if (empty($availableDatabases)) {
725
			$errors[] = array(
726
				'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'),
727
				'hint' => '' //TODO: sane hint
728
			);
729
			$webServerRestart = true;
730
		}
731
732
		// Check if config folder is writable.
733
		if(!OC_Helper::isReadOnlyConfigEnabled()) {
734
			if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
735
				$errors[] = array(
736
					'error' => $l->t('Cannot write into "config" directory'),
737
					'hint' => $l->t('This can usually be fixed by giving the webserver write access to the config directory. See %s',
738
						[$urlGenerator->linkToDocs('admin-dir_permissions')])
739
				);
740
			}
741
		}
742
743
		// Check if there is a writable install folder.
744
		if ($config->getValue('appstoreenabled', true)) {
745
			if (OC_App::getInstallPath() === null
746
				|| !is_writable(OC_App::getInstallPath())
747
				|| !is_readable(OC_App::getInstallPath())
748
			) {
749
				$errors[] = array(
750
					'error' => $l->t('Cannot write into "apps" directory'),
751
					'hint' => $l->t('This can usually be fixed by giving the webserver write access to the apps directory'
752
						. ' or disabling the appstore in the config file. See %s',
753
						[$urlGenerator->linkToDocs('admin-dir_permissions')])
754
				);
755
			}
756
		}
757
		// Create root dir.
758
		if ($config->getValue('installed', false)) {
759
			if (!is_dir($CONFIG_DATADIRECTORY)) {
760
				$success = @mkdir($CONFIG_DATADIRECTORY);
761
				if ($success) {
762
					$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
763
				} else {
764
					$errors[] = [
765
						'error' => $l->t('Cannot create "data" directory'),
766
						'hint' => $l->t('This can usually be fixed by giving the webserver write access to the root directory. See %s',
767
							[$urlGenerator->linkToDocs('admin-dir_permissions')])
768
					];
769
				}
770
			} else if (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
771
				//common hint for all file permissions error messages
772
				$permissionsHint = $l->t('Permissions can usually be fixed by giving the webserver write access to the root directory. See %s.',
773
					[$urlGenerator->linkToDocs('admin-dir_permissions')]);
774
				$errors[] = [
775
					'error' => 'Your data directory is not writable',
776
					'hint' => $permissionsHint
777
				];
778
			} else {
779
				$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
780
			}
781
		}
782
783
		if (!OC_Util::isSetLocaleWorking()) {
784
			$errors[] = array(
785
				'error' => $l->t('Setting locale to %s failed',
786
					array('en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/'
787
						. 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8')),
788
				'hint' => $l->t('Please install one of these locales on your system and restart your webserver.')
789
			);
790
		}
791
792
		// Contains the dependencies that should be checked against
793
		// classes = class_exists
794
		// functions = function_exists
795
		// defined = defined
796
		// ini = ini_get
797
		// If the dependency is not found the missing module name is shown to the EndUser
798
		// When adding new checks always verify that they pass on Travis as well
799
		// for ini settings, see https://github.com/owncloud/administration/blob/master/travis-ci/custom.ini
800
		$dependencies = array(
801
			'classes' => array(
802
				'ZipArchive' => 'zip',
803
				'DOMDocument' => 'dom',
804
				'XMLWriter' => 'XMLWriter',
805
				'XMLReader' => 'XMLReader',
806
			),
807
			'functions' => [
808
				'xml_parser_create' => 'libxml',
809
				'mb_strcut' => 'mb multibyte',
810
				'ctype_digit' => 'ctype',
811
				'json_encode' => 'JSON',
812
				'gd_info' => 'GD',
813
				'gzencode' => 'zlib',
814
				'iconv' => 'iconv',
815
				'simplexml_load_string' => 'SimpleXML',
816
				'hash' => 'HASH Message Digest Framework',
817
				'curl_init' => 'cURL',
818
				'openssl_verify' => 'OpenSSL',
819
			],
820
			'defined' => array(
821
				'PDO::ATTR_DRIVER_NAME' => 'PDO'
822
			),
823
			'ini' => [
824
				'default_charset' => 'UTF-8',
825
			],
826
		);
827
		$missingDependencies = array();
828
		$invalidIniSettings = [];
829
		$moduleHint = $l->t('Please ask your server administrator to install the module.');
830
831
		/**
832
		 * FIXME: The dependency check does not work properly on HHVM on the moment
833
		 *        and prevents installation. Once HHVM is more compatible with our
834
		 *        approach to check for these values we should re-enable those
835
		 *        checks.
836
		 */
837
		$iniWrapper = \OC::$server->getIniWrapper();
838
		if (!self::runningOnHhvm()) {
839
			foreach ($dependencies['classes'] as $class => $module) {
840
				if (!class_exists($class)) {
841
					$missingDependencies[] = $module;
842
				}
843
			}
844
			foreach ($dependencies['functions'] as $function => $module) {
845
				if (!function_exists($function)) {
846
					$missingDependencies[] = $module;
847
				}
848
			}
849
			foreach ($dependencies['defined'] as $defined => $module) {
850
				if (!defined($defined)) {
851
					$missingDependencies[] = $module;
852
				}
853
			}
854
			foreach ($dependencies['ini'] as $setting => $expected) {
855
				if (is_bool($expected)) {
856
					if ($iniWrapper->getBool($setting) !== $expected) {
857
						$invalidIniSettings[] = [$setting, $expected];
858
					}
859
				}
860
				if (is_int($expected)) {
861
					if ($iniWrapper->getNumeric($setting) !== $expected) {
862
						$invalidIniSettings[] = [$setting, $expected];
863
					}
864
				}
865
				if (is_string($expected)) {
866
					if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) {
867
						$invalidIniSettings[] = [$setting, $expected];
868
					}
869
				}
870
			}
871
		}
872
873
		foreach($missingDependencies as $missingDependency) {
874
			$errors[] = array(
875
				'error' => $l->t('PHP module %s not installed.', array($missingDependency)),
876
				'hint' => $moduleHint
877
			);
878
			$webServerRestart = true;
879
		}
880
		foreach($invalidIniSettings as $setting) {
881
			if(is_bool($setting[1])) {
882
				$setting[1] = ($setting[1]) ? 'on' : 'off';
883
			}
884
			$errors[] = [
885
				'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]),
886
				'hint' =>  $l->t('Adjusting this setting in php.ini will make Nextcloud run again')
887
			];
888
			$webServerRestart = true;
889
		}
890
891
		/**
892
		 * The mbstring.func_overload check can only be performed if the mbstring
893
		 * module is installed as it will return null if the checking setting is
894
		 * not available and thus a check on the boolean value fails.
895
		 *
896
		 * TODO: Should probably be implemented in the above generic dependency
897
		 *       check somehow in the long-term.
898
		 */
899
		if($iniWrapper->getBool('mbstring.func_overload') !== null &&
900
			$iniWrapper->getBool('mbstring.func_overload') === true) {
901
			$errors[] = array(
902
				'error' => $l->t('mbstring.func_overload is set to "%s" instead of the expected value "0"', [$iniWrapper->getString('mbstring.func_overload')]),
903
				'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini')
904
			);
905
		}
906
907
		if(function_exists('xml_parser_create') &&
908
			LIBXML_LOADED_VERSION < 20700 ) {
909
			$version = LIBXML_LOADED_VERSION;
910
			$major = floor($version/10000);
911
			$version -= ($major * 10000);
912
			$minor = floor($version/100);
913
			$version -= ($minor * 100);
914
			$patch = $version;
915
			$errors[] = array(
916
				'error' => $l->t('libxml2 2.7.0 is at least required. Currently %s is installed.', [$major . '.' . $minor . '.' . $patch]),
917
				'hint' => $l->t('To fix this issue update your libxml2 version and restart your web server.')
918
			);
919
		}
920
921
		if (!self::isAnnotationsWorking()) {
922
			$errors[] = array(
923
				'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'),
924
				'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')
925
			);
926
		}
927
928
		if (!\OC::$CLI && $webServerRestart) {
929
			$errors[] = array(
930
				'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'),
931
				'hint' => $l->t('Please ask your server administrator to restart the web server.')
932
			);
933
		}
934
935
		$errors = array_merge($errors, self::checkDatabaseVersion());
936
937
		// Cache the result of this function
938
		\OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0);
939
940
		return $errors;
941
	}
942
943
	/**
944
	 * Check the database version
945
	 *
946
	 * @return array errors array
947
	 */
948
	public static function checkDatabaseVersion() {
949
		$l = \OC::$server->getL10N('lib');
950
		$errors = array();
951
		$dbType = \OC::$server->getSystemConfig()->getValue('dbtype', 'sqlite');
952
		if ($dbType === 'pgsql') {
953
			// check PostgreSQL version
954
			try {
955
				$result = \OC_DB::executeAudited('SHOW SERVER_VERSION');
956
				$data = $result->fetchRow();
957
				if (isset($data['server_version'])) {
958
					$version = $data['server_version'];
959
					if (version_compare($version, '9.0.0', '<')) {
960
						$errors[] = array(
961
							'error' => $l->t('PostgreSQL >= 9 required'),
962
							'hint' => $l->t('Please upgrade your database version')
963
						);
964
					}
965
				}
966
			} catch (\Doctrine\DBAL\DBALException $e) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\DBALException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
967
				$logger = \OC::$server->getLogger();
968
				$logger->warning('Error occurred while checking PostgreSQL version, assuming >= 9');
969
				$logger->logException($e);
970
			}
971
		}
972
		return $errors;
973
	}
974
975
	/**
976
	 * Check for correct file permissions of data directory
977
	 *
978
	 * @param string $dataDirectory
979
	 * @return array arrays with error messages and hints
980
	 */
981
	public static function checkDataDirectoryPermissions($dataDirectory) {
982
		if(\OC::$server->getConfig()->getSystemValue('check_data_directory_permissions', true) === false) {
983
			return  [];
984
		}
985
		$l = \OC::$server->getL10N('lib');
986
		$errors = [];
987
		$permissionsModHint = $l->t('Please change the permissions to 0770 so that the directory'
988
			. ' cannot be listed by other users.');
989
		$perms = substr(decoct(@fileperms($dataDirectory)), -3);
990
		if (substr($perms, -1) !== '0') {
991
			chmod($dataDirectory, 0770);
992
			clearstatcache();
993
			$perms = substr(decoct(@fileperms($dataDirectory)), -3);
994
			if ($perms[2] !== '0') {
995
				$errors[] = [
996
					'error' => $l->t('Your data directory is readable by other users'),
997
					'hint' => $permissionsModHint
998
				];
999
			}
1000
		}
1001
		return $errors;
1002
	}
1003
1004
	/**
1005
	 * Check that the data directory exists and is valid by
1006
	 * checking the existence of the ".ocdata" file.
1007
	 *
1008
	 * @param string $dataDirectory data directory path
1009
	 * @return array errors found
1010
	 */
1011
	public static function checkDataDirectoryValidity($dataDirectory) {
1012
		$l = \OC::$server->getL10N('lib');
1013
		$errors = [];
1014 View Code Duplication
		if ($dataDirectory[0] !== '/') {
1015
			$errors[] = [
1016
				'error' => $l->t('Your data directory must be an absolute path'),
1017
				'hint' => $l->t('Check the value of "datadirectory" in your configuration')
1018
			];
1019
		}
1020 View Code Duplication
		if (!file_exists($dataDirectory . '/.ocdata')) {
1021
			$errors[] = [
1022
				'error' => $l->t('Your data directory is invalid'),
1023
				'hint' => $l->t('Ensure there is a file called ".ocdata"' .
1024
					' in the root of the data directory.')
1025
			];
1026
		}
1027
		return $errors;
1028
	}
1029
1030
	/**
1031
	 * Check if the user is logged in, redirects to home if not. With
1032
	 * redirect URL parameter to the request URI.
1033
	 *
1034
	 * @return void
1035
	 */
1036
	public static function checkLoggedIn() {
1037
		// Check if we are a user
1038
		if (!\OC::$server->getUserSession()->isLoggedIn()) {
1039
			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
1040
						'core.login.showLoginForm',
1041
						[
1042
							'redirect_url' => \OC::$server->getRequest()->getRequestUri(),
1043
						]
1044
					)
1045
			);
1046
			exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method checkLoggedIn() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1047
		}
1048
		// Redirect to 2FA challenge selection if 2FA challenge was not solved yet
1049
		if (\OC::$server->getTwoFactorAuthManager()->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
1050
			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
1051
			exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method checkLoggedIn() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1052
		}
1053
	}
1054
1055
	/**
1056
	 * Check if the user is a admin, redirects to home if not
1057
	 *
1058
	 * @return void
1059
	 */
1060
	public static function checkAdminUser() {
1061
		OC_Util::checkLoggedIn();
1062
		if (!OC_User::isAdminUser(OC_User::getUser())) {
1063
			header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
1064
			exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method checkAdminUser() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1065
		}
1066
	}
1067
1068
	/**
1069
	 * Check if the user is a subadmin, redirects to home if not
1070
	 *
1071
	 * @return null|boolean $groups where the current user is subadmin
1072
	 */
1073
	public static function checkSubAdminUser() {
1074
		OC_Util::checkLoggedIn();
1075
		$userObject = \OC::$server->getUserSession()->getUser();
1076
		$isSubAdmin = false;
1077
		if($userObject !== null) {
1078
			$isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject);
1079
		}
1080
1081
		if (!$isSubAdmin) {
1082
			header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
1083
			exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method checkSubAdminUser() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1084
		}
1085
		return true;
1086
	}
1087
1088
	/**
1089
	 * Returns the URL of the default page
1090
	 * based on the system configuration and
1091
	 * the apps visible for the current user
1092
	 *
1093
	 * @return string URL
1094
	 * @suppress PhanDeprecatedFunction
1095
	 */
1096
	public static function getDefaultPageUrl() {
0 ignored issues
show
Coding Style introduced by
getDefaultPageUrl uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1097
		$urlGenerator = \OC::$server->getURLGenerator();
1098
		// Deny the redirect if the URL contains a @
1099
		// This prevents unvalidated redirects like ?redirect_url=:[email protected]
1100
		if (isset($_REQUEST['redirect_url']) && strpos($_REQUEST['redirect_url'], '@') === false) {
1101
			$location = $urlGenerator->getAbsoluteURL(urldecode($_REQUEST['redirect_url']));
1102
		} else {
1103
			$defaultPage = \OC::$server->getAppConfig()->getValue('core', 'defaultpage');
1104
			if ($defaultPage) {
1105
				$location = $urlGenerator->getAbsoluteURL($defaultPage);
1106
			} else {
1107
				$appId = 'files';
1108
				$config = \OC::$server->getConfig();
1109
				$defaultApps = explode(',', $config->getSystemValue('defaultapp', 'files'));
1110
				// find the first app that is enabled for the current user
1111
				foreach ($defaultApps as $defaultApp) {
1112
					$defaultApp = OC_App::cleanAppId(strip_tags($defaultApp));
1113
					if (static::getAppManager()->isEnabledForUser($defaultApp)) {
1114
						$appId = $defaultApp;
1115
						break;
1116
					}
1117
				}
1118
1119
				if($config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true') {
1120
					$location = $urlGenerator->getAbsoluteURL('/apps/' . $appId . '/');
1121
				} else {
1122
					$location = $urlGenerator->getAbsoluteURL('/index.php/apps/' . $appId . '/');
1123
				}
1124
			}
1125
		}
1126
		return $location;
1127
	}
1128
1129
	/**
1130
	 * Redirect to the user default page
1131
	 *
1132
	 * @return void
1133
	 */
1134
	public static function redirectToDefaultPage() {
1135
		$location = self::getDefaultPageUrl();
1136
		header('Location: ' . $location);
1137
		exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method redirectToDefaultPage() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1138
	}
1139
1140
	/**
1141
	 * get an id unique for this instance
1142
	 *
1143
	 * @return string
1144
	 */
1145
	public static function getInstanceId() {
1146
		$id = \OC::$server->getSystemConfig()->getValue('instanceid', null);
1147
		if (is_null($id)) {
1148
			// We need to guarantee at least one letter in instanceid so it can be used as the session_name
1149
			$id = 'oc' . \OC::$server->getSecureRandom()->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_DIGITS);
1150
			\OC::$server->getSystemConfig()->setValue('instanceid', $id);
1151
		}
1152
		return $id;
1153
	}
1154
1155
	/**
1156
	 * Public function to sanitize HTML
1157
	 *
1158
	 * This function is used to sanitize HTML and should be applied on any
1159
	 * string or array of strings before displaying it on a web page.
1160
	 *
1161
	 * @param string|array $value
1162
	 * @return string|array an array of sanitized strings or a single sanitized string, depends on the input parameter.
1163
	 */
1164
	public static function sanitizeHTML($value) {
1165
		if (is_array($value)) {
1166
			$value = array_map(function($value) {
1167
				return self::sanitizeHTML($value);
1168
			}, $value);
1169
		} else {
1170
			// Specify encoding for PHP<5.4
1171
			$value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
1172
		}
1173
		return $value;
1174
	}
1175
1176
	/**
1177
	 * Public function to encode url parameters
1178
	 *
1179
	 * This function is used to encode path to file before output.
1180
	 * Encoding is done according to RFC 3986 with one exception:
1181
	 * Character '/' is preserved as is.
1182
	 *
1183
	 * @param string $component part of URI to encode
1184
	 * @return string
1185
	 */
1186
	public static function encodePath($component) {
1187
		$encoded = rawurlencode($component);
1188
		$encoded = str_replace('%2F', '/', $encoded);
1189
		return $encoded;
1190
	}
1191
1192
1193
	public function createHtaccessTestFile(\OCP\IConfig $config) {
1194
		// php dev server does not support htaccess
1195
		if (php_sapi_name() === 'cli-server') {
1196
			return false;
1197
		}
1198
1199
		// testdata
1200
		$fileName = '/htaccesstest.txt';
1201
		$testContent = 'This is used for testing whether htaccess is properly enabled to disallow access from the outside. This file can be safely removed.';
1202
1203
		// creating a test file
1204
		$testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName;
1205
1206
		if (file_exists($testFile)) {// already running this test, possible recursive call
1207
			return false;
1208
		}
1209
1210
		$fp = @fopen($testFile, 'w');
1211
		if (!$fp) {
1212
			throw new OC\HintException('Can\'t create test file to check for working .htaccess file.',
1213
				'Make sure it is possible for the webserver to write to ' . $testFile);
1214
		}
1215
		fwrite($fp, $testContent);
1216
		fclose($fp);
1217
1218
		return $testContent;
1219
	}
1220
1221
	/**
1222
	 * Check if the .htaccess file is working
1223
	 * @param \OCP\IConfig $config
1224
	 * @return bool
1225
	 * @throws Exception
1226
	 * @throws \OC\HintException If the test file can't get written.
1227
	 */
1228
	public function isHtaccessWorking(\OCP\IConfig $config) {
1229
1230
		if (\OC::$CLI || !$config->getSystemValue('check_for_working_htaccess', true)) {
1231
			return true;
1232
		}
1233
1234
		$testContent = $this->createHtaccessTestFile($config);
1235
		if ($testContent === false) {
1236
			return false;
1237
		}
1238
1239
		$fileName = '/htaccesstest.txt';
1240
		$testFile = $config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $fileName;
1241
1242
		// accessing the file via http
1243
		$url = \OC::$server->getURLGenerator()->getAbsoluteURL(OC::$WEBROOT . '/data' . $fileName);
1244
		try {
1245
			$content = \OC::$server->getHTTPClientService()->newClient()->get($url)->getBody();
1246
		} catch (\Exception $e) {
1247
			$content = false;
1248
		}
1249
1250
		// cleanup
1251
		@unlink($testFile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
1252
1253
		/*
1254
		 * If the content is not equal to test content our .htaccess
1255
		 * is working as required
1256
		 */
1257
		return $content !== $testContent;
1258
	}
1259
1260
	/**
1261
	 * Check if the setlocal call does not work. This can happen if the right
1262
	 * local packages are not available on the server.
1263
	 *
1264
	 * @return bool
1265
	 */
1266
	public static function isSetLocaleWorking() {
1267
		\Patchwork\Utf8\Bootup::initLocale();
1268
		if ('' === basename('§')) {
1269
			return false;
1270
		}
1271
		return true;
1272
	}
1273
1274
	/**
1275
	 * Check if it's possible to get the inline annotations
1276
	 *
1277
	 * @return bool
1278
	 */
1279
	public static function isAnnotationsWorking() {
1280
		$reflection = new \ReflectionMethod(__METHOD__);
1281
		$docs = $reflection->getDocComment();
1282
1283
		return (is_string($docs) && strlen($docs) > 50);
1284
	}
1285
1286
	/**
1287
	 * Check if the PHP module fileinfo is loaded.
1288
	 *
1289
	 * @return bool
1290
	 */
1291
	public static function fileInfoLoaded() {
1292
		return function_exists('finfo_open');
1293
	}
1294
1295
	/**
1296
	 * clear all levels of output buffering
1297
	 *
1298
	 * @return void
1299
	 */
1300
	public static function obEnd() {
1301
		while (ob_get_level()) {
1302
			ob_end_clean();
1303
		}
1304
	}
1305
1306
	/**
1307
	 * Checks whether the server is running on Mac OS X
1308
	 *
1309
	 * @return bool true if running on Mac OS X, false otherwise
1310
	 */
1311
	public static function runningOnMac() {
1312
		return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
1313
	}
1314
1315
	/**
1316
	 * Checks whether server is running on HHVM
1317
	 *
1318
	 * @return bool True if running on HHVM, false otherwise
1319
	 */
1320
	public static function runningOnHhvm() {
1321
		return defined('HHVM_VERSION');
1322
	}
1323
1324
	/**
1325
	 * Handles the case that there may not be a theme, then check if a "default"
1326
	 * theme exists and take that one
1327
	 *
1328
	 * @return string the theme
1329
	 */
1330
	public static function getTheme() {
1331
		$theme = \OC::$server->getSystemConfig()->getValue("theme", '');
1332
1333
		if ($theme === '') {
1334
			if (is_dir(OC::$SERVERROOT . '/themes/default')) {
1335
				$theme = 'default';
1336
			}
1337
		}
1338
1339
		return $theme;
1340
	}
1341
1342
	/**
1343
	 * Clear a single file from the opcode cache
1344
	 * This is useful for writing to the config file
1345
	 * in case the opcode cache does not re-validate files
1346
	 * Returns true if successful, false if unsuccessful:
1347
	 * caller should fall back on clearing the entire cache
1348
	 * with clearOpcodeCache() if unsuccessful
1349
	 *
1350
	 * @param string $path the path of the file to clear from the cache
1351
	 * @return bool true if underlying function returns true, otherwise false
1352
	 */
1353
	public static function deleteFromOpcodeCache($path) {
1354
		$ret = false;
1355
		if ($path) {
1356
			// APC >= 3.1.1
1357
			if (function_exists('apc_delete_file')) {
1358
				$ret = @apc_delete_file($path);
1359
			}
1360
			// Zend OpCache >= 7.0.0, PHP >= 5.5.0
1361
			if (function_exists('opcache_invalidate')) {
1362
				$ret = opcache_invalidate($path);
1363
			}
1364
		}
1365
		return $ret;
1366
	}
1367
1368
	/**
1369
	 * Clear the opcode cache if one exists
1370
	 * This is necessary for writing to the config file
1371
	 * in case the opcode cache does not re-validate files
1372
	 *
1373
	 * @return void
1374
	 * @suppress PhanDeprecatedFunction
1375
	 * @suppress PhanUndeclaredConstant
1376
	 */
1377
	public static function clearOpcodeCache() {
1378
		// APC
1379
		if (function_exists('apc_clear_cache')) {
1380
			apc_clear_cache();
1381
		}
1382
		// Zend Opcache
1383
		if (function_exists('accelerator_reset')) {
1384
			accelerator_reset();
1385
		}
1386
		// XCache
1387
		if (function_exists('xcache_clear_cache')) {
1388
			if (\OC::$server->getIniWrapper()->getBool('xcache.admin.enable_auth')) {
1389
				\OCP\Util::writeLog('core', 'XCache opcode cache will not be cleared because "xcache.admin.enable_auth" is enabled.', \OCP\Util::WARN);
1390
			} else {
1391
				@xcache_clear_cache(XC_TYPE_PHP, 0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
1392
			}
1393
		}
1394
		// Opcache (PHP >= 5.5)
1395
		if (function_exists('opcache_reset')) {
1396
			opcache_reset();
1397
		}
1398
	}
1399
1400
	/**
1401
	 * Normalize a unicode string
1402
	 *
1403
	 * @param string $value a not normalized string
1404
	 * @return bool|string
1405
	 */
1406
	public static function normalizeUnicode($value) {
1407
		if(Normalizer::isNormalized($value)) {
1408
			return $value;
1409
		}
1410
1411
		$normalizedValue = Normalizer::normalize($value);
1412
		if ($normalizedValue === null || $normalizedValue === false) {
1413
			\OC::$server->getLogger()->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
1414
			return $value;
1415
		}
1416
1417
		return $normalizedValue;
1418
	}
1419
1420
	/**
1421
	 * A human readable string is generated based on version and build number
1422
	 *
1423
	 * @return string
1424
	 */
1425
	public static function getHumanVersion() {
1426
		$version = OC_Util::getVersionString();
1427
		$build = OC_Util::getBuild();
1428
		if (!empty($build) and OC_Util::getChannel() === 'daily') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1429
			$version .= ' Build:' . $build;
1430
		}
1431
		return $version;
1432
	}
1433
1434
	/**
1435
	 * Returns whether the given file name is valid
1436
	 *
1437
	 * @param string $file file name to check
1438
	 * @return bool true if the file name is valid, false otherwise
1439
	 * @deprecated use \OC\Files\View::verifyPath()
1440
	 */
1441
	public static function isValidFileName($file) {
1442
		$trimmed = trim($file);
1443
		if ($trimmed === '') {
1444
			return false;
1445
		}
1446
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1447
			return false;
1448
		}
1449
1450
		// detect part files
1451
		if (preg_match('/' . \OCP\Files\FileInfo::BLACKLIST_FILES_REGEX . '/', $trimmed) !== 0) {
1452
			return false;
1453
		}
1454
1455
		foreach (str_split($trimmed) as $char) {
1456
			if (strpos(\OCP\Constants::FILENAME_INVALID_CHARS, $char) !== false) {
1457
				return false;
1458
			}
1459
		}
1460
		return true;
1461
	}
1462
1463
	/**
1464
	 * Check whether the instance needs to perform an upgrade,
1465
	 * either when the core version is higher or any app requires
1466
	 * an upgrade.
1467
	 *
1468
	 * @param \OC\SystemConfig $config
1469
	 * @return bool whether the core or any app needs an upgrade
1470
	 * @throws \OC\HintException When the upgrade from the given version is not allowed
1471
	 */
1472
	public static function needUpgrade(\OC\SystemConfig $config) {
1473
		if ($config->getValue('installed', false)) {
1474
			$installedVersion = $config->getValue('version', '0.0.0');
1475
			$currentVersion = implode('.', \OCP\Util::getVersion());
1476
			$versionDiff = version_compare($currentVersion, $installedVersion);
1477
			if ($versionDiff > 0) {
1478
				return true;
1479
			} else if ($config->getValue('debug', false) && $versionDiff < 0) {
1480
				// downgrade with debug
1481
				$installedMajor = explode('.', $installedVersion);
1482
				$installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
1483
				$currentMajor = explode('.', $currentVersion);
1484
				$currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
1485
				if ($installedMajor === $currentMajor) {
1486
					// Same major, allow downgrade for developers
1487
					return true;
1488
				} else {
1489
					// downgrade attempt, throw exception
1490
					throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
1491
				}
1492
			} else if ($versionDiff < 0) {
1493
				// downgrade attempt, throw exception
1494
				throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
1495
			}
1496
1497
			// also check for upgrades for apps (independently from the user)
1498
			$apps = \OC_App::getEnabledApps(false, true);
1499
			$shouldUpgrade = false;
1500
			foreach ($apps as $app) {
1501
				if (\OC_App::shouldUpgrade($app)) {
1502
					$shouldUpgrade = true;
1503
					break;
1504
				}
1505
			}
1506
			return $shouldUpgrade;
1507
		} else {
1508
			return false;
1509
		}
1510
	}
1511
1512
}
1513