Passed
Push — master ( 79fc7e...55473d )
by Joas
12:07 queued 12s
created

Router::findMatchingRoute()   B

Complexity

Conditions 10
Paths 24

Size

Total Lines 47
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 31
nc 24
nop 1
dl 0
loc 47
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Bernhard Posselt <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Jörn Friedrich Dreyer <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Robin McCorkell <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author Thomas Müller <[email protected]>
16
 * @author Vincent Petry <[email protected]>
17
 *
18
 * @license AGPL-3.0
19
 *
20
 * This code is free software: you can redistribute it and/or modify
21
 * it under the terms of the GNU Affero General Public License, version 3,
22
 * as published by the Free Software Foundation.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
 * GNU Affero General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU Affero General Public License, version 3,
30
 * along with this program. If not, see <http://www.gnu.org/licenses/>
31
 *
32
 */
33
34
namespace OC\Route;
35
36
use OCP\AppFramework\App;
37
use OCP\ILogger;
38
use OCP\Route\IRouter;
39
use OCP\Util;
40
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
41
use Symfony\Component\Routing\Exception\RouteNotFoundException;
42
use Symfony\Component\Routing\Generator\UrlGenerator;
43
use Symfony\Component\Routing\Matcher\UrlMatcher;
44
use Symfony\Component\Routing\RequestContext;
45
use Symfony\Component\Routing\RouteCollection;
46
47
class Router implements IRouter {
0 ignored issues
show
Deprecated Code introduced by
The interface OCP\Route\IRouter has been deprecated: 9.0.0 ( Ignorable by Annotation )

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

47
class Router implements /** @scrutinizer ignore-deprecated */ IRouter {

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

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

Loading history...
48
	/** @var RouteCollection[] */
49
	protected $collections = [];
50
	/** @var null|RouteCollection */
51
	protected $collection = null;
52
	/** @var null|string */
53
	protected $collectionName = null;
54
	/** @var null|RouteCollection */
55
	protected $root = null;
56
	/** @var null|UrlGenerator */
57
	protected $generator = null;
58
	/** @var string[] */
59
	protected $routingFiles;
60
	/** @var bool */
61
	protected $loaded = false;
62
	/** @var array */
63
	protected $loadedApps = [];
64
	/** @var ILogger */
65
	protected $logger;
66
	/** @var RequestContext */
67
	protected $context;
68
69
	/**
70
	 * @param ILogger $logger
71
	 */
72
	public function __construct(ILogger $logger) {
73
		$this->logger = $logger;
74
		$baseUrl = \OC::$WEBROOT;
75
		if (!(\OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) {
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getConfig() has been deprecated. ( Ignorable by Annotation )

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

75
		if (!(/** @scrutinizer ignore-deprecated */ \OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) {
Loading history...
76
			$baseUrl = \OC::$server->getURLGenerator()->linkTo('', 'index.php');
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getURLGenerator() has been deprecated. ( Ignorable by Annotation )

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

76
			$baseUrl = /** @scrutinizer ignore-deprecated */ \OC::$server->getURLGenerator()->linkTo('', 'index.php');
Loading history...
77
		}
78
		if (!\OC::$CLI && isset($_SERVER['REQUEST_METHOD'])) {
79
			$method = $_SERVER['REQUEST_METHOD'];
80
		} else {
81
			$method = 'GET';
82
		}
83
		$request = \OC::$server->getRequest();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getRequest() has been deprecated. ( Ignorable by Annotation )

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

83
		$request = /** @scrutinizer ignore-deprecated */ \OC::$server->getRequest();
Loading history...
84
		$host = $request->getServerHost();
85
		$schema = $request->getServerProtocol();
86
		$this->context = new RequestContext($baseUrl, $method, $host, $schema);
87
		// TODO cache
88
		$this->root = $this->getCollection('root');
89
	}
90
91
	/**
92
	 * Get the files to load the routes from
93
	 *
94
	 * @return string[]
95
	 */
96
	public function getRoutingFiles() {
97
		if (!isset($this->routingFiles)) {
98
			$this->routingFiles = [];
99
			foreach (\OC_APP::getEnabledApps() as $app) {
100
				$appPath = \OC_App::getAppPath($app);
101
				if ($appPath !== false) {
102
					$file = $appPath . '/appinfo/routes.php';
103
					if (file_exists($file)) {
104
						$this->routingFiles[$app] = $file;
105
					}
106
				}
107
			}
108
		}
109
		return $this->routingFiles;
110
	}
111
112
	/**
113
	 * Loads the routes
114
	 *
115
	 * @param null|string $app
116
	 */
117
	public function loadRoutes($app = null) {
118
		if (is_string($app)) {
119
			$app = \OC_App::cleanAppId($app);
120
		}
121
122
		$requestedApp = $app;
123
		if ($this->loaded) {
124
			return;
125
		}
126
		if (is_null($app)) {
127
			$this->loaded = true;
128
			$routingFiles = $this->getRoutingFiles();
129
		} else {
130
			if (isset($this->loadedApps[$app])) {
131
				return;
132
			}
133
			$file = \OC_App::getAppPath($app) . '/appinfo/routes.php';
0 ignored issues
show
Bug introduced by
Are you sure OC_App::getAppPath($app) of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

133
			$file = /** @scrutinizer ignore-type */ \OC_App::getAppPath($app) . '/appinfo/routes.php';
Loading history...
134
			if ($file !== false && file_exists($file)) {
135
				$routingFiles = [$app => $file];
136
			} else {
137
				$routingFiles = [];
138
			}
139
		}
140
		\OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes');
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getEventLogger() has been deprecated. ( Ignorable by Annotation )

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

140
		/** @scrutinizer ignore-deprecated */ \OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes');
Loading history...
141
		foreach ($routingFiles as $app => $file) {
142
			if (!isset($this->loadedApps[$app])) {
143
				if (!\OC_App::isAppLoaded($app)) {
144
					// app MUST be loaded before app routes
145
					// try again next time loadRoutes() is called
146
					$this->loaded = false;
147
					continue;
148
				}
149
				$this->loadedApps[$app] = true;
150
				$this->useCollection($app);
151
				$this->requireRouteFile($file, $app);
152
				$collection = $this->getCollection($app);
153
				$this->root->addCollection($collection);
0 ignored issues
show
Bug introduced by
The method addCollection() does not exist on null. ( Ignorable by Annotation )

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

153
				$this->root->/** @scrutinizer ignore-call */ 
154
                 addCollection($collection);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
154
155
				// Also add the OCS collection
156
				$collection = $this->getCollection($app.'.ocs');
157
				$collection->addPrefix('/ocsapp');
158
				$this->root->addCollection($collection);
159
			}
160
		}
161
		if (!isset($this->loadedApps['core'])) {
162
			$this->loadedApps['core'] = true;
163
			$this->useCollection('root');
164
			require_once __DIR__ . '/../../../core/routes.php';
165
166
			// Also add the OCS collection
167
			$collection = $this->getCollection('root.ocs');
168
			$collection->addPrefix('/ocsapp');
169
			$this->root->addCollection($collection);
170
		}
171
		if ($this->loaded) {
172
			$collection = $this->getCollection('ocs');
173
			$collection->addPrefix('/ocs');
174
			$this->root->addCollection($collection);
175
		}
176
		\OC::$server->getEventLogger()->end('loadroutes' . $requestedApp);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getEventLogger() has been deprecated. ( Ignorable by Annotation )

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

176
		/** @scrutinizer ignore-deprecated */ \OC::$server->getEventLogger()->end('loadroutes' . $requestedApp);
Loading history...
177
	}
178
179
	/**
180
	 * @return string
181
	 * @deprecated
182
	 */
183
	public function getCacheKey() {
184
		return '';
185
	}
186
187
	/**
188
	 * @param string $name
189
	 * @return \Symfony\Component\Routing\RouteCollection
190
	 */
191
	protected function getCollection($name) {
192
		if (!isset($this->collections[$name])) {
193
			$this->collections[$name] = new RouteCollection();
194
		}
195
		return $this->collections[$name];
196
	}
197
198
	/**
199
	 * Sets the collection to use for adding routes
200
	 *
201
	 * @param string $name Name of the collection to use.
202
	 * @return void
203
	 */
204
	public function useCollection($name) {
205
		$this->collection = $this->getCollection($name);
206
		$this->collectionName = $name;
207
	}
208
209
	/**
210
	 * returns the current collection name in use for adding routes
211
	 *
212
	 * @return string the collection name
213
	 */
214
	public function getCurrentCollection() {
215
		return $this->collectionName;
216
	}
217
218
219
	/**
220
	 * Create a \OC\Route\Route.
221
	 *
222
	 * @param string $name Name of the route to create.
223
	 * @param string $pattern The pattern to match
224
	 * @param array $defaults An array of default parameter values
225
	 * @param array $requirements An array of requirements for parameters (regexes)
226
	 * @return \OC\Route\Route
227
	 */
228
	public function create($name,
229
						   $pattern,
230
						   array $defaults = [],
231
						   array $requirements = []) {
232
		$route = new Route($pattern, $defaults, $requirements);
233
		$this->collection->add($name, $route);
0 ignored issues
show
Bug introduced by
The method add() does not exist on null. ( Ignorable by Annotation )

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

233
		$this->collection->/** @scrutinizer ignore-call */ 
234
                     add($name, $route);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
234
		return $route;
235
	}
236
237
	/**
238
	 * Find the route matching $url
239
	 *
240
	 * @param string $url The url to find
241
	 * @throws \Exception
242
	 * @return array
243
	 */
244
	public function findMatchingRoute(string $url): array {
245
		if (substr($url, 0, 6) === '/apps/') {
246
			// empty string / 'apps' / $app / rest of the route
247
			list(, , $app,) = explode('/', $url, 4);
248
249
			$app = \OC_App::cleanAppId($app);
250
			\OC::$REQUESTEDAPP = $app;
251
			$this->loadRoutes($app);
252
		} elseif (substr($url, 0, 13) === '/ocsapp/apps/') {
253
			// empty string / 'ocsapp' / 'apps' / $app / rest of the route
254
			list(, , , $app,) = explode('/', $url, 5);
255
256
			$app = \OC_App::cleanAppId($app);
257
			\OC::$REQUESTEDAPP = $app;
258
			$this->loadRoutes($app);
259
		} elseif (substr($url, 0, 10) === '/settings/') {
260
			$this->loadRoutes('settings');
261
		} elseif (substr($url, 0, 6) === '/core/') {
262
			\OC::$REQUESTEDAPP = $url;
263
			if (!\OC::$server->getConfig()->getSystemValueBool('maintenance') && !Util::needUpgrade()) {
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getConfig() has been deprecated. ( Ignorable by Annotation )

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

263
			if (!/** @scrutinizer ignore-deprecated */ \OC::$server->getConfig()->getSystemValueBool('maintenance') && !Util::needUpgrade()) {
Loading history...
264
				\OC_App::loadApps();
265
			}
266
			$this->loadRoutes('core');
267
		} else {
268
			$this->loadRoutes();
269
		}
270
271
		$matcher = new UrlMatcher($this->root, $this->context);
272
		try {
273
			$parameters = $matcher->match($url);
274
		} catch (ResourceNotFoundException $e) {
275
			if (substr($url, -1) !== '/') {
276
				// We allow links to apps/files? for backwards compatibility reasons
277
				// However, since Symfony does not allow empty route names, the route
278
				// we need to match is '/', so we need to append the '/' here.
279
				try {
280
					$parameters = $matcher->match($url . '/');
281
				} catch (ResourceNotFoundException $newException) {
282
					// If we still didn't match a route, we throw the original exception
283
					throw $e;
284
				}
285
			} else {
286
				throw $e;
287
			}
288
		}
289
290
		return $parameters;
291
	}
292
293
	/**
294
	 * Find and execute the route matching $url
295
	 *
296
	 * @param string $url The url to find
297
	 * @throws \Exception
298
	 * @return void
299
	 */
300
	public function match($url) {
301
		$parameters = $this->findMatchingRoute($url);
302
303
		\OC::$server->getEventLogger()->start('run_route', 'Run route');
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getEventLogger() has been deprecated. ( Ignorable by Annotation )

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

303
		/** @scrutinizer ignore-deprecated */ \OC::$server->getEventLogger()->start('run_route', 'Run route');
Loading history...
304
		if (isset($parameters['caller'])) {
305
			$caller = $parameters['caller'];
306
			unset($parameters['caller']);
307
			$application = $this->getApplicationClass($caller[0]);
308
			\OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters);
309
		} elseif (isset($parameters['action'])) {
310
			$action = $parameters['action'];
311
			if (!is_callable($action)) {
312
				throw new \Exception('not a callable action');
313
			}
314
			unset($parameters['action']);
315
			call_user_func($action, $parameters);
316
		} elseif (isset($parameters['file'])) {
317
			include $parameters['file'];
318
		} else {
319
			throw new \Exception('no action available');
320
		}
321
		\OC::$server->getEventLogger()->end('run_route');
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getEventLogger() has been deprecated. ( Ignorable by Annotation )

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

321
		/** @scrutinizer ignore-deprecated */ \OC::$server->getEventLogger()->end('run_route');
Loading history...
322
	}
323
324
	/**
325
	 * Get the url generator
326
	 *
327
	 * @return \Symfony\Component\Routing\Generator\UrlGenerator
328
	 *
329
	 */
330
	public function getGenerator() {
331
		if (null !== $this->generator) {
332
			return $this->generator;
333
		}
334
335
		return $this->generator = new UrlGenerator($this->root, $this->context);
336
	}
337
338
	/**
339
	 * Generate url based on $name and $parameters
340
	 *
341
	 * @param string $name Name of the route to use.
342
	 * @param array $parameters Parameters for the route
343
	 * @param bool $absolute
344
	 * @return string
345
	 */
346
	public function generate($name,
347
							 $parameters = [],
348
							 $absolute = false) {
349
		$this->loadRoutes();
350
		try {
351
			$referenceType = UrlGenerator::ABSOLUTE_URL;
352
			if ($absolute === false) {
353
				$referenceType = UrlGenerator::ABSOLUTE_PATH;
354
			}
355
			$name = $this->fixLegacyRootName($name);
356
			return $this->getGenerator()->generate($name, $parameters, $referenceType);
357
		} catch (RouteNotFoundException $e) {
358
			$this->logger->logException($e, ['level' => ILogger::INFO]);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::INFO has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

358
			$this->logger->logException($e, ['level' => /** @scrutinizer ignore-deprecated */ ILogger::INFO]);

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

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

Loading history...
359
			return '';
360
		}
361
	}
362
363
	protected function fixLegacyRootName(string $routeName): string {
364
		if ($routeName === 'files.viewcontroller.showFile') {
365
			return 'files.View.showFile';
366
		}
367
		if ($routeName === 'files_sharing.sharecontroller.showShare') {
368
			return 'files_sharing.Share.showShare';
369
		}
370
		if ($routeName === 'files_sharing.sharecontroller.showAuthenticate') {
371
			return 'files_sharing.Share.showAuthenticate';
372
		}
373
		if ($routeName === 'files_sharing.sharecontroller.authenticate') {
374
			return 'files_sharing.Share.authenticate';
375
		}
376
		if ($routeName === 'files_sharing.sharecontroller.downloadShare') {
377
			return 'files_sharing.Share.downloadShare';
378
		}
379
		if ($routeName === 'files_sharing.publicpreview.directLink') {
380
			return 'files_sharing.PublicPreview.directLink';
381
		}
382
		if ($routeName === 'cloud_federation_api.requesthandlercontroller.addShare') {
383
			return 'cloud_federation_api.RequestHandler.addShare';
384
		}
385
		if ($routeName === 'cloud_federation_api.requesthandlercontroller.receiveNotification') {
386
			return 'cloud_federation_api.RequestHandler.receiveNotification';
387
		}
388
		return $routeName;
389
	}
390
391
	/**
392
	 * To isolate the variable scope used inside the $file it is required in it's own method
393
	 *
394
	 * @param string $file the route file location to include
395
	 * @param string $appName
396
	 */
397
	private function requireRouteFile($file, $appName) {
398
		$this->setupRoutes(include_once $file, $appName);
399
	}
400
401
402
	/**
403
	 * If a routes.php file returns an array, try to set up the application and
404
	 * register the routes for the app. The application class will be chosen by
405
	 * camelcasing the appname, e.g.: my_app will be turned into
406
	 * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default
407
	 * App will be intialized. This makes it optional to ship an
408
	 * appinfo/application.php by using the built in query resolver
409
	 *
410
	 * @param array $routes the application routes
411
	 * @param string $appName the name of the app.
412
	 */
413
	private function setupRoutes($routes, $appName) {
414
		if (is_array($routes)) {
0 ignored issues
show
introduced by
The condition is_array($routes) is always true.
Loading history...
415
			$application = $this->getApplicationClass($appName);
416
			$application->registerRoutes($this, $routes);
417
		}
418
	}
419
420
	private function getApplicationClass(string $appName) {
421
		$appNameSpace = App::buildAppNamespace($appName);
422
423
		$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
424
425
		if (class_exists($applicationClassName)) {
426
			$application = \OC::$server->query($applicationClassName);
427
		} else {
428
			$application = new App($appName);
429
		}
430
431
		return $application;
432
	}
433
}
434