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 ( f32111...37d64c )
by Jan-Petter
02:06
created

UserAgentParser   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 15
Bugs 5 Features 2
Metric Value
wmc 33
c 15
b 5
f 2
lcom 1
cbo 3
dl 0
loc 268
rs 9.3999

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
A split() 0 8 2
A validateProduct() 0 12 3
A blacklistCheck() 0 14 3
A validateVersion() 0 14 4
A getUserAgent() 0 6 2
A getProduct() 0 4 1
A getVersion() 0 4 2
A getMostSpecific() 0 15 4
A getUserAgents() 0 7 1
A getVersions() 0 20 3
A explode() 0 8 2
A filterDuplicates() 0 10 3
A getProducts() 0 10 1
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 (!empty($this->version) &&
138
            (
139
                str_replace('+', '', filter_var($this->version, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION)) !== $this->version ||
140
                version_compare($this->version, '0.0.1', '>=') === false
141
            )
142
        ) {
143
            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);
144
        }
145
        $this->version = trim($this->version, '.0');
146
        return true;
147
    }
148
149
    /**
150
     * Get User-agent
151
     *
152
     * @return string
153
     */
154
    public function getUserAgent()
155
    {
156
        $product = $this->getProduct();
157
        $version = $this->getVersion();
158
        return $version === null ? $product : $product . '/' . $version;
159
    }
160
161
    /**
162
     * Get product
163
     *
164
     * @return string
165
     */
166
    public function getProduct()
167
    {
168
        return $this->product;
169
    }
170
171
    /**
172
     * Get version
173
     *
174
     * @return float|int|string|null
175
     */
176
    public function getVersion()
177
    {
178
        return empty($this->version) ? null : $this->version;
179
    }
180
181
    /**
182
     * Find the best matching User-agent
183
     *
184
     * @param string[] $userAgents
185
     * @return string|false
186
     */
187
    public function getMostSpecific(array $userAgents)
188
    {
189
        $array = [];
190
        foreach ($userAgents as $string) {
191
            // Strip non-US-ASCII characters
192
            $array[$string] = strtolower(preg_replace(self::PREG_PATTERN, '', $string));
193
        }
194
        foreach (array_map('strtolower', $this->getUserAgents()) as $generated) {
195
            if (($result = array_search($generated, $array)) !== false) {
196
                // Match found
197
                return $result;
198
            }
199
        }
200
        return false;
201
    }
202
203
    /**
204
     * Get an array of all possible User-agent combinations
205
     *
206
     * @return string[]
207
     */
208
    public function getUserAgents()
209
    {
210
        return array_merge(
211
            preg_filter('/^/', $this->product . '/', $this->getVersions()),
212
            $this->getProducts()
213
        );
214
    }
215
216
    /**
217
     * Get versions
218
     *
219
     * @return float[]|int[]|string[]
220
     */
221
    public function getVersions()
222
    {
223
        while (
224
            !empty($this->version) &&
225
            substr_count($this->version, '.') < 2
226
        ) {
227
            $this->version .= '.0';
228
        }
229
        // Remove part by part of the version.
230
        $result = array_merge(
231
            [$this->version],
232
            $this->explode($this->version, '.')
233
        );
234
        asort($result);
235
        usort($result, function ($a, $b) {
236
            // PHP 7: Switch to the <=> "Spaceship" operator
237
            return strlen($b) - strlen($a);
238
        });
239
        return $this->filterDuplicates($result);
240
    }
241
242
    /**
243
     * Explode
244
     *
245
     * @param string $string
246
     * @param string $delimiter
247
     * @return string[]
248
     */
249
    private function explode($string, $delimiter)
250
    {
251
        $result = [];
252
        while (($pos = strrpos($string, $delimiter)) !== false) {
253
            $result[] = ($string = substr($string, 0, $pos));
254
        }
255
        return $result;
256
    }
257
258
    /**
259
     * Filter duplicates from an array
260
     *
261
     * @param string[] $array
262
     * @return string[]
263
     */
264
    private function filterDuplicates($array)
265
    {
266
        $result = [];
267
        foreach ($array as $value) {
268
            if (!in_array($value, $result)) {
269
                $result[] = $value;
270
            }
271
        }
272
        return array_filter($result);
273
    }
274
275
    /**
276
     * Get products
277
     *
278
     * @return string[]
279
     */
280
    public function getProducts()
281
    {
282
        return $this->filterDuplicates(array_merge([
283
            $this->originProduct,
284
            $this->product,
285
            preg_replace('/[^A-Za-z0-9]/', '', $this->product), // in case of special characters
286
        ],
287
            $this->explode($this->product, '-')
288
        ));
289
    }
290
}
291