Issues (16)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/RouteCollection.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ICanBoogie\Routing;
13
14
use ICanBoogie\HTTP\Request;
15
use ICanBoogie\Prototype\MethodNotDefined;
16
17
/**
18
 * A route collection.
19
 *
20
 * @method RouteCollection any() any(string $pattern, $controller, array $options=[]) Add a route for any HTTP method.
21
 * @method RouteCollection connect() connect(string $pattern, $controller, array $options=[]) Add a route for the HTTP method CONNECT.
22
 * @method RouteCollection delete() delete(string $pattern, $controller, array $options=[]) Add a route for the HTTP method DELETE.
23
 * @method RouteCollection get() get(string $pattern, $controller, array $options=[]) Add a route for the HTTP method GET.
24
 * @method RouteCollection head() head(string $pattern, $controller, array $options=[]) Add a route for the HTTP method HEAD.
25
 * @method RouteCollection options() options(string $pattern, $controller, array $options=[]) Add a route for the HTTP method OPTIONS.
26
 * @method RouteCollection post() post(string $pattern, $controller, array $options=[]) Add a route for the HTTP method POST.
27
 * @method RouteCollection put() put(string $pattern, $controller, array $options=[]) Add a route for the HTTP method PUT.
28
 * @method RouteCollection patch() patch(string $pattern, $controller, array $options=[]) Add a route for the HTTP method PATCH
29
 * @method RouteCollection trace() trace(string $pattern, $controller, array $options=[]) Add a route for the HTTP method TRACE.
30
 */
31
class RouteCollection implements \IteratorAggregate, \ArrayAccess, \Countable
32
{
33
	/**
34
	 * Specify that the route definitions can be trusted.
35
	 */
36
	const TRUSTED_DEFINITIONS = true;
37
38
	/**
39
	 * Class name of the {@link Route} instances.
40
	 */
41
	const DEFAULT_ROUTE_CLASS = Route::class;
42
43
	/**
44
	 * Route definitions.
45
	 *
46
	 * @var array
47
	 */
48
	protected $routes = [];
49
50
	/**
51
	 * Route instances.
52
	 *
53
	 * @var Route[]
54
	 */
55
	protected $instances = [];
56
57
	/**
58
	 * @param array $definitions
59
	 * @param bool $trusted_definitions {@link TRUSTED_DEFINITIONS} if the definition can be
60
	 * trusted. This will speed up the construct process but the definitions will not be checked,
61
	 * nor will they be normalized.
62
	 */
63
	public function __construct(array $definitions = [], $trusted_definitions = false)
64
	{
65
		foreach ($definitions as $id => $definition)
66
		{
67
			if (is_string($id) && empty($definition[RouteDefinition::ID]))
68
			{
69
				$definition[RouteDefinition::ID] = $id;
70
			}
71
72
			$this->add($definition, $trusted_definitions);
73
		}
74
	}
75
76
	/**
77
	 * Adds a route definition using an HTTP method.
78
	 *
79
	 * @param string $method
80
	 * @param array $arguments
81
	 *
82
	 * @return $this
83
	 */
84
	public function __call($method, array $arguments)
85
	{
86
		$method = strtoupper($method);
87
88
		if ($method !== Request::METHOD_ANY && !in_array($method, Request::$methods))
89
		{
90
			throw new MethodNotDefined($method, $this);
91
		}
92
93
		list($pattern, $controller, $options) = $arguments + [ 2 => [] ];
94
95
		$this->revoke_cache();
96
		$this->add([
97
98
			RouteDefinition::CONTROLLER => $controller,
99
			RouteDefinition::PATTERN => $pattern
100
101
		] + $options + [ RouteDefinition::VIA => $method ]);
102
103
		return $this;
104
	}
105
106
	/**
107
	 * Adds a route definition.
108
	 *
109
	 * **Note:** The method does *not* revoke cache.
110
	 *
111
	 * @param array $definition
112
	 * @param bool $trusted_definition {@link TRUSTED_DEFINITIONS} if the method should be trusting the
113
	 * definition, in which case the method doesn't assert if the definition is valid, nor does
114
	 * it normalizes it.
115
	 *
116
	 * @return $this
117
	 */
118
	protected function add(array $definition, $trusted_definition = false)
119
	{
120
		if (!$trusted_definition)
121
		{
122
			RouteDefinition::assert_is_valid($definition);
123
			RouteDefinition::normalize($definition);
124
			RouteDefinition::ensure_has_id($definition);
125
		}
126
127
		$id = $definition[RouteDefinition::ID];
128
		$this->routes[$id] = $definition;
129
130
		return $this;
131
	}
132
133
	/**
134
	 * Adds resource routes.
135
	 *
136
	 * **Note:** The route definitions for the resource are created by
137
	 * {@link RouteMaker::resource}. Both methods accept the same arguments.
138
	 *
139
	 * @see \ICanBoogie\Routing\RoutesMaker::resource
140
	 *
141
	 * @param string $name
142
	 * @param string $controller
143
	 * @param array $options
144
	 *
145
	 * @return array
146
	 */
147
	public function resource($name, $controller, array $options = [])
148
	{
149
		$definitions = RouteMaker::resource($name, $controller, $options);
150
		$this->revoke_cache();
151
152
		foreach ($definitions as $id => $definition)
153
		{
154
			$this->add([ RouteDefinition::ID => $id ] + $definition);
155
		}
156
	}
157
158
	public function getIterator()
159
	{
160
		return new \ArrayIterator($this->routes);
161
	}
162
163
	public function offsetExists($offset)
164
	{
165
		return isset($this->routes[$offset]);
166
	}
167
168
	/**
169
	 * Returns a {@link Route} instance.
170
	 *
171
	 * @param string $id Route identifier.
172
	 *
173
	 * @return Route
174
	 *
175
	 * @throws RouteNotDefined
176
	 */
177
	public function offsetGet($id)
178
	{
179
		if (isset($this->instances[$id]))
180
		{
181
			return $this->instances[$id];
182
		}
183
184
		if (!$this->offsetExists($id))
185
		{
186
			throw new RouteNotDefined($id);
187
		}
188
189
		return $this->instances[$id] = Route::from($this->routes[$id]);
190
	}
191
192
	/**
193
	 * Defines a route.
194
	 *
195
	 * @param string $id The identifier of the route.
196
	 * @param array $route The route definition.
197
	 */
198
	public function offsetSet($id, $route)
199
	{
200
		$this->revoke_cache();
201
		$this->add([ RouteDefinition::ID => $id ] + $route);
202
	}
203
204
	/**
205
	 * Removes a route.
206
	 *
207
	 * @param string $offset The identifier of the route.
208
	 */
209
	public function offsetUnset($offset)
210
	{
211
		unset($this->routes[$offset]);
212
213
		$this->revoke_cache();
214
	}
215
216
	/**
217
	 * Returns the number of routes in the collection.
218
	 *
219
	 * @return int
220
	 */
221
	public function count()
222
	{
223
		return count($this->routes);
224
	}
225
226
	/**
227
	 * Search for a route matching the specified pathname and method.
228
	 *
229
	 * @param string $uri The URI to match. If the URI includes a query string it is removed
230
	 * before searching for a matching route.
231
	 * @param array|null $captured The parameters captured from the URI. If the URI included a
232
	 * query string, its parsed params are stored under the `__query__` key.
233
	 * @param string $method One of HTTP\Request::METHOD_* methods.
234
	 *
235
	 * @return Route|false|null
236
	 */
237
	public function find($uri, &$captured = null, $method = Request::METHOD_ANY)
238
	{
239
		$captured = [];
240
241
		$parsed = (array) parse_url($uri) + [ 'path' => null, 'query' => null ];
242
		$path = $parsed['path'];
243
244
		if (!$path)
245
		{
246
			return false;
247
		}
248
249
		#
250
		# Determine if a route matches prerequisites.
251
		#
252
		$matchable = function($via) use($method) {
253
254
			if ($method != Request::METHOD_ANY)
255
			{
256
				if (is_array($via))
257
				{
258
					if (!in_array($method, $via))
259
					{
260
						return false;
261
					}
262
				}
263
				else if ($via !== Request::METHOD_ANY && $via !== $method)
264
				{
265
					return false;
266
				}
267
			}
268
269
			return true;
270
		};
271
272
		#
273
		# Search for a matching static route.
274
		#
275 View Code Duplication
		$map_static = function($definitions) use($path, &$matchable) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
276
277
			foreach ($definitions as $id => $definition)
278
			{
279
				$pattern = $definition[RouteDefinition::PATTERN];
280
				$via = $definition[RouteDefinition::VIA];
281
282
				if (!$matchable($via) || $pattern != $path)
283
				{
284
					continue;
285
				}
286
287
				return $id;
288
			}
289
290
			return null;
291
		};
292
293
		#
294
		# Search for a matching dynamic route.
295
		#
296 View Code Duplication
		$map_dynamic = function($definitions) use($path, &$matchable, &$captured) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
297
298
			foreach ($definitions as $id => $definition)
299
			{
300
				$pattern = $definition[RouteDefinition::PATTERN];
301
				$via = $definition[RouteDefinition::VIA];
302
303
				if (!$matchable($via) || !Pattern::from($pattern)->match($path, $captured))
304
				{
305
					continue;
306
				}
307
308
				return $id;
309
			}
310
311
			return null;
312
		};
313
314
		list($static, $dynamic) = $this->sort_routes();
315
316
		$id = null;
317
318
		if ($static)
319
		{
320
			$id = $map_static($static);
321
		}
322
323
		if (!$id && $dynamic)
324
		{
325
			$id = $map_dynamic($dynamic);
326
		}
327
328
		if (!$id)
329
		{
330
			return null;
331
		}
332
333
		$query = $parsed['query'];
334
335
		if ($query)
336
		{
337
			parse_str($query, $parsed_query_string);
338
339
			$captured['__query__'] = $parsed_query_string;
340
		}
341
342
		return $this[$id];
343
	}
344
345
	private $static;
346
	private $dynamic;
347
348
	/**
349
	 * Revokes the cache used by the {@link sort_routes} method.
350
	 */
351
	private function revoke_cache()
352
	{
353
		$this->static = null;
354
		$this->dynamic = null;
355
	}
356
357
	/**
358
	 * Sorts routes according to their type and computed weight.
359
	 *
360
	 * Routes and grouped in two groups: static routes and dynamic routes. The difference between
361
	 * static and dynamic routes is that dynamic routes capture parameters from the path and thus
362
	 * require a regex to compute the match, whereas static routes only require is simple string
363
	 * comparison.
364
	 *
365
	 * Dynamic routes are ordered according to their weight, which is computed from the number
366
	 * of static parts before the first capture. The more static parts, the lighter the route is.
367
	 *
368
	 * @return array An array with the static routes and dynamic routes.
369
	 */
370
	private function sort_routes()
371
	{
372
		if ($this->static !== null)
373
		{
374
			return [ $this->static, $this->dynamic ];
375
		}
376
377
		$static = [];
378
		$dynamic = [];
379
		$weights = [];
380
381
		foreach ($this->routes as $id => $definition)
382
		{
383
			$pattern = $definition[RouteDefinition::PATTERN];
384
			$first_capture_position = strpos($pattern, ':') ?: strpos($pattern, '<');
385
386
			if ($first_capture_position === false)
387
			{
388
				$static[$id] = $definition;
389
			}
390
			else
391
			{
392
				$dynamic[$id] = $definition;
393
				$weights[$id] = substr_count($pattern, '/', 0, $first_capture_position);
394
			}
395
		}
396
397
		\ICanBoogie\stable_sort($dynamic, function($v, $k) use($weights) {
398
399
			return -$weights[$k];
400
401
		});
402
403
		$this->static = $static;
404
		$this->dynamic = $dynamic;
405
406
		return [ $static, $dynamic ];
407
	}
408
409
	/**
410
	 * Returns a new collection with filtered routes.
411
	 *
412
	 * @param callable $filter
413
	 *
414
	 * @return RouteCollection
415
	 */
416
	public function filter(callable $filter)
417
	{
418
		$definitions = [];
419
420
		foreach ($this as $id => $definition)
421
		{
422
			if (!$filter($definition, $id))
423
			{
424
				continue;
425
			}
426
427
			$definitions[$id] = $definition;
428
		}
429
430
		return new static($definitions);
431
	}
432
}
433