xref: /plugin/statistics/vendor/matomo/device-detector/Parser/Client/Browser.php (revision d5ef99ddb7dfb0cfae33e9257bd1d788f682c50f)
1*d5ef99ddSAndreas Gohr<?php
2*d5ef99ddSAndreas Gohr
3*d5ef99ddSAndreas Gohr/**
4*d5ef99ddSAndreas Gohr * Device Detector - The Universal Device Detection library for parsing User Agents
5*d5ef99ddSAndreas Gohr *
6*d5ef99ddSAndreas Gohr * @link https://matomo.org
7*d5ef99ddSAndreas Gohr *
8*d5ef99ddSAndreas Gohr * @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
9*d5ef99ddSAndreas Gohr */
10*d5ef99ddSAndreas Gohr
11*d5ef99ddSAndreas Gohrdeclare(strict_types=1);
12*d5ef99ddSAndreas Gohr
13*d5ef99ddSAndreas Gohrnamespace DeviceDetector\Parser\Client;
14*d5ef99ddSAndreas Gohr
15*d5ef99ddSAndreas Gohruse DeviceDetector\Cache\CacheInterface;
16*d5ef99ddSAndreas Gohruse DeviceDetector\ClientHints;
17*d5ef99ddSAndreas Gohruse DeviceDetector\Parser\Client\Browser\Engine;
18*d5ef99ddSAndreas Gohruse DeviceDetector\Parser\Client\Hints\BrowserHints;
19*d5ef99ddSAndreas Gohruse DeviceDetector\Yaml\ParserInterface as YamlParser;
20*d5ef99ddSAndreas Gohr
21*d5ef99ddSAndreas Gohr/**
22*d5ef99ddSAndreas Gohr * Class Browser
23*d5ef99ddSAndreas Gohr *
24*d5ef99ddSAndreas Gohr * Client parser for browser detection
25*d5ef99ddSAndreas Gohr */
26*d5ef99ddSAndreas Gohrclass Browser extends AbstractClientParser
27*d5ef99ddSAndreas Gohr{
28*d5ef99ddSAndreas Gohr    /**
29*d5ef99ddSAndreas Gohr     * @var BrowserHints
30*d5ef99ddSAndreas Gohr     */
31*d5ef99ddSAndreas Gohr    private $browserHints;
32*d5ef99ddSAndreas Gohr
33*d5ef99ddSAndreas Gohr    /**
34*d5ef99ddSAndreas Gohr     * @var string
35*d5ef99ddSAndreas Gohr     */
36*d5ef99ddSAndreas Gohr    protected $fixtureFile = 'regexes/client/browsers.yml';
37*d5ef99ddSAndreas Gohr
38*d5ef99ddSAndreas Gohr    /**
39*d5ef99ddSAndreas Gohr     * @var string
40*d5ef99ddSAndreas Gohr     */
41*d5ef99ddSAndreas Gohr    protected $parserName = 'browser';
42*d5ef99ddSAndreas Gohr
43*d5ef99ddSAndreas Gohr    /**
44*d5ef99ddSAndreas Gohr     * Known browsers mapped to their internal short codes
45*d5ef99ddSAndreas Gohr     *
46*d5ef99ddSAndreas Gohr     * @var array
47*d5ef99ddSAndreas Gohr     */
48*d5ef99ddSAndreas Gohr    protected static $availableBrowsers = [
49*d5ef99ddSAndreas Gohr        'V1' => 'Via',
50*d5ef99ddSAndreas Gohr        '1P' => 'Pure Mini Browser',
51*d5ef99ddSAndreas Gohr        '4P' => 'Pure Lite Browser',
52*d5ef99ddSAndreas Gohr        '1R' => 'Raise Fast Browser',
53*d5ef99ddSAndreas Gohr        'R1' => 'Rabbit Private Browser',
54*d5ef99ddSAndreas Gohr        'FQ' => 'Fast Browser UC Lite',
55*d5ef99ddSAndreas Gohr        'FJ' => 'Fast Explorer',
56*d5ef99ddSAndreas Gohr        '1L' => 'Lightning Browser',
57*d5ef99ddSAndreas Gohr        '1C' => 'Cake Browser',
58*d5ef99ddSAndreas Gohr        '1I' => 'IE Browser Fast',
59*d5ef99ddSAndreas Gohr        '1V' => 'Vegas Browser',
60*d5ef99ddSAndreas Gohr        '1O' => 'OH Browser',
61*d5ef99ddSAndreas Gohr        '3O' => 'OH Private Browser',
62*d5ef99ddSAndreas Gohr        '1X' => 'XBrowser Mini',
63*d5ef99ddSAndreas Gohr        '1S' => 'Sharkee Browser',
64*d5ef99ddSAndreas Gohr        '2L' => 'Lark Browser',
65*d5ef99ddSAndreas Gohr        '3P' => 'Pluma',
66*d5ef99ddSAndreas Gohr        '1A' => 'Anka Browser',
67*d5ef99ddSAndreas Gohr        'AZ' => 'Azka Browser',
68*d5ef99ddSAndreas Gohr        '1D' => 'Dragon Browser',
69*d5ef99ddSAndreas Gohr        '1E' => 'Easy Browser',
70*d5ef99ddSAndreas Gohr        'DW' => 'Dark Web Browser',
71*d5ef99ddSAndreas Gohr        'D6' => 'Dark Browser',
72*d5ef99ddSAndreas Gohr        '18' => '18+ Privacy Browser',
73*d5ef99ddSAndreas Gohr        '1B' => '115 Browser',
74*d5ef99ddSAndreas Gohr        'DM' => '1DM Browser',
75*d5ef99ddSAndreas Gohr        '1M' => '1DM+ Browser',
76*d5ef99ddSAndreas Gohr        '2B' => '2345 Browser',
77*d5ef99ddSAndreas Gohr        '3B' => '360 Secure Browser',
78*d5ef99ddSAndreas Gohr        '36' => '360 Phone Browser',
79*d5ef99ddSAndreas Gohr        '7B' => '7654 Browser',
80*d5ef99ddSAndreas Gohr        'AA' => 'Avant Browser',
81*d5ef99ddSAndreas Gohr        'AB' => 'ABrowse',
82*d5ef99ddSAndreas Gohr        '4A' => 'Acoo Browser',
83*d5ef99ddSAndreas Gohr        'BW' => 'AdBlock Browser',
84*d5ef99ddSAndreas Gohr        'A7' => 'Adult Browser',
85*d5ef99ddSAndreas Gohr        '8A' => 'Ai Browser',
86*d5ef99ddSAndreas Gohr        'A9' => 'Airfind Secure Browser',
87*d5ef99ddSAndreas Gohr        'AF' => 'ANT Fresco',
88*d5ef99ddSAndreas Gohr        'AG' => 'ANTGalio',
89*d5ef99ddSAndreas Gohr        'AL' => 'Aloha Browser',
90*d5ef99ddSAndreas Gohr        'AH' => 'Aloha Browser Lite',
91*d5ef99ddSAndreas Gohr        'A8' => 'ALVA',
92*d5ef99ddSAndreas Gohr        '9A' => 'AltiBrowser',
93*d5ef99ddSAndreas Gohr        'AM' => 'Amaya',
94*d5ef99ddSAndreas Gohr        'A3' => 'Amaze Browser',
95*d5ef99ddSAndreas Gohr        'A5' => 'Amerigo',
96*d5ef99ddSAndreas Gohr        'AO' => 'Amigo',
97*d5ef99ddSAndreas Gohr        'AN' => 'Android Browser',
98*d5ef99ddSAndreas Gohr        '3A' => 'AOL Explorer',
99*d5ef99ddSAndreas Gohr        'AE' => 'AOL Desktop',
100*d5ef99ddSAndreas Gohr        'AD' => 'AOL Shield',
101*d5ef99ddSAndreas Gohr        'A4' => 'AOL Shield Pro',
102*d5ef99ddSAndreas Gohr        '2A' => 'Aplix',
103*d5ef99ddSAndreas Gohr        'A6' => 'AppBrowzer',
104*d5ef99ddSAndreas Gohr        '0A' => 'AppTec Secure Browser',
105*d5ef99ddSAndreas Gohr        'AP' => 'APUS Browser',
106*d5ef99ddSAndreas Gohr        'AR' => 'Arora',
107*d5ef99ddSAndreas Gohr        'AX' => 'Arctic Fox',
108*d5ef99ddSAndreas Gohr        'AV' => 'Amiga Voyager',
109*d5ef99ddSAndreas Gohr        'AW' => 'Amiga Aweb',
110*d5ef99ddSAndreas Gohr        'PN' => 'APN Browser',
111*d5ef99ddSAndreas Gohr        '6A' => 'Arachne',
112*d5ef99ddSAndreas Gohr        'RA' => 'Arc Search',
113*d5ef99ddSAndreas Gohr        'R5' => 'Armorfly Browser',
114*d5ef99ddSAndreas Gohr        'AI' => 'Arvin',
115*d5ef99ddSAndreas Gohr        'AK' => 'Ask.com',
116*d5ef99ddSAndreas Gohr        'AU' => 'Asus Browser',
117*d5ef99ddSAndreas Gohr        'A0' => 'Atom',
118*d5ef99ddSAndreas Gohr        'AT' => 'Atomic Web Browser',
119*d5ef99ddSAndreas Gohr        'A2' => 'Atlas',
120*d5ef99ddSAndreas Gohr        'AS' => 'Avast Secure Browser',
121*d5ef99ddSAndreas Gohr        'VG' => 'AVG Secure Browser',
122*d5ef99ddSAndreas Gohr        'AC' => 'Avira Secure Browser',
123*d5ef99ddSAndreas Gohr        'A1' => 'AwoX',
124*d5ef99ddSAndreas Gohr        '7A' => 'Awesomium',
125*d5ef99ddSAndreas Gohr        '5B' => 'Basic Web Browser',
126*d5ef99ddSAndreas Gohr        'BA' => 'Beaker Browser',
127*d5ef99ddSAndreas Gohr        'BM' => 'Beamrise',
128*d5ef99ddSAndreas Gohr        'F7' => 'BF Browser',
129*d5ef99ddSAndreas Gohr        'BB' => 'BlackBerry Browser',
130*d5ef99ddSAndreas Gohr        '6B' => 'Bluefy',
131*d5ef99ddSAndreas Gohr        'H1' => 'BrowseHere',
132*d5ef99ddSAndreas Gohr        'B8' => 'Browser Hup Pro',
133*d5ef99ddSAndreas Gohr        'BD' => 'Baidu Browser',
134*d5ef99ddSAndreas Gohr        'BS' => 'Baidu Spark',
135*d5ef99ddSAndreas Gohr        'BG' => 'Bang',
136*d5ef99ddSAndreas Gohr        'B9' => 'Bangla Browser',
137*d5ef99ddSAndreas Gohr        'BI' => 'Basilisk',
138*d5ef99ddSAndreas Gohr        'BV' => 'Belva Browser',
139*d5ef99ddSAndreas Gohr        'B5' => 'Beyond Private Browser',
140*d5ef99ddSAndreas Gohr        'BE' => 'Beonex',
141*d5ef99ddSAndreas Gohr        'B2' => 'Berry Browser',
142*d5ef99ddSAndreas Gohr        'BT' => 'Bitchute Browser',
143*d5ef99ddSAndreas Gohr        '9B' => 'BizBrowser',
144*d5ef99ddSAndreas Gohr        'BH' => 'BlackHawk',
145*d5ef99ddSAndreas Gohr        'B0' => 'Bloket',
146*d5ef99ddSAndreas Gohr        'BJ' => 'Bunjalloo',
147*d5ef99ddSAndreas Gohr        'BL' => 'B-Line',
148*d5ef99ddSAndreas Gohr        'B6' => 'Black Lion Browser',
149*d5ef99ddSAndreas Gohr        'BU' => 'Blue Browser',
150*d5ef99ddSAndreas Gohr        'BO' => 'Bonsai',
151*d5ef99ddSAndreas Gohr        'BN' => 'Borealis Navigator',
152*d5ef99ddSAndreas Gohr        'BR' => 'Brave',
153*d5ef99ddSAndreas Gohr        'BK' => 'BriskBard',
154*d5ef99ddSAndreas Gohr        'K2' => 'BroKeep Browser',
155*d5ef99ddSAndreas Gohr        'B3' => 'Browspeed Browser',
156*d5ef99ddSAndreas Gohr        'BX' => 'BrowseX',
157*d5ef99ddSAndreas Gohr        'BZ' => 'Browzar',
158*d5ef99ddSAndreas Gohr        'B7' => 'Browlser',
159*d5ef99ddSAndreas Gohr        'M9' => 'Browser Mini',
160*d5ef99ddSAndreas Gohr        '4B' => 'BrowsBit',
161*d5ef99ddSAndreas Gohr        'BY' => 'Biyubi',
162*d5ef99ddSAndreas Gohr        'BF' => 'Byffox',
163*d5ef99ddSAndreas Gohr        'B4' => 'BXE Browser',
164*d5ef99ddSAndreas Gohr        'CA' => 'Camino',
165*d5ef99ddSAndreas Gohr        '5C' => 'Catalyst',
166*d5ef99ddSAndreas Gohr        'XP' => 'Catsxp',
167*d5ef99ddSAndreas Gohr        '0C' => 'Cave Browser',
168*d5ef99ddSAndreas Gohr        'CL' => 'CCleaner',
169*d5ef99ddSAndreas Gohr        'C8' => 'CG Browser',
170*d5ef99ddSAndreas Gohr        'CJ' => 'ChanjetCloud',
171*d5ef99ddSAndreas Gohr        'C6' => 'Chedot',
172*d5ef99ddSAndreas Gohr        'C9' => 'Cherry Browser',
173*d5ef99ddSAndreas Gohr        'C0' => 'Centaury',
174*d5ef99ddSAndreas Gohr        'CQ' => 'Cliqz',
175*d5ef99ddSAndreas Gohr        'CC' => 'Coc Coc',
176*d5ef99ddSAndreas Gohr        'C4' => 'CoolBrowser',
177*d5ef99ddSAndreas Gohr        'C2' => 'Colibri',
178*d5ef99ddSAndreas Gohr        '6C' => 'Columbus Browser',
179*d5ef99ddSAndreas Gohr        'CD' => 'Comodo Dragon',
180*d5ef99ddSAndreas Gohr        'C1' => 'Coast',
181*d5ef99ddSAndreas Gohr        'CX' => 'Charon',
182*d5ef99ddSAndreas Gohr        'CE' => 'CM Browser',
183*d5ef99ddSAndreas Gohr        'C7' => 'CM Mini',
184*d5ef99ddSAndreas Gohr        'CF' => 'Chrome Frame',
185*d5ef99ddSAndreas Gohr        'HC' => 'Headless Chrome',
186*d5ef99ddSAndreas Gohr        'CH' => 'Chrome',
187*d5ef99ddSAndreas Gohr        'CI' => 'Chrome Mobile iOS',
188*d5ef99ddSAndreas Gohr        'CK' => 'Conkeror',
189*d5ef99ddSAndreas Gohr        'CM' => 'Chrome Mobile',
190*d5ef99ddSAndreas Gohr        '3C' => 'Chowbo',
191*d5ef99ddSAndreas Gohr        '7C' => 'Classilla',
192*d5ef99ddSAndreas Gohr        'CN' => 'CoolNovo',
193*d5ef99ddSAndreas Gohr        '4C' => 'Colom Browser',
194*d5ef99ddSAndreas Gohr        'CO' => 'CometBird',
195*d5ef99ddSAndreas Gohr        '2C' => 'Comfort Browser',
196*d5ef99ddSAndreas Gohr        'CB' => 'COS Browser',
197*d5ef99ddSAndreas Gohr        'CW' => 'Cornowser',
198*d5ef99ddSAndreas Gohr        'C3' => 'Chim Lac',
199*d5ef99ddSAndreas Gohr        'CP' => 'ChromePlus',
200*d5ef99ddSAndreas Gohr        'CR' => 'Chromium',
201*d5ef99ddSAndreas Gohr        'C5' => 'Chromium GOST',
202*d5ef99ddSAndreas Gohr        'CY' => 'Cyberfox',
203*d5ef99ddSAndreas Gohr        'CS' => 'Cheshire',
204*d5ef99ddSAndreas Gohr        '8C' => 'Cromite',
205*d5ef99ddSAndreas Gohr        'RC' => 'Crow Browser',
206*d5ef99ddSAndreas Gohr        'CT' => 'Crusta',
207*d5ef99ddSAndreas Gohr        'CG' => 'Craving Explorer',
208*d5ef99ddSAndreas Gohr        'CZ' => 'Crazy Browser',
209*d5ef99ddSAndreas Gohr        'CU' => 'Cunaguaro',
210*d5ef99ddSAndreas Gohr        'CV' => 'Chrome Webview',
211*d5ef99ddSAndreas Gohr        'YC' => 'CyBrowser',
212*d5ef99ddSAndreas Gohr        'DB' => 'dbrowser',
213*d5ef99ddSAndreas Gohr        'PD' => 'Peeps dBrowser',
214*d5ef99ddSAndreas Gohr        'DK' => 'Dark Web',
215*d5ef99ddSAndreas Gohr        'DP' => 'Dark Web Private',
216*d5ef99ddSAndreas Gohr        'D1' => 'Debuggable Browser',
217*d5ef99ddSAndreas Gohr        'DC' => 'Decentr',
218*d5ef99ddSAndreas Gohr        'DE' => 'Deepnet Explorer',
219*d5ef99ddSAndreas Gohr        'DG' => 'deg-degan',
220*d5ef99ddSAndreas Gohr        'DA' => 'Deledao',
221*d5ef99ddSAndreas Gohr        'DT' => 'Delta Browser',
222*d5ef99ddSAndreas Gohr        'D0' => 'Desi Browser',
223*d5ef99ddSAndreas Gohr        'DS' => 'DeskBrowse',
224*d5ef99ddSAndreas Gohr        'D3' => 'Dezor',
225*d5ef99ddSAndreas Gohr        'II' => 'Diigo Browser',
226*d5ef99ddSAndreas Gohr        'D2' => 'DoCoMo',
227*d5ef99ddSAndreas Gohr        'DF' => 'Dolphin',
228*d5ef99ddSAndreas Gohr        'DZ' => 'Dolphin Zero',
229*d5ef99ddSAndreas Gohr        'DO' => 'Dorado',
230*d5ef99ddSAndreas Gohr        'DR' => 'Dot Browser',
231*d5ef99ddSAndreas Gohr        'DL' => 'Dooble',
232*d5ef99ddSAndreas Gohr        'DI' => 'Dillo',
233*d5ef99ddSAndreas Gohr        'DU' => 'DUC Browser',
234*d5ef99ddSAndreas Gohr        'DD' => 'DuckDuckGo Privacy Browser',
235*d5ef99ddSAndreas Gohr        'E1' => 'East Browser',
236*d5ef99ddSAndreas Gohr        'EC' => 'Ecosia',
237*d5ef99ddSAndreas Gohr        'EW' => 'Edge WebView',
238*d5ef99ddSAndreas Gohr        'EV' => 'Every Browser',
239*d5ef99ddSAndreas Gohr        'EI' => 'Epic',
240*d5ef99ddSAndreas Gohr        'EL' => 'Elinks',
241*d5ef99ddSAndreas Gohr        'EN' => 'EinkBro',
242*d5ef99ddSAndreas Gohr        'EB' => 'Element Browser',
243*d5ef99ddSAndreas Gohr        'EE' => 'Elements Browser',
244*d5ef99ddSAndreas Gohr        'EO' => 'Eolie',
245*d5ef99ddSAndreas Gohr        'EX' => 'Explore Browser',
246*d5ef99ddSAndreas Gohr        'EZ' => 'eZ Browser',
247*d5ef99ddSAndreas Gohr        'E2' => 'EudoraWeb',
248*d5ef99ddSAndreas Gohr        'EU' => 'EUI Browser',
249*d5ef99ddSAndreas Gohr        'EP' => 'GNOME Web',
250*d5ef99ddSAndreas Gohr        'G1' => 'G Browser',
251*d5ef99ddSAndreas Gohr        'ES' => 'Espial TV Browser',
252*d5ef99ddSAndreas Gohr        'FG' => 'fGet',
253*d5ef99ddSAndreas Gohr        'FA' => 'Falkon',
254*d5ef99ddSAndreas Gohr        'FX' => 'Faux Browser',
255*d5ef99ddSAndreas Gohr        'F8' => 'Fire Browser',
256*d5ef99ddSAndreas Gohr        'F4' => 'Fiery Browser',
257*d5ef99ddSAndreas Gohr        'F1' => 'Firefox Mobile iOS',
258*d5ef99ddSAndreas Gohr        'FB' => 'Firebird',
259*d5ef99ddSAndreas Gohr        'FD' => 'Fluid',
260*d5ef99ddSAndreas Gohr        'FE' => 'Fennec',
261*d5ef99ddSAndreas Gohr        'FF' => 'Firefox',
262*d5ef99ddSAndreas Gohr        'FK' => 'Firefox Focus',
263*d5ef99ddSAndreas Gohr        'FY' => 'Firefox Reality',
264*d5ef99ddSAndreas Gohr        'FR' => 'Firefox Rocket',
265*d5ef99ddSAndreas Gohr        '1F' => 'Firefox Klar',
266*d5ef99ddSAndreas Gohr        'F0' => 'Float Browser',
267*d5ef99ddSAndreas Gohr        'FL' => 'Flock',
268*d5ef99ddSAndreas Gohr        'FP' => 'Floorp',
269*d5ef99ddSAndreas Gohr        'FO' => 'Flow',
270*d5ef99ddSAndreas Gohr        'F2' => 'Flow Browser',
271*d5ef99ddSAndreas Gohr        'FM' => 'Firefox Mobile',
272*d5ef99ddSAndreas Gohr        'FW' => 'Fireweb',
273*d5ef99ddSAndreas Gohr        'FN' => 'Fireweb Navigator',
274*d5ef99ddSAndreas Gohr        'FH' => 'Flash Browser',
275*d5ef99ddSAndreas Gohr        'FS' => 'Flast',
276*d5ef99ddSAndreas Gohr        'F5' => 'Flyperlink',
277*d5ef99ddSAndreas Gohr        'F9' => 'FOSS Browser',
278*d5ef99ddSAndreas Gohr        'FU' => 'FreeU',
279*d5ef99ddSAndreas Gohr        'F6' => 'Freedom Browser',
280*d5ef99ddSAndreas Gohr        'FT' => 'Frost',
281*d5ef99ddSAndreas Gohr        'F3' => 'Frost+',
282*d5ef99ddSAndreas Gohr        'FI' => 'Fulldive',
283*d5ef99ddSAndreas Gohr        'GA' => 'Galeon',
284*d5ef99ddSAndreas Gohr        'G8' => 'Gener8',
285*d5ef99ddSAndreas Gohr        'GH' => 'Ghostery Privacy Browser',
286*d5ef99ddSAndreas Gohr        'GI' => 'GinxDroid Browser',
287*d5ef99ddSAndreas Gohr        'GB' => 'Glass Browser',
288*d5ef99ddSAndreas Gohr        'GD' => 'Godzilla Browser',
289*d5ef99ddSAndreas Gohr        'G3' => 'Good Browser',
290*d5ef99ddSAndreas Gohr        'GE' => 'Google Earth',
291*d5ef99ddSAndreas Gohr        'GP' => 'Google Earth Pro',
292*d5ef99ddSAndreas Gohr        'GO' => 'GOG Galaxy',
293*d5ef99ddSAndreas Gohr        'GR' => 'GoBrowser',
294*d5ef99ddSAndreas Gohr        'GK' => 'GoKu',
295*d5ef99ddSAndreas Gohr        'G2' => 'GO Browser',
296*d5ef99ddSAndreas Gohr        'RN' => 'GreenBrowser',
297*d5ef99ddSAndreas Gohr        'HW' => 'Habit Browser',
298*d5ef99ddSAndreas Gohr        'H7' => 'Halo Browser',
299*d5ef99ddSAndreas Gohr        'HB' => 'Harman Browser',
300*d5ef99ddSAndreas Gohr        'HS' => 'HasBrowser',
301*d5ef99ddSAndreas Gohr        'HA' => 'Hawk Turbo Browser',
302*d5ef99ddSAndreas Gohr        'HQ' => 'Hawk Quick Browser',
303*d5ef99ddSAndreas Gohr        'HE' => 'Helio',
304*d5ef99ddSAndreas Gohr        'HN' => 'Herond Browser',
305*d5ef99ddSAndreas Gohr        'HX' => 'Hexa Web Browser',
306*d5ef99ddSAndreas Gohr        'HI' => 'Hi Browser',
307*d5ef99ddSAndreas Gohr        'HO' => 'hola! Browser',
308*d5ef99ddSAndreas Gohr        'H4' => 'Holla Web Browser',
309*d5ef99ddSAndreas Gohr        'H5' => 'HotBrowser',
310*d5ef99ddSAndreas Gohr        'HJ' => 'HotJava',
311*d5ef99ddSAndreas Gohr        'H6' => 'HONOR Browser',
312*d5ef99ddSAndreas Gohr        'HT' => 'HTC Browser',
313*d5ef99ddSAndreas Gohr        'HU' => 'Huawei Browser Mobile',
314*d5ef99ddSAndreas Gohr        'HP' => 'Huawei Browser',
315*d5ef99ddSAndreas Gohr        'H3' => 'HUB Browser',
316*d5ef99ddSAndreas Gohr        'IO' => 'iBrowser',
317*d5ef99ddSAndreas Gohr        'IS' => 'iBrowser Mini',
318*d5ef99ddSAndreas Gohr        'IB' => 'IBrowse',
319*d5ef99ddSAndreas Gohr        'I6' => 'iDesktop PC Browser',
320*d5ef99ddSAndreas Gohr        'IC' => 'iCab',
321*d5ef99ddSAndreas Gohr        'I2' => 'iCab Mobile',
322*d5ef99ddSAndreas Gohr        '4I' => 'iNet Browser',
323*d5ef99ddSAndreas Gohr        'I1' => 'Iridium',
324*d5ef99ddSAndreas Gohr        'I3' => 'Iron Mobile',
325*d5ef99ddSAndreas Gohr        'I4' => 'IceCat',
326*d5ef99ddSAndreas Gohr        'ID' => 'IceDragon',
327*d5ef99ddSAndreas Gohr        'IV' => 'Isivioo',
328*d5ef99ddSAndreas Gohr        'I8' => 'IVVI Browser',
329*d5ef99ddSAndreas Gohr        'IW' => 'Iceweasel',
330*d5ef99ddSAndreas Gohr        '2I' => 'Impervious Browser',
331*d5ef99ddSAndreas Gohr        'N3' => 'Incognito Browser',
332*d5ef99ddSAndreas Gohr        'IN' => 'Inspect Browser',
333*d5ef99ddSAndreas Gohr        'I9' => 'Insta Browser',
334*d5ef99ddSAndreas Gohr        'IE' => 'Internet Explorer',
335*d5ef99ddSAndreas Gohr        'I7' => 'Internet Browser Secure',
336*d5ef99ddSAndreas Gohr        '5I' => 'Internet Webbrowser',
337*d5ef99ddSAndreas Gohr        '3I' => 'Intune Managed Browser',
338*d5ef99ddSAndreas Gohr        'I5' => 'Indian UC Mini Browser',
339*d5ef99ddSAndreas Gohr        'Z0' => 'InBrowser',
340*d5ef99ddSAndreas Gohr        'IG' => 'Involta Go',
341*d5ef99ddSAndreas Gohr        'IM' => 'IE Mobile',
342*d5ef99ddSAndreas Gohr        'IR' => 'Iron',
343*d5ef99ddSAndreas Gohr        'JB' => 'Japan Browser',
344*d5ef99ddSAndreas Gohr        'JS' => 'Jasmine',
345*d5ef99ddSAndreas Gohr        'JA' => 'JavaFX',
346*d5ef99ddSAndreas Gohr        'JL' => 'Jelly',
347*d5ef99ddSAndreas Gohr        'JI' => 'Jig Browser',
348*d5ef99ddSAndreas Gohr        'JP' => 'Jig Browser Plus',
349*d5ef99ddSAndreas Gohr        'JO' => 'JioSphere',
350*d5ef99ddSAndreas Gohr        'JZ' => 'JUZI Browser',
351*d5ef99ddSAndreas Gohr        'KB' => 'K.Browser',
352*d5ef99ddSAndreas Gohr        'KF' => 'Keepsafe Browser',
353*d5ef99ddSAndreas Gohr        'K7' => 'KeepSolid Browser',
354*d5ef99ddSAndreas Gohr        'KS' => 'Kids Safe Browser',
355*d5ef99ddSAndreas Gohr        'KI' => 'Kindle Browser',
356*d5ef99ddSAndreas Gohr        'KM' => 'K-meleon',
357*d5ef99ddSAndreas Gohr        'KJ' => 'K-Ninja',
358*d5ef99ddSAndreas Gohr        'KO' => 'Konqueror',
359*d5ef99ddSAndreas Gohr        'KP' => 'Kapiko',
360*d5ef99ddSAndreas Gohr        'KE' => 'Keyboard Browser',
361*d5ef99ddSAndreas Gohr        'KN' => 'Kinza',
362*d5ef99ddSAndreas Gohr        'K4' => 'Kitt',
363*d5ef99ddSAndreas Gohr        'KW' => 'Kiwi',
364*d5ef99ddSAndreas Gohr        'KD' => 'Kode Browser',
365*d5ef99ddSAndreas Gohr        'KU' => 'KUN',
366*d5ef99ddSAndreas Gohr        'KT' => 'KUTO Mini Browser',
367*d5ef99ddSAndreas Gohr        'KY' => 'Kylo',
368*d5ef99ddSAndreas Gohr        'KZ' => 'Kazehakase',
369*d5ef99ddSAndreas Gohr        'LB' => 'Cheetah Browser',
370*d5ef99ddSAndreas Gohr        'LD' => 'Ladybird',
371*d5ef99ddSAndreas Gohr        'LA' => 'Lagatos Browser',
372*d5ef99ddSAndreas Gohr        'GN' => 'Legan Browser',
373*d5ef99ddSAndreas Gohr        'LR' => 'Lexi Browser',
374*d5ef99ddSAndreas Gohr        'LV' => 'Lenovo Browser',
375*d5ef99ddSAndreas Gohr        'LF' => 'LieBaoFast',
376*d5ef99ddSAndreas Gohr        'LG' => 'LG Browser',
377*d5ef99ddSAndreas Gohr        'LH' => 'Light',
378*d5ef99ddSAndreas Gohr        'L4' => 'Lightning Browser Plus',
379*d5ef99ddSAndreas Gohr        'L1' => 'Lilo',
380*d5ef99ddSAndreas Gohr        'LI' => 'Links',
381*d5ef99ddSAndreas Gohr        'RI' => 'Liri Browser',
382*d5ef99ddSAndreas Gohr        'LC' => 'LogicUI TV Browser',
383*d5ef99ddSAndreas Gohr        'IF' => 'Lolifox',
384*d5ef99ddSAndreas Gohr        'L3' => 'Lotus',
385*d5ef99ddSAndreas Gohr        'LO' => 'Lovense Browser',
386*d5ef99ddSAndreas Gohr        'LT' => 'LT Browser',
387*d5ef99ddSAndreas Gohr        'LU' => 'LuaKit',
388*d5ef99ddSAndreas Gohr        'LJ' => 'LUJO TV Browser',
389*d5ef99ddSAndreas Gohr        'LL' => 'Lulumi',
390*d5ef99ddSAndreas Gohr        'LS' => 'Lunascape',
391*d5ef99ddSAndreas Gohr        'LN' => 'Lunascape Lite',
392*d5ef99ddSAndreas Gohr        'LX' => 'Lynx',
393*d5ef99ddSAndreas Gohr        'L2' => 'Lynket Browser',
394*d5ef99ddSAndreas Gohr        'MD' => 'Mandarin',
395*d5ef99ddSAndreas Gohr        'MP' => 'Maple',
396*d5ef99ddSAndreas Gohr        'M5' => 'MarsLab Web Browser',
397*d5ef99ddSAndreas Gohr        'M7' => 'MaxBrowser',
398*d5ef99ddSAndreas Gohr        'M1' => 'mCent',
399*d5ef99ddSAndreas Gohr        'MB' => 'MicroB',
400*d5ef99ddSAndreas Gohr        'MC' => 'NCSA Mosaic',
401*d5ef99ddSAndreas Gohr        'MZ' => 'Meizu Browser',
402*d5ef99ddSAndreas Gohr        'ME' => 'Mercury',
403*d5ef99ddSAndreas Gohr        'M2' => 'Me Browser',
404*d5ef99ddSAndreas Gohr        'MF' => 'Mobile Safari',
405*d5ef99ddSAndreas Gohr        'MI' => 'Midori',
406*d5ef99ddSAndreas Gohr        'M3' => 'Midori Lite',
407*d5ef99ddSAndreas Gohr        'M6' => 'MixerBox AI',
408*d5ef99ddSAndreas Gohr        'MO' => 'Mobicip',
409*d5ef99ddSAndreas Gohr        'MU' => 'Mi Browser',
410*d5ef99ddSAndreas Gohr        'MS' => 'Mobile Silk',
411*d5ef99ddSAndreas Gohr        'MK' => 'Mogok Browser',
412*d5ef99ddSAndreas Gohr        'M8' => 'Motorola Internet Browser',
413*d5ef99ddSAndreas Gohr        'MN' => 'Minimo',
414*d5ef99ddSAndreas Gohr        'MT' => 'Mint Browser',
415*d5ef99ddSAndreas Gohr        'MX' => 'Maxthon',
416*d5ef99ddSAndreas Gohr        'M4' => 'MaxTube Browser',
417*d5ef99ddSAndreas Gohr        'MA' => 'Maelstrom',
418*d5ef99ddSAndreas Gohr        '3M' => 'Mises',
419*d5ef99ddSAndreas Gohr        'MM' => 'Mmx Browser',
420*d5ef99ddSAndreas Gohr        'NM' => 'MxNitro',
421*d5ef99ddSAndreas Gohr        'MY' => 'Mypal',
422*d5ef99ddSAndreas Gohr        'MR' => 'Monument Browser',
423*d5ef99ddSAndreas Gohr        'MW' => 'MAUI WAP Browser',
424*d5ef99ddSAndreas Gohr        'N7' => 'Naenara Browser',
425*d5ef99ddSAndreas Gohr        'NW' => 'Navigateur Web',
426*d5ef99ddSAndreas Gohr        'NK' => 'Naked Browser',
427*d5ef99ddSAndreas Gohr        'NA' => 'Naked Browser Pro',
428*d5ef99ddSAndreas Gohr        'NR' => 'NFS Browser',
429*d5ef99ddSAndreas Gohr        'N5' => 'Ninetails',
430*d5ef99ddSAndreas Gohr        'NB' => 'Nokia Browser',
431*d5ef99ddSAndreas Gohr        'NO' => 'Nokia OSS Browser',
432*d5ef99ddSAndreas Gohr        'NV' => 'Nokia Ovi Browser',
433*d5ef99ddSAndreas Gohr        'N2' => 'Norton Private Browser',
434*d5ef99ddSAndreas Gohr        'NX' => 'Nox Browser',
435*d5ef99ddSAndreas Gohr        'N1' => 'NOMone VR Browser',
436*d5ef99ddSAndreas Gohr        'N6' => 'NOOK Browser',
437*d5ef99ddSAndreas Gohr        'NE' => 'NetSurf',
438*d5ef99ddSAndreas Gohr        'NF' => 'NetFront',
439*d5ef99ddSAndreas Gohr        'NL' => 'NetFront Life',
440*d5ef99ddSAndreas Gohr        'NP' => 'NetPositive',
441*d5ef99ddSAndreas Gohr        'NS' => 'Netscape',
442*d5ef99ddSAndreas Gohr        'WR' => 'NextWord Browser',
443*d5ef99ddSAndreas Gohr        'N8' => 'Ninesky',
444*d5ef99ddSAndreas Gohr        'NT' => 'NTENT Browser',
445*d5ef99ddSAndreas Gohr        'NU' => 'Nuanti Meta',
446*d5ef99ddSAndreas Gohr        'NI' => 'Nuviu',
447*d5ef99ddSAndreas Gohr        'O9' => 'Ocean Browser',
448*d5ef99ddSAndreas Gohr        'OC' => 'Oculus Browser',
449*d5ef99ddSAndreas Gohr        'O6' => 'Odd Browser',
450*d5ef99ddSAndreas Gohr        'O1' => 'Opera Mini iOS',
451*d5ef99ddSAndreas Gohr        'OB' => 'Obigo',
452*d5ef99ddSAndreas Gohr        'O2' => 'Odin',
453*d5ef99ddSAndreas Gohr        '2O' => 'Odin Browser',
454*d5ef99ddSAndreas Gohr        'H2' => 'OceanHero',
455*d5ef99ddSAndreas Gohr        'OD' => 'Odyssey Web Browser',
456*d5ef99ddSAndreas Gohr        'OF' => 'Off By One',
457*d5ef99ddSAndreas Gohr        'O5' => 'Office Browser',
458*d5ef99ddSAndreas Gohr        'HH' => 'OhHai Browser',
459*d5ef99ddSAndreas Gohr        'OL' => 'OnBrowser Lite',
460*d5ef99ddSAndreas Gohr        'OE' => 'ONE Browser',
461*d5ef99ddSAndreas Gohr        'N4' => 'Onion Browser',
462*d5ef99ddSAndreas Gohr        '1N' => 'ONIONBrowser',
463*d5ef99ddSAndreas Gohr        'Y1' => 'Opera Crypto',
464*d5ef99ddSAndreas Gohr        'OX' => 'Opera GX',
465*d5ef99ddSAndreas Gohr        'OG' => 'Opera Neon',
466*d5ef99ddSAndreas Gohr        'OH' => 'Opera Devices',
467*d5ef99ddSAndreas Gohr        'OI' => 'Opera Mini',
468*d5ef99ddSAndreas Gohr        'OM' => 'Opera Mobile',
469*d5ef99ddSAndreas Gohr        'OP' => 'Opera',
470*d5ef99ddSAndreas Gohr        'ON' => 'Opera Next',
471*d5ef99ddSAndreas Gohr        'OO' => 'Opera Touch',
472*d5ef99ddSAndreas Gohr        'OU' => 'Orbitum',
473*d5ef99ddSAndreas Gohr        'OA' => 'Orca',
474*d5ef99ddSAndreas Gohr        'OS' => 'Ordissimo',
475*d5ef99ddSAndreas Gohr        'OR' => 'Oregano',
476*d5ef99ddSAndreas Gohr        'O0' => 'Origin In-Game Overlay',
477*d5ef99ddSAndreas Gohr        'OY' => 'Origyn Web Browser',
478*d5ef99ddSAndreas Gohr        'O8' => 'OrNET Browser',
479*d5ef99ddSAndreas Gohr        'OV' => 'Openwave Mobile Browser',
480*d5ef99ddSAndreas Gohr        'O3' => 'OpenFin',
481*d5ef99ddSAndreas Gohr        'O4' => 'Open Browser',
482*d5ef99ddSAndreas Gohr        '4U' => 'Open Browser 4U',
483*d5ef99ddSAndreas Gohr        '5G' => 'Open Browser fast 5G',
484*d5ef99ddSAndreas Gohr        '5O' => 'Open Browser Lite',
485*d5ef99ddSAndreas Gohr        'O7' => 'Open TV Browser',
486*d5ef99ddSAndreas Gohr        'OW' => 'OmniWeb',
487*d5ef99ddSAndreas Gohr        'OT' => 'Otter Browser',
488*d5ef99ddSAndreas Gohr        '4O' => 'Owl Browser',
489*d5ef99ddSAndreas Gohr        'JR' => 'OJR Browser',
490*d5ef99ddSAndreas Gohr        'PL' => 'Palm Blazer',
491*d5ef99ddSAndreas Gohr        'PM' => 'Pale Moon',
492*d5ef99ddSAndreas Gohr        'PY' => 'Polypane',
493*d5ef99ddSAndreas Gohr        '8P' => 'Prism',
494*d5ef99ddSAndreas Gohr        'PP' => 'Oppo Browser',
495*d5ef99ddSAndreas Gohr        'P6' => 'Opus Browser',
496*d5ef99ddSAndreas Gohr        'PR' => 'Palm Pre',
497*d5ef99ddSAndreas Gohr        '2E' => 'Pocket Internet Explorer',
498*d5ef99ddSAndreas Gohr        '7I' => 'Puffin Cloud Browser',
499*d5ef99ddSAndreas Gohr        '6I' => 'Puffin Incognito Browser',
500*d5ef99ddSAndreas Gohr        'PU' => 'Puffin Secure Browser',
501*d5ef99ddSAndreas Gohr        '2P' => 'Puffin Web Browser',
502*d5ef99ddSAndreas Gohr        'PW' => 'Palm WebPro',
503*d5ef99ddSAndreas Gohr        'PA' => 'Palmscape',
504*d5ef99ddSAndreas Gohr        'P7' => 'Pawxy',
505*d5ef99ddSAndreas Gohr        '0P' => 'Peach Browser',
506*d5ef99ddSAndreas Gohr        'PE' => 'Perfect Browser',
507*d5ef99ddSAndreas Gohr        'K6' => 'Perk',
508*d5ef99ddSAndreas Gohr        'P1' => 'Phantom.me',
509*d5ef99ddSAndreas Gohr        'PH' => 'Phantom Browser',
510*d5ef99ddSAndreas Gohr        'PX' => 'Phoenix',
511*d5ef99ddSAndreas Gohr        'PB' => 'Phoenix Browser',
512*d5ef99ddSAndreas Gohr        '5P' => 'Photon',
513*d5ef99ddSAndreas Gohr        'N9' => 'Pintar Browser',
514*d5ef99ddSAndreas Gohr        'P9' => 'PirateBrowser',
515*d5ef99ddSAndreas Gohr        'P8' => 'PICO Browser',
516*d5ef99ddSAndreas Gohr        'PF' => 'PlayFree Browser',
517*d5ef99ddSAndreas Gohr        'PK' => 'PocketBook Browser',
518*d5ef99ddSAndreas Gohr        'PO' => 'Polaris',
519*d5ef99ddSAndreas Gohr        'PT' => 'Polarity',
520*d5ef99ddSAndreas Gohr        'LY' => 'PolyBrowser',
521*d5ef99ddSAndreas Gohr        '9P' => 'Presearch',
522*d5ef99ddSAndreas Gohr        'BP' => 'Privacy Browser',
523*d5ef99ddSAndreas Gohr        'PI' => 'PrivacyWall',
524*d5ef99ddSAndreas Gohr        'P4' => 'Privacy Explorer Fast Safe',
525*d5ef99ddSAndreas Gohr        'X5' => 'Privacy Pioneer Browser',
526*d5ef99ddSAndreas Gohr        'P3' => 'Private Internet Browser',
527*d5ef99ddSAndreas Gohr        'P5' => 'Proxy Browser',
528*d5ef99ddSAndreas Gohr        '7P' => 'Proxyium',
529*d5ef99ddSAndreas Gohr        '6P' => 'Proxynet',
530*d5ef99ddSAndreas Gohr        '2F' => 'ProxyFox',
531*d5ef99ddSAndreas Gohr        '2M' => 'ProxyMax',
532*d5ef99ddSAndreas Gohr        'P2' => 'Pi Browser',
533*d5ef99ddSAndreas Gohr        'P0' => 'PronHub Browser',
534*d5ef99ddSAndreas Gohr        'PC' => 'PSI Secure Browser',
535*d5ef99ddSAndreas Gohr        'RW' => 'Reqwireless WebViewer',
536*d5ef99ddSAndreas Gohr        'RO' => 'Roccat',
537*d5ef99ddSAndreas Gohr        'PS' => 'Microsoft Edge',
538*d5ef99ddSAndreas Gohr        'QA' => 'Qazweb',
539*d5ef99ddSAndreas Gohr        'QI' => 'Qiyu',
540*d5ef99ddSAndreas Gohr        'QJ' => 'QJY TV Browser',
541*d5ef99ddSAndreas Gohr        'Q3' => 'Qmamu',
542*d5ef99ddSAndreas Gohr        'Q4' => 'Quick Search TV',
543*d5ef99ddSAndreas Gohr        'Q2' => 'QQ Browser Lite',
544*d5ef99ddSAndreas Gohr        'Q1' => 'QQ Browser Mini',
545*d5ef99ddSAndreas Gohr        'QQ' => 'QQ Browser',
546*d5ef99ddSAndreas Gohr        'QS' => 'Quick Browser',
547*d5ef99ddSAndreas Gohr        'QT' => 'Qutebrowser',
548*d5ef99ddSAndreas Gohr        'QU' => 'Quark',
549*d5ef99ddSAndreas Gohr        'QZ' => 'QupZilla',
550*d5ef99ddSAndreas Gohr        'QM' => 'Qwant Mobile',
551*d5ef99ddSAndreas Gohr        'Q5' => 'QtWeb',
552*d5ef99ddSAndreas Gohr        'QW' => 'QtWebEngine',
553*d5ef99ddSAndreas Gohr        'R3' => 'Rakuten Browser',
554*d5ef99ddSAndreas Gohr        'R4' => 'Rakuten Web Search',
555*d5ef99ddSAndreas Gohr        'R2' => 'Raspbian Chromium',
556*d5ef99ddSAndreas Gohr        'RT' => 'RCA Tor Explorer',
557*d5ef99ddSAndreas Gohr        'RE' => 'Realme Browser',
558*d5ef99ddSAndreas Gohr        'RK' => 'Rekonq',
559*d5ef99ddSAndreas Gohr        'RM' => 'RockMelt',
560*d5ef99ddSAndreas Gohr        'RB' => 'Roku Browser',
561*d5ef99ddSAndreas Gohr        'SB' => 'Samsung Browser',
562*d5ef99ddSAndreas Gohr        '3L' => 'Samsung Browser Lite',
563*d5ef99ddSAndreas Gohr        'SA' => 'Sailfish Browser',
564*d5ef99ddSAndreas Gohr        'R0' => 'SberBrowser',
565*d5ef99ddSAndreas Gohr        'S8' => 'Seewo Browser',
566*d5ef99ddSAndreas Gohr        'SC' => 'SEMC-Browser',
567*d5ef99ddSAndreas Gohr        'SE' => 'Sogou Explorer',
568*d5ef99ddSAndreas Gohr        'SO' => 'Sogou Mobile Browser',
569*d5ef99ddSAndreas Gohr        'RF' => 'SOTI Surf',
570*d5ef99ddSAndreas Gohr        '2S' => 'Soul Browser',
571*d5ef99ddSAndreas Gohr        'T0' => 'Soundy Browser',
572*d5ef99ddSAndreas Gohr        'SF' => 'Safari',
573*d5ef99ddSAndreas Gohr        'PV' => 'Safari Technology Preview',
574*d5ef99ddSAndreas Gohr        'S5' => 'Safe Exam Browser',
575*d5ef99ddSAndreas Gohr        'SW' => 'SalamWeb',
576*d5ef99ddSAndreas Gohr        'VN' => 'Savannah Browser',
577*d5ef99ddSAndreas Gohr        'SD' => 'SavySoda',
578*d5ef99ddSAndreas Gohr        'S9' => 'Secure Browser',
579*d5ef99ddSAndreas Gohr        'SV' => 'SFive',
580*d5ef99ddSAndreas Gohr        'SH' => 'Shiira',
581*d5ef99ddSAndreas Gohr        'K1' => 'Sidekick',
582*d5ef99ddSAndreas Gohr        'S1' => 'SimpleBrowser',
583*d5ef99ddSAndreas Gohr        '3S' => 'SilverMob US',
584*d5ef99ddSAndreas Gohr        'ZB' => 'Singlebox',
585*d5ef99ddSAndreas Gohr        'SY' => 'Sizzy',
586*d5ef99ddSAndreas Gohr        'K3' => 'Skye',
587*d5ef99ddSAndreas Gohr        'SK' => 'Skyfire',
588*d5ef99ddSAndreas Gohr        'KL' => 'SkyLeap',
589*d5ef99ddSAndreas Gohr        'SS' => 'Seraphic Sraf',
590*d5ef99ddSAndreas Gohr        'KK' => 'SiteKiosk',
591*d5ef99ddSAndreas Gohr        'SL' => 'Sleipnir',
592*d5ef99ddSAndreas Gohr        '8B' => 'SlimBoat',
593*d5ef99ddSAndreas Gohr        'S6' => 'Slimjet',
594*d5ef99ddSAndreas Gohr        'S7' => 'SP Browser',
595*d5ef99ddSAndreas Gohr        '9S' => 'Sony Small Browser',
596*d5ef99ddSAndreas Gohr        '8S' => 'Secure Private Browser',
597*d5ef99ddSAndreas Gohr        'X2' => 'SecureX',
598*d5ef99ddSAndreas Gohr        'T1' => 'Stampy Browser',
599*d5ef99ddSAndreas Gohr        '7S' => '7Star',
600*d5ef99ddSAndreas Gohr        'SQ' => 'Smart Browser',
601*d5ef99ddSAndreas Gohr        '6S' => 'Smart Search & Web Browser',
602*d5ef99ddSAndreas Gohr        'LE' => 'Smart Lenovo Browser',
603*d5ef99ddSAndreas Gohr        'OZ' => 'Smooz',
604*d5ef99ddSAndreas Gohr        'SN' => 'Snowshoe',
605*d5ef99ddSAndreas Gohr        'K5' => 'Spark',
606*d5ef99ddSAndreas Gohr        'B1' => 'Spectre Browser',
607*d5ef99ddSAndreas Gohr        'S2' => 'Splash',
608*d5ef99ddSAndreas Gohr        'SI' => 'Sputnik Browser',
609*d5ef99ddSAndreas Gohr        'SR' => 'Sunrise',
610*d5ef99ddSAndreas Gohr        '0S' => 'Sunflower Browser',
611*d5ef99ddSAndreas Gohr        'SP' => 'SuperBird',
612*d5ef99ddSAndreas Gohr        'SU' => 'Super Fast Browser',
613*d5ef99ddSAndreas Gohr        '5S' => 'SuperFast Browser',
614*d5ef99ddSAndreas Gohr        'HR' => 'Sushi Browser',
615*d5ef99ddSAndreas Gohr        'S3' => 'surf',
616*d5ef99ddSAndreas Gohr        '4S' => 'Surf Browser',
617*d5ef99ddSAndreas Gohr        'RY' => 'Surfy Browser',
618*d5ef99ddSAndreas Gohr        'SG' => 'Stargon',
619*d5ef99ddSAndreas Gohr        'S0' => 'START Internet Browser',
620*d5ef99ddSAndreas Gohr        '5A' => 'Stealth Browser',
621*d5ef99ddSAndreas Gohr        'S4' => 'Steam In-Game Overlay',
622*d5ef99ddSAndreas Gohr        'ST' => 'Streamy',
623*d5ef99ddSAndreas Gohr        'SX' => 'Swiftfox',
624*d5ef99ddSAndreas Gohr        'W7' => 'Swiftweasel',
625*d5ef99ddSAndreas Gohr        'SZ' => 'Seznam Browser',
626*d5ef99ddSAndreas Gohr        'W1' => 'Sweet Browser',
627*d5ef99ddSAndreas Gohr        '2X' => 'SX Browser',
628*d5ef99ddSAndreas Gohr        'TP' => 'T+Browser',
629*d5ef99ddSAndreas Gohr        'TR' => 'T-Browser',
630*d5ef99ddSAndreas Gohr        'TO' => 't-online.de Browser',
631*d5ef99ddSAndreas Gohr        'TT' => 'TalkTo',
632*d5ef99ddSAndreas Gohr        'TA' => 'Tao Browser',
633*d5ef99ddSAndreas Gohr        'T2' => 'tararia',
634*d5ef99ddSAndreas Gohr        'TH' => 'Thor',
635*d5ef99ddSAndreas Gohr        '1T' => 'Tor Browser',
636*d5ef99ddSAndreas Gohr        'TF' => 'TenFourFox',
637*d5ef99ddSAndreas Gohr        'TB' => 'Tenta Browser',
638*d5ef99ddSAndreas Gohr        'TE' => 'Tesla Browser',
639*d5ef99ddSAndreas Gohr        'TZ' => 'Tizen Browser',
640*d5ef99ddSAndreas Gohr        'TI' => 'Tint Browser',
641*d5ef99ddSAndreas Gohr        'TL' => 'TrueLocation Browser',
642*d5ef99ddSAndreas Gohr        'TC' => 'TUC Mini Browser',
643*d5ef99ddSAndreas Gohr        'TK' => 'TUSK',
644*d5ef99ddSAndreas Gohr        'TU' => 'Tungsten',
645*d5ef99ddSAndreas Gohr        'TG' => 'ToGate',
646*d5ef99ddSAndreas Gohr        'T3' => 'Total Browser',
647*d5ef99ddSAndreas Gohr        'TQ' => 'TQ Browser',
648*d5ef99ddSAndreas Gohr        'TS' => 'TweakStyle',
649*d5ef99ddSAndreas Gohr        'TV' => 'TV Bro',
650*d5ef99ddSAndreas Gohr        'T4' => 'TV-Browser Internet',
651*d5ef99ddSAndreas Gohr        'U0' => 'U Browser',
652*d5ef99ddSAndreas Gohr        'UB' => 'UBrowser',
653*d5ef99ddSAndreas Gohr        'UC' => 'UC Browser',
654*d5ef99ddSAndreas Gohr        'UH' => 'UC Browser HD',
655*d5ef99ddSAndreas Gohr        'UM' => 'UC Browser Mini',
656*d5ef99ddSAndreas Gohr        'UT' => 'UC Browser Turbo',
657*d5ef99ddSAndreas Gohr        'UI' => 'Ui Browser Mini',
658*d5ef99ddSAndreas Gohr        'UP' => 'UPhone Browser',
659*d5ef99ddSAndreas Gohr        'UR' => 'UR Browser',
660*d5ef99ddSAndreas Gohr        'UZ' => 'Uzbl',
661*d5ef99ddSAndreas Gohr        'UE' => 'Ume Browser',
662*d5ef99ddSAndreas Gohr        'V0' => 'vBrowser',
663*d5ef99ddSAndreas Gohr        'VA' => 'Vast Browser',
664*d5ef99ddSAndreas Gohr        'V3' => 'VD Browser',
665*d5ef99ddSAndreas Gohr        'VR' => 'Veera',
666*d5ef99ddSAndreas Gohr        'VE' => 'Venus Browser',
667*d5ef99ddSAndreas Gohr        'WD' => 'Vewd Browser',
668*d5ef99ddSAndreas Gohr        'V5' => 'VibeMate',
669*d5ef99ddSAndreas Gohr        'N0' => 'Nova Video Downloader Pro',
670*d5ef99ddSAndreas Gohr        'VS' => 'Viasat Browser',
671*d5ef99ddSAndreas Gohr        'VI' => 'Vivaldi',
672*d5ef99ddSAndreas Gohr        'VV' => 'vivo Browser',
673*d5ef99ddSAndreas Gohr        'V2' => 'Vivid Browser Mini',
674*d5ef99ddSAndreas Gohr        'VB' => 'Vision Mobile Browser',
675*d5ef99ddSAndreas Gohr        'V4' => 'Vertex Surf',
676*d5ef99ddSAndreas Gohr        'VM' => 'VMware AirWatch',
677*d5ef99ddSAndreas Gohr        'V6' => 'VMS Mosaic',
678*d5ef99ddSAndreas Gohr        'VK' => 'Vonkeror',
679*d5ef99ddSAndreas Gohr        'VU' => 'Vuhuv',
680*d5ef99ddSAndreas Gohr        'WI' => 'Wear Internet Browser',
681*d5ef99ddSAndreas Gohr        'WP' => 'Web Explorer',
682*d5ef99ddSAndreas Gohr        'W3' => 'Web Browser & Explorer',
683*d5ef99ddSAndreas Gohr        'W5' => 'Webian Shell',
684*d5ef99ddSAndreas Gohr        'W4' => 'WebDiscover',
685*d5ef99ddSAndreas Gohr        'WE' => 'WebPositive',
686*d5ef99ddSAndreas Gohr        'W6' => 'Weltweitimnetz Browser',
687*d5ef99ddSAndreas Gohr        'WX' => 'Wexond',
688*d5ef99ddSAndreas Gohr        'WF' => 'Waterfox',
689*d5ef99ddSAndreas Gohr        'WB' => 'Wave Browser',
690*d5ef99ddSAndreas Gohr        'WA' => 'Wavebox',
691*d5ef99ddSAndreas Gohr        'WH' => 'Whale Browser',
692*d5ef99ddSAndreas Gohr        'W2' => 'Whale TV Browser',
693*d5ef99ddSAndreas Gohr        'WO' => 'wOSBrowser',
694*d5ef99ddSAndreas Gohr        '3W' => 'w3m',
695*d5ef99ddSAndreas Gohr        'WT' => 'WeTab Browser',
696*d5ef99ddSAndreas Gohr        '1W' => 'World Browser',
697*d5ef99ddSAndreas Gohr        'WL' => 'Wolvic',
698*d5ef99ddSAndreas Gohr        'WK' => 'Wukong Browser',
699*d5ef99ddSAndreas Gohr        'WY' => 'Wyzo',
700*d5ef99ddSAndreas Gohr        'YG' => 'YAGI',
701*d5ef99ddSAndreas Gohr        'YJ' => 'Yahoo! Japan Browser',
702*d5ef99ddSAndreas Gohr        'YA' => 'Yandex Browser',
703*d5ef99ddSAndreas Gohr        'Y4' => 'Yandex Browser Corp',
704*d5ef99ddSAndreas Gohr        'YL' => 'Yandex Browser Lite',
705*d5ef99ddSAndreas Gohr        'YN' => 'Yaani Browser',
706*d5ef99ddSAndreas Gohr        'Y2' => 'Yo Browser',
707*d5ef99ddSAndreas Gohr        'YB' => 'Yolo Browser',
708*d5ef99ddSAndreas Gohr        'YO' => 'YouCare',
709*d5ef99ddSAndreas Gohr        'Y3' => 'YouBrowser',
710*d5ef99ddSAndreas Gohr        'YZ' => 'Yuzu Browser',
711*d5ef99ddSAndreas Gohr        'XR' => 'xBrowser',
712*d5ef99ddSAndreas Gohr        'X3' => 'MMBOX XBrowser',
713*d5ef99ddSAndreas Gohr        'XB' => 'X Browser Lite',
714*d5ef99ddSAndreas Gohr        'X0' => 'X-VPN',
715*d5ef99ddSAndreas Gohr        'X1' => 'xBrowser Pro Super Fast',
716*d5ef99ddSAndreas Gohr        'XN' => 'XNX Browser',
717*d5ef99ddSAndreas Gohr        'XT' => 'XtremeCast',
718*d5ef99ddSAndreas Gohr        'XS' => 'xStand',
719*d5ef99ddSAndreas Gohr        'XI' => 'Xiino',
720*d5ef99ddSAndreas Gohr        'X4' => 'XnBrowse',
721*d5ef99ddSAndreas Gohr        'XO' => 'Xooloo Internet',
722*d5ef99ddSAndreas Gohr        'XV' => 'Xvast',
723*d5ef99ddSAndreas Gohr        'ZE' => 'Zetakey',
724*d5ef99ddSAndreas Gohr        'ZV' => 'Zvu',
725*d5ef99ddSAndreas Gohr        'ZI' => 'Zirco Browser',
726*d5ef99ddSAndreas Gohr        'ZR' => 'Zordo Browser',
727*d5ef99ddSAndreas Gohr        'ZT' => 'ZTE Browser',
728*d5ef99ddSAndreas Gohr
729*d5ef99ddSAndreas Gohr        // detected browsers in older versions
730*d5ef99ddSAndreas Gohr        // 'IA' => 'Iceape',  => pim
731*d5ef99ddSAndreas Gohr        // 'SM' => 'SeaMonkey',  => pim
732*d5ef99ddSAndreas Gohr    ];
733*d5ef99ddSAndreas Gohr
734*d5ef99ddSAndreas Gohr    /**
735*d5ef99ddSAndreas Gohr     * Browser families mapped to the short codes of the associated browsers
736*d5ef99ddSAndreas Gohr     *
737*d5ef99ddSAndreas Gohr     * @var array
738*d5ef99ddSAndreas Gohr     */
739*d5ef99ddSAndreas Gohr    protected static $browserFamilies = [
740*d5ef99ddSAndreas Gohr        'Android Browser'    => ['AN'],
741*d5ef99ddSAndreas Gohr        'BlackBerry Browser' => ['BB'],
742*d5ef99ddSAndreas Gohr        'Baidu'              => ['BD', 'BS', 'H6'],
743*d5ef99ddSAndreas Gohr        'Amiga'              => ['AV', 'AW'],
744*d5ef99ddSAndreas Gohr        'Chrome'             => [
745*d5ef99ddSAndreas Gohr            'CH', '2B', '7S', 'A0', 'AC', 'A4', 'AE', 'AH', 'AI',
746*d5ef99ddSAndreas Gohr            'AO', 'AS', 'BA', 'BM', 'BR', 'C2', 'C3', 'C5', 'C4',
747*d5ef99ddSAndreas Gohr            'C6', 'CC', 'CD', 'CE', 'CF', 'CG', '1B', 'CI', 'CL',
748*d5ef99ddSAndreas Gohr            'CM', 'CN', 'CP', 'CR', 'CV', 'CW', 'DA', 'DD', 'DG',
749*d5ef99ddSAndreas Gohr            'DR', 'EC', 'EE', 'EU', 'EW', 'FA', 'FS', 'GB', 'GI',
750*d5ef99ddSAndreas Gohr            'H2', 'HA', 'HE', 'HH', 'HS', 'I3', 'IR', 'JB', 'KN',
751*d5ef99ddSAndreas Gohr            'KW', 'LF', 'LL', 'LO', 'M1', 'MA', 'MD', 'MR', 'MS',
752*d5ef99ddSAndreas Gohr            'MT', 'MZ', 'NM', 'NR', 'O0', 'O2', 'O3', 'OC', 'PB',
753*d5ef99ddSAndreas Gohr            'PT', 'QU', 'QW', 'RM', 'S4', 'S6', 'S8', 'S9', 'SB',
754*d5ef99ddSAndreas Gohr            'SG', 'SS', 'SU', 'SV', 'SW', 'SY', 'SZ', 'T1', 'TA',
755*d5ef99ddSAndreas Gohr            'TB', 'TG', 'TR', 'TS', 'TU', 'TV', 'UB', 'UR', 'VE',
756*d5ef99ddSAndreas Gohr            'VG', 'VI', 'VM', 'WP', 'WH', 'XV', 'YJ', 'YN', 'FH',
757*d5ef99ddSAndreas Gohr            'B1', 'BO', 'HB', 'PC', 'LA', 'LT', 'PD', 'HR', 'HU',
758*d5ef99ddSAndreas Gohr            'HP', 'IO', 'TP', 'CJ', 'HQ', 'HI', 'PN', 'BW', 'YO',
759*d5ef99ddSAndreas Gohr            'DC', 'G8', 'DT', 'AP', 'AK', 'UI', 'SD', 'VN', '4S',
760*d5ef99ddSAndreas Gohr            '2S', 'RF', 'LR', 'SQ', 'BV', 'L1', 'F0', 'KS', 'V0',
761*d5ef99ddSAndreas Gohr            'C8', 'AZ', 'MM', 'BT', 'N0', 'P0', 'F3', 'VS', 'DU',
762*d5ef99ddSAndreas Gohr            'D0', 'P1', 'O4', '8S', 'H3', 'TE', 'WB', 'K1', 'P2',
763*d5ef99ddSAndreas Gohr            'XO', 'U0', 'B0', 'VA', 'X0', 'NX', 'O5', 'R1', 'I1',
764*d5ef99ddSAndreas Gohr            'HO', 'A5', 'X1', '18', 'B5', 'B6', 'TC', 'A6', '2X',
765*d5ef99ddSAndreas Gohr            'F4', 'YG', 'WR', 'NA', 'DM', '1M', 'A7', 'XN', 'XT',
766*d5ef99ddSAndreas Gohr            'XB', 'W1', 'HT', 'B8', 'F5', 'B9', 'WA', 'T0', 'HC',
767*d5ef99ddSAndreas Gohr            'O6', 'P7', 'LJ', 'LC', 'O7', 'N2', 'A8', 'P8', 'RB',
768*d5ef99ddSAndreas Gohr            '1W', 'EV', 'I9', 'V4', 'H4', '1T', 'M5', '0S', '0C',
769*d5ef99ddSAndreas Gohr            'ZR', 'D6', 'F6', 'RC', 'WD', 'P3', 'FT', 'A9', 'X2',
770*d5ef99ddSAndreas Gohr            'N3', 'GD', 'O9', 'Q3', 'F7', 'K2', 'P5', 'H5', 'V3',
771*d5ef99ddSAndreas Gohr            'K3', 'Q4', 'G2', 'R2', 'WX', 'XP', '3I', 'BG', 'R0',
772*d5ef99ddSAndreas Gohr            'JO', 'OL', 'GN', 'W4', 'QI', 'E1', 'RI', '8B', '5B',
773*d5ef99ddSAndreas Gohr            'K4', 'WK', 'T3', 'K5', 'MU', '9P', 'K6', 'VR', 'N9',
774*d5ef99ddSAndreas Gohr            'M9', 'F9', '0P', '0A', 'JR', 'D3', 'TK', 'BP', '2F',
775*d5ef99ddSAndreas Gohr            '2M', 'K7', '1N', '8A', 'H7', 'X3', 'T4', 'X4', '5O',
776*d5ef99ddSAndreas Gohr            '8C', '3M', '6I', '2P', 'PU', '7I', 'X5', 'AL', '3P',
777*d5ef99ddSAndreas Gohr            'W2', 'ZB', 'HN',
778*d5ef99ddSAndreas Gohr        ],
779*d5ef99ddSAndreas Gohr        'Firefox'            => [
780*d5ef99ddSAndreas Gohr            'FF', 'BI', 'BF', 'BH', 'BN', 'C0', 'CU', 'EI', 'F1',
781*d5ef99ddSAndreas Gohr            'FB', 'FE', 'AX', 'FM', 'FR', 'FY', 'I4', 'IF', '8P',
782*d5ef99ddSAndreas Gohr            'IW', 'LH', 'LY', 'MB', 'MN', 'MO', 'MY', 'OA', 'OS',
783*d5ef99ddSAndreas Gohr            'PI', 'PX', 'QA', 'S5', 'SX', 'TF', 'TO', 'WF', 'ZV',
784*d5ef99ddSAndreas Gohr            'FP', 'AD', '2I', 'P9', 'KJ', 'WY', 'VK', 'W5',
785*d5ef99ddSAndreas Gohr            '7C', 'N7', 'W7',
786*d5ef99ddSAndreas Gohr        ],
787*d5ef99ddSAndreas Gohr        'Internet Explorer'  => ['IE', 'CZ', 'BZ', 'IM', 'PS', '3A', '4A', 'RN', '2E'],
788*d5ef99ddSAndreas Gohr        'Konqueror'          => ['KO'],
789*d5ef99ddSAndreas Gohr        'NetFront'           => ['NF'],
790*d5ef99ddSAndreas Gohr        'NetSurf'            => ['NE'],
791*d5ef99ddSAndreas Gohr        'Nokia Browser'      => ['NB', 'DO', 'NO', 'NV'],
792*d5ef99ddSAndreas Gohr        'Opera'              => ['OP', 'OG', 'OH', 'OI', 'OM', 'ON', 'OO', 'O1', 'OX', 'Y1'],
793*d5ef99ddSAndreas Gohr        'Safari'             => ['SF', 'S7', 'MF', 'SO', 'PV'],
794*d5ef99ddSAndreas Gohr        'Sailfish Browser'   => ['SA'],
795*d5ef99ddSAndreas Gohr    ];
796*d5ef99ddSAndreas Gohr
797*d5ef99ddSAndreas Gohr    /**
798*d5ef99ddSAndreas Gohr     * Browsers that are available for mobile devices only
799*d5ef99ddSAndreas Gohr     *
800*d5ef99ddSAndreas Gohr     * @var array<string>
801*d5ef99ddSAndreas Gohr     */
802*d5ef99ddSAndreas Gohr    protected static $mobileOnlyBrowsers = [
803*d5ef99ddSAndreas Gohr        '36', 'AH', 'AI', 'BL', 'C1', 'C4', 'CB', 'CW', 'DB',
804*d5ef99ddSAndreas Gohr        '3M', 'DT', 'EU', 'EZ', 'FK', 'FM', 'FR', 'FX', 'GH',
805*d5ef99ddSAndreas Gohr        'GI', 'GR', 'HA', 'HU', 'IV', 'JB', 'KD', 'M1', 'MF',
806*d5ef99ddSAndreas Gohr        'MN', 'MZ', 'NX', 'OC', 'OI', 'OM', 'OZ', '2P', 'PI',
807*d5ef99ddSAndreas Gohr        'PE', 'QU', 'RE', 'S0', 'S7', 'SA', 'SB', 'SG', 'SK',
808*d5ef99ddSAndreas Gohr        'ST', 'SU', 'T1', 'UH', 'UM', 'UT', 'VE', 'VV', 'WI',
809*d5ef99ddSAndreas Gohr        'WP', 'YN', 'IO', 'IS', 'HQ', 'RW', 'HI', 'PN', 'BW',
810*d5ef99ddSAndreas Gohr        'YO', 'PK', 'MR', 'AP', 'AK', 'UI', 'SD', 'VN', '4S',
811*d5ef99ddSAndreas Gohr        'RF', 'LR', 'SQ', 'BV', 'L1', 'F0', 'KS', 'V0', 'C8',
812*d5ef99ddSAndreas Gohr        'AZ', 'MM', 'BT', 'N0', 'P0', 'F3', 'DU', 'D0', 'P1',
813*d5ef99ddSAndreas Gohr        'O4', 'XO', 'U0', 'B0', 'VA', 'X0', 'A5', 'X1', '18',
814*d5ef99ddSAndreas Gohr        'B5', 'B6', 'TC', 'A6', '2X', 'F4', 'YG', 'WR', 'NA',
815*d5ef99ddSAndreas Gohr        'DM', '1M', 'A7', 'XN', 'XT', 'XB', 'W1', 'HT', 'B7',
816*d5ef99ddSAndreas Gohr        'B9', 'T0', 'I8', 'O6', 'P7', 'O8', '4B', 'A8', 'P8',
817*d5ef99ddSAndreas Gohr        '1W', 'EV', 'Z0', 'I9', 'V4', 'H4', 'M5', '0S', '0C',
818*d5ef99ddSAndreas Gohr        'ZR', 'D6', 'F6', 'P3', 'FT', 'A9', 'X2', 'NI', 'FG',
819*d5ef99ddSAndreas Gohr        'TH', 'N3', 'GD', 'O9', 'Q3', 'F7', 'K2', 'N4', 'P5',
820*d5ef99ddSAndreas Gohr        'H5', 'V3', 'G2', 'BG', 'OL', 'II', 'TL', 'M6', 'Y3',
821*d5ef99ddSAndreas Gohr        'M7', 'GN', 'JR', 'IG', 'HW', '4O', 'OU', '5P', 'KE',
822*d5ef99ddSAndreas Gohr        '5A', 'TT', '6P', 'G3', '7P', 'VU', 'F8', 'L4', 'DK',
823*d5ef99ddSAndreas Gohr        'DP', 'KL', 'K4', 'N6', 'KU', 'WK', 'M8', 'UP', 'ZT',
824*d5ef99ddSAndreas Gohr        '9P', 'N8', 'VR', 'N9', 'M9', 'F9', '0P', '0A', '2F',
825*d5ef99ddSAndreas Gohr        '2M', 'K7', '1N', '8A', 'H7', 'X3', 'X4', '5O', '6I',
826*d5ef99ddSAndreas Gohr        '7I', 'X5', '3P', '2E',
827*d5ef99ddSAndreas Gohr    ];
828*d5ef99ddSAndreas Gohr
829*d5ef99ddSAndreas Gohr    /**
830*d5ef99ddSAndreas Gohr     * Contains a list of mappings from OS names we use to known client hint values
831*d5ef99ddSAndreas Gohr     *
832*d5ef99ddSAndreas Gohr     * @var array<string, array<string>>
833*d5ef99ddSAndreas Gohr     */
834*d5ef99ddSAndreas Gohr    protected static $clientHintMapping = [
835*d5ef99ddSAndreas Gohr        'Chrome'                     => ['Google Chrome'],
836*d5ef99ddSAndreas Gohr        'Chrome Webview'             => ['Android WebView'],
837*d5ef99ddSAndreas Gohr        'DuckDuckGo Privacy Browser' => ['DuckDuckGo'],
838*d5ef99ddSAndreas Gohr        'Edge WebView'               => ['Microsoft Edge WebView2'],
839*d5ef99ddSAndreas Gohr        'Mi Browser'                 => ['Miui Browser', 'XiaoMiBrowser'],
840*d5ef99ddSAndreas Gohr        'Microsoft Edge'             => ['Edge'],
841*d5ef99ddSAndreas Gohr        'Norton Private Browser'     => ['Norton Secure Browser'],
842*d5ef99ddSAndreas Gohr        'Vewd Browser'               => ['Vewd Core'],
843*d5ef99ddSAndreas Gohr    ];
844*d5ef99ddSAndreas Gohr
845*d5ef99ddSAndreas Gohr    /**
846*d5ef99ddSAndreas Gohr     * Browser constructor.
847*d5ef99ddSAndreas Gohr     *
848*d5ef99ddSAndreas Gohr     * @param string           $ua
849*d5ef99ddSAndreas Gohr     * @param ClientHints|null $clientHints
850*d5ef99ddSAndreas Gohr     */
851*d5ef99ddSAndreas Gohr    public function __construct(string $ua = '', ?ClientHints $clientHints = null)
852*d5ef99ddSAndreas Gohr    {
853*d5ef99ddSAndreas Gohr        $this->browserHints = new BrowserHints($ua, $clientHints);
854*d5ef99ddSAndreas Gohr        parent::__construct($ua, $clientHints);
855*d5ef99ddSAndreas Gohr    }
856*d5ef99ddSAndreas Gohr
857*d5ef99ddSAndreas Gohr    /**
858*d5ef99ddSAndreas Gohr     * Sets the client hints to parse
859*d5ef99ddSAndreas Gohr     *
860*d5ef99ddSAndreas Gohr     * @param ?ClientHints $clientHints client hints
861*d5ef99ddSAndreas Gohr     */
862*d5ef99ddSAndreas Gohr    public function setClientHints(?ClientHints $clientHints): void
863*d5ef99ddSAndreas Gohr    {
864*d5ef99ddSAndreas Gohr        parent::setClientHints($clientHints);
865*d5ef99ddSAndreas Gohr        $this->browserHints->setClientHints($clientHints);
866*d5ef99ddSAndreas Gohr    }
867*d5ef99ddSAndreas Gohr
868*d5ef99ddSAndreas Gohr    /**
869*d5ef99ddSAndreas Gohr     * Sets the user agent to parse
870*d5ef99ddSAndreas Gohr     *
871*d5ef99ddSAndreas Gohr     * @param string $ua user agent
872*d5ef99ddSAndreas Gohr     */
873*d5ef99ddSAndreas Gohr    public function setUserAgent(string $ua): void
874*d5ef99ddSAndreas Gohr    {
875*d5ef99ddSAndreas Gohr        parent::setUserAgent($ua);
876*d5ef99ddSAndreas Gohr        $this->browserHints->setUserAgent($ua);
877*d5ef99ddSAndreas Gohr    }
878*d5ef99ddSAndreas Gohr
879*d5ef99ddSAndreas Gohr    /**
880*d5ef99ddSAndreas Gohr     * Sets the Cache class
881*d5ef99ddSAndreas Gohr     *
882*d5ef99ddSAndreas Gohr     * @param CacheInterface $cache
883*d5ef99ddSAndreas Gohr     */
884*d5ef99ddSAndreas Gohr    public function setCache(CacheInterface $cache): void
885*d5ef99ddSAndreas Gohr    {
886*d5ef99ddSAndreas Gohr        parent::setCache($cache);
887*d5ef99ddSAndreas Gohr        $this->browserHints->setCache($cache);
888*d5ef99ddSAndreas Gohr    }
889*d5ef99ddSAndreas Gohr
890*d5ef99ddSAndreas Gohr    /**
891*d5ef99ddSAndreas Gohr     * Returns list of all available browsers
892*d5ef99ddSAndreas Gohr     * @return array
893*d5ef99ddSAndreas Gohr     */
894*d5ef99ddSAndreas Gohr    public static function getAvailableBrowsers(): array
895*d5ef99ddSAndreas Gohr    {
896*d5ef99ddSAndreas Gohr        return self::$availableBrowsers;
897*d5ef99ddSAndreas Gohr    }
898*d5ef99ddSAndreas Gohr
899*d5ef99ddSAndreas Gohr    /**
900*d5ef99ddSAndreas Gohr     * Returns list of all available browser families
901*d5ef99ddSAndreas Gohr     * @return array
902*d5ef99ddSAndreas Gohr     */
903*d5ef99ddSAndreas Gohr    public static function getAvailableBrowserFamilies(): array
904*d5ef99ddSAndreas Gohr    {
905*d5ef99ddSAndreas Gohr        return self::$browserFamilies;
906*d5ef99ddSAndreas Gohr    }
907*d5ef99ddSAndreas Gohr
908*d5ef99ddSAndreas Gohr    /**
909*d5ef99ddSAndreas Gohr     * @param string $name name browser
910*d5ef99ddSAndreas Gohr     *
911*d5ef99ddSAndreas Gohr     * @return string
912*d5ef99ddSAndreas Gohr     */
913*d5ef99ddSAndreas Gohr    public static function getBrowserShortName(string $name): ?string
914*d5ef99ddSAndreas Gohr    {
915*d5ef99ddSAndreas Gohr        foreach (self::getAvailableBrowsers() as $browserShort => $browserName) {
916*d5ef99ddSAndreas Gohr            if (\strtolower($name) === \strtolower($browserName)) {
917*d5ef99ddSAndreas Gohr                return (string) $browserShort;
918*d5ef99ddSAndreas Gohr            }
919*d5ef99ddSAndreas Gohr        }
920*d5ef99ddSAndreas Gohr
921*d5ef99ddSAndreas Gohr        return null;
922*d5ef99ddSAndreas Gohr    }
923*d5ef99ddSAndreas Gohr
924*d5ef99ddSAndreas Gohr    /**
925*d5ef99ddSAndreas Gohr     * @param string $browserLabel name or short name
926*d5ef99ddSAndreas Gohr     *
927*d5ef99ddSAndreas Gohr     * @return string|null If null, "Unknown"
928*d5ef99ddSAndreas Gohr     */
929*d5ef99ddSAndreas Gohr    public static function getBrowserFamily(string $browserLabel): ?string
930*d5ef99ddSAndreas Gohr    {
931*d5ef99ddSAndreas Gohr        if (\in_array($browserLabel, self::$availableBrowsers)) {
932*d5ef99ddSAndreas Gohr            $browserLabel = \array_search($browserLabel, self::$availableBrowsers);
933*d5ef99ddSAndreas Gohr        }
934*d5ef99ddSAndreas Gohr
935*d5ef99ddSAndreas Gohr        foreach (self::$browserFamilies as $browserFamily => $browserLabels) {
936*d5ef99ddSAndreas Gohr            if (\in_array($browserLabel, $browserLabels)) {
937*d5ef99ddSAndreas Gohr                return $browserFamily;
938*d5ef99ddSAndreas Gohr            }
939*d5ef99ddSAndreas Gohr        }
940*d5ef99ddSAndreas Gohr
941*d5ef99ddSAndreas Gohr        return null;
942*d5ef99ddSAndreas Gohr    }
943*d5ef99ddSAndreas Gohr
944*d5ef99ddSAndreas Gohr    /**
945*d5ef99ddSAndreas Gohr     * Returns if the given browser is mobile only
946*d5ef99ddSAndreas Gohr     *
947*d5ef99ddSAndreas Gohr     * @param string $browser Label or name of browser
948*d5ef99ddSAndreas Gohr     *
949*d5ef99ddSAndreas Gohr     * @return bool
950*d5ef99ddSAndreas Gohr     */
951*d5ef99ddSAndreas Gohr    public static function isMobileOnlyBrowser(string $browser): bool
952*d5ef99ddSAndreas Gohr    {
953*d5ef99ddSAndreas Gohr        return \in_array($browser, self::$mobileOnlyBrowsers) || (\in_array($browser, self::$availableBrowsers)
954*d5ef99ddSAndreas Gohr                && \in_array(\array_search($browser, self::$availableBrowsers), self::$mobileOnlyBrowsers));
955*d5ef99ddSAndreas Gohr    }
956*d5ef99ddSAndreas Gohr
957*d5ef99ddSAndreas Gohr    /**
958*d5ef99ddSAndreas Gohr     * Sets the YamlParser class
959*d5ef99ddSAndreas Gohr     *
960*d5ef99ddSAndreas Gohr     * @param YamlParser $yamlParser
961*d5ef99ddSAndreas Gohr     */
962*d5ef99ddSAndreas Gohr    public function setYamlParser(YamlParser $yamlParser): void
963*d5ef99ddSAndreas Gohr    {
964*d5ef99ddSAndreas Gohr        parent::setYamlParser($yamlParser);
965*d5ef99ddSAndreas Gohr        $this->browserHints->setYamlParser($this->getYamlParser());
966*d5ef99ddSAndreas Gohr    }
967*d5ef99ddSAndreas Gohr
968*d5ef99ddSAndreas Gohr    /**
969*d5ef99ddSAndreas Gohr     * @inheritdoc
970*d5ef99ddSAndreas Gohr     */
971*d5ef99ddSAndreas Gohr    public function parse(): ?array
972*d5ef99ddSAndreas Gohr    {
973*d5ef99ddSAndreas Gohr        $browserFromClientHints = $this->parseBrowserFromClientHints();
974*d5ef99ddSAndreas Gohr        $browserFromUserAgent   = $this->parseBrowserFromUserAgent();
975*d5ef99ddSAndreas Gohr
976*d5ef99ddSAndreas Gohr        // use client hints in favor of user agent data if possible
977*d5ef99ddSAndreas Gohr        if (!empty($browserFromClientHints['name']) && !empty($browserFromClientHints['version'])) {
978*d5ef99ddSAndreas Gohr            $name          = $browserFromClientHints['name'];
979*d5ef99ddSAndreas Gohr            $version       = $browserFromClientHints['version'];
980*d5ef99ddSAndreas Gohr            $short         = $browserFromClientHints['short_name'];
981*d5ef99ddSAndreas Gohr            $engine        = '';
982*d5ef99ddSAndreas Gohr            $engineVersion = '';
983*d5ef99ddSAndreas Gohr
984*d5ef99ddSAndreas Gohr            // If the version reported from the client hints is YYYY or YYYY.MM (e.g., 2022 or 2022.04),
985*d5ef99ddSAndreas Gohr            // then it is the Iridium browser
986*d5ef99ddSAndreas Gohr            // https://iridiumbrowser.de/news/
987*d5ef99ddSAndreas Gohr            if (\preg_match('/^202[0-4]/', $version)) {
988*d5ef99ddSAndreas Gohr                $name  = 'Iridium';
989*d5ef99ddSAndreas Gohr                $short = 'I1';
990*d5ef99ddSAndreas Gohr            }
991*d5ef99ddSAndreas Gohr
992*d5ef99ddSAndreas Gohr            // https://bbs.360.cn/thread-16096544-1-1.html
993*d5ef99ddSAndreas Gohr            if (\preg_match('/^15/', $version) && \preg_match('/^114/', $browserFromUserAgent['version'])) {
994*d5ef99ddSAndreas Gohr                $name          = '360 Secure Browser';
995*d5ef99ddSAndreas Gohr                $short         = '3B';
996*d5ef99ddSAndreas Gohr                $engine        = $browserFromUserAgent['engine'] ?? '';
997*d5ef99ddSAndreas Gohr                $engineVersion = $browserFromUserAgent['engine_version'] ?? '';
998*d5ef99ddSAndreas Gohr            }
999*d5ef99ddSAndreas Gohr
1000*d5ef99ddSAndreas Gohr            // If client hints report the following browsers, we use the version from useragent
1001*d5ef99ddSAndreas Gohr            if (!empty($browserFromUserAgent['version'])
1002*d5ef99ddSAndreas Gohr                && \in_array($short, ['A0', 'AL', 'HP', 'JR', 'MU', 'OM', 'OP', 'VR'])
1003*d5ef99ddSAndreas Gohr            ) {
1004*d5ef99ddSAndreas Gohr                $version = $browserFromUserAgent['version'];
1005*d5ef99ddSAndreas Gohr            }
1006*d5ef99ddSAndreas Gohr
1007*d5ef99ddSAndreas Gohr            if ('Vewd Browser' === $name) {
1008*d5ef99ddSAndreas Gohr                $engine        = $browserFromUserAgent['engine'] ?? '';
1009*d5ef99ddSAndreas Gohr                $engineVersion = $browserFromUserAgent['engine_version'] ?? '';
1010*d5ef99ddSAndreas Gohr            }
1011*d5ef99ddSAndreas Gohr
1012*d5ef99ddSAndreas Gohr            // If client hints report Chromium, but user agent detects a Chromium based browser, we favor this instead
1013*d5ef99ddSAndreas Gohr            if (('Chromium' === $name || 'Chrome Webview' === $name)
1014*d5ef99ddSAndreas Gohr                && !empty($browserFromUserAgent['name'])
1015*d5ef99ddSAndreas Gohr                && !\in_array($browserFromUserAgent['short_name'], ['CR', 'CV', 'AN'])
1016*d5ef99ddSAndreas Gohr            ) {
1017*d5ef99ddSAndreas Gohr                $name    = $browserFromUserAgent['name'];
1018*d5ef99ddSAndreas Gohr                $short   = $browserFromUserAgent['short_name'];
1019*d5ef99ddSAndreas Gohr                $version = $browserFromUserAgent['version'];
1020*d5ef99ddSAndreas Gohr            }
1021*d5ef99ddSAndreas Gohr
1022*d5ef99ddSAndreas Gohr            // Fix mobile browser names e.g. Chrome => Chrome Mobile
1023*d5ef99ddSAndreas Gohr            if ($name . ' Mobile' === $browserFromUserAgent['name']) {
1024*d5ef99ddSAndreas Gohr                $name  = $browserFromUserAgent['name'];
1025*d5ef99ddSAndreas Gohr                $short = $browserFromUserAgent['short_name'];
1026*d5ef99ddSAndreas Gohr            }
1027*d5ef99ddSAndreas Gohr
1028*d5ef99ddSAndreas Gohr            // If user agent detects another browser, but the family matches, we use the detected engine from user agent
1029*d5ef99ddSAndreas Gohr            if ($name !== $browserFromUserAgent['name']
1030*d5ef99ddSAndreas Gohr                && self::getBrowserFamily($name) === self::getBrowserFamily($browserFromUserAgent['name'])
1031*d5ef99ddSAndreas Gohr            ) {
1032*d5ef99ddSAndreas Gohr                $engine        = $browserFromUserAgent['engine'] ?? '';
1033*d5ef99ddSAndreas Gohr                $engineVersion = $browserFromUserAgent['engine_version'] ?? '';
1034*d5ef99ddSAndreas Gohr            }
1035*d5ef99ddSAndreas Gohr
1036*d5ef99ddSAndreas Gohr            if ($name === $browserFromUserAgent['name']) {
1037*d5ef99ddSAndreas Gohr                $engine        = $browserFromUserAgent['engine'] ?? '';
1038*d5ef99ddSAndreas Gohr                $engineVersion = $browserFromUserAgent['engine_version'] ?? '';
1039*d5ef99ddSAndreas Gohr            }
1040*d5ef99ddSAndreas Gohr
1041*d5ef99ddSAndreas Gohr            // In case the user agent reports a more detailed version, we try to use this instead
1042*d5ef99ddSAndreas Gohr            if (!empty($browserFromUserAgent['version'])
1043*d5ef99ddSAndreas Gohr                && 0 === \strpos($browserFromUserAgent['version'], $version)
1044*d5ef99ddSAndreas Gohr                && \version_compare($version, $browserFromUserAgent['version'], '<')
1045*d5ef99ddSAndreas Gohr            ) {
1046*d5ef99ddSAndreas Gohr                $version = $browserFromUserAgent['version'];
1047*d5ef99ddSAndreas Gohr            }
1048*d5ef99ddSAndreas Gohr
1049*d5ef99ddSAndreas Gohr            if ('DuckDuckGo Privacy Browser' === $name) {
1050*d5ef99ddSAndreas Gohr                $version = '';
1051*d5ef99ddSAndreas Gohr            }
1052*d5ef99ddSAndreas Gohr
1053*d5ef99ddSAndreas Gohr            // In case client hints report a more detailed engine version, we try to use this instead
1054*d5ef99ddSAndreas Gohr            if ('Blink' === $engine && 'Iridium' !== $name
1055*d5ef99ddSAndreas Gohr                && \version_compare($engineVersion, $browserFromClientHints['version'], '<')
1056*d5ef99ddSAndreas Gohr            ) {
1057*d5ef99ddSAndreas Gohr                $engineVersion = $browserFromClientHints['version'];
1058*d5ef99ddSAndreas Gohr            }
1059*d5ef99ddSAndreas Gohr        } else {
1060*d5ef99ddSAndreas Gohr            $name          = $browserFromUserAgent['name'];
1061*d5ef99ddSAndreas Gohr            $version       = $browserFromUserAgent['version'];
1062*d5ef99ddSAndreas Gohr            $short         = $browserFromUserAgent['short_name'];
1063*d5ef99ddSAndreas Gohr            $engine        = $browserFromUserAgent['engine'];
1064*d5ef99ddSAndreas Gohr            $engineVersion = $browserFromUserAgent['engine_version'];
1065*d5ef99ddSAndreas Gohr        }
1066*d5ef99ddSAndreas Gohr
1067*d5ef99ddSAndreas Gohr        $family  = self::getBrowserFamily((string) $short);
1068*d5ef99ddSAndreas Gohr        $appHash = $this->browserHints->parse();
1069*d5ef99ddSAndreas Gohr
1070*d5ef99ddSAndreas Gohr        if (null !== $appHash && $name !== $appHash['name']) {
1071*d5ef99ddSAndreas Gohr            $name    = $appHash['name'];
1072*d5ef99ddSAndreas Gohr            $version = '';
1073*d5ef99ddSAndreas Gohr            $short   = self::getBrowserShortName($name);
1074*d5ef99ddSAndreas Gohr
1075*d5ef99ddSAndreas Gohr            if (\preg_match('~Chrome/.+ Safari/537.36~i', $this->userAgent)) {
1076*d5ef99ddSAndreas Gohr                $engine        = 'Blink';
1077*d5ef99ddSAndreas Gohr                $family        = self::getBrowserFamily((string) $short) ?? 'Chrome';
1078*d5ef99ddSAndreas Gohr                $engineVersion = $this->buildEngineVersion($engine);
1079*d5ef99ddSAndreas Gohr            }
1080*d5ef99ddSAndreas Gohr
1081*d5ef99ddSAndreas Gohr            if (null === $short) {
1082*d5ef99ddSAndreas Gohr                // This Exception should never be thrown. If so a defined browser name is missing in $availableBrowsers
1083*d5ef99ddSAndreas Gohr                throw new \Exception(\sprintf(
1084*d5ef99ddSAndreas Gohr                    'Detected browser name "%s" was not found in $availableBrowsers. Tried to parse user agent: %s',
1085*d5ef99ddSAndreas Gohr                    $name,
1086*d5ef99ddSAndreas Gohr                    $this->userAgent
1087*d5ef99ddSAndreas Gohr                )); // @codeCoverageIgnore
1088*d5ef99ddSAndreas Gohr            }
1089*d5ef99ddSAndreas Gohr        }
1090*d5ef99ddSAndreas Gohr
1091*d5ef99ddSAndreas Gohr        if (empty($name) || 1 === \preg_match('/Cypress|PhantomJS/', $this->userAgent)) {
1092*d5ef99ddSAndreas Gohr            return [];
1093*d5ef99ddSAndreas Gohr        }
1094*d5ef99ddSAndreas Gohr
1095*d5ef99ddSAndreas Gohr        // exclude Blink engine version for browsers
1096*d5ef99ddSAndreas Gohr        if ('Blink' === $engine && 'Flow Browser' === $name) {
1097*d5ef99ddSAndreas Gohr            $engineVersion = '';
1098*d5ef99ddSAndreas Gohr        }
1099*d5ef99ddSAndreas Gohr
1100*d5ef99ddSAndreas Gohr        // the browser simulate ua for Android OS
1101*d5ef99ddSAndreas Gohr        if ('Every Browser' === $name) {
1102*d5ef99ddSAndreas Gohr            $family        = 'Chrome';
1103*d5ef99ddSAndreas Gohr            $engine        = 'Blink';
1104*d5ef99ddSAndreas Gohr            $engineVersion = '';
1105*d5ef99ddSAndreas Gohr        }
1106*d5ef99ddSAndreas Gohr
1107*d5ef99ddSAndreas Gohr        // This browser simulates user-agent of Firefox
1108*d5ef99ddSAndreas Gohr        if ('TV-Browser Internet' === $name && 'Gecko' === $engine) {
1109*d5ef99ddSAndreas Gohr            $family        = 'Chrome';
1110*d5ef99ddSAndreas Gohr            $engine        = 'Blink';
1111*d5ef99ddSAndreas Gohr            $engineVersion = '';
1112*d5ef99ddSAndreas Gohr        }
1113*d5ef99ddSAndreas Gohr
1114*d5ef99ddSAndreas Gohr        if ('Wolvic' === $name && 'Blink' === $engine) {
1115*d5ef99ddSAndreas Gohr            $family = 'Chrome';
1116*d5ef99ddSAndreas Gohr        }
1117*d5ef99ddSAndreas Gohr
1118*d5ef99ddSAndreas Gohr        if ('Wolvic' === $name && 'Gecko' === $engine) {
1119*d5ef99ddSAndreas Gohr            $family = 'Firefox';
1120*d5ef99ddSAndreas Gohr        }
1121*d5ef99ddSAndreas Gohr
1122*d5ef99ddSAndreas Gohr        return [
1123*d5ef99ddSAndreas Gohr            'type'           => 'browser',
1124*d5ef99ddSAndreas Gohr            'name'           => $name,
1125*d5ef99ddSAndreas Gohr            'short_name'     => $short,
1126*d5ef99ddSAndreas Gohr            'version'        => $version,
1127*d5ef99ddSAndreas Gohr            'engine'         => $engine,
1128*d5ef99ddSAndreas Gohr            'engine_version' => $engineVersion,
1129*d5ef99ddSAndreas Gohr            'family'         => $family,
1130*d5ef99ddSAndreas Gohr        ];
1131*d5ef99ddSAndreas Gohr    }
1132*d5ef99ddSAndreas Gohr
1133*d5ef99ddSAndreas Gohr    /**
1134*d5ef99ddSAndreas Gohr     * Returns the browser that can be safely detected from client hints
1135*d5ef99ddSAndreas Gohr     *
1136*d5ef99ddSAndreas Gohr     * @return array
1137*d5ef99ddSAndreas Gohr     */
1138*d5ef99ddSAndreas Gohr    protected function parseBrowserFromClientHints(): array
1139*d5ef99ddSAndreas Gohr    {
1140*d5ef99ddSAndreas Gohr        $name = $version = $short = '';
1141*d5ef99ddSAndreas Gohr
1142*d5ef99ddSAndreas Gohr        if ($this->clientHints instanceof ClientHints && $this->clientHints->getBrandList()) {
1143*d5ef99ddSAndreas Gohr            $brands = $this->clientHints->getBrandList();
1144*d5ef99ddSAndreas Gohr
1145*d5ef99ddSAndreas Gohr            foreach ($brands as $brand => $brandVersion) {
1146*d5ef99ddSAndreas Gohr                $brand = $this->applyClientHintMapping($brand);
1147*d5ef99ddSAndreas Gohr
1148*d5ef99ddSAndreas Gohr                foreach (self::$availableBrowsers as $browserShort => $browserName) {
1149*d5ef99ddSAndreas Gohr                    if ($this->fuzzyCompare("{$brand}", $browserName)
1150*d5ef99ddSAndreas Gohr                        || $this->fuzzyCompare($brand . ' Browser', $browserName)
1151*d5ef99ddSAndreas Gohr                        || $this->fuzzyCompare("{$brand}", $browserName . ' Browser')
1152*d5ef99ddSAndreas Gohr                    ) {
1153*d5ef99ddSAndreas Gohr                        $name    = $browserName;
1154*d5ef99ddSAndreas Gohr                        $short   = $browserShort;
1155*d5ef99ddSAndreas Gohr                        $version = $brandVersion;
1156*d5ef99ddSAndreas Gohr
1157*d5ef99ddSAndreas Gohr                        break;
1158*d5ef99ddSAndreas Gohr                    }
1159*d5ef99ddSAndreas Gohr                }
1160*d5ef99ddSAndreas Gohr
1161*d5ef99ddSAndreas Gohr                // If we detected a brand, that is not Chromium, we will use it, otherwise we will look further
1162*d5ef99ddSAndreas Gohr                if ('' !== $name && 'Chromium' !== $name && 'Microsoft Edge' !== $name) {
1163*d5ef99ddSAndreas Gohr                    break;
1164*d5ef99ddSAndreas Gohr                }
1165*d5ef99ddSAndreas Gohr            }
1166*d5ef99ddSAndreas Gohr
1167*d5ef99ddSAndreas Gohr            $version = $this->clientHints->getBrandVersion() ?: $version;
1168*d5ef99ddSAndreas Gohr        }
1169*d5ef99ddSAndreas Gohr
1170*d5ef99ddSAndreas Gohr        return [
1171*d5ef99ddSAndreas Gohr            'name'       => $name,
1172*d5ef99ddSAndreas Gohr            'short_name' => $short,
1173*d5ef99ddSAndreas Gohr            'version'    => $this->buildVersion($version, []),
1174*d5ef99ddSAndreas Gohr        ];
1175*d5ef99ddSAndreas Gohr    }
1176*d5ef99ddSAndreas Gohr
1177*d5ef99ddSAndreas Gohr    /**
1178*d5ef99ddSAndreas Gohr     * Returns the browser that can be detected from user agent
1179*d5ef99ddSAndreas Gohr     *
1180*d5ef99ddSAndreas Gohr     * @return array
1181*d5ef99ddSAndreas Gohr     *
1182*d5ef99ddSAndreas Gohr     * @throws \Exception
1183*d5ef99ddSAndreas Gohr     */
1184*d5ef99ddSAndreas Gohr    protected function parseBrowserFromUserAgent(): array
1185*d5ef99ddSAndreas Gohr    {
1186*d5ef99ddSAndreas Gohr        foreach ($this->getRegexes() as $regex) {
1187*d5ef99ddSAndreas Gohr            $matches = $this->matchUserAgent($regex['regex']);
1188*d5ef99ddSAndreas Gohr
1189*d5ef99ddSAndreas Gohr            if ($matches) {
1190*d5ef99ddSAndreas Gohr                break;
1191*d5ef99ddSAndreas Gohr            }
1192*d5ef99ddSAndreas Gohr        }
1193*d5ef99ddSAndreas Gohr
1194*d5ef99ddSAndreas Gohr        if (empty($matches) || empty($regex)) {
1195*d5ef99ddSAndreas Gohr            return [
1196*d5ef99ddSAndreas Gohr                'name'           => '',
1197*d5ef99ddSAndreas Gohr                'short_name'     => '',
1198*d5ef99ddSAndreas Gohr                'version'        => '',
1199*d5ef99ddSAndreas Gohr                'engine'         => '',
1200*d5ef99ddSAndreas Gohr                'engine_version' => '',
1201*d5ef99ddSAndreas Gohr            ];
1202*d5ef99ddSAndreas Gohr        }
1203*d5ef99ddSAndreas Gohr
1204*d5ef99ddSAndreas Gohr        $name         = $this->buildByMatch($regex['name'], $matches);
1205*d5ef99ddSAndreas Gohr        $browserShort = self::getBrowserShortName($name);
1206*d5ef99ddSAndreas Gohr
1207*d5ef99ddSAndreas Gohr        if (null !== $browserShort) {
1208*d5ef99ddSAndreas Gohr            $version       = $this->buildVersion((string) $regex['version'], $matches);
1209*d5ef99ddSAndreas Gohr            $engine        = $this->buildEngine($regex['engine'] ?? [], $version);
1210*d5ef99ddSAndreas Gohr            $engineVersion = $this->buildEngineVersion($engine);
1211*d5ef99ddSAndreas Gohr
1212*d5ef99ddSAndreas Gohr            return [
1213*d5ef99ddSAndreas Gohr                'name'           => $name,
1214*d5ef99ddSAndreas Gohr                'short_name'     => $browserShort,
1215*d5ef99ddSAndreas Gohr                'version'        => $version,
1216*d5ef99ddSAndreas Gohr                'engine'         => $engine,
1217*d5ef99ddSAndreas Gohr                'engine_version' => $engineVersion,
1218*d5ef99ddSAndreas Gohr            ];
1219*d5ef99ddSAndreas Gohr        }
1220*d5ef99ddSAndreas Gohr
1221*d5ef99ddSAndreas Gohr        // This Exception should never be thrown. If so a defined browser name is missing in $availableBrowsers
1222*d5ef99ddSAndreas Gohr        throw new \Exception(\sprintf(
1223*d5ef99ddSAndreas Gohr            'Detected browser name "%s" was not found in $availableBrowsers. Tried to parse user agent: %s',
1224*d5ef99ddSAndreas Gohr            $name,
1225*d5ef99ddSAndreas Gohr            $this->userAgent
1226*d5ef99ddSAndreas Gohr        )); // @codeCoverageIgnore
1227*d5ef99ddSAndreas Gohr    }
1228*d5ef99ddSAndreas Gohr
1229*d5ef99ddSAndreas Gohr    /**
1230*d5ef99ddSAndreas Gohr     * @param array  $engineData
1231*d5ef99ddSAndreas Gohr     * @param string $browserVersion
1232*d5ef99ddSAndreas Gohr     *
1233*d5ef99ddSAndreas Gohr     * @return string
1234*d5ef99ddSAndreas Gohr     */
1235*d5ef99ddSAndreas Gohr    protected function buildEngine(array $engineData, string $browserVersion): string
1236*d5ef99ddSAndreas Gohr    {
1237*d5ef99ddSAndreas Gohr        $engine = '';
1238*d5ef99ddSAndreas Gohr
1239*d5ef99ddSAndreas Gohr        // if an engine is set as default
1240*d5ef99ddSAndreas Gohr        if (isset($engineData['default'])) {
1241*d5ef99ddSAndreas Gohr            $engine = $engineData['default'];
1242*d5ef99ddSAndreas Gohr        }
1243*d5ef99ddSAndreas Gohr
1244*d5ef99ddSAndreas Gohr        // check if engine is set for browser version
1245*d5ef99ddSAndreas Gohr        if (\array_key_exists('versions', $engineData) && \is_array($engineData['versions'])) {
1246*d5ef99ddSAndreas Gohr            foreach ($engineData['versions'] as $version => $versionEngine) {
1247*d5ef99ddSAndreas Gohr                if (\version_compare($browserVersion, (string) $version) < 0) {
1248*d5ef99ddSAndreas Gohr                    continue;
1249*d5ef99ddSAndreas Gohr                }
1250*d5ef99ddSAndreas Gohr
1251*d5ef99ddSAndreas Gohr                $engine = $versionEngine;
1252*d5ef99ddSAndreas Gohr            }
1253*d5ef99ddSAndreas Gohr        }
1254*d5ef99ddSAndreas Gohr
1255*d5ef99ddSAndreas Gohr        // try to detect the engine using the regexes
1256*d5ef99ddSAndreas Gohr        if (empty($engine)) {
1257*d5ef99ddSAndreas Gohr            $engineParser = new Engine();
1258*d5ef99ddSAndreas Gohr            $engineParser->setYamlParser($this->getYamlParser());
1259*d5ef99ddSAndreas Gohr            $engineParser->setCache($this->getCache());
1260*d5ef99ddSAndreas Gohr            $engineParser->setUserAgent($this->userAgent);
1261*d5ef99ddSAndreas Gohr            $result = $engineParser->parse();
1262*d5ef99ddSAndreas Gohr            $engine = $result['engine'] ?? '';
1263*d5ef99ddSAndreas Gohr        }
1264*d5ef99ddSAndreas Gohr
1265*d5ef99ddSAndreas Gohr        return $engine;
1266*d5ef99ddSAndreas Gohr    }
1267*d5ef99ddSAndreas Gohr
1268*d5ef99ddSAndreas Gohr    /**
1269*d5ef99ddSAndreas Gohr     * @param string $engine
1270*d5ef99ddSAndreas Gohr     *
1271*d5ef99ddSAndreas Gohr     * @return string
1272*d5ef99ddSAndreas Gohr     */
1273*d5ef99ddSAndreas Gohr    protected function buildEngineVersion(string $engine): string
1274*d5ef99ddSAndreas Gohr    {
1275*d5ef99ddSAndreas Gohr        $engineVersionParser = new Engine\Version($this->userAgent, $engine);
1276*d5ef99ddSAndreas Gohr        $result              = $engineVersionParser->parse();
1277*d5ef99ddSAndreas Gohr
1278*d5ef99ddSAndreas Gohr        return $result['version'] ?? '';
1279*d5ef99ddSAndreas Gohr    }
1280*d5ef99ddSAndreas Gohr}
1281