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; 14 15use DeviceDetector\ClientHints; 16 17/** 18 * Class OperatingSystem 19 * 20 * Parses the useragent for operating system information 21 * 22 * Detected operating systems can be found in self::$operatingSystems and /regexes/oss.yml 23 * This class also defined some operating system families and methods to get the family for a specific os 24 */ 25class OperatingSystem extends AbstractParser 26{ 27 /** 28 * @var string 29 */ 30 protected $fixtureFile = 'regexes/oss.yml'; 31 32 /** 33 * @var string 34 */ 35 protected $parserName = 'os'; 36 37 /** 38 * Known operating systems mapped to their internal short codes 39 * 40 * @var array 41 */ 42 protected static $operatingSystems = [ 43 'AIX' => 'AIX', 44 'AND' => 'Android', 45 'ADR' => 'Android TV', 46 'ALP' => 'Alpine Linux', 47 'AMZ' => 'Amazon Linux', 48 'AMG' => 'AmigaOS', 49 'ARM' => 'Armadillo OS', 50 'ARO' => 'AROS', 51 'ATV' => 'tvOS', 52 'ARL' => 'Arch Linux', 53 'AOS' => 'AOSC OS', 54 'ASP' => 'ASPLinux', 55 'AZU' => 'Azure Linux', 56 'BTR' => 'BackTrack', 57 'SBA' => 'Bada', 58 'BYI' => 'Baidu Yi', 59 'BEO' => 'BeOS', 60 'BLB' => 'BlackBerry OS', 61 'QNX' => 'BlackBerry Tablet OS', 62 'PAN' => 'blackPanther OS', 63 'BOS' => 'Bliss OS', 64 'BMP' => 'Brew', 65 'BSN' => 'BrightSignOS', 66 'CAI' => 'Caixa Mágica', 67 'CES' => 'CentOS', 68 'CST' => 'CentOS Stream', 69 'CLO' => 'Clear Linux OS', 70 'CLR' => 'ClearOS Mobile', 71 'COS' => 'Chrome OS', 72 'CRS' => 'Chromium OS', 73 'CHN' => 'China OS', 74 'COL' => 'Coolita OS', 75 'CYN' => 'CyanogenMod', 76 'DEB' => 'Debian', 77 'DEE' => 'Deepin', 78 'DFB' => 'DragonFly', 79 'DVK' => 'DVKBuntu', 80 'ELE' => 'ElectroBSD', 81 'EUL' => 'EulerOS', 82 'FED' => 'Fedora', 83 'FEN' => 'Fenix', 84 'FOS' => 'Firefox OS', 85 'FIR' => 'Fire OS', 86 'FOR' => 'Foresight Linux', 87 'FRE' => 'Freebox', 88 'BSD' => 'FreeBSD', 89 'FRI' => 'FRITZ!OS', 90 'FYD' => 'FydeOS', 91 'FUC' => 'Fuchsia', 92 'GNT' => 'Gentoo', 93 'GNX' => 'GENIX', 94 'GEO' => 'GEOS', 95 'GNS' => 'gNewSense', 96 'GRI' => 'GridOS', 97 'GTV' => 'Google TV', 98 'HPX' => 'HP-UX', 99 'HAI' => 'Haiku OS', 100 'IPA' => 'iPadOS', 101 'HAR' => 'HarmonyOS', 102 'HAS' => 'HasCodingOS', 103 'HEL' => 'HELIX OS', 104 'IRI' => 'IRIX', 105 'INF' => 'Inferno', 106 'JME' => 'Java ME', 107 'JOL' => 'Joli OS', 108 'KOS' => 'KaiOS', 109 'KAL' => 'Kali', 110 'KAN' => 'Kanotix', 111 'KIN' => 'KIN OS', 112 'KNO' => 'Knoppix', 113 'KTV' => 'KreaTV', 114 'KBT' => 'Kubuntu', 115 'LIN' => 'GNU/Linux', 116 'LEA' => 'LeafOS', 117 'LND' => 'LindowsOS', 118 'LNS' => 'Linspire', 119 'LEN' => 'Lineage OS', 120 'LIR' => 'Liri OS', 121 'LOO' => 'Loongnix', 122 'LBT' => 'Lubuntu', 123 'LOS' => 'Lumin OS', 124 'LUN' => 'LuneOS', 125 'VLN' => 'VectorLinux', 126 'MAC' => 'Mac', 127 'MAE' => 'Maemo', 128 'MAG' => 'Mageia', 129 'MDR' => 'Mandriva', 130 'SMG' => 'MeeGo', 131 'MET' => 'Meta Horizon', 132 'MCD' => 'MocorDroid', 133 'MON' => 'moonOS', 134 'EZX' => 'Motorola EZX', 135 'MIN' => 'Mint', 136 'MLD' => 'MildWild', 137 'MOR' => 'MorphOS', 138 'NBS' => 'NetBSD', 139 'MTK' => 'MTK / Nucleus', 140 'MRE' => 'MRE', 141 'NXT' => 'NeXTSTEP', 142 'NWS' => 'NEWS-OS', 143 'WII' => 'Nintendo', 144 'NDS' => 'Nintendo Mobile', 145 'NOV' => 'Nova', 146 'OS2' => 'OS/2', 147 'T64' => 'OSF1', 148 'OBS' => 'OpenBSD', 149 'OVS' => 'OpenVMS', 150 'OVZ' => 'OpenVZ', 151 'OWR' => 'OpenWrt', 152 'OTV' => 'Opera TV', 153 'ORA' => 'Oracle Linux', 154 'ORD' => 'Ordissimo', 155 'PAR' => 'Pardus', 156 'PCL' => 'PCLinuxOS', 157 'PIC' => 'PICO OS', 158 'PLA' => 'Plasma Mobile', 159 'PSP' => 'PlayStation Portable', 160 'PS3' => 'PlayStation', 161 'PVE' => 'Proxmox VE', 162 'PUF' => 'Puffin OS', 163 'PUR' => 'PureOS', 164 'QTP' => 'Qtopia', 165 'PIO' => 'Raspberry Pi OS', 166 'RAS' => 'Raspbian', 167 'RHT' => 'Red Hat', 168 'RST' => 'Red Star', 169 'RED' => 'RedOS', 170 'REV' => 'Revenge OS', 171 'RIS' => 'risingOS', 172 'ROS' => 'RISC OS', 173 'ROC' => 'Rocky Linux', 174 'ROK' => 'Roku OS', 175 'RSO' => 'Rosa', 176 'ROU' => 'RouterOS', 177 'REM' => 'Remix OS', 178 'RRS' => 'Resurrection Remix OS', 179 'REX' => 'REX', 180 'RZD' => 'RazoDroiD', 181 'RXT' => 'RTOS & Next', 182 'SAB' => 'Sabayon', 183 'SSE' => 'SUSE', 184 'SAF' => 'Sailfish OS', 185 'SCI' => 'Scientific Linux', 186 'SEE' => 'SeewoOS', 187 'SER' => 'SerenityOS', 188 'SIR' => 'Sirin OS', 189 'SLW' => 'Slackware', 190 'SOS' => 'Solaris', 191 'SBL' => 'Star-Blade OS', 192 'SYL' => 'Syllable', 193 'SYM' => 'Symbian', 194 'SYS' => 'Symbian OS', 195 'S40' => 'Symbian OS Series 40', 196 'S60' => 'Symbian OS Series 60', 197 'SY3' => 'Symbian^3', 198 'TEN' => 'TencentOS', 199 'TDX' => 'ThreadX', 200 'TIZ' => 'Tizen', 201 'TIV' => 'TiVo OS', 202 'TOS' => 'TmaxOS', 203 'TUR' => 'Turbolinux', 204 'UBT' => 'Ubuntu', 205 'ULT' => 'ULTRIX', 206 'UOS' => 'UOS', 207 'VID' => 'VIDAA', 208 'VIZ' => 'ViziOS', 209 'WAS' => 'watchOS', 210 'WER' => 'Wear OS', 211 'WTV' => 'WebTV', 212 'WHS' => 'Whale OS', 213 'WIN' => 'Windows', 214 'WCE' => 'Windows CE', 215 'WIO' => 'Windows IoT', 216 'WMO' => 'Windows Mobile', 217 'WPH' => 'Windows Phone', 218 'WRT' => 'Windows RT', 219 'WPO' => 'WoPhone', 220 'XBX' => 'Xbox', 221 'XBT' => 'Xubuntu', 222 'YNS' => 'YunOS', 223 'ZEN' => 'Zenwalk', 224 'ZOR' => 'ZorinOS', 225 'IOS' => 'iOS', 226 'POS' => 'palmOS', 227 'WEB' => 'Webian', 228 'WOS' => 'webOS', 229 ]; 230 231 /** 232 * Operating system families mapped to the short codes of the associated operating systems 233 * 234 * @var array 235 */ 236 protected static $osFamilies = [ 237 'Android' => [ 238 'AND', 'CYN', 'FIR', 'REM', 'RZD', 'MLD', 'MCD', 'YNS', 'GRI', 'HAR', 239 'ADR', 'CLR', 'BOS', 'REV', 'LEN', 'SIR', 'RRS', 'WER', 'PIC', 'ARM', 240 'HEL', 'BYI', 'RIS', 'PUF', 'LEA', 'MET', 241 ], 242 'AmigaOS' => ['AMG', 'MOR', 'ARO'], 243 'BlackBerry' => ['BLB', 'QNX'], 244 'Brew' => ['BMP'], 245 'BeOS' => ['BEO', 'HAI'], 246 'Chrome OS' => ['COS', 'CRS', 'FYD', 'SEE'], 247 'Firefox OS' => ['FOS', 'KOS'], 248 'Gaming Console' => ['WII', 'PS3'], 249 'Google TV' => ['GTV'], 250 'IBM' => ['OS2'], 251 'iOS' => ['IOS', 'ATV', 'WAS', 'IPA'], 252 'RISC OS' => ['ROS'], 253 'GNU/Linux' => [ 254 'LIN', 'ARL', 'DEB', 'KNO', 'MIN', 'UBT', 'KBT', 'XBT', 'LBT', 'FED', 255 'RHT', 'VLN', 'MDR', 'GNT', 'SAB', 'SLW', 'SSE', 'CES', 'BTR', 'SAF', 256 'ORD', 'TOS', 'RSO', 'DEE', 'FRE', 'MAG', 'FEN', 'CAI', 'PCL', 'HAS', 257 'LOS', 'DVK', 'ROK', 'OWR', 'OTV', 'KTV', 'PUR', 'PLA', 'FUC', 'PAR', 258 'FOR', 'MON', 'KAN', 'ZEN', 'LND', 'LNS', 'CHN', 'AMZ', 'TEN', 'CST', 259 'NOV', 'ROU', 'ZOR', 'RED', 'KAL', 'ORA', 'VID', 'TIV', 'BSN', 'RAS', 260 'UOS', 'PIO', 'FRI', 'LIR', 'WEB', 'SER', 'ASP', 'AOS', 'LOO', 'EUL', 261 'SCI', 'ALP', 'CLO', 'ROC', 'OVZ', 'PVE', 'RST', 'EZX', 'GNS', 'JOL', 262 'TUR', 'QTP', 'WPO', 'PAN', 'VIZ', 'AZU', 'COL', 263 ], 264 'Mac' => ['MAC'], 265 'Mobile Gaming Console' => ['PSP', 'NDS', 'XBX'], 266 'OpenVMS' => ['OVS'], 267 'Real-time OS' => ['MTK', 'TDX', 'MRE', 'JME', 'REX', 'RXT'], 268 'Other Mobile' => ['WOS', 'POS', 'SBA', 'TIZ', 'SMG', 'MAE', 'LUN', 'GEO'], 269 'Symbian' => ['SYM', 'SYS', 'SY3', 'S60', 'S40'], 270 'Unix' => [ 271 'SOS', 'AIX', 'HPX', 'BSD', 'NBS', 'OBS', 'DFB', 'SYL', 'IRI', 'T64', 272 'INF', 'ELE', 'GNX', 'ULT', 'NWS', 'NXT', 'SBL', 273 ], 274 'WebTV' => ['WTV'], 275 'Windows' => ['WIN'], 276 'Windows Mobile' => ['WPH', 'WMO', 'WCE', 'WRT', 'WIO', 'KIN'], 277 'Other Smart TV' => ['WHS'], 278 ]; 279 280 /** 281 * Contains a list of mappings from OS names we use to known client hint values 282 * 283 * @var array<string, array<string>> 284 */ 285 protected static $clientHintMapping = [ 286 'GNU/Linux' => ['Linux'], 287 'Mac' => ['MacOS'], 288 ]; 289 290 /** 291 * Operating system families that are known as desktop only 292 * 293 * @var array 294 */ 295 protected static $desktopOsArray = [ 296 'AmigaOS', 'IBM', 'GNU/Linux', 'Mac', 'Unix', 'Windows', 'BeOS', 'Chrome OS', 297 ]; 298 299 /** 300 * Fire OS version mapping 301 * 302 * @var array 303 */ 304 private $fireOsVersionMapping = [ 305 '11' => '8', 306 '10' => '8', 307 '9' => '7', 308 '7' => '6', 309 '5' => '5', 310 '4.4.3' => '4.5.1', 311 '4.4.2' => '4', 312 '4.2.2' => '3', 313 '4.0.3' => '3', 314 '4.0.2' => '3', 315 '4' => '2', 316 '2' => '1', 317 ]; 318 319 /** 320 * Lineage OS version mapping 321 * 322 * @var array 323 */ 324 private $lineageOsVersionMapping = [ 325 '15' => '22', 326 '14' => '21', 327 '13' => '20.0', 328 '12.1' => '19.1', 329 '12' => '19.0', 330 '11' => '18.0', 331 '10' => '17.0', 332 '9' => '16.0', 333 '8.1.0' => '15.1', 334 '8.0.0' => '15.0', 335 '7.1.2' => '14.1', 336 '7.1.1' => '14.1', 337 '7.0' => '14.0', 338 '6.0.1' => '13.0', 339 '6.0' => '13.0', 340 '5.1.1' => '12.1', 341 '5.0.2' => '12.0', 342 '5.0' => '12.0', 343 '4.4.4' => '11.0', 344 '4.3' => '10.2', 345 '4.2.2' => '10.1', 346 '4.0.4' => '9.1.0', 347 ]; 348 349 /** 350 * Returns all available operating systems 351 * 352 * @return array 353 */ 354 public static function getAvailableOperatingSystems(): array 355 { 356 return self::$operatingSystems; 357 } 358 359 /** 360 * Returns all available operating system families 361 * 362 * @return array 363 */ 364 public static function getAvailableOperatingSystemFamilies(): array 365 { 366 return self::$osFamilies; 367 } 368 369 /** 370 * Returns the os name and shot name 371 * 372 * @param string $name 373 * 374 * @return array 375 */ 376 public static function getShortOsData(string $name): array 377 { 378 $short = 'UNK'; 379 380 foreach (self::$operatingSystems as $osShort => $osName) { 381 if (\strtolower($name) !== \strtolower($osName)) { 382 continue; 383 } 384 385 $name = $osName; 386 $short = $osShort; 387 388 break; 389 } 390 391 return \compact('short', 'name'); 392 } 393 394 /** 395 * @inheritdoc 396 */ 397 public function parse(): ?array 398 { 399 $this->restoreUserAgentFromClientHints(); 400 401 $osFromClientHints = $this->parseOsFromClientHints(); 402 $osFromUserAgent = $this->parseOsFromUserAgent(); 403 404 if (!empty($osFromClientHints['name'])) { 405 $name = $osFromClientHints['name']; 406 $version = $osFromClientHints['version']; 407 408 // use version from user agent if non was provided in client hints, but os family from useragent matches 409 if (empty($version) 410 && self::getOsFamily($name) === self::getOsFamily($osFromUserAgent['name']) 411 ) { 412 $version = $osFromUserAgent['version']; 413 } 414 415 // On Windows, version 0.0.0 can be either 7, 8 or 8.1 416 if ('Windows' === $name && '0.0.0' === $version) { 417 $version = ('10' === $osFromUserAgent['version']) ? '' : $osFromUserAgent['version']; 418 } 419 420 // If the OS name detected from client hints matches the OS family from user agent 421 // but the os name is another, we use the one from user agent, as it might be more detailed 422 if (self::getOsFamily($osFromUserAgent['name']) === $name && $osFromUserAgent['name'] !== $name) { 423 $name = $osFromUserAgent['name']; 424 425 if ('LeafOS' === $name || 'HarmonyOS' === $name) { 426 $version = ''; 427 } 428 429 if ('PICO OS' === $name) { 430 $version = $osFromUserAgent['version']; 431 } 432 433 if ('Fire OS' === $name && !empty($osFromClientHints['version'])) { 434 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0'); 435 436 $version = $this->fireOsVersionMapping[$version] 437 ?? $this->fireOsVersionMapping[$majorVersion] ?? ''; 438 } 439 } 440 441 $short = $osFromClientHints['short_name']; 442 443 // Chrome OS is in some cases reported as Linux in client hints, we fix this only if the version matches 444 if ('GNU/Linux' === $name 445 && 'Chrome OS' === $osFromUserAgent['name'] 446 && $osFromClientHints['version'] === $osFromUserAgent['version'] 447 ) { 448 $name = $osFromUserAgent['name']; 449 $short = $osFromUserAgent['short_name']; 450 } 451 452 // Chrome OS is in some cases reported as Android in client hints 453 if ('Android' === $name && 'Chrome OS' === $osFromUserAgent['name']) { 454 $name = $osFromUserAgent['name']; 455 $version = ''; 456 $short = $osFromUserAgent['short_name']; 457 } 458 459 // Meta Horizon is reported as Linux in client hints 460 if ('GNU/Linux' === $name && 'Meta Horizon' === $osFromUserAgent['name']) { 461 $name = $osFromUserAgent['name']; 462 $short = $osFromUserAgent['short_name']; 463 } 464 } elseif (!empty($osFromUserAgent['name'])) { 465 $name = $osFromUserAgent['name']; 466 $version = $osFromUserAgent['version']; 467 $short = $osFromUserAgent['short_name']; 468 } else { 469 return []; 470 } 471 472 $platform = $this->parsePlatform(); 473 $family = self::getOsFamily($short); 474 $androidApps = [ 475 'com.hisense.odinbrowser', 'com.seraphic.openinet.pre', 'com.appssppa.idesktoppcbrowser', 476 'every.browser.inc', 477 ]; 478 479 if (null !== $this->clientHints) { 480 if (\in_array($this->clientHints->getApp(), $androidApps) && 'Android' !== $name) { 481 $name = 'Android'; 482 $family = 'Android'; 483 $short = 'ADR'; 484 $version = ''; 485 } 486 487 if ('org.lineageos.jelly' === $this->clientHints->getApp() && 'Lineage OS' !== $name) { 488 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0'); 489 490 $name = 'Lineage OS'; 491 $family = 'Android'; 492 $short = 'LEN'; 493 $version = $this->lineageOsVersionMapping[$version] 494 ?? $this->lineageOsVersionMapping[$majorVersion] ?? ''; 495 } 496 497 if ('org.mozilla.tv.firefox' === $this->clientHints->getApp() && 'Fire OS' !== $name) { 498 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0'); 499 500 $name = 'Fire OS'; 501 $family = 'Android'; 502 $short = 'FIR'; 503 $version = $this->fireOsVersionMapping[$version] ?? $this->fireOsVersionMapping[$majorVersion] ?? ''; 504 } 505 } 506 507 $return = [ 508 'name' => $name, 509 'short_name' => $short, 510 'version' => $version, 511 'platform' => $platform, 512 'family' => $family, 513 ]; 514 515 if (\in_array($return['name'], self::$operatingSystems)) { 516 $return['short_name'] = \array_search($return['name'], self::$operatingSystems); 517 } 518 519 return $return; 520 } 521 522 /** 523 * Returns the operating system family for the given operating system 524 * 525 * @param string $osLabel name or short name 526 * 527 * @return string|null If null, "Unknown" 528 */ 529 public static function getOsFamily(string $osLabel): ?string 530 { 531 if (\in_array($osLabel, self::$operatingSystems)) { 532 $osLabel = \array_search($osLabel, self::$operatingSystems); 533 } 534 535 foreach (self::$osFamilies as $family => $labels) { 536 if (\in_array($osLabel, $labels)) { 537 return (string) $family; 538 } 539 } 540 541 return null; 542 } 543 544 /** 545 * Returns true if OS is desktop 546 * 547 * @param string $osName OS short name 548 * 549 * @return bool 550 */ 551 public static function isDesktopOs(string $osName): bool 552 { 553 $osFamily = self::getOsFamily($osName); 554 555 return \in_array($osFamily, self::$desktopOsArray); 556 } 557 558 /** 559 * Returns the full name for the given short name 560 * 561 * @param string $os 562 * @param string|null $ver 563 * 564 * @return ?string 565 */ 566 public static function getNameFromId(string $os, ?string $ver = null): ?string 567 { 568 if (\array_key_exists($os, self::$operatingSystems)) { 569 $osFullName = self::$operatingSystems[$os]; 570 571 return \trim($osFullName . ' ' . $ver); 572 } 573 574 return null; 575 } 576 577 /** 578 * Returns the OS that can be safely detected from client hints 579 * 580 * @return array 581 */ 582 protected function parseOsFromClientHints(): array 583 { 584 $name = $version = $short = ''; 585 586 if ($this->clientHints instanceof ClientHints && $this->clientHints->getOperatingSystem()) { 587 $hintName = $this->applyClientHintMapping($this->clientHints->getOperatingSystem()); 588 589 foreach (self::$operatingSystems as $osShort => $osName) { 590 if ($this->fuzzyCompare($hintName, $osName)) { 591 $name = $osName; 592 $short = $osShort; 593 594 break; 595 } 596 } 597 598 $version = $this->clientHints->getOperatingSystemVersion(); 599 600 if ('Windows' === $name) { 601 $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0'); 602 $minorVersion = (int) (\explode('.', $version, 2)[1] ?? '0'); 603 604 if (0 === $majorVersion) { 605 $minorVersionMapping = [1 => '7', 2 => '8', 3 => '8.1']; 606 $version = $minorVersionMapping[$minorVersion] ?? $version; 607 } elseif ($majorVersion > 0 && $majorVersion < 11) { 608 $version = '10'; 609 } elseif ($majorVersion > 10) { 610 $version = '11'; 611 } 612 } 613 614 // On Windows, version 0.0.0 can be either 7, 8 or 8.1, so we return 0.0.0 615 if ('Windows' !== $name && '0.0.0' !== $version && 0 === (int) $version) { 616 $version = ''; 617 } 618 } 619 620 return [ 621 'name' => $name, 622 'short_name' => $short, 623 'version' => $this->buildVersion($version, []), 624 ]; 625 } 626 627 /** 628 * Returns the OS that can be detected from useragent 629 * 630 * @return array 631 * 632 * @throws \Exception 633 */ 634 protected function parseOsFromUserAgent(): array 635 { 636 $osRegex = $matches = []; 637 $name = $version = $short = ''; 638 639 foreach ($this->getRegexes() as $osRegex) { 640 $matches = $this->matchUserAgent($osRegex['regex']); 641 642 if ($matches) { 643 break; 644 } 645 } 646 647 if (!empty($matches)) { 648 $name = $this->buildByMatch($osRegex['name'], $matches); 649 ['name' => $name, 'short' => $short] = self::getShortOsData($name); 650 651 $version = \array_key_exists('version', $osRegex) 652 ? $this->buildVersion((string) $osRegex['version'], $matches) 653 : ''; 654 655 foreach ($osRegex['versions'] ?? [] as $regex) { 656 $matches = $this->matchUserAgent($regex['regex']); 657 658 if (!$matches) { 659 continue; 660 } 661 662 if (\array_key_exists('name', $regex)) { 663 $name = $this->buildByMatch($regex['name'], $matches); 664 ['name' => $name, 'short' => $short] = self::getShortOsData($name); 665 } 666 667 if (\array_key_exists('version', $regex)) { 668 $version = $this->buildVersion((string) $regex['version'], $matches); 669 } 670 671 break; 672 } 673 } 674 675 return [ 676 'name' => $name, 677 'short_name' => $short, 678 'version' => $version, 679 ]; 680 } 681 682 /** 683 * Parse current UserAgent string for the operating system platform 684 * 685 * @return string 686 */ 687 protected function parsePlatform(): string 688 { 689 // Use architecture from client hints if available 690 if ($this->clientHints instanceof ClientHints && $this->clientHints->getArchitecture()) { 691 $arch = \strtolower($this->clientHints->getArchitecture()); 692 693 if (false !== \strpos($arch, 'arm')) { 694 return 'ARM'; 695 } 696 697 if (false !== \strpos($arch, 'loongarch64')) { 698 return 'LoongArch64'; 699 } 700 701 if (false !== \strpos($arch, 'mips')) { 702 return 'MIPS'; 703 } 704 705 if (false !== \strpos($arch, 'sh4')) { 706 return 'SuperH'; 707 } 708 709 if (false !== \strpos($arch, 'sparc64')) { 710 return 'SPARC64'; 711 } 712 713 if (false !== \strpos($arch, 'x64') 714 || (false !== \strpos($arch, 'x86') && '64' === $this->clientHints->getBitness()) 715 ) { 716 return 'x64'; 717 } 718 719 if (false !== \strpos($arch, 'x86')) { 720 return 'x86'; 721 } 722 } 723 724 if ($this->matchUserAgent('arm[ _;)ev]|.*arm$|.*arm64|aarch64|Apple ?TV|Watch ?OS|Watch1,[12]')) { 725 return 'ARM'; 726 } 727 728 if ($this->matchUserAgent('loongarch64')) { 729 return 'LoongArch64'; 730 } 731 732 if ($this->matchUserAgent('mips')) { 733 return 'MIPS'; 734 } 735 736 if ($this->matchUserAgent('sh4')) { 737 return 'SuperH'; 738 } 739 740 if ($this->matchUserAgent('sparc64')) { 741 return 'SPARC64'; 742 } 743 744 if ($this->matchUserAgent('64-?bit|WOW64|(?:Intel)?x64|WINDOWS_64|win64|.*amd64|.*x86_?64')) { 745 return 'x64'; 746 } 747 748 if ($this->matchUserAgent('.*32bit|.*win32|(?:i[0-9]|x)86|i86pc')) { 749 return 'x86'; 750 } 751 752 return ''; 753 } 754} 755