Passed
Push — master ( 613f0f...8f650f )
by Roeland
11:57 queued 12s
created

Router::getCacheKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
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 Felix Epp <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Jörn Friedrich Dreyer <[email protected]>
11
 * @author Lukas Reschke <[email protected]>
12
 * @author Morris Jobke <[email protected]>
13
 * @author Robin Appelman <[email protected]>
14
 * @author Robin McCorkell <[email protected]>
15
 * @author Roeland Jago Douma <[email protected]>
16
 * @author Thomas Müller <[email protected]>
17
 * @author Vincent Petry <[email protected]>
18
 *
19
 * @license AGPL-3.0
20
 *
21
 * This code is free software: you can redistribute it and/or modify
22
 * it under the terms of the GNU Affero General Public License, version 3,
23
 * as published by the Free Software Foundation.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
 * GNU Affero General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU Affero General Public License, version 3,
31
 * along with this program. If not, see <http://www.gnu.org/licenses/>
32
 *
33
 */
34
35
namespace OC\Route;
36
37
use OCP\AppFramework\App;
38
use OCP\ILogger;
39
use OCP\Route\IRouter;
40
use OCP\Util;
41
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
42
use Symfony\Component\Routing\Exception\RouteNotFoundException;
43
use Symfony\Component\Routing\Generator\UrlGenerator;
44
use Symfony\Component\Routing\Matcher\UrlMatcher;
45
use Symfony\Component\Routing\RequestContext;
46
use Symfony\Component\Routing\RouteCollection;
47
48
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

48
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...
49
	/** @var RouteCollection[] */
50
	protected $collections = [];
51
	/** @var null|RouteCollection */
52
	protected $collection = null;
53
	/** @var null|string */
54
	protected $collectionName = null;
55
	/** @var null|RouteCollection */
56
	protected $root = null;
57
	/** @var null|UrlGenerator */
58
	protected $generator = null;
59
	/** @var string[] */
60
	protected $routingFiles;
61
	/** @var bool */
62
	protected $loaded = false;
63
	/** @var array */
64
	protected $loadedApps = [];
65
	/** @var ILogger */
66
	protected $logger;
67
	/** @var RequestContext */
68
	protected $context;
69
70
	/**
71
	 * @param ILogger $logger
72
	 */
73
	public function __construct(ILogger $logger) {
74
		$this->logger = $logger;
75
		$baseUrl = \OC::$WEBROOT;
76
		if (!(\OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) {
77
			$baseUrl = \OC::$server->getURLGenerator()->linkTo('', 'index.php');
78
		}
79
		if (!\OC::$CLI && isset($_SERVER['REQUEST_METHOD'])) {
80
			$method = $_SERVER['REQUEST_METHOD'];
81
		} else {
82
			$method = 'GET';
83
		}
84
		$request = \OC::$server->getRequest();
85
		$host = $request->getServerHost();
86
		$schema = $request->getServerProtocol();
87
		$this->context = new RequestContext($baseUrl, $method, $host, $schema);
88
		// TODO cache
89
		$this->root = $this->getCollection('root');
90
	}
91
92
	/**
93
	 * Get the files to load the routes from
94
	 *
95
	 * @return string[]
96
	 */
97
	public function getRoutingFiles() {
98
		if (!isset($this->routingFiles)) {
99
			$this->routingFiles = [];
100
			foreach (\OC_APP::getEnabledApps() as $app) {
101
				$appPath = \OC_App::getAppPath($app);
0 ignored issues
show
Deprecated Code introduced by
The function OC_App::getAppPath() has been deprecated: 11.0.0 use \OC::$server->getAppManager()->getAppPath() ( Ignorable by Annotation )

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

101
				$appPath = /** @scrutinizer ignore-deprecated */ \OC_App::getAppPath($app);

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

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

Loading history...
102
				if ($appPath !== false) {
103
					$file = $appPath . '/appinfo/routes.php';
104
					if (file_exists($file)) {
105
						$this->routingFiles[$app] = $file;
106
					}
107
				}
108
			}
109
		}
110
		return $this->routingFiles;
111
	}
112
113
	/**
114
	 * Loads the routes
115
	 *
116
	 * @param null|string $app
117
	 */
118
	public function loadRoutes($app = null) {
119
		if (is_string($app)) {
120
			$app = \OC_App::cleanAppId($app);
121
		}
122
123
		$requestedApp = $app;
124
		if ($this->loaded) {
125
			return;
126
		}
127
		if (is_null($app)) {
128
			$this->loaded = true;
129
			$routingFiles = $this->getRoutingFiles();
130
		} else {
131
			if (isset($this->loadedApps[$app])) {
132
				return;
133
			}
134
			$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

134
			$file = /** @scrutinizer ignore-type */ \OC_App::getAppPath($app) . '/appinfo/routes.php';
Loading history...
Deprecated Code introduced by
The function OC_App::getAppPath() has been deprecated: 11.0.0 use \OC::$server->getAppManager()->getAppPath() ( Ignorable by Annotation )

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

134
			$file = /** @scrutinizer ignore-deprecated */ \OC_App::getAppPath($app) . '/appinfo/routes.php';

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

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

Loading history...
135
			if ($file !== false && file_exists($file)) {
136
				$routingFiles = [$app => $file];
137
			} else {
138
				$routingFiles = [];
139
			}
140
		}
141
		\OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes');
142
		foreach ($routingFiles as $app => $file) {
143
			if (!isset($this->loadedApps[$app])) {
144
				if (!\OC_App::isAppLoaded($app)) {
145
					// app MUST be loaded before app routes
146
					// try again next time loadRoutes() is called
147
					$this->loaded = false;
148
					continue;
149
				}
150
				$this->loadedApps[$app] = true;
151
				$this->useCollection($app);
152
				$this->requireRouteFile($file, $app);
153
				$collection = $this->getCollection($app);
154
				$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

154
				$this->root->/** @scrutinizer ignore-call */ 
155
                 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...
155
156
				// Also add the OCS collection
157
				$collection = $this->getCollection($app.'.ocs');
158
				$collection->addPrefix('/ocsapp');
159
				$this->root->addCollection($collection);
160
			}
161
		}
162
		if (!isset($this->loadedApps['core'])) {
163
			$this->loadedApps['core'] = true;
164
			$this->useCollection('root');
165
			require_once __DIR__ . '/../../../core/routes.php';
166
167
			// Also add the OCS collection
168
			$collection = $this->getCollection('root.ocs');
169
			$collection->addPrefix('/ocsapp');
170
			$this->root->addCollection($collection);
171
		}
172
		if ($this->loaded) {
173
			$collection = $this->getCollection('ocs');
174
			$collection->addPrefix('/ocs');
175
			$this->root->addCollection($collection);
176
		}
177
		\OC::$server->getEventLogger()->end('loadroutes' . $requestedApp);
178
	}
179
180
	/**
181
	 * @return string
182
	 * @deprecated
183
	 */
184
	public function getCacheKey() {
185
		return '';
186
	}
187
188
	/**
189
	 * @param string $name
190
	 * @return \Symfony\Component\Routing\RouteCollection
191
	 */
192
	protected function getCollection($name) {
193
		if (!isset($this->collections[$name])) {
194
			$this->collections[$name] = new RouteCollection();
195
		}
196
		return $this->collections[$name];
197
	}
198
199
	/**
200
	 * Sets the collection to use for adding routes
201
	 *
202
	 * @param string $name Name of the collection to use.
203
	 * @return void
204
	 */
205
	public function useCollection($name) {
206
		$this->collection = $this->getCollection($name);
207
		$this->collectionName = $name;
208
	}
209
210
	/**
211
	 * returns the current collection name in use for adding routes
212
	 *
213
	 * @return string the collection name
214
	 */
215
	public function getCurrentCollection() {
216
		return $this->collectionName;
217
	}
218
219
220
	/**
221
	 * Create a \OC\Route\Route.
222
	 *
223
	 * @param string $name Name of the route to create.
224
	 * @param string $pattern The pattern to match
225
	 * @param array $defaults An array of default parameter values
226
	 * @param array $requirements An array of requirements for parameters (regexes)
227
	 * @return \OC\Route\Route
228
	 */
229
	public function create($name,
230
						   $pattern,
231
						   array $defaults = [],
232
						   array $requirements = []) {
233
		$route = new Route($pattern, $defaults, $requirements);
234
		$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

234
		$this->collection->/** @scrutinizer ignore-call */ 
235
                     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...
235
		return $route;
236
	}
237
238
	/**
239
	 * Find the route matching $url
240
	 *
241
	 * @param string $url The url to find
242
	 * @throws \Exception
243
	 * @return void
244
	 */
245
	public function match($url) {
246
		if (substr($url, 0, 6) === '/apps/') {
247
			// empty string / 'apps' / $app / rest of the route
248
			list(, , $app,) = explode('/', $url, 4);
249
250
			$app = \OC_App::cleanAppId($app);
251
			\OC::$REQUESTEDAPP = $app;
252
			$this->loadRoutes($app);
253
		} elseif (substr($url, 0, 13) === '/ocsapp/apps/') {
254
			// empty string / 'ocsapp' / 'apps' / $app / rest of the route
255
			list(, , , $app,) = explode('/', $url, 5);
256
257
			$app = \OC_App::cleanAppId($app);
258
			\OC::$REQUESTEDAPP = $app;
259
			$this->loadRoutes($app);
260
		} elseif (substr($url, 0, 10) === '/settings/') {
261
			$this->loadRoutes('settings');
262
		} elseif (substr($url, 0, 6) === '/core/') {
263
			\OC::$REQUESTEDAPP = $url;
264
			if (!\OC::$server->getConfig()->getSystemValueBool('maintenance') && !Util::needUpgrade()) {
265
				\OC_App::loadApps();
266
			}
267
			$this->loadRoutes('core');
268
		} else {
269
			$this->loadRoutes();
270
		}
271
272
		$matcher = new UrlMatcher($this->root, $this->context);
273
		try {
274
			$parameters = $matcher->match($url);
275
		} catch (ResourceNotFoundException $e) {
276
			if (substr($url, -1) !== '/') {
277
				// We allow links to apps/files? for backwards compatibility reasons
278
				// However, since Symfony does not allow empty route names, the route
279
				// we need to match is '/', so we need to append the '/' here.
280
				try {
281
					$parameters = $matcher->match($url . '/');
282
				} catch (ResourceNotFoundException $newException) {
283
					// If we still didn't match a route, we throw the original exception
284
					throw $e;
285
				}
286
			} else {
287
				throw $e;
288
			}
289
		}
290
291
		\OC::$server->getEventLogger()->start('run_route', 'Run route');
292
		if (isset($parameters['action'])) {
293
			$action = $parameters['action'];
294
			if (!is_callable($action)) {
295
				throw new \Exception('not a callable action');
296
			}
297
			unset($parameters['action']);
298
			call_user_func($action, $parameters);
299
		} elseif (isset($parameters['file'])) {
300
			include $parameters['file'];
301
		} else {
302
			throw new \Exception('no action available');
303
		}
304
		\OC::$server->getEventLogger()->end('run_route');
305
	}
306
307
	/**
308
	 * Get the url generator
309
	 *
310
	 * @return \Symfony\Component\Routing\Generator\UrlGenerator
311
	 *
312
	 */
313
	public function getGenerator() {
314
		if (null !== $this->generator) {
315
			return $this->generator;
316
		}
317
318
		return $this->generator = new UrlGenerator($this->root, $this->context);
319
	}
320
321
	/**
322
	 * Generate url based on $name and $parameters
323
	 *
324
	 * @param string $name Name of the route to use.
325
	 * @param array $parameters Parameters for the route
326
	 * @param bool $absolute
327
	 * @return string
328
	 */
329
	public function generate($name,
330
							 $parameters = [],
331
							 $absolute = false) {
332
		$this->loadRoutes();
333
		try {
334
			$referenceType = UrlGenerator::ABSOLUTE_URL;
335
			if ($absolute === false) {
336
				$referenceType = UrlGenerator::ABSOLUTE_PATH;
337
			}
338
			$name = $this->fixLegacyRootName($name);
339
			return $this->getGenerator()->generate($name, $parameters, $referenceType);
340
		} catch (RouteNotFoundException $e) {
341
			$this->logger->logException($e);
342
			return '';
343
		}
344
	}
345
346
	protected function fixLegacyRootName(string $routeName): string {
347
		if ($routeName === 'files.viewcontroller.showFile') {
348
			return 'files.View.showFile';
349
		}
350
		if ($routeName === 'files_sharing.sharecontroller.showShare') {
351
			return 'files_sharing.Share.showShare';
352
		}
353
		if ($routeName === 'files_sharing.sharecontroller.showAuthenticate') {
354
			return 'files_sharing.Share.showAuthenticate';
355
		}
356
		if ($routeName === 'files_sharing.sharecontroller.authenticate') {
357
			return 'files_sharing.Share.authenticate';
358
		}
359
		if ($routeName === 'files_sharing.sharecontroller.downloadShare') {
360
			return 'files_sharing.Share.downloadShare';
361
		}
362
		if ($routeName === 'files_sharing.publicpreview.directLink') {
363
			return 'files_sharing.PublicPreview.directLink';
364
		}
365
		if ($routeName === 'cloud_federation_api.requesthandlercontroller.addShare') {
366
			return 'cloud_federation_api.RequestHandler.addShare';
367
		}
368
		if ($routeName === 'cloud_federation_api.requesthandlercontroller.receiveNotification') {
369
			return 'cloud_federation_api.RequestHandler.receiveNotification';
370
		}
371
		return $routeName;
372
	}
373
374
	/**
375
	 * To isolate the variable scope used inside the $file it is required in it's own method
376
	 *
377
	 * @param string $file the route file location to include
378
	 * @param string $appName
379
	 */
380
	private function requireRouteFile($file, $appName) {
381
		$this->setupRoutes(include_once $file, $appName);
382
	}
383
384
385
	/**
386
	 * If a routes.php file returns an array, try to set up the application and
387
	 * register the routes for the app. The application class will be chosen by
388
	 * camelcasing the appname, e.g.: my_app will be turned into
389
	 * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default
390
	 * App will be intialized. This makes it optional to ship an
391
	 * appinfo/application.php by using the built in query resolver
392
	 *
393
	 * @param array $routes the application routes
394
	 * @param string $appName the name of the app.
395
	 */
396
	private function setupRoutes($routes, $appName) {
397
		if (is_array($routes)) {
0 ignored issues
show
introduced by
The condition is_array($routes) is always true.
Loading history...
398
			$appNameSpace = App::buildAppNamespace($appName);
399
400
			$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
401
402
			if (class_exists($applicationClassName)) {
403
				$application = \OC::$server->query($applicationClassName);
404
			} else {
405
				$application = new App($appName);
406
			}
407
408
			$application->registerRoutes($this, $routes);
409
		}
410
	}
411
}
412