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