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 ( 699fee...fc68af )
by Jan-Petter
03:11
created

UserAgentParser::validateVersion()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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