This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * vipnytt/UserAgentParser |
||
4 | * |
||
5 | * @link https://github.com/VIPnytt/UserAgentParser |
||
6 | * @license https://github.com/VIPnytt/UserAgentParser/blob/master/LICENSE The MIT License (MIT) |
||
7 | */ |
||
8 | |||
9 | namespace vipnytt; |
||
10 | |||
11 | use vipnytt\UserAgentParser\Exceptions; |
||
12 | |||
13 | /** |
||
14 | * Class UserAgentParser |
||
15 | * |
||
16 | * @link https://tools.ietf.org/html/rfc7231 |
||
17 | * @link https://tools.ietf.org/html/rfc7230 |
||
18 | * |
||
19 | * @package vipnytt |
||
20 | */ |
||
21 | class UserAgentParser |
||
22 | { |
||
23 | /** |
||
24 | * RFC 7231 - Section 5.5.3 - User-agent |
||
25 | */ |
||
26 | const RFC_README = 'https://tools.ietf.org/html/rfc7231#section-5.5.3'; |
||
27 | |||
28 | /** |
||
29 | * Origin product |
||
30 | * @var string |
||
31 | */ |
||
32 | private $originProduct; |
||
33 | |||
34 | /** |
||
35 | * Product |
||
36 | * @var string |
||
37 | */ |
||
38 | private $product; |
||
39 | |||
40 | /** |
||
41 | * Version |
||
42 | * @var float|int|string|null |
||
43 | */ |
||
44 | private $version; |
||
45 | |||
46 | /** |
||
47 | * UserAgentParser constructor |
||
48 | * |
||
49 | * @param string $product |
||
50 | * @param float|int|string|null $version |
||
51 | */ |
||
52 | public function __construct($product, $version = null) |
||
53 | { |
||
54 | $this->product = $product; |
||
55 | $this->version = $version; |
||
56 | if (strpos($this->product, '/') !== false) { |
||
57 | $this->split(); |
||
58 | } |
||
59 | $this->validateProduct(); |
||
60 | $this->validateVersion(); |
||
61 | } |
||
62 | |||
63 | /** |
||
64 | * Split Product and Version |
||
65 | * |
||
66 | * @return bool |
||
67 | */ |
||
68 | private function split() |
||
69 | { |
||
70 | if (count($parts = explode('/', trim($this->product . '/' . $this->version, '/'), 2)) === 2) { |
||
71 | $this->product = $parts[0]; |
||
72 | $this->version = $parts[1]; |
||
73 | } |
||
74 | return true; |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * Validate the Product format |
||
79 | * @link https://tools.ietf.org/html/rfc7231#section-5.5.3 |
||
80 | * @link https://tools.ietf.org/html/rfc7230#section-3.2.4 |
||
81 | * |
||
82 | * @return bool |
||
83 | * @throws Exceptions\ProductException |
||
84 | */ |
||
85 | private function validateProduct() |
||
86 | { |
||
87 | $this->blacklistCheck($this->product); |
||
88 | $this->originProduct = $this->product; |
||
89 | if ($this->originProduct !== ($this->product = $this->strip($this->product))) { |
||
0 ignored issues
–
show
|
|||
90 | trigger_error("Product name contains invalid characters. Truncated to `$this->product`.", E_USER_NOTICE); |
||
91 | } |
||
92 | if (empty($this->product)) { |
||
93 | throw new Exceptions\ProductException('Product string cannot be empty.'); |
||
94 | } |
||
95 | return true; |
||
96 | } |
||
97 | |||
98 | /** |
||
99 | * Check for blacklisted strings or characters |
||
100 | * |
||
101 | * @param float|int|string|null $input |
||
102 | * @return bool |
||
103 | * @throws Exceptions\FormatException |
||
104 | */ |
||
105 | private function blacklistCheck($input) |
||
106 | { |
||
107 | foreach ([ |
||
108 | 'mozilla', |
||
109 | 'compatible', |
||
110 | '(', |
||
111 | ')', |
||
112 | ] as $string) { |
||
113 | if (stripos($input, $string) !== false) { |
||
114 | throw new Exceptions\FormatException('Invalid User-agent format (`' . trim($this->product . '/' . $this->version, '/') . '`). Examples of valid User-agents: `MyCustomBot`, `MyFetcher-news`, `MyCrawler/2.1` and `MyBot-images/1.2`. See also ' . self::RFC_README); |
||
115 | } |
||
116 | } |
||
117 | return true; |
||
118 | } |
||
119 | |||
120 | /** |
||
121 | * Strip invalid characters |
||
122 | * |
||
123 | * @param string|string[] $string |
||
124 | * @return string|string[]|null |
||
125 | */ |
||
126 | private function strip($string) |
||
127 | { |
||
128 | return preg_replace('/[^\x21-\x7E]/', '', $string); |
||
129 | } |
||
130 | |||
131 | /** |
||
132 | * Validate the Version and it's format |
||
133 | * @link https://tools.ietf.org/html/rfc7231#section-5.5.3 |
||
134 | * |
||
135 | * @return bool |
||
136 | * @throws Exceptions\VersionException |
||
137 | */ |
||
138 | private function validateVersion() |
||
139 | { |
||
140 | $this->blacklistCheck($this->version); |
||
141 | if (!empty($this->version) && |
||
142 | ( |
||
143 | str_replace('+', '', filter_var($this->version, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)) !== $this->version || |
||
144 | version_compare($this->version, '0.0.1', '>=') === false |
||
145 | ) |
||
146 | ) { |
||
147 | throw new Exceptions\VersionException("Invalid version format (`$this->version`). See http://semver.org/ for guidelines. In addition, dev/alpha/beta/rc tags is disallowed. See also " . self::RFC_README); |
||
148 | } |
||
149 | $this->version = trim($this->version, '.0'); |
||
150 | return true; |
||
151 | } |
||
152 | |||
153 | /** |
||
154 | * Get User-agent |
||
155 | * |
||
156 | * @return string |
||
157 | */ |
||
158 | public function getUserAgent() |
||
159 | { |
||
160 | $product = $this->getProduct(); |
||
161 | $version = $this->getVersion(); |
||
162 | return $version === null ? $product : $product . '/' . $version; |
||
163 | } |
||
164 | |||
165 | /** |
||
166 | * Get product |
||
167 | * |
||
168 | * @return string |
||
169 | */ |
||
170 | public function getProduct() |
||
171 | { |
||
172 | return $this->product; |
||
173 | } |
||
174 | |||
175 | /** |
||
176 | * Get version |
||
177 | * |
||
178 | * @return float|int|string|null |
||
179 | */ |
||
180 | public function getVersion() |
||
181 | { |
||
182 | return empty($this->version) ? null : $this->version; |
||
183 | } |
||
184 | |||
185 | /** |
||
186 | * Find the best matching User-agent |
||
187 | * |
||
188 | * @param string[] $userAgents |
||
189 | * @return string|false |
||
190 | */ |
||
191 | public function getMostSpecific(array $userAgents) |
||
192 | { |
||
193 | $array = []; |
||
194 | foreach ($userAgents as $string) { |
||
195 | // Strip non-US-ASCII characters |
||
196 | $array[$string] = strtolower($this->strip($string)); |
||
197 | } |
||
198 | foreach (array_map('strtolower', $this->getUserAgents()) as $generated) { |
||
199 | if (($result = array_search($generated, $array)) !== false) { |
||
200 | // Match found |
||
201 | return $result; |
||
202 | } |
||
203 | } |
||
204 | return false; |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * Get an array of all possible User-agent combinations |
||
209 | * |
||
210 | * @return string[] |
||
211 | */ |
||
212 | public function getUserAgents() |
||
213 | { |
||
214 | return array_merge( |
||
215 | preg_filter('/^/', $this->product . '/', $this->getVersions()), |
||
216 | $this->getProducts() |
||
217 | ); |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Get versions |
||
222 | * |
||
223 | * @return float[]|int[]|string[] |
||
224 | */ |
||
225 | public function getVersions() |
||
226 | { |
||
227 | while ( |
||
228 | !empty($this->version) && |
||
229 | substr_count($this->version, '.') < 2 |
||
230 | ) { |
||
231 | $this->version .= '.0'; |
||
232 | } |
||
233 | // Remove part by part of the version. |
||
234 | $result = array_merge( |
||
235 | [$this->version], |
||
236 | $this->explode($this->version, '.') |
||
237 | ); |
||
238 | asort($result); |
||
239 | usort($result, function ($a, $b) { |
||
240 | // PHP 7: Switch to the <=> "Spaceship" operator |
||
241 | return strlen($b) - strlen($a); |
||
242 | }); |
||
243 | return $this->filterDuplicates($result); |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * Explode |
||
248 | * |
||
249 | * @param string $string |
||
250 | * @param string $delimiter |
||
251 | * @return string[] |
||
252 | */ |
||
253 | private function explode($string, $delimiter) |
||
254 | { |
||
255 | $result = []; |
||
256 | while (($pos = strrpos($string, $delimiter)) !== false) { |
||
257 | $result[] = ($string = substr($string, 0, $pos)); |
||
258 | } |
||
259 | return $result; |
||
260 | } |
||
261 | |||
262 | /** |
||
263 | * Filter duplicates from an array |
||
264 | * |
||
265 | * @param string[] $array |
||
266 | * @return string[] |
||
267 | */ |
||
268 | private function filterDuplicates($array) |
||
269 | { |
||
270 | $result = []; |
||
271 | foreach ($array as $value) { |
||
272 | if (!in_array($value, $result)) { |
||
273 | $result[] = $value; |
||
274 | } |
||
275 | } |
||
276 | return array_filter($result); |
||
277 | } |
||
278 | |||
279 | /** |
||
280 | * Get products |
||
281 | * |
||
282 | * @return string[] |
||
283 | */ |
||
284 | public function getProducts() |
||
285 | { |
||
286 | return $this->filterDuplicates(array_merge([ |
||
287 | $this->originProduct, |
||
288 | $this->product, |
||
289 | preg_replace('/[^A-Za-z0-9]/', '', $this->product), // in case of special characters |
||
290 | ], |
||
291 | $this->explode($this->product, '-') |
||
292 | )); |
||
293 | } |
||
294 | } |
||
295 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.