Issues (4)

Security Analysis    not enabled

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.

src/QueryString.php (1 issue)

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
namespace BenTools\QueryString;
4
5
use BenTools\QueryString\Parser\NativeParser;
6
use BenTools\QueryString\Parser\QueryStringParserInterface;
7
use BenTools\QueryString\Renderer\NativeRenderer;
8
use BenTools\QueryString\Renderer\QueryStringRendererInterface;
9
use Traversable;
10
11
final class QueryString
12
{
13
    /**
14
     * @var array
15
     */
16
    private $params = [];
17
18
    /**
19
     * @var QueryStringRendererInterface
20
     */
21
    private $renderer;
22
23
    /**
24
     * @var QueryStringRendererInterface
25
     */
26
    private static $defaultRenderer;
27
28
    /**
29
     * @var QueryStringParserInterface
30
     */
31
    private static $defaultParser;
32
33
    /**
34
     * QueryString constructor.
35
     * @param array|null                       $params
36
     * @throws \InvalidArgumentException
37
     */
38
    protected function __construct(?array $params = [])
39
    {
40
        $params = $params ?? [];
41
        foreach ($params as $key => $value) {
42
            $this->params[(string) $key] = $value;
43
        }
44
        $this->renderer = self::getDefaultRenderer();
45
    }
46
47
    /**
48
     * @param array $params
49
     * @return QueryString
50
     */
51
    private static function createFromParams(array $params): self
52
    {
53
        return new self($params);
54
    }
55
56
    /**
57
     * @param \Psr\Http\Message\UriInterface  $uri
58
     * @param QueryStringParserInterface $queryStringParser
59
     * @return QueryString
60
     */
61
    private static function createFromUri($uri, QueryStringParserInterface $queryStringParser): self
62
    {
63
        return self::createFromString($uri->getQuery(), $queryStringParser);
64
    }
65
66
    /**
67
     * @param string                     $string
68
     * @param QueryStringParserInterface $queryStringParser
69
     * @return QueryString
70
     */
71
    private static function createFromString(string $string, QueryStringParserInterface $queryStringParser): self
72
    {
73
        return new self($queryStringParser->parse($string));
74
    }
75
76
    /**
77
     * @param QueryStringParserInterface|null $queryStringParser
78
     * @return QueryString
79
     * @throws \RuntimeException
80
     */
81
    public static function createFromCurrentLocation(QueryStringParserInterface $queryStringParser = null): self
82
    {
83
        if (!isset($_SERVER['REQUEST_URI'])) {
84
            throw new \RuntimeException('$_SERVER[\'REQUEST_URI\'] has not been set.');
85
        }
86
        return self::createFromString($_SERVER['REQUEST_URI'], $queryStringParser ?? self::getDefaultParser());
87
    }
88
89
    /**
90
     * @return QueryString
91
     * @throws \RuntimeException
92
     */
93
    public function withCurrentLocation(): self
94
    {
95
        return self::createFromCurrentLocation();
96
    }
97
98
    /**
99
     * @param                                 $input
100
     * @param QueryStringParserInterface|null $queryStringParser
101
     * @return QueryString
102
     * @throws \InvalidArgumentException
103
     * @throws \TypeError
104
     */
105
    public static function factory($input = null, QueryStringParserInterface $queryStringParser = null): self
106
    {
107
        if (is_array($input)) {
108
            return self::createFromParams($input);
109
        } elseif (null === $input) {
110
            return self::createFromParams([]);
111
        } elseif (is_a($input, 'Psr\Http\Message\UriInterface')) {
112
            return self::createFromUri($input, $queryStringParser ?? self::getDefaultParser());
113
        } elseif (is_string($input)) {
114
            return self::createFromString($input, $queryStringParser ?? self::getDefaultParser());
115
        }
116
        throw new \InvalidArgumentException(sprintf('Expected array, string or Psr\Http\Message\UriInterface, got %s', is_object($input) ? get_class($input) : gettype($input)));
117
    }
118
119
    /**
120
     * @return array
121
     */
122
    public function getParams(): ?array
123
    {
124
        return $this->params;
125
    }
126
127
    /**
128
     * @param string $key
129
     * @param array  ...$deepKeys
130
     * @return mixed|null
131
     */
132
    public function getParam(string $key, ...$deepKeys)
133
    {
134
        $param = $this->params[$key] ?? null;
135
        foreach ($deepKeys as $key) {
136
            if (!isset($param[$key])) {
137
                return null;
138
            }
139
            $param = $param[$key];
140
        }
141
        return $param;
142
    }
143
144
    /**
145
     * @param string $key
146
     * @return bool
147
     */
148
    public function hasParam(string $key, ...$deepKeys): bool
149
    {
150
        return [] === $deepKeys ? array_key_exists($key, $this->params) : null !== $this->getParam($key, ...$deepKeys);
151
    }
152
153
    /**
154
     * Yield key => value pairs.
155
     *
156
     * @param bool $decodeKeys
157
     * @param bool $decodeValues
158
     * @return Traversable
159
     */
160
    public function getPairs(bool $decodeKeys = false, bool $decodeValues = false): Traversable
161
    {
162
        return new Pairs((string) $this, $decodeKeys, $decodeValues, $this->getRenderer()->getSeparator());
163
    }
164
165
    /**
166
     * @param string $key
167
     * @param        $value
168
     * @return QueryString
169
     */
170
    public function withParam(string $key, $value): self
171
    {
172
        $clone = clone $this;
173
        $clone->params[$key] = $value;
174
        return $clone;
175
    }
176
177
    /**
178
     * @param array $params
179
     * @return QueryString
180
     */
181
    public function withParams(array $params): self
182
    {
183
        $clone = clone $this;
184
        $clone->params = [];
185
        foreach ($params as $key => $value) {
186
            $clone->params[(string) $key] = $value;
187
        }
188
        return $clone;
189
    }
190
191
    /**
192
     * @param string $key
193
     * @param array  ...$deepKeys
194
     * @return QueryString
195
     */
196
    public function withoutParam(string $key, ...$deepKeys): self
197
    {
198
        $clone = clone $this;
199
200
        // $key does not exist
201
        if (!isset($clone->params[$key])) {
202
            return $clone;
203
        }
204
205
        // $key exists and there are no $deepKeys
206
        if ([] === $deepKeys) {
207
            unset($clone->params[$key]);
208
            return $clone;
209
        }
210
211
        // Deepkeys
212
        $clone->params[$key] = $this->removeFromPath($clone->params[$key], ...$deepKeys);
213
        return $clone;
214
    }
215
216
    /**
217
     * @return QueryStringRendererInterface
218
     */
219
    public function getRenderer(): QueryStringRendererInterface
220
    {
221
        return $this->renderer;
222
    }
223
224
    /**
225
     * @param QueryStringRendererInterface $renderer
226
     * @return QueryString
227
     */
228
    public function withRenderer(QueryStringRendererInterface $renderer): self
229
    {
230
        $clone = clone $this;
231
        $clone->renderer = $renderer;
232
        return $clone;
233
    }
234
235
    /**
236
     * @return string
237
     */
238
    public function __toString(): string
239
    {
240
        return $this->renderer->render($this);
241
    }
242
243
    /**
244
     * @param array $array
245
     * @return bool
246
     */
247
    private function isAnIndexedArray(array $array): bool
248
    {
249
        $keys = array_keys($array);
250
        return $keys === array_filter($keys, 'is_int');
251
    }
252
253
    /**
254
     * @param array $params
255
     * @param array ...$keys
256
     * @return array
257
     */
258
    private function removeFromPath(array $params, ...$keys): array
259
    {
260
        $nbKeys = count($keys);
261
        $lastIndex = $nbKeys - 1;
262
        $cursor = &$params;
263
264
        foreach ($keys as $k => $key) {
265
            if (!isset($cursor[$key])) {
266
                return $params; // End here if not found
267
            }
268
269
            if ($k === $lastIndex) {
270
                unset($cursor[$key]);
271
                if (is_array($cursor) && $this->isAnIndexedArray($cursor)) {
272
                    $cursor = array_values($cursor);
273
                }
274
                break;
275
            }
276
277
            $cursor = &$cursor[$key];
278
        }
279
280
        return $params;
281
    }
282
283
    /**
284
     * Returns the default renderer.
285
     *
286
     * @return QueryStringRendererInterface
287
     */
288
    public static function getDefaultRenderer(): QueryStringRendererInterface
289
    {
290
        if (!isset(self::$defaultRenderer)) {
291
            self::restoreDefaultRenderer();
292
        }
293
        return self::$defaultRenderer;
294
    }
295
296
    /**
297
     * Changes default renderer.
298
     *
299
     * @param QueryStringRendererInterface $defaultRenderer
300
     */
301
    public static function setDefaultRenderer(QueryStringRendererInterface $defaultRenderer): void
302
    {
303
        self::$defaultRenderer = $defaultRenderer;
304
    }
305
306
    /**
307
     * Restores the default renderer.
308
     */
309
    public static function restoreDefaultRenderer(): void
310
    {
311
        self::$defaultRenderer = NativeRenderer::factory();
0 ignored issues
show
Documentation Bug introduced by
It seems like \BenTools\QueryString\Re...tiveRenderer::factory() of type object<self> is incompatible with the declared type object<BenTools\QueryStr...tringRendererInterface> of property $defaultRenderer.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
312
    }
313
314
    /**
315
     * Returns the default parser.
316
     *
317
     * @return QueryStringParserInterface
318
     */
319
    public static function getDefaultParser(): QueryStringParserInterface
320
    {
321
        if (!isset(self::$defaultParser)) {
322
            self::restoreDefaultParser();
323
        }
324
        return self::$defaultParser;
325
    }
326
327
    /**
328
     * Changes default parser.
329
     *
330
     * @param QueryStringParserInterface $defaultParser
331
     */
332
    public static function setDefaultParser(QueryStringParserInterface $defaultParser): void
333
    {
334
        self::$defaultParser = $defaultParser;
335
    }
336
337
    /**
338
     * Restores the default parser.
339
     */
340
    public static function restoreDefaultParser(): void
341
    {
342
        self::$defaultParser = new NativeParser();
343
    }
344
}
345