GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( b2c5a8...c149c2 )
by Jan-Petter
02:05
created

UserAgentParser::validateVersion()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 0
loc 16
rs 8.8571
cc 5
eloc 10
nc 3
nop 0
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\FormatException;
12
use vipnytt\UserAgentParser\Exceptions\ProductException;
13
use vipnytt\UserAgentParser\Exceptions\VersionException;
14
15
/**
16
 * Class UserAgentParser
17
 *
18
 * @link https://tools.ietf.org/html/rfc7231
19
 * @link https://tools.ietf.org/html/rfc7230
20
 *
21
 * @package vipnytt
22
 */
23
class UserAgentParser
24
{
25
    /**
26
     * RFC 7231 - Section 5.5.3 - User-agent
27
     */
28
    const RFC_README = 'https://tools.ietf.org/html/rfc7231#section-5.5.3';
29
30
    /**
31
     * PREG pattern for valid characters
32
     */
33
    const PREG_PATTERN = '/[^\x21-\x7E]/';
34
35
    /**
36
     * Origin product
37
     * @var string
38
     */
39
    private $originProduct;
40
41
    /**
42
     * Product
43
     * @var string
44
     */
45
    private $product;
46
47
    /**
48
     * Version
49
     * @var float|int|string|null
50
     */
51
    private $version;
52
53
    /**
54
     * UserAgentParser constructor
55
     *
56
     * @param string $product
57
     * @param float|int|string|null $version
58
     */
59
    public function __construct($product, $version = null)
60
    {
61
        $this->product = $product;
62
        $this->version = $version;
63
        if (strpos($this->product, '/') !== false) {
64
            $this->split();
65
        }
66
        $this->validateProduct();
67
        $this->validateVersion();
68
    }
69
70
    /**
71
     * Split Product and Version
72
     *
73
     * @return bool
74
     */
75
    private function split()
76
    {
77
        if (count($parts = explode('/', trim($this->product . '/' . $this->version, '/'), 2)) === 2) {
78
            $this->product = $parts[0];
79
            $this->version = $parts[1];
80
        }
81
        return true;
82
    }
83
84
    /**
85
     * Validate the Product format
86
     * @link https://tools.ietf.org/html/rfc7231#section-5.5.3
87
     * @link https://tools.ietf.org/html/rfc7230#section-3.2.4
88
     *
89
     * @return bool
90
     * @throws ProductException
91
     */
92
    private function validateProduct()
93
    {
94
        $this->blacklistCheck($this->product);
95
        $this->originProduct = $this->product;
96
        if ($this->originProduct !== ($this->product = preg_replace(self::PREG_PATTERN, '', $this->product))) {
97
            trigger_error("Product name contains invalid characters. Truncated to `$this->product`.", E_USER_NOTICE);
98
        }
99
        if (empty($this->product)) {
100
            throw new ProductException('Product string cannot be empty.');
101
        }
102
        return true;
103
    }
104
105
    /**
106
     * Check for blacklisted strings or characters
107
     *
108
     * @param float|int|string|null $input
109
     * @return bool
110
     * @throws FormatException
111
     */
112
    private function blacklistCheck($input)
113
    {
114
        foreach ([
115
                     'mozilla',
116
                     'compatible',
117
                     '(',
118
                     ')',
119
                 ] as $string) {
120
            if (stripos($input, $string) !== false) {
121
                throw new 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);
122
            }
123
        }
124
        return true;
125
    }
126
127
    /**
128
     * Validate the Version and it's format
129
     * @link https://tools.ietf.org/html/rfc7231#section-5.5.3
130
     *
131
     * @return bool
132
     * @throws VersionException
133
     */
134
    private function validateVersion()
135
    {
136
        $this->blacklistCheck($this->version);
137
        if (
138
            !empty($this->version) &&
139
            (
140
                str_replace('+', '', filter_var($this->version, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)) ||
141
                version_compare($this->version, '0.0.1', '>=') === false
142
            )
143
        ) {
144
            throw new 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);
145
        }
146
        $new = trim($this->version, '.0');
147
        $this->version = empty($new) ? null : $new;
148
        return true;
149
    }
150
151
    /**
152
     * Get User-agent
153
     *
154
     * @return string
155
     */
156
    public function getUserAgent()
157
    {
158
        $product = $this->getProduct();
159
        $version = $this->getVersion();
160
        return $version === null ? $product : $product . '/' . $version;
161
    }
162
163
    /**
164
     * Get product
165
     *
166
     * @return string
167
     */
168
    public function getProduct()
169
    {
170
        return $this->product;
171
    }
172
173
    /**
174
     * Get version
175
     *
176
     * @return float|int|string|null
177
     */
178
    public function getVersion()
179
    {
180
        return empty($this->version) ? null : $this->version;
181
    }
182
183
    /**
184
     * Find the best matching User-agent
185
     *
186
     * @param string[] $userAgents
187
     * @return string|false
188
     */
189
    public function getMostSpecific(array $userAgents)
190
    {
191
        $array = [];
192
        foreach ($userAgents as $string) {
193
            // Strip non-US-ASCII characters
194
            $array[$string] = strtolower(preg_replace(self::PREG_PATTERN, '', $string));
195
        }
196
        foreach (array_map('strtolower', $this->getUserAgents()) as $generated) {
197
            if (($result = array_search($generated, $array)) !== false) {
198
                // Match found
199
                return $result;
200
            }
201
        }
202
        return false;
203
    }
204
205
    /**
206
     * Get an array of all possible User-agent combinations
207
     *
208
     * @return string[]
209
     */
210
    public function getUserAgents()
211
    {
212
        return array_merge(
213
            preg_filter('/^/', $this->product . '/', $this->getVersions()),
214
            $this->getProducts()
215
        );
216
    }
217
218
    /**
219
     * Get versions
220
     *
221
     * @return float[]|int[]|string[]
222
     */
223
    public function getVersions()
224
    {
225
        while (
226
            !empty($this->version) &&
227
            substr_count($this->version, '.') < 2
228
        ) {
229
            $this->version .= '.0';
230
        }
231
        // Remove part by part of the version.
232
        $result = array_merge(
233
            [$this->version],
234
            $this->explode($this->version, '.')
235
        );
236
        asort($result);
237
        usort($result, function ($a, $b) {
238
            // PHP 7: Switch to the <=> "Spaceship" operator
239
            return strlen($b) - strlen($a);
240
        });
241
        return $this->filterDuplicates($result);
242
    }
243
244
    /**
245
     * Explode
246
     *
247
     * @param string $string
248
     * @param string $delimiter
249
     * @return string[]
250
     */
251
    private function explode($string, $delimiter)
252
    {
253
        $result = [];
254
        while (($pos = strrpos($string, $delimiter)) !== false) {
255
            $result[] = ($string = substr($string, 0, $pos));
256
        }
257
        return $result;
258
    }
259
260
    /**
261
     * Filter duplicates from an array
262
     *
263
     * @param string[] $array
264
     * @return string[]
265
     */
266
    private function filterDuplicates($array)
267
    {
268
        $result = [];
269
        foreach ($array as $value) {
270
            if (!in_array($value, $result)) {
271
                $result[] = $value;
272
            }
273
        }
274
        return array_filter($result);
275
    }
276
277
    /**
278
     * Get products
279
     *
280
     * @return string[]
281
     */
282
    public function getProducts()
283
    {
284
        return $this->filterDuplicates(array_merge([
285
            $this->originProduct,
286
            $this->product,
287
            preg_replace('/[^A-Za-z0-9]/', '', $this->product), // in case of special characters
288
        ],
289
            $this->explode($this->product, '-')
290
        ));
291
    }
292
}
293