Passed
Push — master ( 0d75a3...fb5c5f )
by Blizzz
16:39 queued 13s
created

OC::performSameSiteCookieProtection()   B

Complexity

Conditions 9
Paths 14

Size

Total Lines 45
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 21
c 0
b 0
f 0
nc 14
nop 1
dl 0
loc 45
rs 8.0555
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Adam Williamson <[email protected]>
9
 * @author Andreas Fischer <[email protected]>
10
 * @author Arthur Schiwon <[email protected]>
11
 * @author Bart Visscher <[email protected]>
12
 * @author Bernhard Posselt <[email protected]>
13
 * @author Bjoern Schiessle <[email protected]>
14
 * @author Björn Schießle <[email protected]>
15
 * @author Christoph Wurst <[email protected]>
16
 * @author Côme Chilliet <[email protected]>
17
 * @author Damjan Georgievski <[email protected]>
18
 * @author Daniel Kesselberg <[email protected]>
19
 * @author davidgumberg <[email protected]>
20
 * @author Eric Masseran <[email protected]>
21
 * @author Florin Peter <[email protected]>
22
 * @author Greta Doci <[email protected]>
23
 * @author J0WI <[email protected]>
24
 * @author Jakob Sack <[email protected]>
25
 * @author jaltek <[email protected]>
26
 * @author Jan-Christoph Borchardt <[email protected]>
27
 * @author Joachim Sokolowski <[email protected]>
28
 * @author Joas Schilling <[email protected]>
29
 * @author John Molakvoæ <[email protected]>
30
 * @author Jörn Friedrich Dreyer <[email protected]>
31
 * @author Jose Quinteiro <[email protected]>
32
 * @author Juan Pablo Villafáñez <[email protected]>
33
 * @author Julius Härtl <[email protected]>
34
 * @author Ko- <[email protected]>
35
 * @author Lukas Reschke <[email protected]>
36
 * @author MartB <[email protected]>
37
 * @author Michael Gapczynski <[email protected]>
38
 * @author Morris Jobke <[email protected]>
39
 * @author Owen Winkler <[email protected]>
40
 * @author Phil Davis <[email protected]>
41
 * @author Ramiro Aparicio <[email protected]>
42
 * @author Robin Appelman <[email protected]>
43
 * @author Robin McCorkell <[email protected]>
44
 * @author Roeland Jago Douma <[email protected]>
45
 * @author Sebastian Wessalowski <[email protected]>
46
 * @author Stefan Weil <[email protected]>
47
 * @author Thomas Müller <[email protected]>
48
 * @author Thomas Tanghus <[email protected]>
49
 * @author Tobia De Koninck <[email protected]>
50
 * @author Vincent Petry <[email protected]>
51
 * @author Volkan Gezer <[email protected]>
52
 *
53
 * @license AGPL-3.0
54
 *
55
 * This code is free software: you can redistribute it and/or modify
56
 * it under the terms of the GNU Affero General Public License, version 3,
57
 * as published by the Free Software Foundation.
58
 *
59
 * This program is distributed in the hope that it will be useful,
60
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
61
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
62
 * GNU Affero General Public License for more details.
63
 *
64
 * You should have received a copy of the GNU Affero General Public License, version 3,
65
 * along with this program. If not, see <http://www.gnu.org/licenses/>
66
 *
67
 */
68
69
use OC\Encryption\HookManager;
70
use OC\EventDispatcher\SymfonyAdapter;
71
use OC\Files\Filesystem;
72
use OC\Share20\Hooks;
73
use OCP\EventDispatcher\IEventDispatcher;
74
use OCP\Group\Events\UserRemovedEvent;
75
use OCP\ILogger;
76
use OCP\IRequest;
77
use OCP\IURLGenerator;
78
use OCP\IUserSession;
79
use OCP\Server;
80
use OCP\Share;
81
use OCP\User\Events\UserChangedEvent;
82
use Psr\Log\LoggerInterface;
83
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
84
use function OCP\Log\logger;
85
86
require_once 'public/Constants.php';
87
88
/**
89
 * Class that is a namespace for all global OC variables
90
 * No, we can not put this class in its own file because it is used by
91
 * OC_autoload!
92
 */
93
class OC {
94
	/**
95
	 * Associative array for autoloading. classname => filename
96
	 */
97
	public static array $CLASSPATH = [];
98
	/**
99
	 * The installation path for Nextcloud  on the server (e.g. /srv/http/nextcloud)
100
	 */
101
	public static string $SERVERROOT = '';
102
	/**
103
	 * the current request path relative to the Nextcloud root (e.g. files/index.php)
104
	 */
105
	private static string $SUBURI = '';
106
	/**
107
	 * the Nextcloud root path for http requests (e.g. nextcloud/)
108
	 */
109
	public static string $WEBROOT = '';
110
	/**
111
	 * The installation path array of the apps folder on the server (e.g. /srv/http/nextcloud) 'path' and
112
	 * web path in 'url'
113
	 */
114
	public static array $APPSROOTS = [];
115
116
	public static string $configDir;
117
118
	/**
119
	 * requested app
120
	 */
121
	public static string $REQUESTEDAPP = '';
122
123
	/**
124
	 * check if Nextcloud runs in cli mode
125
	 */
126
	public static bool $CLI = false;
127
128
	public static \OC\Autoloader $loader;
129
130
	public static \Composer\Autoload\ClassLoader $composerAutoloader;
131
132
	public static \OC\Server $server;
133
134
	private static \OC\Config $config;
135
136
	/**
137
	 * @throws \RuntimeException when the 3rdparty directory is missing or
138
	 * the app path list is empty or contains an invalid path
139
	 */
140
	public static function initPaths(): void {
141
		if (defined('PHPUNIT_CONFIG_DIR')) {
142
			self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/';
0 ignored issues
show
Bug introduced by
The constant PHPUNIT_CONFIG_DIR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
143
		} elseif (defined('PHPUNIT_RUN') and PHPUNIT_RUN and is_dir(OC::$SERVERROOT . '/tests/config/')) {
0 ignored issues
show
Bug introduced by
The constant PHPUNIT_RUN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
144
			self::$configDir = OC::$SERVERROOT . '/tests/config/';
145
		} elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) {
146
			self::$configDir = rtrim($dir, '/') . '/';
147
		} else {
148
			self::$configDir = OC::$SERVERROOT . '/config/';
149
		}
150
		self::$config = new \OC\Config(self::$configDir);
151
152
		OC::$SUBURI = str_replace("\\", "/", substr(realpath($_SERVER["SCRIPT_FILENAME"] ?? ''), strlen(OC::$SERVERROOT)));
153
		/**
154
		 * FIXME: The following lines are required because we can't yet instantiate
155
		 *        Server::get(\OCP\IRequest::class) since \OC::$server does not yet exist.
156
		 */
157
		$params = [
158
			'server' => [
159
				'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'] ?? null,
160
				'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'] ?? null,
161
			],
162
		];
163
		$fakeRequest = new \OC\AppFramework\Http\Request(
164
			$params,
165
			new \OC\AppFramework\Http\RequestId($_SERVER['UNIQUE_ID'] ?? '', new \OC\Security\SecureRandom()),
166
			new \OC\AllConfig(new \OC\SystemConfig(self::$config))
167
		);
168
		$scriptName = $fakeRequest->getScriptName();
169
		if (substr($scriptName, -1) == '/') {
170
			$scriptName .= 'index.php';
171
			//make sure suburi follows the same rules as scriptName
172
			if (substr(OC::$SUBURI, -9) != 'index.php') {
173
				if (substr(OC::$SUBURI, -1) != '/') {
174
					OC::$SUBURI = OC::$SUBURI . '/';
175
				}
176
				OC::$SUBURI = OC::$SUBURI . 'index.php';
177
			}
178
		}
179
180
181
		if (OC::$CLI) {
182
			OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
183
		} else {
184
			if (substr($scriptName, 0 - strlen(OC::$SUBURI)) === OC::$SUBURI) {
185
				OC::$WEBROOT = substr($scriptName, 0, 0 - strlen(OC::$SUBURI));
186
187
				if (OC::$WEBROOT != '' && OC::$WEBROOT[0] !== '/') {
188
					OC::$WEBROOT = '/' . OC::$WEBROOT;
189
				}
190
			} else {
191
				// The scriptName is not ending with OC::$SUBURI
192
				// This most likely means that we are calling from CLI.
193
				// However some cron jobs still need to generate
194
				// a web URL, so we use overwritewebroot as a fallback.
195
				OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
196
			}
197
198
			// Resolve /nextcloud to /nextcloud/ to ensure to always have a trailing
199
			// slash which is required by URL generation.
200
			if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === \OC::$WEBROOT &&
201
					substr($_SERVER['REQUEST_URI'], -1) !== '/') {
202
				header('Location: '.\OC::$WEBROOT.'/');
203
				exit();
204
			}
205
		}
206
207
		// search the apps folder
208
		$config_paths = self::$config->getValue('apps_paths', []);
209
		if (!empty($config_paths)) {
210
			foreach ($config_paths as $paths) {
211
				if (isset($paths['url']) && isset($paths['path'])) {
212
					$paths['url'] = rtrim($paths['url'], '/');
213
					$paths['path'] = rtrim($paths['path'], '/');
214
					OC::$APPSROOTS[] = $paths;
215
				}
216
			}
217
		} elseif (file_exists(OC::$SERVERROOT . '/apps')) {
218
			OC::$APPSROOTS[] = ['path' => OC::$SERVERROOT . '/apps', 'url' => '/apps', 'writable' => true];
219
		}
220
221
		if (empty(OC::$APPSROOTS)) {
222
			throw new \RuntimeException('apps directory not found! Please put the Nextcloud apps folder in the Nextcloud folder'
223
				. '. You can also configure the location in the config.php file.');
224
		}
225
		$paths = [];
226
		foreach (OC::$APPSROOTS as $path) {
227
			$paths[] = $path['path'];
228
			if (!is_dir($path['path'])) {
229
				throw new \RuntimeException(sprintf('App directory "%s" not found! Please put the Nextcloud apps folder in the'
230
					. ' Nextcloud folder. You can also configure the location in the config.php file.', $path['path']));
231
			}
232
		}
233
234
		// set the right include path
235
		set_include_path(
236
			implode(PATH_SEPARATOR, $paths)
237
		);
238
	}
239
240
	public static function checkConfig(): void {
241
		$l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
242
243
		// Create config if it does not already exist
244
		$configFilePath = self::$configDir .'/config.php';
245
		if (!file_exists($configFilePath)) {
246
			@touch($configFilePath);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for touch(). 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

246
			/** @scrutinizer ignore-unhandled */ @touch($configFilePath);

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...
247
		}
248
249
		// Check if config is writable
250
		$configFileWritable = is_writable($configFilePath);
251
		if (!$configFileWritable && !OC_Helper::isReadOnlyConfigEnabled()
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! $configFileWritable &...OCP\Util::needUpgrade(), Probably Intended Meaning: ! $configFileWritable &&...CP\Util::needUpgrade())
Loading history...
252
			|| !$configFileWritable && \OCP\Util::needUpgrade()) {
253
			$urlGenerator = Server::get(IURLGenerator::class);
254
255
			if (self::$CLI) {
256
				echo $l->t('Cannot write into "config" directory!')."\n";
257
				echo $l->t('This can usually be fixed by giving the web server write access to the config directory.')."\n";
258
				echo "\n";
259
				echo $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.')."\n";
260
				echo $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ])."\n";
261
				exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
262
			} else {
263
				OC_Template::printErrorPage(
264
					$l->t('Cannot write into "config" directory!'),
265
					$l->t('This can usually be fixed by giving the web server write access to the config directory.') . ' '
266
					. $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . ' '
267
					. $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]),
268
					503
269
				);
270
			}
271
		}
272
	}
273
274
	public static function checkInstalled(\OC\SystemConfig $systemConfig): void {
275
		if (defined('OC_CONSOLE')) {
276
			return;
277
		}
278
		// Redirect to installer if not installed
279
		if (!$systemConfig->getValue('installed', false) && OC::$SUBURI !== '/index.php' && OC::$SUBURI !== '/status.php') {
280
			if (OC::$CLI) {
281
				throw new Exception('Not installed');
282
			} else {
283
				$url = OC::$WEBROOT . '/index.php';
284
				header('Location: ' . $url);
285
			}
286
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
287
		}
288
	}
289
290
	public static function checkMaintenanceMode(\OC\SystemConfig $systemConfig): void {
291
		// Allow ajax update script to execute without being stopped
292
		if (((bool) $systemConfig->getValue('maintenance', false)) && OC::$SUBURI != '/core/ajax/update.php') {
293
			// send http status 503
294
			http_response_code(503);
295
			header('X-Nextcloud-Maintenance-Mode: 1');
296
			header('Retry-After: 120');
297
298
			// render error page
299
			$template = new OC_Template('', 'update.user', 'guest');
300
			\OCP\Util::addScript('core', 'maintenance');
301
			\OCP\Util::addStyle('core', 'guest');
302
			$template->printPage();
303
			die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
304
		}
305
	}
306
307
	/**
308
	 * Prints the upgrade page
309
	 */
310
	private static function printUpgradePage(\OC\SystemConfig $systemConfig): void {
311
		$disableWebUpdater = $systemConfig->getValue('upgrade.disable-web', false);
312
		$tooBig = false;
313
		if (!$disableWebUpdater) {
314
			$apps = Server::get(\OCP\App\IAppManager::class);
315
			if ($apps->isInstalled('user_ldap')) {
316
				$qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
317
318
				$result = $qb->select($qb->func()->count('*', 'user_count'))
319
					->from('ldap_user_mapping')
320
					->executeQuery();
321
				$row = $result->fetch();
322
				$result->closeCursor();
323
324
				$tooBig = ($row['user_count'] > 50);
325
			}
326
			if (!$tooBig && $apps->isInstalled('user_saml')) {
327
				$qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
328
329
				$result = $qb->select($qb->func()->count('*', 'user_count'))
330
					->from('user_saml_users')
331
					->executeQuery();
332
				$row = $result->fetch();
333
				$result->closeCursor();
334
335
				$tooBig = ($row['user_count'] > 50);
336
			}
337
			if (!$tooBig) {
338
				// count users
339
				$stats = Server::get(\OCP\IUserManager::class)->countUsers();
340
				$totalUsers = array_sum($stats);
341
				$tooBig = ($totalUsers > 50);
342
			}
343
		}
344
		$ignoreTooBigWarning = isset($_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup']) &&
345
			$_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'] === 'IAmSuperSureToDoThis';
346
347
		if ($disableWebUpdater || ($tooBig && !$ignoreTooBigWarning)) {
348
			// send http status 503
349
			http_response_code(503);
350
			header('Retry-After: 120');
351
352
			// render error page
353
			$template = new OC_Template('', 'update.use-cli', 'guest');
354
			$template->assign('productName', 'nextcloud'); // for now
355
			$template->assign('version', OC_Util::getVersionString());
356
			$template->assign('tooBig', $tooBig);
357
358
			$template->printPage();
359
			die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
360
		}
361
362
		// check whether this is a core update or apps update
363
		$installedVersion = $systemConfig->getValue('version', '0.0.0');
364
		$currentVersion = implode('.', \OCP\Util::getVersion());
365
366
		// if not a core upgrade, then it's apps upgrade
367
		$isAppsOnlyUpgrade = version_compare($currentVersion, $installedVersion, '=');
368
369
		$oldTheme = $systemConfig->getValue('theme');
370
		$systemConfig->setValue('theme', '');
371
		\OCP\Util::addScript('core', 'common');
372
		\OCP\Util::addScript('core', 'main');
373
		\OCP\Util::addTranslations('core');
374
		\OCP\Util::addScript('core', 'update');
375
376
		/** @var \OC\App\AppManager $appManager */
377
		$appManager = Server::get(\OCP\App\IAppManager::class);
378
379
		$tmpl = new OC_Template('', 'update.admin', 'guest');
380
		$tmpl->assign('version', OC_Util::getVersionString());
381
		$tmpl->assign('isAppsOnlyUpgrade', $isAppsOnlyUpgrade);
382
383
		// get third party apps
384
		$ocVersion = \OCP\Util::getVersion();
385
		$ocVersion = implode('.', $ocVersion);
386
		$incompatibleApps = $appManager->getIncompatibleApps($ocVersion);
387
		$incompatibleShippedApps = [];
388
		foreach ($incompatibleApps as $appInfo) {
389
			if ($appManager->isShipped($appInfo['id'])) {
390
				$incompatibleShippedApps[] = $appInfo['name'] . ' (' . $appInfo['id'] . ')';
391
			}
392
		}
393
394
		if (!empty($incompatibleShippedApps)) {
395
			$l = Server::get(\OCP\L10N\IFactory::class)->get('core');
396
			$hint = $l->t('The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server.', [implode(', ', $incompatibleShippedApps)]);
397
			throw new \OCP\HintException('The files of the app ' . implode(', ', $incompatibleShippedApps) . ' were not replaced correctly. Make sure it is a version compatible with the server.', $hint);
398
		}
399
400
		$tmpl->assign('appsToUpgrade', $appManager->getAppsNeedingUpgrade($ocVersion));
401
		$tmpl->assign('incompatibleAppsList', $incompatibleApps);
402
		try {
403
			$defaults = new \OC_Defaults();
404
			$tmpl->assign('productName', $defaults->getName());
405
		} catch (Throwable $error) {
406
			$tmpl->assign('productName', 'Nextcloud');
407
		}
408
		$tmpl->assign('oldTheme', $oldTheme);
409
		$tmpl->printPage();
410
	}
411
412
	public static function initSession(): void {
413
		$request = Server::get(IRequest::class);
414
415
		// TODO: Temporary disabled again to solve issues with CalDAV/CardDAV clients like DAVx5 that use cookies
416
		// TODO: See https://github.com/nextcloud/server/issues/37277#issuecomment-1476366147 and the other comments
417
		// TODO: for further information.
418
		// $isDavRequest = strpos($request->getRequestUri(), '/remote.php/dav') === 0 || strpos($request->getRequestUri(), '/remote.php/webdav') === 0;
419
		// if ($request->getHeader('Authorization') !== '' && is_null($request->getCookie('cookie_test')) && $isDavRequest && !isset($_COOKIE['nc_session_id'])) {
420
		// setcookie('cookie_test', 'test', time() + 3600);
421
		// // Do not initialize the session if a request is authenticated directly
422
		// // unless there is a session cookie already sent along
423
		// return;
424
		// }
425
426
		if ($request->getServerProtocol() === 'https') {
427
			ini_set('session.cookie_secure', 'true');
428
		}
429
430
		// prevents javascript from accessing php session cookies
431
		ini_set('session.cookie_httponly', 'true');
432
433
		// set the cookie path to the Nextcloud directory
434
		$cookie_path = OC::$WEBROOT ? : '/';
435
		ini_set('session.cookie_path', $cookie_path);
436
437
		// Let the session name be changed in the initSession Hook
438
		$sessionName = OC_Util::getInstanceId();
439
440
		try {
441
			// set the session name to the instance id - which is unique
442
			$session = new \OC\Session\Internal($sessionName);
443
444
			$cryptoWrapper = Server::get(\OC\Session\CryptoWrapper::class);
445
			$session = $cryptoWrapper->wrapSession($session);
446
			self::$server->setSession($session);
447
448
			// if session can't be started break with http 500 error
449
		} catch (Exception $e) {
450
			Server::get(LoggerInterface::class)->error($e->getMessage(), ['app' => 'base','exception' => $e]);
451
			//show the user a detailed error page
452
			OC_Template::printExceptionErrorPage($e, 500);
453
			die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
454
		}
455
456
		//try to set the session lifetime
457
		$sessionLifeTime = self::getSessionLifeTime();
458
		@ini_set('gc_maxlifetime', (string)$sessionLifeTime);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ini_set(). 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

458
		/** @scrutinizer ignore-unhandled */ @ini_set('gc_maxlifetime', (string)$sessionLifeTime);

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...
459
460
		// session timeout
461
		if ($session->exists('LAST_ACTIVITY') && (time() - $session->get('LAST_ACTIVITY') > $sessionLifeTime)) {
462
			if (isset($_COOKIE[session_name()])) {
463
				setcookie(session_name(), '', -1, self::$WEBROOT ? : '/');
464
			}
465
			Server::get(IUserSession::class)->logout();
466
		}
467
468
		if (!self::hasSessionRelaxedExpiry()) {
469
			$session->set('LAST_ACTIVITY', time());
470
		}
471
		$session->close();
472
	}
473
474
	private static function getSessionLifeTime(): int {
475
		return Server::get(\OC\AllConfig::class)->getSystemValueInt('session_lifetime', 60 * 60 * 24);
476
	}
477
478
	/**
479
	 * @return bool true if the session expiry should only be done by gc instead of an explicit timeout
480
	 */
481
	public static function hasSessionRelaxedExpiry(): bool {
482
		return Server::get(\OC\AllConfig::class)->getSystemValueBool('session_relaxed_expiry', false);
483
	}
484
485
	/**
486
	 * Try to set some values to the required Nextcloud default
487
	 */
488
	public static function setRequiredIniValues(): void {
489
		@ini_set('default_charset', 'UTF-8');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ini_set(). 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

489
		/** @scrutinizer ignore-unhandled */ @ini_set('default_charset', 'UTF-8');

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...
490
		@ini_set('gd.jpeg_ignore_warning', '1');
491
	}
492
493
	/**
494
	 * Send the same site cookies
495
	 */
496
	private static function sendSameSiteCookies(): void {
497
		$cookieParams = session_get_cookie_params();
498
		$secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : '';
499
		$policies = [
500
			'lax',
501
			'strict',
502
		];
503
504
		// Append __Host to the cookie if it meets the requirements
505
		$cookiePrefix = '';
506
		if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
507
			$cookiePrefix = '__Host-';
508
		}
509
510
		foreach ($policies as $policy) {
511
			header(
512
				sprintf(
513
					'Set-Cookie: %snc_sameSiteCookie%s=true; path=%s; httponly;' . $secureCookie . 'expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=%s',
514
					$cookiePrefix,
515
					$policy,
516
					$cookieParams['path'],
517
					$policy
518
				),
519
				false
520
			);
521
		}
522
	}
523
524
	/**
525
	 * Same Site cookie to further mitigate CSRF attacks. This cookie has to
526
	 * be set in every request if cookies are sent to add a second level of
527
	 * defense against CSRF.
528
	 *
529
	 * If the cookie is not sent this will set the cookie and reload the page.
530
	 * We use an additional cookie since we want to protect logout CSRF and
531
	 * also we can't directly interfere with PHP's session mechanism.
532
	 */
533
	private static function performSameSiteCookieProtection(\OCP\IConfig $config): void {
534
		$request = Server::get(IRequest::class);
535
536
		// Some user agents are notorious and don't really properly follow HTTP
537
		// specifications. For those, have an automated opt-out. Since the protection
538
		// for remote.php is applied in base.php as starting point we need to opt out
539
		// here.
540
		$incompatibleUserAgents = $config->getSystemValue('csrf.optout');
541
542
		// Fallback, if csrf.optout is unset
543
		if (!is_array($incompatibleUserAgents)) {
544
			$incompatibleUserAgents = [
545
				// OS X Finder
546
				'/^WebDAVFS/',
547
				// Windows webdav drive
548
				'/^Microsoft-WebDAV-MiniRedir/',
549
			];
550
		}
551
552
		if ($request->isUserAgent($incompatibleUserAgents)) {
553
			return;
554
		}
555
556
		if (count($_COOKIE) > 0) {
557
			$requestUri = $request->getScriptName();
558
			$processingScript = explode('/', $requestUri);
559
			$processingScript = $processingScript[count($processingScript) - 1];
560
561
			// index.php routes are handled in the middleware
562
			if ($processingScript === 'index.php') {
563
				return;
564
			}
565
566
			// All other endpoints require the lax and the strict cookie
567
			if (!$request->passesStrictCookieCheck()) {
568
				self::sendSameSiteCookies();
569
				// Debug mode gets access to the resources without strict cookie
570
				// due to the fact that the SabreDAV browser also lives there.
571
				if (!$config->getSystemValue('debug', false)) {
572
					http_response_code(\OCP\AppFramework\Http::STATUS_SERVICE_UNAVAILABLE);
573
					exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
574
				}
575
			}
576
		} elseif (!isset($_COOKIE['nc_sameSiteCookielax']) || !isset($_COOKIE['nc_sameSiteCookiestrict'])) {
577
			self::sendSameSiteCookies();
578
		}
579
	}
580
581
	public static function init(): void {
582
		// calculate the root directories
583
		OC::$SERVERROOT = str_replace("\\", '/', substr(__DIR__, 0, -4));
584
585
		// register autoloader
586
		$loaderStart = microtime(true);
587
		require_once __DIR__ . '/autoloader.php';
588
		self::$loader = new \OC\Autoloader([
589
			OC::$SERVERROOT . '/lib/private/legacy',
590
		]);
591
		if (defined('PHPUNIT_RUN')) {
592
			self::$loader->addValidRoot(OC::$SERVERROOT . '/tests');
593
		}
594
		spl_autoload_register([self::$loader, 'load']);
595
		$loaderEnd = microtime(true);
596
597
		self::$CLI = (php_sapi_name() == 'cli');
598
599
		// Add default composer PSR-4 autoloader
600
		self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php';
601
		self::$composerAutoloader->setApcuPrefix('composer_autoload');
602
603
		try {
604
			self::initPaths();
605
			// setup 3rdparty autoloader
606
			$vendorAutoLoad = OC::$SERVERROOT. '/3rdparty/autoload.php';
607
			if (!file_exists($vendorAutoLoad)) {
608
				throw new \RuntimeException('Composer autoloader not found, unable to continue. Check the folder "3rdparty". Running "git submodule update --init" will initialize the git submodule that handles the subfolder "3rdparty".');
609
			}
610
			require_once $vendorAutoLoad;
611
		} catch (\RuntimeException $e) {
612
			if (!self::$CLI) {
613
				http_response_code(503);
614
			}
615
			// we can't use the template error page here, because this needs the
616
			// DI container which isn't available yet
617
			print($e->getMessage());
618
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
619
		}
620
621
		// setup the basic server
622
		self::$server = new \OC\Server(\OC::$WEBROOT, self::$config);
623
		self::$server->boot();
624
625
		$eventLogger = Server::get(\OCP\Diagnostics\IEventLogger::class);
626
		$eventLogger->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd);
627
		$eventLogger->start('boot', 'Initialize');
628
629
		// Override php.ini and log everything if we're troubleshooting
630
		if (self::$config->getValue('loglevel') === ILogger::DEBUG) {
631
			error_reporting(E_ALL);
632
		}
633
634
		// Don't display errors and log them
635
		@ini_set('display_errors', '0');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ini_set(). 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

635
		/** @scrutinizer ignore-unhandled */ @ini_set('display_errors', '0');

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...
636
		@ini_set('log_errors', '1');
637
638
		if (!date_default_timezone_set('UTC')) {
639
			throw new \RuntimeException('Could not set timezone to UTC');
640
		}
641
642
643
		//try to configure php to enable big file uploads.
644
		//this doesn´t work always depending on the webserver and php configuration.
645
		//Let´s try to overwrite some defaults if they are smaller than 1 hour
646
647
		if (intval(@ini_get('max_execution_time') ?? 0) < 3600) {
648
			@ini_set('max_execution_time', strval(3600));
649
		}
650
651
		if (intval(@ini_get('max_input_time') ?? 0) < 3600) {
652
			@ini_set('max_input_time', strval(3600));
653
		}
654
655
		//try to set the maximum execution time to the largest time limit we have
656
		if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
657
			@set_time_limit(max(intval(@ini_get('max_execution_time')), intval(@ini_get('max_input_time'))));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for set_time_limit(). 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

657
			/** @scrutinizer ignore-unhandled */ @set_time_limit(max(intval(@ini_get('max_execution_time')), intval(@ini_get('max_input_time'))));

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...
658
		}
659
660
		self::setRequiredIniValues();
661
		self::handleAuthHeaders();
662
		$systemConfig = Server::get(\OC\SystemConfig::class);
663
		self::registerAutoloaderCache($systemConfig);
664
665
		// initialize intl fallback if necessary
666
		OC_Util::isSetLocaleWorking();
667
668
		$config = Server::get(\OCP\IConfig::class);
669
		if (!defined('PHPUNIT_RUN')) {
670
			$errorHandler = new OC\Log\ErrorHandler(
671
				\OCP\Server::get(\Psr\Log\LoggerInterface::class),
672
			);
673
			$exceptionHandler = [$errorHandler, 'onException'];
674
			if ($config->getSystemValue('debug', false)) {
675
				set_error_handler([$errorHandler, 'onAll'], E_ALL);
676
				if (\OC::$CLI) {
677
					$exceptionHandler = ['OC_Template', 'printExceptionErrorPage'];
678
				}
679
			} else {
680
				set_error_handler([$errorHandler, 'onError']);
681
			}
682
			register_shutdown_function([$errorHandler, 'onShutdown']);
683
			set_exception_handler($exceptionHandler);
684
		}
685
686
		/** @var \OC\AppFramework\Bootstrap\Coordinator $bootstrapCoordinator */
687
		$bootstrapCoordinator = Server::get(\OC\AppFramework\Bootstrap\Coordinator::class);
688
		$bootstrapCoordinator->runInitialRegistration();
689
690
		$eventLogger->start('init_session', 'Initialize session');
691
		OC_App::loadApps(['session']);
692
		if (!self::$CLI) {
693
			self::initSession();
694
		}
695
		$eventLogger->end('init_session');
696
		self::checkConfig();
697
		self::checkInstalled($systemConfig);
698
699
		OC_Response::addSecurityHeaders();
700
701
		self::performSameSiteCookieProtection($config);
702
703
		if (!defined('OC_CONSOLE')) {
704
			$errors = OC_Util::checkServer($systemConfig);
705
			if (count($errors) > 0) {
706
				if (!self::$CLI) {
707
					http_response_code(503);
708
					OC_Util::addStyle('guest');
709
					try {
710
						OC_Template::printGuestPage('', 'error', ['errors' => $errors]);
711
						exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
712
					} catch (\Exception $e) {
713
						// In case any error happens when showing the error page, we simply fall back to posting the text.
714
						// This might be the case when e.g. the data directory is broken and we can not load/write SCSS to/from it.
715
					}
716
				}
717
718
				// Convert l10n string into regular string for usage in database
719
				$staticErrors = [];
720
				foreach ($errors as $error) {
721
					echo $error['error'] . "\n";
722
					echo $error['hint'] . "\n\n";
723
					$staticErrors[] = [
724
						'error' => (string)$error['error'],
725
						'hint' => (string)$error['hint'],
726
					];
727
				}
728
729
				try {
730
					$config->setAppValue('core', 'cronErrors', json_encode($staticErrors));
731
				} catch (\Exception $e) {
732
					echo('Writing to database failed');
733
				}
734
				exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
735
			} elseif (self::$CLI && $config->getSystemValue('installed', false)) {
736
				$config->deleteAppValue('core', 'cronErrors');
737
			}
738
		}
739
740
		// User and Groups
741
		if (!$systemConfig->getValue("installed", false)) {
742
			self::$server->getSession()->set('user_id', '');
743
		}
744
745
		OC_User::useBackend(new \OC\User\Database());
746
		Server::get(\OCP\IGroupManager::class)->addBackend(new \OC\Group\Database());
747
748
		// Subscribe to the hook
749
		\OCP\Util::connectHook(
750
			'\OCA\Files_Sharing\API\Server2Server',
751
			'preLoginNameUsedAsUserName',
752
			'\OC\User\Database',
753
			'preLoginNameUsedAsUserName'
754
		);
755
756
		//setup extra user backends
757
		if (!\OCP\Util::needUpgrade()) {
758
			OC_User::setupBackends();
759
		} else {
760
			// Run upgrades in incognito mode
761
			OC_User::setIncognitoMode(true);
762
		}
763
764
		self::registerCleanupHooks($systemConfig);
765
		self::registerShareHooks($systemConfig);
766
		self::registerEncryptionWrapperAndHooks();
767
		self::registerAccountHooks();
768
		self::registerResourceCollectionHooks();
769
		self::registerFileReferenceEventListener();
770
		self::registerRenderReferenceEventListener();
771
		self::registerAppRestrictionsHooks();
772
773
		// Make sure that the application class is not loaded before the database is setup
774
		if ($systemConfig->getValue("installed", false)) {
775
			OC_App::loadApp('settings');
776
			/* Build core application to make sure that listeners are registered */
777
			Server::get(\OC\Core\Application::class);
778
		}
779
780
		//make sure temporary files are cleaned up
781
		$tmpManager = Server::get(\OCP\ITempManager::class);
782
		register_shutdown_function([$tmpManager, 'clean']);
783
		$lockProvider = Server::get(\OCP\Lock\ILockingProvider::class);
784
		register_shutdown_function([$lockProvider, 'releaseAll']);
785
786
		// Check whether the sample configuration has been copied
787
		if ($systemConfig->getValue('copied_sample_config', false)) {
788
			$l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
789
			OC_Template::printErrorPage(
790
				$l->t('Sample configuration detected'),
791
				$l->t('It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php'),
792
				503
793
			);
794
			return;
795
		}
796
797
		$request = Server::get(IRequest::class);
798
		$host = $request->getInsecureServerHost();
799
		/**
800
		 * if the host passed in headers isn't trusted
801
		 * FIXME: Should not be in here at all :see_no_evil:
802
		 */
803
		if (!OC::$CLI
804
			&& !Server::get(\OC\Security\TrustedDomainHelper::class)->isTrustedDomain($host)
805
			&& $config->getSystemValue('installed', false)
806
		) {
807
			// Allow access to CSS resources
808
			$isScssRequest = false;
809
			if (strpos($request->getPathInfo() ?: '', '/css/') === 0) {
810
				$isScssRequest = true;
811
			}
812
813
			if (substr($request->getRequestUri(), -11) === '/status.php') {
814
				http_response_code(400);
815
				header('Content-Type: application/json');
816
				echo '{"error": "Trusted domain error.", "code": 15}';
817
				exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
818
			}
819
820
			if (!$isScssRequest) {
821
				http_response_code(400);
822
				Server::get(LoggerInterface::class)->info(
823
					'Trusted domain error. "{remoteAddress}" tried to access using "{host}" as host.',
824
					[
825
						'app' => 'core',
826
						'remoteAddress' => $request->getRemoteAddress(),
827
						'host' => $host,
828
					]
829
				);
830
831
				$tmpl = new OCP\Template('core', 'untrustedDomain', 'guest');
832
				$tmpl->assign('docUrl', Server::get(IURLGenerator::class)->linkToDocs('admin-trusted-domains'));
833
				$tmpl->printPage();
834
835
				exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
836
			}
837
		}
838
		$eventLogger->end('boot');
839
		$eventLogger->log('init', 'OC::init', $loaderStart, microtime(true));
840
		$eventLogger->start('runtime', 'Runtime');
841
		$eventLogger->start('request', 'Full request after boot');
842
		register_shutdown_function(function () use ($eventLogger) {
843
			$eventLogger->end('request');
844
		});
845
	}
846
847
	/**
848
	 * register hooks for the cleanup of cache and bruteforce protection
849
	 */
850
	public static function registerCleanupHooks(\OC\SystemConfig $systemConfig): void {
851
		//don't try to do this before we are properly setup
852
		if ($systemConfig->getValue('installed', false) && !\OCP\Util::needUpgrade()) {
853
			// NOTE: This will be replaced to use OCP
854
			$userSession = Server::get(\OC\User\Session::class);
855
			$userSession->listen('\OC\User', 'postLogin', function () use ($userSession) {
856
				if (!defined('PHPUNIT_RUN') && $userSession->isLoggedIn()) {
857
					// reset brute force delay for this IP address and username
858
					$uid = $userSession->getUser()->getUID();
859
					$request = Server::get(IRequest::class);
860
					$throttler = Server::get(\OC\Security\Bruteforce\Throttler::class);
861
					$throttler->resetDelay($request->getRemoteAddress(), 'login', ['user' => $uid]);
862
				}
863
864
				try {
865
					$cache = new \OC\Cache\File();
866
					$cache->gc();
867
				} catch (\OC\ServerNotAvailableException $e) {
868
					// not a GC exception, pass it on
869
					throw $e;
870
				} catch (\OC\ForbiddenException $e) {
871
					// filesystem blocked for this request, ignore
872
				} catch (\Exception $e) {
873
					// a GC exception should not prevent users from using OC,
874
					// so log the exception
875
					Server::get(LoggerInterface::class)->warning('Exception when running cache gc.', [
876
						'app' => 'core',
877
						'exception' => $e,
878
					]);
879
				}
880
			});
881
		}
882
	}
883
884
	private static function registerEncryptionWrapperAndHooks(): void {
885
		$manager = Server::get(\OCP\Encryption\IManager::class);
886
		\OCP\Util::connectHook('OC_Filesystem', 'preSetup', $manager, 'setupStorage');
887
888
		$enabled = $manager->isEnabled();
889
		if ($enabled) {
890
			\OCP\Util::connectHook(Share::class, 'post_shared', HookManager::class, 'postShared');
891
			\OCP\Util::connectHook(Share::class, 'post_unshare', HookManager::class, 'postUnshared');
892
			\OCP\Util::connectHook('OC_Filesystem', 'post_rename', HookManager::class, 'postRename');
893
			\OCP\Util::connectHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', HookManager::class, 'postRestore');
894
		}
895
	}
896
897
	private static function registerAccountHooks(): void {
898
		/** @var IEventDispatcher $dispatcher */
899
		$dispatcher = Server::get(IEventDispatcher::class);
900
		$dispatcher->addServiceListener(UserChangedEvent::class, \OC\Accounts\Hooks::class);
901
	}
902
903
	private static function registerAppRestrictionsHooks(): void {
904
		/** @var \OC\Group\Manager $groupManager */
905
		$groupManager = Server::get(\OCP\IGroupManager::class);
906
		$groupManager->listen('\OC\Group', 'postDelete', function (\OCP\IGroup $group) {
907
			$appManager = Server::get(\OCP\App\IAppManager::class);
908
			$apps = $appManager->getEnabledAppsForGroup($group);
909
			foreach ($apps as $appId) {
910
				$restrictions = $appManager->getAppRestriction($appId);
911
				if (empty($restrictions)) {
912
					continue;
913
				}
914
				$key = array_search($group->getGID(), $restrictions);
915
				unset($restrictions[$key]);
916
				$restrictions = array_values($restrictions);
917
				if (empty($restrictions)) {
918
					$appManager->disableApp($appId);
919
				} else {
920
					$appManager->enableAppForGroups($appId, $restrictions);
921
				}
922
			}
923
		});
924
	}
925
926
	private static function registerResourceCollectionHooks(): void {
927
		\OC\Collaboration\Resources\Listener::register(Server::get(SymfonyAdapter::class), Server::get(IEventDispatcher::class));
928
	}
929
930
	private static function registerFileReferenceEventListener(): void {
931
		\OC\Collaboration\Reference\File\FileReferenceEventListener::register(Server::get(IEventDispatcher::class));
932
	}
933
934
	private static function registerRenderReferenceEventListener() {
935
		\OC\Collaboration\Reference\RenderReferenceEventListener::register(Server::get(IEventDispatcher::class));
936
	}
937
938
	/**
939
	 * register hooks for sharing
940
	 */
941
	public static function registerShareHooks(\OC\SystemConfig $systemConfig): void {
942
		if ($systemConfig->getValue('installed')) {
943
			OC_Hook::connect('OC_User', 'post_deleteUser', Hooks::class, 'post_deleteUser');
944
			OC_Hook::connect('OC_User', 'post_deleteGroup', Hooks::class, 'post_deleteGroup');
945
946
			/** @var IEventDispatcher $dispatcher */
947
			$dispatcher = Server::get(IEventDispatcher::class);
948
			$dispatcher->addServiceListener(UserRemovedEvent::class, \OC\Share20\UserRemovedListener::class);
949
		}
950
	}
951
952
	protected static function registerAutoloaderCache(\OC\SystemConfig $systemConfig): void {
953
		// The class loader takes an optional low-latency cache, which MUST be
954
		// namespaced. The instanceid is used for namespacing, but might be
955
		// unavailable at this point. Furthermore, it might not be possible to
956
		// generate an instanceid via \OC_Util::getInstanceId() because the
957
		// config file may not be writable. As such, we only register a class
958
		// loader cache if instanceid is available without trying to create one.
959
		$instanceId = $systemConfig->getValue('instanceid', null);
960
		if ($instanceId) {
961
			try {
962
				$memcacheFactory = Server::get(\OCP\ICacheFactory::class);
963
				self::$loader->setMemoryCache($memcacheFactory->createLocal('Autoloader'));
964
			} catch (\Exception $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
965
			}
966
		}
967
	}
968
969
	/**
970
	 * Handle the request
971
	 */
972
	public static function handleRequest(): void {
973
		Server::get(\OCP\Diagnostics\IEventLogger::class)->start('handle_request', 'Handle request');
974
		$systemConfig = Server::get(\OC\SystemConfig::class);
975
976
		// Check if Nextcloud is installed or in maintenance (update) mode
977
		if (!$systemConfig->getValue('installed', false)) {
978
			\OC::$server->getSession()->clear();
979
			$setupHelper = new OC\Setup(
980
				$systemConfig,
981
				Server::get(\bantu\IniGetWrapper\IniGetWrapper::class),
982
				Server::get(\OCP\L10N\IFactory::class)->get('lib'),
983
				Server::get(\OCP\Defaults::class),
984
				Server::get(\Psr\Log\LoggerInterface::class),
985
				Server::get(\OCP\Security\ISecureRandom::class),
986
				Server::get(\OC\Installer::class)
987
			);
988
			$controller = new OC\Core\Controller\SetupController($setupHelper);
989
			$controller->run($_POST);
990
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
991
		}
992
993
		$request = Server::get(IRequest::class);
994
		$requestPath = $request->getRawPathInfo();
995
		if ($requestPath === '/heartbeat') {
996
			return;
997
		}
998
		if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade
999
			self::checkMaintenanceMode($systemConfig);
1000
1001
			if (\OCP\Util::needUpgrade()) {
1002
				if (function_exists('opcache_reset')) {
1003
					opcache_reset();
1004
				}
1005
				if (!((bool) $systemConfig->getValue('maintenance', false))) {
1006
					self::printUpgradePage($systemConfig);
1007
					exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1008
				}
1009
			}
1010
		}
1011
1012
		// emergency app disabling
1013
		if ($requestPath === '/disableapp'
1014
			&& $request->getMethod() === 'POST'
1015
		) {
1016
			\OC_JSON::callCheck();
1017
			\OC_JSON::checkAdminUser();
1018
			$appIds = (array)$request->getParam('appid');
1019
			foreach ($appIds as $appId) {
1020
				$appId = \OC_App::cleanAppId($appId);
1021
				Server::get(\OCP\App\IAppManager::class)->disableApp($appId);
1022
			}
1023
			\OC_JSON::success();
1024
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1025
		}
1026
1027
		// Always load authentication apps
1028
		OC_App::loadApps(['authentication']);
1029
1030
		// Load minimum set of apps
1031
		if (!\OCP\Util::needUpgrade()
1032
			&& !((bool) $systemConfig->getValue('maintenance', false))) {
1033
			// For logged-in users: Load everything
1034
			if (Server::get(IUserSession::class)->isLoggedIn()) {
1035
				OC_App::loadApps();
1036
			} else {
1037
				// For guests: Load only filesystem and logging
1038
				OC_App::loadApps(['filesystem', 'logging']);
1039
1040
				// Don't try to login when a client is trying to get a OAuth token.
1041
				// OAuth needs to support basic auth too, so the login is not valid
1042
				// inside Nextcloud and the Login exception would ruin it.
1043
				if ($request->getRawPathInfo() !== '/apps/oauth2/api/v1/token') {
1044
					self::handleLogin($request);
1045
				}
1046
			}
1047
		}
1048
1049
		if (!self::$CLI) {
1050
			try {
1051
				if (!((bool) $systemConfig->getValue('maintenance', false)) && !\OCP\Util::needUpgrade()) {
1052
					OC_App::loadApps(['filesystem', 'logging']);
1053
					OC_App::loadApps();
1054
				}
1055
				Server::get(\OC\Route\Router::class)->match($request->getRawPathInfo());
1056
				return;
1057
			} catch (Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
1058
				//header('HTTP/1.0 404 Not Found');
1059
			} catch (Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
1060
				http_response_code(405);
1061
				return;
1062
			}
1063
		}
1064
1065
		// Handle WebDAV
1066
		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
1067
			// not allowed any more to prevent people
1068
			// mounting this root directly.
1069
			// Users need to mount remote.php/webdav instead.
1070
			http_response_code(405);
1071
			return;
1072
		}
1073
1074
		// Handle requests for JSON or XML
1075
		$acceptHeader = $request->getHeader('Accept');
1076
		if (in_array($acceptHeader, ['application/json', 'application/xml'], true)) {
1077
			http_response_code(404);
1078
			return;
1079
		}
1080
1081
		// Handle resources that can't be found
1082
		// This prevents browsers from redirecting to the default page and then
1083
		// attempting to parse HTML as CSS and similar.
1084
		$destinationHeader = $request->getHeader('Sec-Fetch-Dest');
1085
		if (in_array($destinationHeader, ['font', 'script', 'style'])) {
1086
			http_response_code(404);
1087
			return;
1088
		}
1089
1090
		// Redirect to the default app or login only as an entry point
1091
		if ($requestPath === '') {
1092
			// Someone is logged in
1093
			if (Server::get(IUserSession::class)->isLoggedIn()) {
1094
				header('Location: ' . Server::get(IURLGenerator::class)->linkToDefaultPageUrl());
1095
			} else {
1096
				// Not handled and not logged in
1097
				header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute('core.login.showLoginForm'));
1098
			}
1099
			return;
1100
		}
1101
1102
		try {
1103
			Server::get(\OC\Route\Router::class)->match('/error/404');
1104
		} catch (\Exception $e) {
1105
			if (!$e instanceof MethodNotAllowedException) {
1106
				logger('core')->emergency($e->getMessage(), ['exception' => $e]);
1107
			}
1108
			$l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
1109
			OC_Template::printErrorPage(
1110
				$l->t('404'),
1111
				$l->t('The page could not be found on the server.'),
1112
				404
1113
			);
1114
		}
1115
	}
1116
1117
	/**
1118
	 * Check login: apache auth, auth token, basic auth
1119
	 */
1120
	public static function handleLogin(OCP\IRequest $request): bool {
1121
		$userSession = Server::get(\OC\User\Session::class);
1122
		if (OC_User::handleApacheAuth()) {
1123
			return true;
1124
		}
1125
		if ($userSession->tryTokenLogin($request)) {
1126
			return true;
1127
		}
1128
		if (isset($_COOKIE['nc_username'])
1129
			&& isset($_COOKIE['nc_token'])
1130
			&& isset($_COOKIE['nc_session_id'])
1131
			&& $userSession->loginWithCookie($_COOKIE['nc_username'], $_COOKIE['nc_token'], $_COOKIE['nc_session_id'])) {
1132
			return true;
1133
		}
1134
		if ($userSession->tryBasicAuthLogin($request, Server::get(\OC\Security\Bruteforce\Throttler::class))) {
1135
			return true;
1136
		}
1137
		return false;
1138
	}
1139
1140
	protected static function handleAuthHeaders(): void {
1141
		//copy http auth headers for apache+php-fcgid work around
1142
		if (isset($_SERVER['HTTP_XAUTHORIZATION']) && !isset($_SERVER['HTTP_AUTHORIZATION'])) {
1143
			$_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['HTTP_XAUTHORIZATION'];
1144
		}
1145
1146
		// Extract PHP_AUTH_USER/PHP_AUTH_PW from other headers if necessary.
1147
		$vars = [
1148
			'HTTP_AUTHORIZATION', // apache+php-cgi work around
1149
			'REDIRECT_HTTP_AUTHORIZATION', // apache+php-cgi alternative
1150
		];
1151
		foreach ($vars as $var) {
1152
			if (isset($_SERVER[$var]) && is_string($_SERVER[$var]) && preg_match('/Basic\s+(.*)$/i', $_SERVER[$var], $matches)) {
1153
				$credentials = explode(':', base64_decode($matches[1]), 2);
1154
				if (count($credentials) === 2) {
1155
					$_SERVER['PHP_AUTH_USER'] = $credentials[0];
1156
					$_SERVER['PHP_AUTH_PW'] = $credentials[1];
1157
					break;
1158
				}
1159
			}
1160
		}
1161
	}
1162
}
1163
1164
OC::init();
1165