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