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