pax_global_header00006660000000000000000000000064144045504040014512gustar00rootroot0000000000000052 comment=a549e24350ff63e15dc691b6b21b73909b4fa9bd device-detector-6.1.1/000077500000000000000000000000001440455040400145655ustar00rootroot00000000000000device-detector-6.1.1/Cache/000077500000000000000000000000001440455040400155705ustar00rootroot00000000000000device-detector-6.1.1/Cache/CacheInterface.php000066400000000000000000000016051440455040400211270ustar00rootroot00000000000000cache = $cache; } /** * @inheritDoc */ public function fetch(string $id) { return $this->cache->fetch($id); } /** * @inheritDoc */ public function contains(string $id): bool { return $this->cache->contains($id); } /** * @inheritDoc */ public function save(string $id, $data, int $lifeTime = 0): bool { return $this->cache->save($id, $data, $lifeTime); } /** * @inheritDoc */ public function delete(string $id): bool { return $this->cache->delete($id); } /** * @inheritDoc */ public function flushAll(): bool { return $this->cache->flushAll(); } } device-detector-6.1.1/Cache/LaravelCache.php000066400000000000000000000020111440455040400206050ustar00rootroot00000000000000cache = $cache; } /** * @inheritDoc */ public function fetch(string $id) { return $this->cache->get($id, false); } /** * @inheritDoc */ public function contains(string $id): bool { return $this->cache->has($id); } /** * @inheritDoc */ public function save(string $id, $data, int $lifeTime = 0): bool { return $this->cache->set($id, $data, \func_num_args() < 3 ? null : $lifeTime); } /** * @inheritDoc */ public function delete(string $id): bool { return $this->cache->delete($id); } /** * @inheritDoc */ public function flushAll(): bool { return $this->cache->clear(); } } device-detector-6.1.1/Cache/PSR6Bridge.php000066400000000000000000000027471440455040400201620ustar00rootroot00000000000000pool = $pool; } /** * @inheritDoc */ public function fetch(string $id) { $item = $this->pool->getItem($id); return $item->isHit() ? $item->get() : false; } /** * @inheritDoc */ public function contains(string $id): bool { return $this->pool->hasItem($id); } /** * @inheritDoc */ public function save(string $id, $data, int $lifeTime = 0): bool { $item = $this->pool->getItem($id); $item->set($data); if (\func_num_args() > 2) { $item->expiresAfter($lifeTime); } return $this->pool->save($item); } /** * @inheritDoc */ public function delete(string $id): bool { return $this->pool->deleteItem($id); } /** * @inheritDoc */ public function flushAll(): bool { return $this->pool->clear(); } } device-detector-6.1.1/Cache/StaticCache.php000066400000000000000000000025301440455040400204540ustar00rootroot00000000000000contains($id) ? self::$staticCache[$id] : false; } /** * @inheritdoc */ public function contains(string $id): bool { return isset(self::$staticCache[$id]) || \array_key_exists($id, self::$staticCache); } /** * @inheritdoc */ public function save(string $id, $data, int $lifeTime = 0): bool { self::$staticCache[$id] = $data; return true; } /** * @inheritdoc */ public function delete(string $id): bool { unset(self::$staticCache[$id]); return true; } /** * @inheritdoc */ public function flushAll(): bool { self::$staticCache = []; return true; } } device-detector-6.1.1/ClientHints.php000066400000000000000000000222551440455040400175300ustar00rootroot00000000000000model = $model; $this->platform = $platform; $this->platformVersion = $platformVersion; $this->uaFullVersion = $uaFullVersion; $this->fullVersionList = $fullVersionList; $this->mobile = $mobile; $this->architecture = $architecture; $this->bitness = $bitness; $this->app = $app; } /** * Magic method to directly allow accessing the protected properties * * @param string $variable * * @return mixed * * @throws \Exception */ public function __get(string $variable) { if (\property_exists($this, $variable)) { return $this->$variable; } throw new \Exception('Invalid ClientHint property requested.'); } /** * Returns if the client hints * * @return bool */ public function isMobile(): bool { return $this->mobile; } /** * Returns the Architecture * * @return string */ public function getArchitecture(): string { return $this->architecture; } /** * Returns the Bitness * * @return string */ public function getBitness(): string { return $this->bitness; } /** * Returns the device model * * @return string */ public function getModel(): string { return $this->model; } /** * Returns the Operating System * * @return string */ public function getOperatingSystem(): string { return $this->platform; } /** * Returns the Operating System version * * @return string */ public function getOperatingSystemVersion(): string { return $this->platformVersion; } /** * Returns the Browser name * * @return array */ public function getBrandList(): array { if (\is_array($this->fullVersionList) && \count($this->fullVersionList)) { $brands = \array_column($this->fullVersionList, 'brand'); $versions = \array_column($this->fullVersionList, 'version'); if (\count($brands) === \count($versions)) { return \array_combine($brands, $versions); } } return []; } /** * Returns the Browser version * * @return string */ public function getBrandVersion(): string { if (!empty($this->uaFullVersion)) { return $this->uaFullVersion; } return ''; } /** * Returns the Android app id * * @return string */ public function getApp(): string { return $this->app; } /** * Factory method to easily instantiate this class using an array containing all available (client hint) headers * * @param array $headers * * @return ClientHints */ public static function factory(array $headers): ClientHints { $model = $platform = $platformVersion = $uaFullVersion = $architecture = $bitness = ''; $app = ''; $mobile = false; $fullVersionList = []; foreach ($headers as $name => $value) { switch (\str_replace('_', '-', \strtolower((string) $name))) { case 'http-sec-ch-ua-arch': case 'sec-ch-ua-arch': case 'arch': case 'architecture': $architecture = \trim($value, '"'); break; case 'http-sec-ch-ua-bitness': case 'sec-ch-ua-bitness': case 'bitness': $bitness = \trim($value, '"'); break; case 'http-sec-ch-ua-mobile': case 'sec-ch-ua-mobile': case 'mobile': $mobile = true === $value || '1' === $value || '?1' === $value; break; case 'http-sec-ch-ua-model': case 'sec-ch-ua-model': case 'model': $model = \trim($value, '"'); break; case 'http-sec-ch-ua-full-version': case 'sec-ch-ua-full-version': case 'uafullversion': $uaFullVersion = \trim($value, '"'); break; case 'http-sec-ch-ua-platform': case 'sec-ch-ua-platform': case 'platform': $platform = \trim($value, '"'); break; case 'http-sec-ch-ua-platform-version': case 'sec-ch-ua-platform-version': case 'platformversion': $platformVersion = \trim($value, '"'); break; case 'brands': if (!empty($fullVersionList)) { break; } // use this only if no other header already set the list case 'fullversionlist': $fullVersionList = \is_array($value) ? $value : $fullVersionList; break; case 'http-sec-ch-ua': case 'sec-ch-ua': if (!empty($fullVersionList)) { break; } // use this only if no other header already set the list case 'http-sec-ch-ua-full-version-list': case 'sec-ch-ua-full-version-list': $reg = '/^"([^"]+)"; ?v="([^"]+)"(?:, )?/'; $list = []; while (\preg_match($reg, $value, $matches)) { $list[] = ['brand' => $matches[1], 'version' => $matches[2]]; $value = \substr($value, \strlen($matches[0])); } if (\count($list)) { $fullVersionList = $list; } break; case 'http-x-requested-with': case 'x-requested-with': if ('xmlhttprequest' !== \strtolower($value)) { $app = $value; } break; } } return new self( $model, $platform, $platformVersion, $uaFullVersion, $fullVersionList, $mobile, $architecture, $bitness, $app ); } } device-detector-6.1.1/DeviceDetector.php000066400000000000000000000745121440455040400202000ustar00rootroot00000000000000 */ protected $clientParsers = []; /** * @var array */ protected $deviceParsers = []; /** * @var array */ public $botParsers = []; /** * @var bool */ private $parsed = false; /** * Constructor * * @param string $userAgent UA to parse * @param ClientHints $clientHints Browser client hints to parse */ public function __construct(string $userAgent = '', ?ClientHints $clientHints = null) { if ('' !== $userAgent) { $this->setUserAgent($userAgent); } if ($clientHints instanceof ClientHints) { $this->setClientHints($clientHints); } $this->addClientParser(new FeedReader()); $this->addClientParser(new MobileApp()); $this->addClientParser(new MediaPlayer()); $this->addClientParser(new PIM()); $this->addClientParser(new Browser()); $this->addClientParser(new Library()); $this->addDeviceParser(new HbbTv()); $this->addDeviceParser(new ShellTv()); $this->addDeviceParser(new Notebook()); $this->addDeviceParser(new Console()); $this->addDeviceParser(new CarBrowser()); $this->addDeviceParser(new Camera()); $this->addDeviceParser(new PortableMediaPlayer()); $this->addDeviceParser(new Mobile()); $this->addBotParser(new Bot()); } /** * @param string $methodName * @param array $arguments * * @return bool */ public function __call(string $methodName, array $arguments): bool { foreach (AbstractDeviceParser::getAvailableDeviceTypes() as $deviceName => $deviceType) { if (\strtolower($methodName) === 'is' . \strtolower(\str_replace(' ', '', $deviceName))) { return $this->getDevice() === $deviceType; } } foreach ($this->clientTypes as $client) { if (\strtolower($methodName) === 'is' . \strtolower(\str_replace(' ', '', $client))) { return $this->getClient('type') === $client; } } throw new \BadMethodCallException("Method {$methodName} not found"); } /** * Sets the useragent to be parsed * * @param string $userAgent */ public function setUserAgent(string $userAgent): void { if ($this->userAgent !== $userAgent) { $this->reset(); } $this->userAgent = $userAgent; } /** * Sets the browser client hints to be parsed * * @param ?ClientHints $clientHints */ public function setClientHints(?ClientHints $clientHints = null): void { if ($this->clientHints !== $clientHints) { $this->reset(); } $this->clientHints = $clientHints; } /** * @param AbstractClientParser $parser * * @throws \Exception */ public function addClientParser(AbstractClientParser $parser): void { $this->clientParsers[] = $parser; $this->clientTypes[] = $parser->getName(); } /** * @return array */ public function getClientParsers(): array { return $this->clientParsers; } /** * @param AbstractDeviceParser $parser * * @throws \Exception */ public function addDeviceParser(AbstractDeviceParser $parser): void { $this->deviceParsers[] = $parser; } /** * @return array */ public function getDeviceParsers(): array { return $this->deviceParsers; } /** * @param AbstractBotParser $parser */ public function addBotParser(AbstractBotParser $parser): void { $this->botParsers[] = $parser; } /** * @return array */ public function getBotParsers(): array { return $this->botParsers; } /** * Sets whether to discard additional bot information * If information is discarded it's only possible check whether UA was detected as bot or not. * (Discarding information speeds up the detection a bit) * * @param bool $discard */ public function discardBotInformation(bool $discard = true): void { $this->discardBotInformation = $discard; } /** * Sets whether to skip bot detection. * It is needed if we want bots to be processed as a simple clients. So we can detect if it is mobile client, * or desktop, or enything else. By default all this information is not retrieved for the bots. * * @param bool $skip */ public function skipBotDetection(bool $skip = true): void { $this->skipBotDetection = $skip; } /** * Returns if the parsed UA was identified as a Bot * * @see bots.yml for a list of detected bots * * @return bool */ public function isBot(): bool { return !empty($this->bot); } /** * Returns if the parsed UA was identified as a touch enabled device * * Note: That only applies to windows 8 tablets * * @return bool */ public function isTouchEnabled(): bool { $regex = 'Touch'; return !!$this->matchUserAgent($regex); } /** * Returns if the parsed UA is detected as a mobile device * * @return bool */ public function isMobile(): bool { // Client hints indicate a mobile device if ($this->clientHints instanceof ClientHints && $this->clientHints->isMobile()) { return true; } // Mobile device types if (\in_array($this->device, [ AbstractDeviceParser::DEVICE_TYPE_FEATURE_PHONE, AbstractDeviceParser::DEVICE_TYPE_SMARTPHONE, AbstractDeviceParser::DEVICE_TYPE_TABLET, AbstractDeviceParser::DEVICE_TYPE_PHABLET, AbstractDeviceParser::DEVICE_TYPE_CAMERA, AbstractDeviceParser::DEVICE_TYPE_PORTABLE_MEDIA_PAYER, ]) ) { return true; } // non mobile device types if (\in_array($this->device, [ AbstractDeviceParser::DEVICE_TYPE_TV, AbstractDeviceParser::DEVICE_TYPE_SMART_DISPLAY, AbstractDeviceParser::DEVICE_TYPE_CONSOLE, ]) ) { return false; } // Check for browsers available for mobile devices only if ($this->usesMobileBrowser()) { return true; } $osName = $this->getOs('name'); if (empty($osName) || self::UNKNOWN === $osName) { return false; } return !$this->isBot() && !$this->isDesktop(); } /** * Returns if the parsed UA was identified as desktop device * Desktop devices are all devices with an unknown type that are running a desktop os * * @see OperatingSystem::$desktopOsArray * * @return bool */ public function isDesktop(): bool { $osName = $this->getOsAttribute('name'); if (empty($osName) || self::UNKNOWN === $osName) { return false; } // Check for browsers available for mobile devices only if ($this->usesMobileBrowser()) { return false; } return OperatingSystem::isDesktopOs($osName); } /** * Returns the operating system data extracted from the parsed UA * * If $attr is given only that property will be returned * * @param string $attr property to return(optional) * * @return array|string|null */ public function getOs(string $attr = '') { if ('' === $attr) { return $this->os; } return $this->getOsAttribute($attr); } /** * Returns the client data extracted from the parsed UA * * If $attr is given only that property will be returned * * @param string $attr property to return(optional) * * @return array|string|null */ public function getClient(string $attr = '') { if ('' === $attr) { return $this->client; } return $this->getClientAttribute($attr); } /** * Returns the device type extracted from the parsed UA * * @see AbstractDeviceParser::$deviceTypes for available device types * * @return int|null */ public function getDevice(): ?int { return $this->device; } /** * Returns the device type extracted from the parsed UA * * @see AbstractDeviceParser::$deviceTypes for available device types * * @return string */ public function getDeviceName(): string { if (null !== $this->getDevice()) { return AbstractDeviceParser::getDeviceName($this->getDevice()); } return ''; } /** * Returns the device brand extracted from the parsed UA * * @see self::$deviceBrand for available device brands * * @return string * * @deprecated since 4.0 - short codes might be removed in next major release */ public function getBrand(): string { return AbstractDeviceParser::getShortCode($this->brand); } /** * Returns the full device brand name extracted from the parsed UA * * @see self::$deviceBrand for available device brands * * @return string */ public function getBrandName(): string { return $this->brand; } /** * Returns the device model extracted from the parsed UA * * @return string */ public function getModel(): string { return $this->model; } /** * Returns the user agent that is set to be parsed * * @return string */ public function getUserAgent(): string { return $this->userAgent; } /** * Returns the client hints that is set to be parsed * * @return ?ClientHints */ public function getClientHints(): ?ClientHints { return $this->clientHints; } /** * Returns the bot extracted from the parsed UA * * @return array|bool|null */ public function getBot() { return $this->bot; } /** * Returns true, if userAgent was already parsed with parse() * * @return bool */ public function isParsed(): bool { return $this->parsed; } /** * Triggers the parsing of the current user agent */ public function parse(): void { if ($this->isParsed()) { return; } $this->parsed = true; // skip parsing for empty useragents or those not containing any letter (if no client hints were provided) if ((empty($this->userAgent) || !\preg_match('/([a-z])/i', $this->userAgent)) && empty($this->clientHints) ) { return; } $this->parseBot(); if ($this->isBot()) { return; } $this->parseOs(); /** * Parse Clients * Clients might be browsers, Feed Readers, Mobile Apps, Media Players or * any other application accessing with an parseable UA */ $this->parseClient(); $this->parseDevice(); } /** * Parses a useragent and returns the detected data * * ATTENTION: Use that method only for testing or very small applications * To get fast results from DeviceDetector you need to make your own implementation, * that should use one of the caching mechanisms. See README.md for more information. * * @internal * * @deprecated * * @param string $ua UserAgent to parse * @param ?ClientHints $clientHints Client Hints to parse * * @return array */ public static function getInfoFromUserAgent(string $ua, ?ClientHints $clientHints = null): array { static $deviceDetector; if (!($deviceDetector instanceof DeviceDetector)) { $deviceDetector = new DeviceDetector(); } $deviceDetector->setUserAgent($ua); $deviceDetector->setClientHints($clientHints); $deviceDetector->parse(); if ($deviceDetector->isBot()) { return [ 'user_agent' => $deviceDetector->getUserAgent(), 'bot' => $deviceDetector->getBot(), ]; } /** @var array $client */ $client = $deviceDetector->getClient(); $browserFamily = 'Unknown'; if ($deviceDetector->isBrowser() && true === \is_array($client) && true === \array_key_exists('family', $client) && null !== $client['family'] ) { $browserFamily = $client['family']; } unset($client['short_name'], $client['family']); /** @var array $os */ $os = $deviceDetector->getOs(); $osFamily = $os['family'] ?? 'Unknown'; unset($os['short_name'], $os['family']); return [ 'user_agent' => $deviceDetector->getUserAgent(), 'os' => $os, 'client' => $client, 'device' => [ 'type' => $deviceDetector->getDeviceName(), 'brand' => $deviceDetector->getBrandName(), 'model' => $deviceDetector->getModel(), ], 'os_family' => $osFamily, 'browser_family' => $browserFamily, ]; } /** * Sets the Cache class * * @param CacheInterface $cache */ public function setCache(CacheInterface $cache): void { $this->cache = $cache; } /** * Returns Cache object * * @return CacheInterface */ public function getCache(): CacheInterface { if (!empty($this->cache)) { return $this->cache; } return new StaticCache(); } /** * Sets the Yaml Parser class * * @param YamlParser $yamlParser */ public function setYamlParser(YamlParser $yamlParser): void { $this->yamlParser = $yamlParser; } /** * Returns Yaml Parser object * * @return YamlParser */ public function getYamlParser(): YamlParser { if (!empty($this->yamlParser)) { return $this->yamlParser; } return new Spyc(); } /** * @param string $attr * * @return string */ protected function getClientAttribute(string $attr): string { if (!isset($this->client[$attr])) { return self::UNKNOWN; } return $this->client[$attr]; } /** * @param string $attr * * @return string */ protected function getOsAttribute(string $attr): string { if (!isset($this->os[$attr])) { return self::UNKNOWN; } return $this->os[$attr]; } /** * Returns if the parsed UA contains the 'Android; Tablet;' fragment * * @return bool */ protected function hasAndroidTableFragment(): bool { $regex = 'Android( [\.0-9]+)?; Tablet;'; return !!$this->matchUserAgent($regex); } /** * Returns if the parsed UA contains the 'Android; Mobile;' fragment * * @return bool */ protected function hasAndroidMobileFragment(): bool { $regex = 'Android( [\.0-9]+)?; Mobile;'; return !!$this->matchUserAgent($regex); } /** * Returns if the parsed UA contains the 'Desktop x64;' or 'Desktop x32;' or 'Desktop WOW64' fragment * * @return bool */ protected function hasDesktopFragment(): bool { $regex = 'Desktop (x(?:32|64)|WOW64);'; return !!$this->matchUserAgent($regex); } /** * Returns if the parsed UA contains usage of a mobile only browser * * @return bool */ protected function usesMobileBrowser(): bool { return 'browser' === $this->getClient('type') && Browser::isMobileOnlyBrowser($this->getClientAttribute('name')); } /** * Parses the UA for bot information using the Bot parser */ protected function parseBot(): void { if ($this->skipBotDetection) { $this->bot = false; return; } $parsers = $this->getBotParsers(); foreach ($parsers as $parser) { $parser->setYamlParser($this->getYamlParser()); $parser->setCache($this->getCache()); $parser->setUserAgent($this->getUserAgent()); $parser->setClientHints($this->getClientHints()); if ($this->discardBotInformation) { $parser->discardDetails(); } $bot = $parser->parse(); if (!empty($bot)) { $this->bot = $bot; break; } } } /** * Tries to detect the client (e.g. browser, mobile app, ...) */ protected function parseClient(): void { $parsers = $this->getClientParsers(); foreach ($parsers as $parser) { $parser->setYamlParser($this->getYamlParser()); $parser->setCache($this->getCache()); $parser->setUserAgent($this->getUserAgent()); $parser->setClientHints($this->getClientHints()); $client = $parser->parse(); if (!empty($client)) { $this->client = $client; break; } } } /** * Tries to detect the device type, model and brand */ protected function parseDevice(): void { $parsers = $this->getDeviceParsers(); foreach ($parsers as $parser) { $parser->setYamlParser($this->getYamlParser()); $parser->setCache($this->getCache()); $parser->setUserAgent($this->getUserAgent()); $parser->setClientHints($this->getClientHints()); if ($parser->parse()) { $this->device = $parser->getDeviceType(); $this->model = $parser->getModel(); $this->brand = $parser->getBrand(); break; } } /** * If no model could be parsed from useragent, we use the one from client hints if available */ if ($this->clientHints instanceof ClientHints && empty($this->model)) { $this->model = $this->clientHints->getModel(); } /** * If no brand has been assigned try to match by known vendor fragments */ if (empty($this->brand)) { $vendorParser = new VendorFragment($this->getUserAgent()); $vendorParser->setYamlParser($this->getYamlParser()); $vendorParser->setCache($this->getCache()); $this->brand = $vendorParser->parse()['brand'] ?? ''; } $osName = $this->getOsAttribute('name'); $osFamily = $this->getOsAttribute('family'); $osVersion = $this->getOsAttribute('version'); $clientName = $this->getClientAttribute('name'); /** * Assume all devices running iOS / Mac OS are from Apple */ if (empty($this->brand) && \in_array($osName, ['iPadOS', 'tvOS', 'watchOS', 'iOS', 'Mac'])) { $this->brand = 'Apple'; } /** * Chrome on Android passes the device type based on the keyword 'Mobile' * If it is present the device should be a smartphone, otherwise it's a tablet * See https://developer.chrome.com/multidevice/user-agent#chrome_for_android_user_agent * Note: We do not check for browser (family) here, as there might be mobile apps using Chrome, that won't have * a detected browser, but can still be detected. So we check the useragent for Chrome instead. */ if (null === $this->device && 'Android' === $osFamily && $this->matchUserAgent('Chrome/[\.0-9]*') ) { if ($this->matchUserAgent('(?:Mobile|eliboM) Safari/')) { $this->device = AbstractDeviceParser::DEVICE_TYPE_SMARTPHONE; } elseif ($this->matchUserAgent('(?!Mobile )Safari/')) { $this->device = AbstractDeviceParser::DEVICE_TYPE_TABLET; } } /** * Some UA contain the fragment 'Pad/APad', so we assume those devices as tablets */ if (AbstractDeviceParser::DEVICE_TYPE_SMARTPHONE === $this->device && $this->matchUserAgent('Pad/APad')) { $this->device = AbstractDeviceParser::DEVICE_TYPE_TABLET; } /** * Some UA contain the fragment 'Android; Tablet;' or 'Opera Tablet', so we assume those devices as tablets */ if (null === $this->device && ($this->hasAndroidTableFragment() || $this->matchUserAgent('Opera Tablet')) ) { $this->device = AbstractDeviceParser::DEVICE_TYPE_TABLET; } /** * Some user agents simply contain the fragment 'Android; Mobile;', so we assume those devices as smartphones */ if (null === $this->device && $this->hasAndroidMobileFragment()) { $this->device = AbstractDeviceParser::DEVICE_TYPE_SMARTPHONE; } /** * Android up to 3.0 was designed for smartphones only. But as 3.0, which was tablet only, was published * too late, there were a bunch of tablets running with 2.x * With 4.0 the two trees were merged and it is for smartphones and tablets * * So were are expecting that all devices running Android < 2 are smartphones * Devices running Android 3.X are tablets. Device type of Android 2.X and 4.X+ are unknown */ if (null === $this->device && 'Android' === $osName && '' !== $osVersion) { if (-1 === \version_compare($osVersion, '2.0')) { $this->device = AbstractDeviceParser::DEVICE_TYPE_SMARTPHONE; } elseif (\version_compare($osVersion, '3.0') >= 0 && -1 === \version_compare($osVersion, '4.0') ) { $this->device = AbstractDeviceParser::DEVICE_TYPE_TABLET; } } /** * All detected feature phones running android are more likely a smartphone */ if (AbstractDeviceParser::DEVICE_TYPE_FEATURE_PHONE === $this->device && 'Android' === $osFamily) { $this->device = AbstractDeviceParser::DEVICE_TYPE_SMARTPHONE; } /** * All unknown devices under running Java ME are more likely a features phones */ if ('Java ME' === $osName && null === $this->device) { $this->device = AbstractDeviceParser::DEVICE_TYPE_FEATURE_PHONE; } /** * According to http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx * Internet Explorer 10 introduces the "Touch" UA string token. If this token is present at the end of the * UA string, the computer has touch capability, and is running Windows 8 (or later). * This UA string will be transmitted on a touch-enabled system running Windows 8 (RT) * * As most touch enabled devices are tablets and only a smaller part are desktops/notebooks we assume that * all Windows 8 touch devices are tablets. */ if (null === $this->device && ('Windows RT' === $osName || ('Windows' === $osName && \version_compare($osVersion, '8') >= 0)) && $this->isTouchEnabled() ) { $this->device = AbstractDeviceParser::DEVICE_TYPE_TABLET; } /** * All devices running Opera TV Store are assumed to be a tv */ if ($this->matchUserAgent('Opera TV Store| OMI/')) { $this->device = AbstractDeviceParser::DEVICE_TYPE_TV; } /** * All devices that contain Andr0id in string are assumed to be a tv */ if ($this->matchUserAgent('Andr0id|Android TV|\(lite\) TV')) { $this->device = AbstractDeviceParser::DEVICE_TYPE_TV; } /** * All devices running Tizen TV or SmartTV are assumed to be a tv */ if (null === $this->device && $this->matchUserAgent('SmartTV|Tizen.+ TV .+$')) { $this->device = AbstractDeviceParser::DEVICE_TYPE_TV; } /** * Devices running Kylo or Espital TV Browsers are assumed to be a TV */ if (null === $this->device && \in_array($clientName, ['Kylo', 'Espial TV Browser'])) { $this->device = AbstractDeviceParser::DEVICE_TYPE_TV; } /** * All devices containing TV fragment are assumed to be a tv */ if (null === $this->device && $this->matchUserAgent('\(TV;')) { $this->device = AbstractDeviceParser::DEVICE_TYPE_TV; } /** * Set device type desktop if string ua contains desktop */ $hasDesktop = AbstractDeviceParser::DEVICE_TYPE_DESKTOP !== $this->device && false !== \strpos($this->userAgent, 'Desktop') && $this->hasDesktopFragment(); if ($hasDesktop) { $this->device = AbstractDeviceParser::DEVICE_TYPE_DESKTOP; } // set device type to desktop for all devices running a desktop os that were not detected as another device type if (null !== $this->device || !$this->isDesktop()) { return; } $this->device = AbstractDeviceParser::DEVICE_TYPE_DESKTOP; } /** * Tries to detect the operating system */ protected function parseOs(): void { $osParser = new OperatingSystem(); $osParser->setUserAgent($this->getUserAgent()); $osParser->setClientHints($this->getClientHints()); $osParser->setYamlParser($this->getYamlParser()); $osParser->setCache($this->getCache()); $this->os = $osParser->parse(); } /** * @param string $regex * * @return array|null */ protected function matchUserAgent(string $regex): ?array { $regex = '/(?:^|[^A-Z_-])(?:' . \str_replace('/', '\/', $regex) . ')/i'; if (\preg_match($regex, $this->userAgent, $matches)) { return $matches; } return null; } /** * Resets all detected data */ protected function reset(): void { $this->bot = null; $this->client = null; $this->device = null; $this->os = null; $this->brand = ''; $this->model = ''; $this->parsed = false; } } device-detector-6.1.1/LICENSE000066400000000000000000000167441440455040400156060ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. device-detector-6.1.1/Parser/000077500000000000000000000000001440455040400160215ustar00rootroot00000000000000device-detector-6.1.1/Parser/AbstractBotParser.php000066400000000000000000000007701440455040400221230ustar00rootroot00000000000000> */ protected static $clientHintMapping = []; /** * Holds an array with method that should be available global * @var array */ protected $globalMethods; /** * Holds an array with regexes to parse, if already loaded * @var array */ protected $regexList; /** * Holds the concatenated regex for all items in regex list * @var string */ protected $overAllMatch; /** * Indicates how deep versioning will be detected * if $maxMinorParts is 0 only the major version will be returned * @var int */ protected static $maxMinorParts = 1; /** * Versioning constant used to set max versioning to major version only * Version examples are: 3, 5, 6, 200, 123, ... */ public const VERSION_TRUNCATION_MAJOR = 0; /** * Versioning constant used to set max versioning to minor version * Version examples are: 3.4, 5.6, 6.234, 0.200, 1.23, ... */ public const VERSION_TRUNCATION_MINOR = 1; /** * Versioning constant used to set max versioning to path level * Version examples are: 3.4.0, 5.6.344, 6.234.2, 0.200.3, 1.2.3, ... */ public const VERSION_TRUNCATION_PATCH = 2; /** * Versioning constant used to set versioning to build number * Version examples are: 3.4.0.12, 5.6.334.0, 6.234.2.3, 0.200.3.1, 1.2.3.0, ... */ public const VERSION_TRUNCATION_BUILD = 3; /** * Versioning constant used to set versioning to unlimited (no truncation) */ public const VERSION_TRUNCATION_NONE = -1; /** * @var CacheInterface */ protected $cache; /** * @var YamlParser */ protected $yamlParser; /** * parses the currently set useragents and returns possible results * * @return array|null */ abstract public function parse(): ?array; /** * AbstractParser constructor. * * @param string $ua * @param ?ClientHints $clientHints */ public function __construct(string $ua = '', ?ClientHints $clientHints = null) { $this->setUserAgent($ua); $this->setClientHints($clientHints); } /** * Set how DeviceDetector should return versions * @param int $type Any of the VERSION_TRUNCATION_* constants */ public static function setVersionTruncation(int $type): void { if (!\in_array($type, [ self::VERSION_TRUNCATION_BUILD, self::VERSION_TRUNCATION_NONE, self::VERSION_TRUNCATION_MAJOR, self::VERSION_TRUNCATION_MINOR, self::VERSION_TRUNCATION_PATCH, ]) ) { return; } static::$maxMinorParts = $type; } /** * Sets the user agent to parse * * @param string $ua user agent */ public function setUserAgent(string $ua): void { $this->userAgent = $ua; } /** * Sets the client hints to parse * * @param ?ClientHints $clientHints client hints */ public function setClientHints(?ClientHints $clientHints): void { $this->clientHints = $clientHints; } /** * Returns the internal name of the parser * * @return string */ public function getName(): string { return $this->parserName; } /** * Sets the Cache class * * @param CacheInterface $cache */ public function setCache(CacheInterface $cache): void { $this->cache = $cache; } /** * Returns Cache object * * @return CacheInterface */ public function getCache(): CacheInterface { if (!empty($this->cache)) { return $this->cache; } return new StaticCache(); } /** * Sets the YamlParser class * * @param YamlParser $yamlParser */ public function setYamlParser(YamlParser $yamlParser): void { $this->yamlParser = $yamlParser; } /** * Returns YamlParser object * * @return YamlParser */ public function getYamlParser(): YamlParser { if (!empty($this->yamlParser)) { return $this->yamlParser; } return new Spyc(); } /** * Returns the result of the parsed yml file defined in $fixtureFile * * @return array */ protected function getRegexes(): array { if (empty($this->regexList)) { $cacheKey = 'DeviceDetector-' . DeviceDetector::VERSION . 'regexes-' . $this->getName(); $cacheKey = (string) \preg_replace('/([^a-z0-9_-]+)/i', '', $cacheKey); $this->regexList = $this->getCache()->fetch($cacheKey); if (empty($this->regexList)) { $this->regexList = $this->getYamlParser()->parseFile( $this->getRegexesDirectory() . DIRECTORY_SEPARATOR . $this->fixtureFile ); $this->getCache()->save($cacheKey, $this->regexList); } } return $this->regexList; } /** * Returns the provided name after applying client hint mappings. * This is used to map names provided in client hints to the names we use. * * @param string $name * * @return string */ protected function applyClientHintMapping(string $name): string { foreach (static::$clientHintMapping as $mappedName => $clientHints) { foreach ($clientHints as $clientHint) { if (\strtolower($name) === \strtolower($clientHint)) { return $mappedName; } } } return $name; } /** * @return string */ protected function getRegexesDirectory(): string { return \dirname(__DIR__); } /** * Matches the useragent against the given regex * * @param string $regex * * @return ?array * * @throws \Exception */ protected function matchUserAgent(string $regex): ?array { $matches = []; // only match if useragent begins with given regex or there is no letter before it $regex = '/(?:^|[^A-Z0-9\-_]|[^A-Z0-9\-]_|sprd-|MZ-)(?:' . \str_replace('/', '\/', $regex) . ')/i'; try { if (\preg_match($regex, $this->userAgent, $matches)) { return $matches; } } catch (\Exception $exception) { throw new \Exception( \sprintf("%s\nRegex: %s", $exception->getMessage(), $regex), $exception->getCode(), $exception ); } return null; } /** * @param string $item * @param array $matches * * @return string */ protected function buildByMatch(string $item, array $matches): string { $search = []; $replace = []; for ($nb = 1; $nb <= \count($matches); $nb++) { $search[] = '$' . $nb; $replace[] = $matches[$nb] ?? ''; } return \trim(\str_replace($search, $replace, $item)); } /** * Builds the version with the given $versionString and $matches * * Example: * $versionString = 'v$2' * $matches = ['version_1_0_1', '1_0_1'] * return value would be v1.0.1 * * @param string $versionString * @param array $matches * * @return string */ protected function buildVersion(string $versionString, array $matches): string { $versionString = $this->buildByMatch($versionString, $matches); $versionString = \str_replace('_', '.', $versionString); if (self::VERSION_TRUNCATION_NONE !== static::$maxMinorParts && \substr_count($versionString, '.') > static::$maxMinorParts ) { $versionParts = \explode('.', $versionString); $versionParts = \array_slice($versionParts, 0, 1 + static::$maxMinorParts); $versionString = \implode('.', $versionParts); } return \trim($versionString, ' .'); } /** * Tests the useragent against a combination of all regexes * * All regexes returned by getRegexes() will be reversed and concatenated with '|' * Afterwards the big regex will be tested against the user agent * * Method can be used to speed up detections by making a big check before doing checks for every single regex * * @return ?array */ protected function preMatchOverall(): ?array { $regexes = $this->getRegexes(); $cacheKey = $this->parserName . DeviceDetector::VERSION . '-all'; $cacheKey = (string) \preg_replace('/([^a-z0-9_-]+)/i', '', $cacheKey); if (empty($this->overAllMatch)) { $this->overAllMatch = $this->getCache()->fetch($cacheKey); } if (empty($this->overAllMatch)) { // reverse all regexes, so we have the generic one first, which already matches most patterns $this->overAllMatch = \array_reduce(\array_reverse($regexes), static function ($val1, $val2) { return !empty($val1) ? $val1 . '|' . $val2['regex'] : $val2['regex']; }); $this->getCache()->save($cacheKey, $this->overAllMatch); } return $this->matchUserAgent($this->overAllMatch); } /** * Compares if two strings equals after lowering their case and removing spaces * * @param string $value1 * @param string $value2 * * @return bool */ protected function fuzzyCompare(string $value1, string $value2): bool { return \str_replace(' ', '', \strtolower($value1)) === \str_replace(' ', '', \strtolower($value2)); } } device-detector-6.1.1/Parser/Bot.php000066400000000000000000000037051440455040400172630ustar00rootroot00000000000000discardDetails = true; } /** * Parses the current UA and checks whether it contains bot information * * @see bots.yml for list of detected bots * * Step 1: Build a big regex containing all regexes and match UA against it * -> If no matches found: return * -> Otherwise: * Step 2: Walk through the list of regexes in bots.yml and try to match every one * -> Return the matched data * * If $discardDetails is set to TRUE, the Step 2 will be skipped * $bot will be set to TRUE instead * * NOTE: Doing the big match before matching every single regex speeds up the detection * * @return array|null */ public function parse(): ?array { $result = null; if ($this->preMatchOverall()) { if ($this->discardDetails) { return [true]; } foreach ($this->getRegexes() as $regex) { $matches = $this->matchUserAgent($regex['regex']); if ($matches) { unset($regex['regex']); $result = $regex; break; } } } return $result; } } device-detector-6.1.1/Parser/Client/000077500000000000000000000000001440455040400172375ustar00rootroot00000000000000device-detector-6.1.1/Parser/Client/AbstractClientParser.php000066400000000000000000000044731440455040400240370ustar00rootroot00000000000000 If no matches found: return * -> Otherwise: * Step 2: Walk through the list of regexes in feed_readers.yml and try to match every one * -> Return the matched feed reader * * NOTE: Doing the big match before matching every single regex speeds up the detection * * @return array|null */ public function parse(): ?array { $result = null; if ($this->preMatchOverall()) { foreach ($this->getRegexes() as $regex) { $matches = $this->matchUserAgent($regex['regex']); if ($matches) { $result = [ 'type' => $this->parserName, 'name' => $this->buildByMatch($regex['name'], $matches), 'version' => $this->buildVersion((string) $regex['version'], $matches), ]; break; } } } return $result; } /** * Returns all names defined in the regexes * * Attention: This method might not return all names of detected clients * * @return array */ public static function getAvailableClients(): array { $instance = new static(); // @phpstan-ignore-line $regexes = $instance->getRegexes(); $names = []; foreach ($regexes as $regex) { if ('$1' === $regex['name']) { continue; } $names[] = $regex['name']; } \natcasesort($names); return \array_unique($names); } } device-detector-6.1.1/Parser/Client/Browser.php000066400000000000000000001020201440455040400213660ustar00rootroot00000000000000 'Via', '1P' => 'Pure Mini Browser', '4P' => 'Pure Lite Browser', '1R' => 'Raise Fast Browser', 'R1' => 'Rabbit Private Browser', 'FQ' => 'Fast Browser UC Lite', 'FJ' => 'Fast Explorer', '1L' => 'Lightning Browser', '1C' => 'Cake Browser', '1I' => 'IE Browser Fast', '1V' => 'Vegas Browser', '1O' => 'OH Browser', '3O' => 'OH Private Browser', '1X' => 'XBrowser Mini', '1S' => 'Sharkee Browser', '2L' => 'Lark Browser', '3P' => 'Pluma', '1A' => 'Anka Browser', 'AZ' => 'Azka Browser', '1D' => 'Dragon Browser', '1E' => 'Easy Browser', 'DW' => 'Dark Web Browser', '18' => '18+ Privacy Browser', '1B' => '115 Browser', 'DM' => '1DM Browser', '1M' => '1DM+ Browser', '2B' => '2345 Browser', '3B' => '360 Browser', '36' => '360 Phone Browser', '7B' => '7654 Browser', 'AA' => 'Avant Browser', 'AB' => 'ABrowse', 'BW' => 'AdBlock Browser', 'A7' => 'Adult Browser', 'AF' => 'ANT Fresco', 'AG' => 'ANTGalio', 'AL' => 'Aloha Browser', 'AH' => 'Aloha Browser Lite', 'AM' => 'Amaya', 'A3' => 'Amaze Browser', 'A5' => 'Amerigo', 'AO' => 'Amigo', 'AN' => 'Android Browser', 'AE' => 'AOL Desktop', 'AD' => 'AOL Shield', 'A4' => 'AOL Shield Pro', 'A6' => 'AppBrowzer', 'AP' => 'APUS Browser', 'AR' => 'Arora', 'AX' => 'Arctic Fox', 'AV' => 'Amiga Voyager', 'AW' => 'Amiga Aweb', 'PN' => 'APN Browser', 'AI' => 'Arvin', 'AK' => 'Ask.com', 'AU' => 'Asus Browser', 'A0' => 'Atom', 'AT' => 'Atomic Web Browser', 'A2' => 'Atlas', 'AS' => 'Avast Secure Browser', 'VG' => 'AVG Secure Browser', 'AC' => 'Avira Scout', 'A1' => 'AwoX', 'BA' => 'Beaker Browser', 'BM' => 'Beamrise', 'BB' => 'BlackBerry Browser', 'H1' => 'BrowseHere', 'B8' => 'Browser Hup Pro', 'BD' => 'Baidu Browser', 'BS' => 'Baidu Spark', 'B9' => 'Bangla Browser', 'BI' => 'Basilisk', 'BV' => 'Belva Browser', 'B5' => 'Beyond Private Browser', 'BE' => 'Beonex', 'B2' => 'Berry Browser', 'BT' => 'Bitchute Browser', 'BH' => 'BlackHawk', 'B0' => 'Bloket', 'BJ' => 'Bunjalloo', 'BL' => 'B-Line', 'B6' => 'Black Lion Browser', 'BU' => 'Blue Browser', 'BO' => 'Bonsai', 'BN' => 'Borealis Navigator', 'BR' => 'Brave', 'BK' => 'BriskBard', 'B3' => 'Browspeed Browser', 'BX' => 'BrowseX', 'BZ' => 'Browzar', 'B7' => 'Browlser', 'BY' => 'Biyubi', 'BF' => 'Byffox', 'B4' => 'BF Browser', 'CA' => 'Camino', 'CL' => 'CCleaner', 'C8' => 'CG Browser', 'CJ' => 'ChanjetCloud', 'C6' => 'Chedot', 'C9' => 'Cherry Browser', 'C0' => 'Centaury', 'CC' => 'Coc Coc', 'C4' => 'CoolBrowser', 'C2' => 'Colibri', 'CD' => 'Comodo Dragon', 'C1' => 'Coast', 'CX' => 'Charon', 'CE' => 'CM Browser', 'C7' => 'CM Mini', 'CF' => 'Chrome Frame', 'HC' => 'Headless Chrome', 'CH' => 'Chrome', 'CI' => 'Chrome Mobile iOS', 'CK' => 'Conkeror', 'CM' => 'Chrome Mobile', '3C' => 'Chowbo', 'CN' => 'CoolNovo', 'CO' => 'CometBird', '2C' => 'Comfort Browser', 'CB' => 'COS Browser', 'CW' => 'Cornowser', 'C3' => 'Chim Lac', 'CP' => 'ChromePlus', 'CR' => 'Chromium', 'C5' => 'Chromium GOST', 'CY' => 'Cyberfox', 'CS' => 'Cheshire', 'CT' => 'Crusta', 'CG' => 'Craving Explorer', 'CZ' => 'Crazy Browser', 'CU' => 'Cunaguaro', 'CV' => 'Chrome Webview', 'YC' => 'CyBrowser', 'DB' => 'dbrowser', 'PD' => 'Peeps dBrowser', 'D1' => 'Debuggable Browser', 'DC' => 'Decentr', 'DE' => 'Deepnet Explorer', 'DG' => 'deg-degan', 'DA' => 'Deledao', 'DT' => 'Delta Browser', 'D0' => 'Desi Browser', 'DS' => 'DeskBrowse', 'DF' => 'Dolphin', 'DZ' => 'Dolphin Zero', 'DO' => 'Dorado', 'DR' => 'Dot Browser', 'DL' => 'Dooble', 'DI' => 'Dillo', 'DU' => 'DUC Browser', 'DD' => 'DuckDuckGo Privacy Browser', 'EC' => 'Ecosia', 'EW' => 'Edge WebView', 'EI' => 'Epic', 'EL' => 'Elinks', 'EN' => 'EinkBro', 'EB' => 'Element Browser', 'EE' => 'Elements Browser', 'EX' => 'Explore Browser', 'EZ' => 'eZ Browser', 'EU' => 'EUI Browser', 'EP' => 'GNOME Web', 'G1' => 'G Browser', 'ES' => 'Espial TV Browser', 'FA' => 'Falkon', 'FX' => 'Faux Browser', 'F4' => 'Fiery Browser', 'F1' => 'Firefox Mobile iOS', 'FB' => 'Firebird', 'FD' => 'Fluid', 'FE' => 'Fennec', 'FF' => 'Firefox', 'FK' => 'Firefox Focus', 'FY' => 'Firefox Reality', 'FR' => 'Firefox Rocket', '1F' => 'Firefox Klar', 'F0' => 'Float Browser', 'FL' => 'Flock', 'FP' => 'Floorp', 'FO' => 'Flow', 'F2' => 'Flow Browser', 'FM' => 'Firefox Mobile', 'FW' => 'Fireweb', 'FN' => 'Fireweb Navigator', 'FH' => 'Flash Browser', 'FS' => 'Flast', 'F5' => 'Flyperlink', 'FU' => 'FreeU', 'F3' => 'Frost+', 'FI' => 'Fulldive', 'GA' => 'Galeon', 'G8' => 'Gener8', 'GH' => 'Ghostery Privacy Browser', 'GI' => 'GinxDroid Browser', 'GB' => 'Glass Browser', 'GE' => 'Google Earth', 'GP' => 'Google Earth Pro', 'GO' => 'GOG Galaxy', 'GR' => 'GoBrowser', 'HB' => 'Harman Browser', 'HS' => 'HasBrowser', 'HA' => 'Hawk Turbo Browser', 'HQ' => 'Hawk Quick Browser', 'HE' => 'Helio', 'HX' => 'Hexa Web Browser', 'HI' => 'Hi Browser', 'HO' => 'hola! Browser', 'HJ' => 'HotJava', 'HT' => 'HTC Browser', 'HU' => 'Huawei Browser Mobile', 'HP' => 'Huawei Browser', 'H3' => 'HUB Browser', 'IO' => 'iBrowser', 'IS' => 'iBrowser Mini', 'IB' => 'IBrowse', 'I6' => 'iDesktop PC Browser', 'IC' => 'iCab', 'I2' => 'iCab Mobile', 'I1' => 'Iridium', 'I3' => 'Iron Mobile', 'I4' => 'IceCat', 'ID' => 'IceDragon', 'IV' => 'Isivioo', 'IW' => 'Iceweasel', 'IN' => 'Inspect Browser', 'IE' => 'Internet Explorer', 'I7' => 'Internet Browser Secure', 'I5' => 'Indian UC Mini Browser', 'IM' => 'IE Mobile', 'IR' => 'Iron', 'JB' => 'Japan Browser', 'JS' => 'Jasmine', 'JA' => 'JavaFX', 'JL' => 'Jelly', 'JI' => 'Jig Browser', 'JP' => 'Jig Browser Plus', 'JO' => 'Jio Browser', 'J1' => 'JioPages', 'KB' => 'K.Browser', 'KF' => 'Keepsafe Browser', 'KS' => 'Kids Safe Browser', 'KI' => 'Kindle Browser', 'KM' => 'K-meleon', 'KO' => 'Konqueror', 'KP' => 'Kapiko', 'KN' => 'Kinza', 'KW' => 'Kiwi', 'KD' => 'Kode Browser', 'KT' => 'KUTO Mini Browser', 'KY' => 'Kylo', 'KZ' => 'Kazehakase', 'LB' => 'Cheetah Browser', 'LA' => 'Lagatos Browser', 'LR' => 'Lexi Browser', 'LV' => 'Lenovo Browser', 'LF' => 'LieBaoFast', 'LG' => 'LG Browser', 'LH' => 'Light', 'L1' => 'Lilo', 'LI' => 'Links', 'IF' => 'Lolifox', 'LO' => 'Lovense Browser', 'LT' => 'LT Browser', 'LU' => 'LuaKit', 'LL' => 'Lulumi', 'LS' => 'Lunascape', 'LN' => 'Lunascape Lite', 'LX' => 'Lynx', 'L2' => 'Lynket Browser', 'MD' => 'Mandarin', 'M1' => 'mCent', 'MB' => 'MicroB', 'MC' => 'NCSA Mosaic', 'MZ' => 'Meizu Browser', 'ME' => 'Mercury', 'M2' => 'Me Browser', 'MF' => 'Mobile Safari', 'MI' => 'Midori', 'M3' => 'Midori Lite', 'MO' => 'Mobicip', 'MU' => 'MIUI Browser', 'MS' => 'Mobile Silk', 'MN' => 'Minimo', 'MT' => 'Mint Browser', 'MX' => 'Maxthon', 'M4' => 'MaxTube Browser', 'MA' => 'Maelstrom', 'MM' => 'Mmx Browser', 'NM' => 'MxNitro', 'MY' => 'Mypal', 'MR' => 'Monument Browser', 'MW' => 'MAUI WAP Browser', 'NW' => 'Navigateur Web', 'NK' => 'Naked Browser', 'NA' => 'Naked Browser Pro', 'NR' => 'NFS Browser', 'NB' => 'Nokia Browser', 'NO' => 'Nokia OSS Browser', 'NV' => 'Nokia Ovi Browser', 'NX' => 'Nox Browser', 'NE' => 'NetSurf', 'NF' => 'NetFront', 'NL' => 'NetFront Life', 'NP' => 'NetPositive', 'NS' => 'Netscape', 'WR' => 'NextWord Browser', 'NT' => 'NTENT Browser', 'OC' => 'Oculus Browser', 'O1' => 'Opera Mini iOS', 'OB' => 'Obigo', 'O2' => 'Odin', '2O' => 'Odin Browser', 'H2' => 'OceanHero', 'OD' => 'Odyssey Web Browser', 'OF' => 'Off By One', 'O5' => 'Office Browser', 'HH' => 'OhHai Browser', 'OE' => 'ONE Browser', 'Y1' => 'Opera Crypto', 'OX' => 'Opera GX', 'OG' => 'Opera Neon', 'OH' => 'Opera Devices', 'OI' => 'Opera Mini', 'OM' => 'Opera Mobile', 'OP' => 'Opera', 'ON' => 'Opera Next', 'OO' => 'Opera Touch', 'OA' => 'Orca', 'OS' => 'Ordissimo', 'OR' => 'Oregano', 'O0' => 'Origin In-Game Overlay', 'OY' => 'Origyn Web Browser', 'OV' => 'Openwave Mobile Browser', 'O3' => 'OpenFin', 'O4' => 'Open Browser', '4U' => 'Open Browser 4U', '5G' => 'Open Browser fast 5G', 'OW' => 'OmniWeb', 'OT' => 'Otter Browser', 'PL' => 'Palm Blazer', 'PM' => 'Pale Moon', 'PY' => 'Polypane', 'PP' => 'Oppo Browser', 'PR' => 'Palm Pre', 'PU' => 'Puffin', '2P' => 'Puffin Web Browser', 'PW' => 'Palm WebPro', 'PA' => 'Palmscape', 'PE' => 'Perfect Browser', 'P1' => 'Phantom.me', 'PH' => 'Phantom Browser', 'PX' => 'Phoenix', 'PB' => 'Phoenix Browser', 'PF' => 'PlayFree Browser', 'PK' => 'PocketBook Browser', 'PO' => 'Polaris', 'PT' => 'Polarity', 'LY' => 'PolyBrowser', 'PI' => 'PrivacyWall', 'P4' => 'Privacy Explorer Fast Safe', 'P2' => 'Pi Browser', 'P0' => 'PronHub Browser', 'PC' => 'PSI Secure Browser', 'RW' => 'Reqwireless WebViewer', 'PS' => 'Microsoft Edge', 'QA' => 'Qazweb', 'Q2' => 'QQ Browser Lite', 'Q1' => 'QQ Browser Mini', 'QQ' => 'QQ Browser', 'QS' => 'Quick Browser', 'QT' => 'Qutebrowser', 'QU' => 'Quark', 'QZ' => 'QupZilla', 'QM' => 'Qwant Mobile', 'QW' => 'QtWebEngine', 'RE' => 'Realme Browser', 'RK' => 'Rekonq', 'RM' => 'RockMelt', 'SB' => 'Samsung Browser', 'SA' => 'Sailfish Browser', 'S8' => 'Seewo Browser', 'SC' => 'SEMC-Browser', 'SE' => 'Sogou Explorer', 'SO' => 'Sogou Mobile Browser', 'RF' => 'SOTI Surf', '2S' => 'Soul Browser', 'SF' => 'Safari', 'PV' => 'Safari Technology Preview', 'S5' => 'Safe Exam Browser', 'SW' => 'SalamWeb', 'VN' => 'Savannah Browser', 'SD' => 'SavySoda', 'S9' => 'Secure Browser', 'SV' => 'SFive', 'SH' => 'Shiira', 'K1' => 'Sidekick', 'S1' => 'SimpleBrowser', '3S' => 'SilverMob US', 'SY' => 'Sizzy', 'SK' => 'Skyfire', 'SS' => 'Seraphic Sraf', 'KK' => 'SiteKiosk', 'SL' => 'Sleipnir', 'S6' => 'Slimjet', 'S7' => 'SP Browser', '9S' => 'Sony Small Browser', '8S' => 'Secure Private Browser', 'T1' => 'Stampy Browser', '7S' => '7Star', 'SQ' => 'Smart Browser', '6S' => 'Smart Search & Web Browser', 'LE' => 'Smart Lenovo Browser', 'OZ' => 'Smooz', 'SN' => 'Snowshoe', 'B1' => 'Spectre Browser', 'S2' => 'Splash', 'SI' => 'Sputnik Browser', 'SR' => 'Sunrise', 'SP' => 'SuperBird', 'SU' => 'Super Fast Browser', '5S' => 'SuperFast Browser', 'HR' => 'Sushi Browser', 'S3' => 'surf', '4S' => 'Surf Browser', 'SG' => 'Stargon', 'S0' => 'START Internet Browser', 'S4' => 'Steam In-Game Overlay', 'ST' => 'Streamy', 'SX' => 'Swiftfox', 'SZ' => 'Seznam Browser', 'W1' => 'Sweet Browser', '2X' => 'SX Browser', 'TP' => 'T+Browser', 'TR' => 'T-Browser', 'TO' => 't-online.de Browser', 'TA' => 'Tao Browser', 'TF' => 'TenFourFox', 'TB' => 'Tenta Browser', 'TE' => 'Tesla Browser', 'TZ' => 'Tizen Browser', 'TI' => 'Tint Browser', 'TC' => 'TUC Mini Browser', 'TU' => 'Tungsten', 'TG' => 'ToGate', 'TS' => 'TweakStyle', 'TV' => 'TV Bro', 'U0' => 'U Browser', 'UB' => 'UBrowser', 'UC' => 'UC Browser', 'UH' => 'UC Browser HD', 'UM' => 'UC Browser Mini', 'UT' => 'UC Browser Turbo', 'UI' => 'Ui Browser Mini', 'UR' => 'UR Browser', 'UZ' => 'Uzbl', 'UE' => 'Ume Browser', 'V0' => 'vBrowser', 'VA' => 'Vast Browser', 'VE' => 'Venus Browser', 'N0' => 'Nova Video Downloader Pro', 'VS' => 'Viasat Browser', 'VI' => 'Vivaldi', 'VV' => 'vivo Browser', 'V2' => 'Vivid Browser Mini', 'VB' => 'Vision Mobile Browser', 'VM' => 'VMware AirWatch', 'WI' => 'Wear Internet Browser', 'WP' => 'Web Explorer', 'W3' => 'Web Browser & Explorer', 'WE' => 'WebPositive', 'WF' => 'Waterfox', 'WB' => 'Wave Browser', 'WH' => 'Whale Browser', 'WO' => 'wOSBrowser', 'WT' => 'WeTab Browser', 'WL' => 'Wolvic', 'YG' => 'YAGI', 'YJ' => 'Yahoo! Japan Browser', 'YA' => 'Yandex Browser', 'YL' => 'Yandex Browser Lite', 'YN' => 'Yaani Browser', 'Y2' => 'Yo Browser', 'YB' => 'Yolo Browser', 'YO' => 'YouCare', 'YZ' => 'Yuzu Browser', 'XR' => 'xBrowser', 'XB' => 'X Browser Lite', 'X0' => 'X-VPN', 'X1' => 'xBrowser Pro Super Fast', 'XN' => 'XNX Browser', 'XT' => 'XtremeCast', 'XS' => 'xStand', 'XI' => 'Xiino', 'XO' => 'Xooloo Internet', 'XV' => 'Xvast', 'ZE' => 'Zetakey', 'ZV' => 'Zvu', 'ZI' => 'Zirco Browser', // detected browsers in older versions // 'IA' => 'Iceape', => pim // 'SM' => 'SeaMonkey', => pim ]; /** * Browser families mapped to the short codes of the associated browsers * * @var array */ protected static $browserFamilies = [ 'Android Browser' => ['AN', 'MU'], 'BlackBerry Browser' => ['BB'], 'Baidu' => ['BD', 'BS'], 'Amiga' => ['AV', 'AW'], 'Chrome' => [ '1B', '2B', '7S', 'A0', 'AC', 'A4', 'AE', 'AH', 'AI', 'AO', 'AS', 'BA', 'BM', 'BR', 'C2', 'C3', 'C5', 'C4', 'C6', 'CC', 'CD', 'CE', 'CF', 'CG', 'CH', 'CI', 'CL', 'CM', 'CN', 'CP', 'CR', 'CV', 'CW', 'DA', 'DD', 'DG', 'DR', 'EC', 'EE', 'EU', 'EW', 'FA', 'FS', 'GB', 'GI', 'H2', 'HA', 'HE', 'HH', 'HS', 'I3', 'IR', 'JB', 'KN', 'KW', 'LF', 'LL', 'LO', 'M1', 'MA', 'MD', 'MR', 'MS', 'MT', 'MZ', 'NM', 'NR', 'O0', 'O2', 'O3', 'OC', 'PB', 'PT', 'QU', 'QW', 'RM', 'S4', 'S6', 'S8', 'S9', 'SB', 'SG', 'SS', 'SU', 'SV', 'SW', 'SY', 'SZ', 'T1', 'TA', 'TB', 'TG', 'TR', 'TS', 'TU', 'TV', 'UB', 'UR', 'VE', 'VG', 'VI', 'VM', 'WP', 'WH', 'XV', 'YJ', 'YN', 'FH', 'B1', 'BO', 'HB', 'PC', 'LA', 'LT', 'PD', 'HR', 'HU', 'HP', 'IO', 'TP', 'CJ', 'HQ', 'HI', 'PN', 'BW', 'YO', 'DC', 'G8', 'DT', 'AP', 'AK', 'UI', 'SD', 'VN', '4S', '2S', 'RF', 'LR', 'SQ', 'BV', 'L1', 'F0', 'KS', 'V0', 'C8', 'AZ', 'MM', 'BT', 'N0', 'P0', 'F3', 'VS', 'DU', 'D0', 'P1', 'O4', '8S', 'H3', 'TE', 'WB', 'K1', 'P2', 'XO', 'U0', 'B0', 'VA', 'X0', 'NX', 'O5', 'R1', 'I1', 'HO', 'A5', 'X1', '18', 'B5', 'B6', 'TC', 'A6', '2X', 'F4', 'YG', 'WR', 'NA', 'DM', '1M', 'A7', 'XN', 'XT', 'XB', 'W1', 'HT', 'B8', 'F5', 'B9', ], 'Firefox' => [ 'AX', 'BI', 'BF', 'BH', 'BN', 'C0', 'CU', 'EI', 'F1', 'FB', 'FE', 'FF', 'FM', 'FR', 'FY', 'GZ', 'I4', 'IF', 'IW', 'LH', 'LY', 'MB', 'MN', 'MO', 'MY', 'OA', 'OS', 'PI', 'PX', 'QA', 'QM', 'S5', 'SX', 'TF', 'TO', 'WF', 'ZV', 'FP', 'AD', 'WL', ], 'Internet Explorer' => ['BZ', 'CZ', 'IE', 'IM', 'PS'], 'Konqueror' => ['KO'], 'NetFront' => ['NF'], 'NetSurf' => ['NE'], 'Nokia Browser' => ['DO', 'NB', 'NO', 'NV'], 'Opera' => ['O1', 'OG', 'OH', 'OI', 'OM', 'ON', 'OO', 'OP', 'OX', 'Y1'], 'Safari' => ['MF', 'S7', 'SF', 'SO', 'PV'], 'Sailfish Browser' => ['SA'], ]; /** * Browsers that are available for mobile devices only * * @var array */ protected static $mobileOnlyBrowsers = [ '36', 'AH', 'AI', 'BL', 'C1', 'C4', 'CB', 'CW', 'DB', 'DD', 'DT', 'EU', 'EZ', 'FK', 'FM', 'FR', 'FX', 'GH', 'GI', 'GR', 'HA', 'HU', 'IV', 'JB', 'KD', 'M1', 'MF', 'MN', 'MZ', 'NX', 'OC', 'OI', 'OM', 'OZ', 'PU', 'PI', 'PE', 'QU', 'RE', 'S0', 'S7', 'SA', 'SB', 'SG', 'SK', 'ST', 'SU', 'T1', 'UH', 'UM', 'UT', 'VE', 'VV', 'WI', 'WP', 'YN', 'IO', 'IS', 'HQ', 'RW', 'HI', 'PN', 'BW', 'YO', 'PK', 'MR', 'AP', 'AK', 'UI', 'SD', 'VN', '4S', 'RF', 'LR', 'SQ', 'BV', 'L1', 'F0', 'KS', 'V0', 'C8', 'AZ', 'MM', 'BT', 'N0', 'P0', 'F3', 'DU', 'D0', 'P1', 'O4', 'XO', 'U0', 'B0', 'VA', 'X0', 'A5', 'X1', '18', 'B5', 'B6', 'TC', 'A6', '2X', 'F4', 'YG', 'WR', 'NA', 'DM', '1M', 'A7', 'XN', 'XT', 'XB', 'W1', 'HT', 'B7', 'B9', ]; /** * Contains a list of mappings from OS names we use to known client hint values * * @var array> */ protected static $clientHintMapping = [ 'Chrome' => ['Google Chrome'], ]; /** * Browser constructor. * * @param string $ua * @param ClientHints|null $clientHints */ public function __construct(string $ua = '', ?ClientHints $clientHints = null) { $this->browserHints = new BrowserHints($ua, $clientHints); parent::__construct($ua, $clientHints); } /** * Sets the client hints to parse * * @param ?ClientHints $clientHints client hints */ public function setClientHints(?ClientHints $clientHints): void { parent::setClientHints($clientHints); $this->browserHints->setClientHints($clientHints); } /** * Sets the user agent to parse * * @param string $ua user agent */ public function setUserAgent(string $ua): void { parent::setUserAgent($ua); $this->browserHints->setUserAgent($ua); } /** * Sets the Cache class * * @param CacheInterface $cache */ public function setCache(CacheInterface $cache): void { parent::setCache($cache); $this->browserHints->setCache($cache); } /** * Returns list of all available browsers * @return array */ public static function getAvailableBrowsers(): array { return self::$availableBrowsers; } /** * Returns list of all available browser families * @return array */ public static function getAvailableBrowserFamilies(): array { return self::$browserFamilies; } /** * @param string $name name browser * * @return string */ public static function getBrowserShortName(string $name): ?string { foreach (self::getAvailableBrowsers() as $browserShort => $browserName) { if (\strtolower($name) === \strtolower($browserName)) { return (string) $browserShort; } } return null; } /** * @param string $browserLabel name or short name * * @return string|null If null, "Unknown" */ public static function getBrowserFamily(string $browserLabel): ?string { if (\in_array($browserLabel, self::$availableBrowsers)) { $browserLabel = \array_search($browserLabel, self::$availableBrowsers); } foreach (self::$browserFamilies as $browserFamily => $browserLabels) { if (\in_array($browserLabel, $browserLabels)) { return $browserFamily; } } return null; } /** * Returns if the given browser is mobile only * * @param string $browser Label or name of browser * * @return bool */ public static function isMobileOnlyBrowser(string $browser): bool { return \in_array($browser, self::$mobileOnlyBrowsers) || (\in_array($browser, self::$availableBrowsers) && \in_array(\array_search($browser, self::$availableBrowsers), self::$mobileOnlyBrowsers)); } /** * Sets the YamlParser class * * @param YamlParser $yamlParser */ public function setYamlParser(YamlParser $yamlParser): void { parent::setYamlParser($yamlParser); $this->browserHints->setYamlParser($this->getYamlParser()); } /** * @inheritdoc */ public function parse(): ?array { $browserFromClientHints = $this->parseBrowserFromClientHints(); $browserFromUserAgent = $this->parseBrowserFromUserAgent(); // use client hints in favor of user agent data if possible if (!empty($browserFromClientHints['name']) && !empty($browserFromClientHints['version'])) { $name = $browserFromClientHints['name']; $version = $browserFromClientHints['version']; $short = $browserFromClientHints['short_name']; $engine = ''; $engineVersion = ''; // If version from client hints report 2022 or 2022.04, then is the Iridium browser // https://iridiumbrowser.de/news/2022/05/16/version-2022-04-released if ('2021.12' === $version || '2022' === $version || '2022.04' === $version) { $name = 'Iridium'; $short = 'I1'; $engine = $browserFromUserAgent['engine']; $engineVersion = $browserFromUserAgent['engine_version']; } if ('Atom' === $name || 'Huawei Browser' === $name) { $version = $browserFromUserAgent['version']; } // If client hints report Chromium, but user agent detects a Chromium based browser, we favor this instead if ('Chromium' === $name && !empty($browserFromUserAgent['name']) && 'Chromium' !== $browserFromUserAgent['name'] ) { $name = $browserFromUserAgent['name']; $short = $browserFromUserAgent['short_name']; $version = $browserFromUserAgent['version']; } // Fix mobile browser names e.g. Chrome => Chrome Mobile if ($name . ' Mobile' === $browserFromUserAgent['name']) { $name = $browserFromUserAgent['name']; $short = $browserFromUserAgent['short_name']; } // If useragent detects another browser, but the family matches, we use the detected engine from useragent if ($name !== $browserFromUserAgent['name'] && self::getBrowserFamily($name) === self::getBrowserFamily($browserFromUserAgent['name']) ) { $engine = $browserFromUserAgent['engine'] ?? ''; $engineVersion = $browserFromUserAgent['engine_version'] ?? ''; } if ($name === $browserFromUserAgent['name']) { $engine = $browserFromUserAgent['engine'] ?? ''; $engineVersion = $browserFromUserAgent['engine_version'] ?? ''; // In case the user agent reports a more detailed version, we try to use this instead if (!empty($browserFromUserAgent['version']) && 0 === \strpos($browserFromUserAgent['version'], $version) && \version_compare($version, $browserFromUserAgent['version'], '<') ) { $version = $browserFromUserAgent['version']; } } } else { $name = $browserFromUserAgent['name']; $version = $browserFromUserAgent['version']; $short = $browserFromUserAgent['short_name']; $engine = $browserFromUserAgent['engine']; $engineVersion = $browserFromUserAgent['engine_version']; } $family = self::getBrowserFamily((string) $short); $appHash = $this->browserHints->parse(); if (null !== $appHash && $name !== $appHash['name']) { $name = $appHash['name']; $version = ''; $short = self::getBrowserShortName($name); if (\preg_match('~Chrome/.+ Safari/537.36~i', $this->userAgent)) { $engine = 'Blink'; $family = self::getBrowserFamily((string) $short) ?? 'Chrome'; $engineVersion = $this->buildEngineVersion($engine); } if (null === $short) { // This Exception should never be thrown. If so a defined browser name is missing in $availableBrowsers throw new \Exception(\sprintf( 'Detected browser name "%s" was not found in $availableBrowsers. Tried to parse user agent: %s', $name, $this->userAgent )); // @codeCoverageIgnore } } if (empty($name)) { return []; } // exclude Blink engine version for browsers if ('Blink' === $engine && 'Flow Browser' === $name) { $engineVersion = ''; } return [ 'type' => 'browser', 'name' => $name, 'short_name' => $short, 'version' => $version, 'engine' => $engine, 'engine_version' => $engineVersion, 'family' => $family, ]; } /** * Returns the browser that can be safely detected from client hints * * @return array */ protected function parseBrowserFromClientHints(): array { $name = $version = $short = ''; if ($this->clientHints instanceof ClientHints && $this->clientHints->getBrandList()) { $brands = $this->clientHints->getBrandList(); foreach ($brands as $brand => $brandVersion) { $brand = $this->applyClientHintMapping($brand); foreach (self::$availableBrowsers as $browserShort => $browserName) { if ($this->fuzzyCompare("{$brand}", $browserName) || $this->fuzzyCompare($brand . ' Browser', $browserName) || $this->fuzzyCompare("{$brand}", $browserName . ' Browser') ) { $name = $browserName; $short = $browserShort; $version = $brandVersion; break; } } // If we detected a brand, that is not chromium, we will use it, otherwise we will look further if ('' !== $name && 'Chromium' !== $name) { break; } } $version = $this->clientHints->getBrandVersion() ?: $version; } return [ 'name' => $name, 'short_name' => $short, 'version' => $this->buildVersion($version, []), ]; } /** * Returns the browser that can be detected from useragent * * @return array * * @throws \Exception */ protected function parseBrowserFromUserAgent(): array { foreach ($this->getRegexes() as $regex) { $matches = $this->matchUserAgent($regex['regex']); if ($matches) { break; } } if (empty($matches) || empty($regex)) { return [ 'name' => '', 'short_name' => '', 'version' => '', 'engine' => '', 'engine_version' => '', ]; } $name = $this->buildByMatch($regex['name'], $matches); $browserShort = self::getBrowserShortName($name); if (null !== $browserShort) { $version = $this->buildVersion((string) $regex['version'], $matches); $engine = $this->buildEngine($regex['engine'] ?? [], $version); $engineVersion = $this->buildEngineVersion($engine); return [ 'name' => $name, 'short_name' => $browserShort, 'version' => $version, 'engine' => $engine, 'engine_version' => $engineVersion, ]; } // This Exception should never be thrown. If so a defined browser name is missing in $availableBrowsers throw new \Exception(\sprintf( 'Detected browser name "%s" was not found in $availableBrowsers. Tried to parse user agent: %s', $name, $this->userAgent )); // @codeCoverageIgnore } /** * @param array $engineData * @param string $browserVersion * * @return string */ protected function buildEngine(array $engineData, string $browserVersion): string { $engine = ''; // if an engine is set as default if (isset($engineData['default'])) { $engine = $engineData['default']; } // check if engine is set for browser version if (\array_key_exists('versions', $engineData) && \is_array($engineData['versions'])) { foreach ($engineData['versions'] as $version => $versionEngine) { if (\version_compare($browserVersion, (string) $version) < 0) { continue; } $engine = $versionEngine; } } // try to detect the engine using the regexes if (empty($engine)) { $engineParser = new Engine(); $engineParser->setYamlParser($this->getYamlParser()); $engineParser->setCache($this->getCache()); $engineParser->setUserAgent($this->userAgent); $result = $engineParser->parse(); $engine = $result['engine'] ?? ''; } return $engine; } /** * @param string $engine * * @return string */ protected function buildEngineVersion(string $engine): string { $engineVersionParser = new Engine\Version($this->userAgent, $engine); $result = $engineVersionParser->parse(); return $result['version'] ?? ''; } } device-detector-6.1.1/Parser/Client/Browser/000077500000000000000000000000001440455040400206625ustar00rootroot00000000000000device-detector-6.1.1/Parser/Client/Browser/Engine.php000066400000000000000000000043161440455040400226040ustar00rootroot00000000000000getRegexes() as $regex) { $matches = $this->matchUserAgent($regex['regex']); if ($matches) { break; } } if (empty($matches) || empty($regex)) { return null; } $name = $this->buildByMatch($regex['name'], $matches); foreach (self::getAvailableEngines() as $engineName) { if (\strtolower($name) === \strtolower($engineName)) { return ['engine' => $engineName]; } } // This Exception should never be thrown. If so a defined browser name is missing in $availableEngines throw new \Exception(\sprintf( 'Detected browser engine was not found in $availableEngines. Tried to parse user agent: %s', $this->userAgent )); // @codeCoverageIgnore } } device-detector-6.1.1/Parser/Client/Browser/Engine/000077500000000000000000000000001440455040400220675ustar00rootroot00000000000000device-detector-6.1.1/Parser/Client/Browser/Engine/Version.php000066400000000000000000000031051440455040400242240ustar00rootroot00000000000000engine = $engine; } /** * @inheritdoc */ public function parse(): ?array { if (empty($this->engine)) { return null; } if ('Gecko' === $this->engine) { $pattern = '~[ ](?:rv[: ]([0-9\.]+)).*gecko/[0-9]{8,10}~i'; if (\preg_match($pattern, $this->userAgent, $matches)) { return ['version' => \array_pop($matches)]; } } $engineToken = $this->engine; if ('Blink' === $this->engine) { $engineToken = 'Chrome'; } \preg_match( "~{$engineToken}\s*/?\s*((?(?=\d+\.\d)\d+[.\d]*|\d{1,7}(?=(?:\D|$))))~i", $this->userAgent, $matches ); if (!$matches) { return null; } return ['version' => \array_pop($matches)]; } } device-detector-6.1.1/Parser/Client/FeedReader.php000066400000000000000000000010721440455040400217360ustar00rootroot00000000000000clientHints) { return null; } $appId = $this->clientHints->getApp(); $name = $this->getRegexes()[$appId] ?? null; if ('' === (string) $name) { return null; } return [ 'name' => $name, ]; } } device-detector-6.1.1/Parser/Client/Hints/BrowserHints.php000066400000000000000000000017451440455040400234750ustar00rootroot00000000000000clientHints) { return null; } $appId = $this->clientHints->getApp(); $name = $this->getRegexes()[$appId] ?? null; if ('' === (string) $name) { return null; } return [ 'name' => $name, ]; } } device-detector-6.1.1/Parser/Client/Library.php000066400000000000000000000010611440455040400213520ustar00rootroot00000000000000appHints = new AppHints($ua, $clientHints); parent::__construct($ua, $clientHints); } /** * Sets the client hints to parse * * @param ?ClientHints $clientHints client hints */ public function setClientHints(?ClientHints $clientHints): void { parent::setClientHints($clientHints); $this->appHints->setClientHints($clientHints); } /** * Sets the user agent to parse * * @param string $ua user agent */ public function setUserAgent(string $ua): void { parent::setUserAgent($ua); $this->appHints->setUserAgent($ua); } /** * Sets the Cache class * * @param CacheInterface $cache */ public function setCache(CacheInterface $cache): void { parent::setCache($cache); $this->appHints->setCache($cache); } /** * Sets the YamlParser class * * @param YamlParser $yamlParser */ public function setYamlParser(YamlParser $yamlParser): void { parent::setYamlParser($yamlParser); $this->appHints->setYamlParser($this->getYamlParser()); } /** * Parses the current UA and checks whether it contains any client information * See parent::parse() for more details. * * @return array|null */ public function parse(): ?array { $result = parent::parse(); $name = $result['name'] ?? ''; $version = $result['version'] ?? ''; $appHash = $this->appHints->parse(); if (null !== $appHash && $appHash['name'] !== $name) { $name = $appHash['name']; $version = ''; } if (empty($name)) { return null; } return [ 'type' => $this->parserName, 'name' => $name, 'version' => $version, ]; } } device-detector-6.1.1/Parser/Client/PIM.php000066400000000000000000000010621440455040400203740ustar00rootroot00000000000000 self::DEVICE_TYPE_DESKTOP, 'smartphone' => self::DEVICE_TYPE_SMARTPHONE, 'tablet' => self::DEVICE_TYPE_TABLET, 'feature phone' => self::DEVICE_TYPE_FEATURE_PHONE, 'console' => self::DEVICE_TYPE_CONSOLE, 'tv' => self::DEVICE_TYPE_TV, 'car browser' => self::DEVICE_TYPE_CAR_BROWSER, 'smart display' => self::DEVICE_TYPE_SMART_DISPLAY, 'camera' => self::DEVICE_TYPE_CAMERA, 'portable media player' => self::DEVICE_TYPE_PORTABLE_MEDIA_PAYER, 'phablet' => self::DEVICE_TYPE_PHABLET, 'smart speaker' => self::DEVICE_TYPE_SMART_SPEAKER, 'wearable' => self::DEVICE_TYPE_WEARABLE, 'peripheral' => self::DEVICE_TYPE_PERIPHERAL, ]; /** * Known device brands * * Note: Before using a new brand in on of the regex files, it needs to be added here * * @var array */ public static $deviceBrands = [ '5E' => '2E', '2F' => 'F2 Mobile', '3Q' => '3Q', 'J7' => '7 Mobile', '2Q' => '3GNET', '4G' => '4Good', '27' => '3GO', '04' => '4ife', '36' => '360', '88' => '8848', '10M' => '10moons', '41' => 'A1', '00' => 'Accent', 'AE' => 'Ace', 'AC' => 'Acer', '3K' => 'Acteck', 'ACT' => 'actiMirror', 'A9' => 'Advan', 'AD' => 'Advance', '76' => 'Adronix', 'AF' => 'AfriOne', 'FY' => 'AFFIX', 'A3' => 'AGM', 'J0' => 'AG Mobile', 'AZ' => 'Ainol', 'AIR' => 'Airis', 'AI' => 'Airness', 'ARP' => 'Airpha', 'AT' => 'Airties', '7U' => 'Airtel', 'U0' => 'AIRON', '0A' => 'AIS', 'AW' => 'Aiwa', '85' => 'Aiuto', 'U7' => 'AIDATA', 'AK' => 'Akai', 'Q3' => 'AKIRA', '1A' => 'Alba', 'AL' => 'Alcatel', '20' => 'Alcor', 'XY' => 'Alps', '7L' => 'ALDI NORD', '6L' => 'ALDI SÜD', '3L' => 'Alfawise', '4A' => 'Aligator', 'AA' => 'AllCall', '3A' => 'AllDocube', 'A2' => 'Allview', 'ALI' => 'ALLINmobile', 'A7' => 'Allwinner', 'A1' => 'Altech UEC', '66' => 'Altice', 'A5' => 'altron', 'KN' => 'Amazon', 'AMA' => 'AMA', 'AG' => 'AMGOO', '9A' => 'Amigoo', 'AO' => 'Amoi', '3J' => 'Amino', '54' => 'AMCV', '60' => 'Andowl', '6J' => 'Angelcare', '7A' => 'Anry', 'A0' => 'ANS', '74' => 'Anker', '3N' => 'Aoson', 'O8' => 'AOC', 'J2' => 'AOYODKG', '55' => 'AOpen', 'RW' => 'Aoro', '9Y' => 'Aocos', 'AP' => 'Apple', 'AR' => 'Archos', 'AB' => 'Arian Space', 'A6' => 'Ark', '5A' => 'ArmPhone', 'AN' => 'Arnova', 'AS' => 'ARRIS', 'AQ' => 'Aspera', 'HJ' => 'Aquarius', '40' => 'Artel', '21' => 'Artizlee', '59' => 'ArtLine', '8A' => 'Asano', '90' => 'Asanzo', '1U' => 'Astro', 'A4' => 'Ask', 'A8' => 'Assistant', 'AU' => 'Asus', '6A' => 'AT&T', 'ATH' => 'Athesi', '5Q' => 'Atmaca Elektronik', 'YH' => 'ATMAN', '2A' => 'Atom', 'ATO' => 'ATOL', 'Z2' => 'Atvio', 'ATI' => 'Attila', 'AX' => 'Audiovox', 'AJ' => 'AURIS', 'YZ' => 'Autan', 'ZA' => 'Avenzo', 'AH' => 'AVH', 'AV' => 'Avvio', 'AVA' => 'Avaya', 'AY' => 'Axxion', 'AXX' => 'AXXA', 'YR' => 'AYYA', 'XA' => 'Axioo', 'AM' => 'Azumi Mobile', 'WW' => 'Awow', 'XU' => 'AUX', 'BAC' => 'Backcell', 'BO' => 'BangOlufsen', 'BN' => 'Barnes & Noble', 'BB' => 'BBK', '0B' => 'BB Mobile', 'B6' => 'BDF', 'QD' => 'BDQ', '8Z' => 'BDsharing', 'BEF' => 'Beafon', 'BE' => 'Becker', 'B5' => 'Beeline', 'B0' => 'Beelink', 'BL' => 'Beetel', '2X' => 'Benco', 'BQ' => 'BenQ', 'BS' => 'BenQ-Siemens', '4Y' => 'Benzo', 'XJ' => 'Benesse', 'BEN' => 'BenWee', 'YB' => 'Beista', 'BY' => 'BS Mobile', 'BZ' => 'Bezkam', '9B' => 'Bellphone', '63' => 'Beyond', 'BG' => 'BGH', '6B' => 'Bigben', 'B8' => 'BIHEE', '1B' => 'Billion', 'BA' => 'BilimLand', 'BIL' => 'Billow', 'BH' => 'BioRugged', 'BI' => 'Bird', 'BT' => 'Bitel', 'B7' => 'Bitmore', 'ZB' => 'Bittium', 'BK' => 'Bkav', '5B' => 'Black Bear', 'BF' => 'Black Fox', 'BPC' => 'Blackpcs', 'B2' => 'Blackview', '2Y' => 'b2m', 'BP' => 'Blaupunkt', 'BU' => 'Blu', 'BUS' => 'BluSlate', 'BUZ' => 'BuzzTV', 'B3' => 'Bluboo', '2B' => 'Bluedot', 'BD' => 'Bluegood', 'LB' => 'Bluewave', 'J8' => 'Bluebird', 'BSS' => 'BlueSky', '7B' => 'Blloc', 'UB' => 'Bleck', 'Q2' => 'Blow', 'BLI' => 'BLISS', 'BM' => 'Bmobile', 'Y5' => 'BMAX', 'BMX' => 'BMXC', 'B9' => 'Bobarry', 'B4' => 'bogo', 'BW' => 'Boway', 'BOO' => 'Boost', 'BOK' => 'Bookeen', 'BX' => 'bq', '8B' => 'Brandt', 'BRA' => 'BrandCode', 'BV' => 'Bravis', 'BRV' => 'BRAVE', 'BRG' => 'Brigmton', 'BR' => 'Brondi', 'XF' => 'BROR', 'BJ' => 'BrightSign', 'B1' => 'Bush', '4Q' => 'Bundy', 'Y8' => 'Bubblegum', 'C9' => 'CAGI', 'CT' => 'Capitel', 'G3' => 'CG Mobile', '37' => 'CGV', 'CP' => 'Captiva', 'CF' => 'Carrefour', 'CA1' => 'Carbon Mobile', 'CS' => 'Casio', 'R4' => 'Casper', 'CA' => 'Cat', 'BC' => 'Camfone', 'CJ' => 'Cavion', '4D' => 'Canal Digital', 'CEI' => 'Ceibal', '02' => 'Cell-C', 'CEL' => 'Cellacom', '34' => 'CellAllure', '7C' => 'Celcus', 'CE' => 'Celkon', 'CG' => 'Cellution', '62' => 'Centric', 'C2' => 'Changhong', 'CHA' => 'Chainway', 'CHG' => 'ChiliGreen', 'CH' => 'Cherry Mobile', 'C3' => 'China Mobile', 'U9' => 'China Telecom', 'CI' => 'Chico Mobile', 'CIP' => 'CipherLab', 'CIT' => 'Citycall', '1C' => 'Chuwi', 'L8' => 'Clarmin', '25' => 'Claresta', '1J' => 'Cloud', 'CD' => 'Cloudfone', '6C' => 'Cloudpad', 'C0' => 'Clout', 'CN' => 'CnM', 'CY' => 'Coby Kyros', 'XC' => 'Cobalt', 'C6' => 'Comio', 'CL' => 'Compal', 'CQ' => 'Compaq', 'C7' => 'ComTrade Tesla', '7Z' => 'COMPUMAX', 'C8' => 'Concord', 'CC' => 'ConCorde', 'C5' => 'Condor', 'C5M' => 'C5 Mobile', '4C' => 'Conquest', '3C' => 'Contixo', '8C' => 'Connex', '53' => 'Connectce', '9C' => 'Colors', 'CO' => 'Coolpad', 'COO' => 'Coopers', '4R' => 'CORN', '1O' => 'Cosmote', 'CW' => 'Cowon', '75' => 'Covia', 'QG' => 'COYOTE', 'YW' => 'ClearPHONE', '33' => 'Clementoni', 'CR' => 'CreNova', 'CX' => 'Crescent', 'CK' => 'Cricket', 'CM' => 'Crius Mea', '0C' => 'Crony', 'C1' => 'Crosscall', '4W' => 'Crown', 'CU' => 'Cube', 'CB' => 'CUBOT', 'CV' => 'CVTE', 'CWO' => 'Cwowdefu', 'C4' => 'Cyrus', 'D5' => 'Daewoo', 'DA' => 'Danew', 'DAN' => 'Dany', 'DT' => 'Datang', 'D7' => 'Datawind', '7D' => 'Datamini', '6D' => 'Datalogic', 'D1' => 'Datsun', 'DZ' => 'Dazen', 'DAS' => 'DASS', 'DB' => 'Dbtel', 'DBP' => 'DbPhone', 'DCO' => 'Dcode', 'DL' => 'Dell', 'DL0' => 'DL', 'DE' => 'Denver', 'DS' => 'Desay', 'DSI' => 'DSIC', 'DW' => 'DeWalt', 'DX' => 'DEXP', 'DEY' => 'DEYI', 'DEN' => 'Denali', 'DEA' => 'DEALDIG', '8D' => 'DF', 'DGT' => 'DGTEC', 'DG' => 'Dialog', 'DI' => 'Dicam', 'D4' => 'Digi', 'D3' => 'Digicel', 'DDG' => 'Digidragon', 'DH' => 'Digihome', 'DD' => 'Digiland', 'DIG' => 'Digit4G', 'DIC' => 'DIGICOM', 'Q0' => 'DIGIFORS', 'DQ' => 'DISH', '9D' => 'Ditecma', 'D2' => 'Digma', '1D' => 'Diva', 'DIV' => 'DiverMax', 'D6' => 'Divisat', 'X6' => 'DIXON', 'DIM' => 'DIMO', '5D' => 'DING DING', 'DIN' => 'Dinax', 'DM' => 'DMM', 'DN' => 'DNS', 'DC' => 'DoCoMo', 'DF' => 'Doffler', 'D9' => 'Dolamee', 'DO' => 'Doogee', 'D0' => 'Doopro', 'DV' => 'Doov', 'DOM' => 'Dom.ru', 'DP' => 'Dopod', 'JQ' => 'Doppio', 'DR' => 'Doro', 'ZD' => 'DORLAND', 'D8' => 'Droxio', 'DJ' => 'Dragon Touch', 'DRA' => 'DRAGON', 'DY' => 'Dreamgate', 'DRE' => 'DreamTab', 'DR1' => 'DreamStar', 'DTA' => 'Dtac', 'DU' => 'Dune HD', 'UD' => 'DUNNS Mobile', 'DUU' => 'Duubee', 'DTE' => 'D-Tech', 'DLI' => 'D-Link', 'ENO' => 'eNOVA', 'IN2' => 'iNOVA', 'EB' => 'E-Boda', 'EJ' => 'Engel', 'ENA' => 'ENACOM', 'ENI' => 'ENIE', '2E' => 'E-Ceros', 'E8' => 'E-tel', 'EP' => 'Easypix', 'EQ' => 'Eagle', 'EA' => 'EBEST', 'YC' => 'EBEN', 'E4' => 'Echo Mobiles', 'EQ1' => 'Equator', 'ES' => 'ECS', '35' => 'ECON', 'ECC' => 'ECOO', 'ZZ' => 'ecom', 'E6' => 'EE', 'GW' => 'EGL', 'EFT' => 'EFT', 'EK' => 'EKO', 'EY' => 'Einstein', 'EM' => 'Eks Mobility', 'UE' => 'Ematic', 'EMR' => 'Emporia', '4K' => 'EKT', '7E' => 'ELARI', '03' => 'Electroneum', 'Z8' => 'ELECTRONIA', 'EL1' => 'Elecson', 'L0' => 'Element', 'EG' => 'Elenberg', 'EL' => 'Elephone', 'JE' => 'Elekta', 'ELE' => 'Elevate', '4E' => 'Eltex', 'ELM' => 'Elong Mobile', 'ED' => 'Energizer', 'E1' => 'Energy Sistem', '3E' => 'Enot', 'ENT' => 'Entity', 'ENV' => 'Envizen', '8E' => 'Epik One', 'XP' => 'Epson', 'EPH' => 'Ephone', 'E7' => 'Ergo', 'EC' => 'Ericsson', '05' => 'Erisson', 'ER' => 'Ericy', 'EE' => 'Essential', 'E2' => 'Essentielb', '6E' => 'eSTAR', 'EN' => 'Eton', 'ET' => 'eTouch', '1E' => 'Etuline', 'EU' => 'Eurostar', '4J' => 'Eurocase', 'E9' => 'Evercoss', 'EV' => 'Evertek', 'EVE' => 'Everest', 'EV1' => 'Everex', 'E3' => 'Evolio', 'EO' => 'Evolveo', '0Q' => 'Evoo', '5U' => 'EVPAD', 'E0' => 'EvroMedia', 'XE' => 'ExMobile', '4Z' => 'Exmart', 'EH' => 'EXO', 'EX' => 'Explay', 'E5' => 'Extrem', 'EF' => 'EXCEED', 'QE' => 'EWIS', 'EI' => 'Ezio', 'EZ' => 'Ezze', 'UF' => 'EYU', 'UE1' => 'UE', '5F' => 'F150', 'F6' => 'Facebook', 'FAC' => 'Facetel', 'FA1' => 'Facime', 'FA' => 'Fairphone', 'FM' => 'Famoco', 'FAM' => 'Famous', '17' => 'FarEasTone', '9R' => 'FaRao Pro', 'FAR' => 'Farassoo', 'FB' => 'Fantec', 'FE' => 'Fengxiang', 'F7' => 'Fero', '67' => 'FEONAL', 'FI' => 'FiGO', 'J9' => 'FiGi', 'FIG' => 'Figgers', 'F9' => 'FiiO', 'F1' => 'FinePower', 'FX' => 'Finlux', 'F3' => 'FireFly Mobile', 'F8' => 'FISE', 'FIL' => 'FILIX', 'FL' => 'Fly', 'QC' => 'FLYCAT', 'FLU' => 'Fluo', 'FN' => 'FNB', 'FD' => 'Fondi', '0F' => 'Fourel', '44' => 'Four Mobile', 'F0' => 'Fonos', 'F2' => 'FORME', 'F5' => 'Formuler', 'FR' => 'Forstar', 'RF' => 'Fortis', 'FO' => 'Foxconn', 'FOD' => 'FoxxD', 'FJ' => 'FOODO', 'FT' => 'Freetel', 'FRU' => 'Frunsi', 'F4' => 'F&U', '1F' => 'FMT', 'FPT' => 'FPT', 'FG' => 'Fuego', 'FU' => 'Fujitsu', '4F' => 'Funai', '5J' => 'Fusion5', 'FF' => 'Future Mobile Technology', 'FFF' => 'FFF SmartLife', 'FW' => 'FNF', 'FXT' => 'Fxtec', 'GT' => 'G-TiDE', 'G9' => 'G-Touch', 'GTM' => 'GTMEDIA', '0G' => 'GFive', 'GM' => 'Garmin-Asus', 'GA' => 'Gateway', '99' => 'Galaxy Innovations', 'GAZ' => 'Gazer', 'GEA' => 'Geanee', 'GD' => 'Gemini', 'GN' => 'General Mobile', '2G' => 'Genesis', 'G2' => 'GEOFOX', 'GE' => 'Geotel', 'Q4' => 'Geotex', 'GEO' => 'GEOZON', 'GER' => 'Gear Mobile', 'GH' => 'Ghia', '2C' => 'Ghong', 'GJ' => 'Ghost', 'GG' => 'Gigabyte', 'GS' => 'Gigaset', 'GZ' => 'Ginzzu', '1G' => 'Gini', 'GI' => 'Gionee', 'GIR' => 'GIRASOLE', 'G4' => 'Globex', '38' => 'GLONYX', 'U6' => 'Glofiish', 'G7' => 'GoGEN', 'GC' => 'GOCLEVER', '5G' => 'Gocomma', 'GB' => 'Gol Mobile', 'GL' => 'Goly', 'GOL' => 'GoldMaster', 'GX' => 'GLX', 'G5' => 'Gome', 'G1' => 'GoMobile', 'GO' => 'Google', 'G0' => 'Goophone', '6G' => 'Gooweel', '8G' => 'Gplus', 'GR' => 'Gradiente', 'GP' => 'Grape', 'G6' => 'Gree', 'GRA' => 'Great Asia', '3G' => 'Greentel', 'GF' => 'Gretel', '82' => 'Gresso', 'GU' => 'Grundig', 'GV' => 'Gtel', 'CUO' => 'Guophone', 'H13' => 'H133', '9Z' => 'H96', 'HF' => 'Hafury', '9F' => 'HAOVM', 'HA' => 'Haier', 'XH' => 'Haipai', 'HAN' => 'Handheld', 'HE' => 'HannSpree', 'HK' => 'Hardkernel', 'HAR' => 'Harper', 'HA1' => 'Hartens', 'HS' => 'Hasee', '8H' => 'Hamlet', 'HAM' => 'Hammer', 'H6' => 'Helio', 'HQ' => 'HERO', 'ZH' => 'Hezire', 'HEX' => 'HexaByte', 'HEW' => 'HeadWolf', 'HL' => 'Hi-Level', '3H' => 'Hi', 'HIB' => 'Hiberg', 'HIH' => 'HiHi', 'HIK' => 'HiKing', 'H2' => 'Highscreen', 'Q1' => 'High Q', '1H' => 'Hipstreet', 'HI' => 'Hisense', 'HC' => 'Hitachi', 'H8' => 'Hitech', 'W3' => 'HiMax', '8X' => 'Hi Nova', 'HLL' => 'HLLO', '8W' => 'HKPro', 'H1' => 'Hoffmann', 'H0' => 'Hometech', 'HM' => 'Homtom', 'HZ' => 'Hoozo', 'H7' => 'Horizon', '4H' => 'Horizont', 'HO' => 'Hosin', 'H3' => 'Hotel', 'HV' => 'Hotwav', 'U8' => 'Hot Pepper', 'JH' => 'HOTREALS', 'HW' => 'How', 'WH' => 'Honeywell', 'HP' => 'HP', 'HDC' => 'HDC', 'HT' => 'HTC', 'QZ' => 'Huagan', 'HD' => 'Huadoo', 'HG' => 'Huavi', 'HU' => 'Huawei', 'HX' => 'Humax', 'HR' => 'Hurricane', 'H5' => 'Huskee', 'HUG' => 'Hugerock', 'HY' => 'Hyrican', 'HN' => 'Hyundai', '7H' => 'Hyve', 'HYT' => 'Hytera', 'HYK' => 'Hykker', '3I' => 'i-Cherry', 'IJ' => 'i-Joy', 'IM' => 'i-mate', 'IO' => 'i-mobile', 'INN' => 'I-INN', 'OF' => 'iOutdoor', 'IB' => 'iBall', 'IY' => 'iBerry', '7I' => 'iBrit', 'I2' => 'IconBIT', 'IC' => 'iDroid', '6Z' => 'iData', 'IG' => 'iGet', 'IH' => 'iHunt', 'IA' => 'Ikea', 'IYO' => 'iYou', '8I' => 'IKU Mobile', '2K' => 'IKI Mobile', 'IK' => 'iKoMo', '58' => 'iKon', 'I7' => 'iLA', '2I' => 'iLife', '1I' => 'iMars', 'IMI' => 'iMI', 'U4' => 'iMan', 'IL' => 'IMO Mobile', 'IM1' => 'Imose', 'I3' => 'Impression', 'FC' => 'INCAR', '2H' => 'Inch', '6I' => 'Inco', 'IW' => 'iNew', 'IF' => 'Infinix', 'INF' => 'Infiniton', 'I0' => 'InFocus', 'IN1' => 'InFone', 'II' => 'Inkti', '81' => 'InfoKit', 'I5' => 'InnJoo', '26' => 'Innos', 'IN' => 'Innostream', 'I4' => 'Inoi', 'INO' => 'iNo Mobile', 'IQ' => 'INQ', 'QN' => 'iQ&T', 'IS' => 'Insignia', 'YI' => 'INSYS', 'IT' => 'Intek', 'INT' => 'Intel', 'IX' => 'Intex', 'IV' => 'Inverto', '32' => 'Invens', '4I' => 'Invin', 'INA' => 'iNavi', 'I1' => 'iOcean', 'IMU' => 'iMuz', 'IP' => 'iPro', 'X9' => 'iPEGTOP', '8Q' => 'IQM', 'Q8' => 'IRA', 'I6' => 'Irbis', '5I' => 'Iris', 'IRE' => 'iReplace', 'IR' => 'iRola', 'IU' => 'iRulu', 'IRO' => 'iRobot', '9I' => 'iSWAG', '9J' => 'iSafe Mobile', 'IST' => 'iStar', '86' => 'IT', 'IZ' => 'iTel', '0I' => 'iTruck', 'I8' => 'iVA', 'IE' => 'iView', '0J' => 'iVooMi', 'UI' => 'ivvi', 'QW' => 'iWaylink', 'I9' => 'iZotron', 'IXT' => 'iXTech', 'JA' => 'JAY-Tech', 'KJ' => 'Jiake', 'JD' => 'Jedi', 'J6' => 'Jeka', 'JF' => 'JFone', 'JI' => 'Jiayu', 'JG' => 'Jinga', 'JX' => 'Jio', 'VJ' => 'Jivi', 'JK' => 'JKL', 'JR1' => 'JREN', 'JO' => 'Jolla', 'JP' => 'Joy', 'JOY' => 'JoySurf', 'UJ' => 'Juniper Systems', 'J5' => 'Just5', '7J' => 'Jumper', 'JPA' => 'JPay', 'JV' => 'JVC', 'JXD' => 'JXD', 'JS' => 'Jesy', 'KT' => 'K-Touch', 'KLT' => 'K-Lite', 'K4' => 'Kaan', 'K7' => 'Kaiomy', 'KL' => 'Kalley', 'K6' => 'Kanji', 'KA' => 'Karbonn', 'K5' => 'KATV1', 'KAP' => 'Kapsys', 'K0' => 'Kata', 'KZ' => 'Kazam', '9K' => 'Kazuna', 'KD' => 'KDDI', 'KHA' => 'Khadas', 'KS' => 'Kempler & Strauss', 'K3' => 'Keneksi', 'KX' => 'Kenxinda', 'KEN' => 'Kenbo', 'KZG' => 'KZG', 'K1' => 'Kiano', '5W' => 'Kingbox', 'KI' => 'Kingsun', 'KF' => 'KINGZONE', 'KIN' => 'Kingstar', '46' => 'Kiowa', 'KV' => 'Kivi', '64' => 'Kvant', '0K' => 'Klipad', 'KC' => 'Kocaso', 'KK' => 'Kodak', 'KG' => 'Kogan', 'KM' => 'Komu', 'KO' => 'Konka', 'KW' => 'Konrow', 'KB' => 'Koobee', '7K' => 'Koolnee', 'K9' => 'Kooper', 'KP' => 'KOPO', 'KR' => 'Koridy', 'XK' => 'Koslam', 'K2' => 'KRONO', 'KE' => 'Krüger&Matz', '5K' => 'KREZ', 'WK' => 'KRIP', 'KH' => 'KT-Tech', 'Z6' => 'KUBO', 'K8' => 'Kuliao', '8K' => 'Kult', 'KU' => 'Kumai', '6K' => 'Kurio', 'KY' => 'Kyocera', 'KQ' => 'Kyowon', '1K' => 'Kzen', 'LQ' => 'LAIQ', 'L6' => 'Land Rover', 'L2' => 'Landvo', 'LA' => 'Lanix', 'LA1' => 'Lanin', 'LK' => 'Lark', 'Z3' => 'Laurus', 'LEC' => 'Lectrus', 'LV' => 'Lava', 'LC' => 'LCT', 'L5' => 'Leagoo', 'U3' => 'Leben', 'LEB' => 'LeBest', 'LD' => 'Ledstar', 'LEE' => 'Leelbox', 'L1' => 'LeEco', '4B' => 'Leff', 'LEG' => 'Legend', 'L4' => 'Lemhoov', 'W9' => 'LEMFO', 'LN' => 'Lenco', 'LE' => 'Lenovo', 'LT' => 'Leotec', 'LP' => 'Le Pan', 'ZJ' => 'Leke', 'L7' => 'Lephone', 'LZ' => 'Lesia', 'L3' => 'Lexand', 'LX' => 'Lexibook', 'LG' => 'LG', '39' => 'Liberton', '5L' => 'Linsar', 'LF' => 'Lifemaxx', 'LI' => 'Lingwin', 'LJ' => 'L-Max', 'LW' => 'Linnex', 'JJ' => 'Listo', 'LNM' => 'LNMBBS', 'LO' => 'Loewe', 'YL' => 'Loview', 'LOV' => 'Lovme', '1L' => 'Logic', 'LH' => 'Logic Instrument', 'LM' => 'Logicom', 'GY' => 'LOKMAT', 'LPX' => 'LPX-G', '0L' => 'Lumigon', 'LU' => 'Lumus', 'LUM' => 'Lumitel', 'L9' => 'Luna', 'LR' => 'Luxor', 'LY' => 'LYF', 'LL' => 'Leader Phone', 'QL' => 'LT Mobile', 'MQ' => 'M.T.T.', 'MN' => 'M4tel', 'XM' => 'Macoox', '92' => 'MAC AUDIO', 'MJ' => 'Majestic', 'FQ' => 'Mafe', '6Y' => 'Magicsee', '23' => 'Magnus', 'NH' => 'Manhattan', 'MAN' => 'Mango', '5M' => 'Mann', 'MA' => 'Manta Multimedia', 'Z0' => 'Mantra', 'J4' => 'Mara', 'MAR' => 'Marshal', '8Y' => 'Massgo', 'MA1' => 'Mascom', '2M' => 'Masstel', '3X' => 'Mastertech', 'MAS' => 'Master-G', '50' => 'Matrix', '7M' => 'Maxcom', '7M1' => 'Maxfone', 'ZM' => 'Maximus', '6X' => 'Maxtron', '0D' => 'MAXVI', 'XZ' => 'MAXX', 'MW' => 'Maxwest', 'M0' => 'Maze', 'YM' => 'Maze Speed', '87' => 'Malata', '28' => 'MBOX', 'FK' => 'MBI', '3D' => 'MDC Store', '1Y' => 'MDTV', '09' => 'meanIT', 'M3' => 'Mecer', 'M3M' => 'M3 Mobile', '0M' => 'Mecool', 'MC' => 'Mediacom', 'MK' => 'MediaTek', 'MD' => 'Medion', 'M2' => 'MEEG', 'MP' => 'MegaFon', 'X0' => 'mPhone', '3M' => 'Meitu', 'M1' => 'Meizu', '0E' => 'Melrose', 'MU' => 'Memup', 'ME' => 'Metz', 'MX' => 'MEU', 'MI' => 'MicroMax', 'MS' => 'Microsoft', '6Q' => 'Microtech', '1X' => 'Minix', 'OM' => 'Mintt', 'MIN' => 'Mint', 'MO' => 'Mio', 'X7' => 'Mione', 'M7' => 'Miray', '8M' => 'Mito', 'MT' => 'Mitsubishi', '0Y' => 'Mitsui', 'M5' => 'MIXC', '2D' => 'MIVO', '1Z' => 'MiXzo', 'MIW' => 'MIWANG', 'ML' => 'MLLED', 'LS' => 'MLS', '5H' => 'MMI', '4M' => 'Mobicel', 'M6' => 'Mobiistar', 'MOK' => 'Mobile Kingdom', 'MH' => 'Mobiola', 'MB' => 'Mobistel', 'ID' => 'MobiIoT', '6W' => 'MobiWire', '9M' => 'Mobo', 'MOB' => 'Mobell', 'M4' => 'Modecom', 'MF' => 'Mofut', 'MR' => 'Motorola', 'MV' => 'Movic', 'MOV' => 'Movitel', 'MO1' => 'MOVISUN', 'MOS' => 'Mosimosi', 'MOX' => 'Moxee', 'MM' => 'Mpman', 'MZ' => 'MSI', '3R' => 'MStar', 'M9' => 'MTC', 'N4' => 'MTN', '72' => 'M-Tech', '9H' => 'M-Horse', '1R' => 'Multilaser', '1M' => 'MYFON', 'MY' => 'MyPhone', '51' => 'Myros', 'M8' => 'Myria', '6M' => 'Mystery', '3T' => 'MyTab', 'MG' => 'MyWigo', 'J3' => 'Mymaga', 'MYM' => 'MyMobile', '07' => 'MyGica', 'MYG' => 'MygPad', 'MWA' => 'MwalimuPlus', 'NEO' => 'neoCore', '08' => 'Nabi', 'N7' => 'National', 'NC' => 'Navcity', '6N' => 'Navitech', '7V' => 'Navitel', 'N3' => 'Navon', '7R' => 'NavRoad', 'NAS' => 'NASCO', 'NP' => 'Naomi Phone', 'NE' => 'NEC', 'NDP' => 'Nedaphone', '8N' => 'Necnot', 'NF' => 'Neffos', '9X' => 'Neo', 'NEK' => 'NEKO', '1N' => 'Neomi', '7Q' => 'Neon IQ', '8F' => 'Neolix', 'NA' => 'Netgear', 'NEM' => 'Netmak', 'NU' => 'NeuImage', 'NW' => 'Newgen', 'N9' => 'Newland', '0N' => 'Newman', 'NS' => 'NewsMy', 'ND' => 'Newsday', 'HB' => 'New Balance', 'BRI' => 'New Bridge', 'XB' => 'NEXBOX', 'NX' => 'Nexian', '7X' => 'Nexa', 'N8' => 'NEXON', 'N2' => 'Nextbit', 'NT' => 'NextBook', 'NTT' => 'NTT West', '4N' => 'NextTab', 'NEX' => 'NEXT', 'NJO' => 'nJoy', 'NG' => 'NGM', 'NZ' => 'NG Optics', 'NZP' => 'NGpon', 'NN' => 'Nikon', 'NI' => 'Nintendo', 'NIN' => 'NINETEC', 'N5' => 'NOA', 'N1' => 'Noain', 'N6' => 'Nobby', 'NOV' => 'Novey', 'NO1' => 'NOVO', '57' => 'Nubia', 'JN' => 'NOBUX', 'NB' => 'Noblex', 'OG' => 'NOGA', 'NK' => 'Nokia', 'NM' => 'Nomi', '2N' => 'Nomu', '6H' => 'Noontec', 'NR' => 'Nordmende', '7N' => 'NorthTech', 'NOT' => 'Nothing Phone', '5N' => 'Nos', 'NO' => 'Nous', 'NQ' => 'Novex', 'NJ' => 'NuAns', 'NL' => 'NUU Mobile', 'N0' => 'Nuvo', 'NUV' => 'NuVision', 'NV' => 'Nvidia', 'NY' => 'NYX Mobile', 'O3' => 'O+', 'OT' => 'O2', 'O7' => 'Oale', 'OC' => 'OASYS', 'OB' => 'Obi', 'OQ' => 'Meta', 'O1' => 'Odys', 'ODP' => 'Odotpad', 'O9' => 'Ok', 'OA' => 'Okapia', 'OLA' => 'Olax', 'OLY' => 'Olympia', 'OLT' => 'OLTO', 'OJ' => 'Ookee', 'OD' => 'Onda', 'ON' => 'OnePlus', 'ONC' => 'OneClick', 'OAN' => 'Oangcc', 'OX' => 'Onix', '3O' => 'ONYX BOOX', 'O4' => 'ONN', '9Q' => 'Onkyo', '2O' => 'OpelMobile', 'OH' => 'Openbox', '7Y' => 'Obabox', 'OP' => 'OPPO', 'OO' => 'Opsson', 'OPT' => 'Optoma', 'OPH' => 'Ophone', 'OR' => 'Orange', 'O5' => 'Orbic', 'Y6' => 'Orbita', 'ORB' => 'Orbsmart', 'OS' => 'Ordissimo', '8O' => 'Orion', 'OTT' => 'OTTO', 'OK' => 'Ouki', '0O' => 'OINOM', 'QK' => 'OKWU', 'QQ' => 'OMIX', '56' => 'OKSI', 'OE' => 'Oukitel', 'OU' => 'OUYA', 'JB' => 'OUJIA', 'OV' => 'Overmax', '30' => 'Ovvi', 'O2' => 'Owwo', 'OSC' => 'OSCAL', 'OY' => 'Oysters', 'QF' => 'OYSIN', 'O6' => 'Oyyu', 'OZ' => 'OzoneHD', 'OLL' => 'Ollee', '7P' => 'P-UP', 'YP' => 'Paladin', 'PM' => 'Palm', 'PN' => 'Panacom', 'PA' => 'Panasonic', 'PT' => 'Pantech', 'PAN' => 'Pano', '94' => 'Packard Bell', 'H9' => 'Parrot Mobile', 'PAR' => 'Partner Mobile', 'PAP' => 'PAPYRE', 'PB' => 'PCBOX', 'PCS' => 'PC Smart', 'PC' => 'PCD', 'PD' => 'PCD Argentina', 'PE' => 'PEAQ', 'PG' => 'Pentagram', 'PQ' => 'Pendoo', '93' => 'Perfeo', '8J' => 'Pelitt', '1P' => 'Phicomm', '4P' => 'Philco', 'PH' => 'Philips', '5P' => 'Phonemax', 'PO' => 'phoneOne', 'PI' => 'Pioneer', 'PIC' => 'Pioneer Computers', 'PJ' => 'PiPO', '8P' => 'Pixelphone', '9O' => 'Pixela', 'PX' => 'Pixus', 'QP' => 'Pico', 'PIR' => 'PIRANHA', 'PIN' => 'PINE', '9P' => 'Planet Computers', 'PY' => 'Ployer', 'P4' => 'Plum', 'PLU' => 'PlusStyle', '22' => 'Pluzz', 'P8' => 'PocketBook', '0P' => 'POCO', 'FH' => 'Point Mobile', 'PV' => 'Point of View', 'PL' => 'Polaroid', 'Q6' => 'Polar', '97' => 'PolarLine', 'PP' => 'PolyPad', 'P5' => 'Polytron', 'P2' => 'Pomp', 'P0' => 'Poppox', '0X' => 'POPTEL', 'PS' => 'Positivo', '3P' => 'Positivo BGH', '3F' => 'Porsche', 'P3' => 'PPTV', 'FP' => 'Premio', 'PR' => 'Prestigio', 'P9' => 'Primepad', 'PRM' => 'PRIME', '6P' => 'Primux', '2P' => 'Prixton', 'PRI' => 'Pritom', 'PF' => 'PROFiLO', 'P6' => 'Proline', '5O' => 'Prology', 'P1' => 'ProScan', 'P7' => 'Protruly', 'R0' => 'ProVision', '7O' => 'Polestar', 'PU' => 'PULID', 'UP' => 'Purism', 'QFX' => 'QFX', 'Q7' => 'Q-Box', 'QH' => 'Q-Touch', 'QB' => 'Q.Bell', 'QI' => 'Qilive', 'QM' => 'QMobile', 'QT' => 'Qtek', 'Q9' => 'QTECH', 'QA' => 'Quantum', 'QUE' => 'Quest', 'QUA' => 'Quatro', 'QU' => 'Quechua', 'QUI' => 'Quipus', 'QO' => 'Qumo', 'UQ' => 'Qubo', 'YQ' => 'QLink', 'QY' => 'Qnet Mobile', 'WJ' => 'Qware', 'R2' => 'R-TV', 'RA' => 'Ramos', '0R' => 'Raspberry', 'R9' => 'Ravoz', 'RZ' => 'Razer', '95' => 'Rakuten', 'RAY' => 'Raylandz', 'RC' => 'RCA Tablets', '2R' => 'Reach', 'REL' => 'RelNAT', 'RB' => 'Readboy', 'RE' => 'Realme', 'RE1' => 'Redbean', 'R8' => 'RED', 'REW' => 'Redway', '6F' => 'Redfox', 'RD' => 'Reeder', 'Z9' => 'REGAL', 'RH' => 'Remdun', 'RP' => 'Revo', 'REV' => 'Revomovil', '8R' => 'Retroid Pocket', 'RIC' => 'Ricoh', 'RI' => 'Rikomagic', 'RM' => 'RIM', 'RN' => 'Rinno', 'RX' => 'Ritmix', 'R7' => 'Ritzviva', 'RV' => 'Riviera', '6R' => 'Rivo', 'RIZ' => 'Rizzen', 'RR' => 'Roadrover', 'QR' => 'ROADMAX', 'ROC' => 'Roam Cat', 'R1' => 'Rokit', 'RK' => 'Roku', 'R3' => 'Rombica', 'R5' => 'Ross&Moor', 'RO' => 'Rover', 'R6' => 'RoverPad', 'RQ' => 'RoyQueen', 'RJ' => 'Royole', 'RT' => 'RT Project', 'RG' => 'RugGear', 'RUG' => 'Ruggex', 'RU' => 'Runbo', 'RUP' => 'Rupa', 'RL' => 'Ruio', 'RY' => 'Ryte', 'X5' => 'Saba', '8L' => 'S-TELL', '4O' => 'S2Tel', '89' => 'Seatel', 'SEW' => 'Sewoo', 'Y7' => 'Saiet', 'X1' => 'Safaricom', 'SG' => 'Sagem', '4L' => 'Salora', 'SA' => 'Samsung', 'SAT' => 'Samtech', 'SNA' => 'SNAMI', 'S0' => 'Sanei', '12' => 'Sansui', 'SAK' => 'Sankey', 'SQ' => 'Santin', 'SY' => 'Sanyo', 'SAN' => 'SANY', 'S9' => 'Savio', 'Y4' => 'SCBC', 'CZ' => 'Schneider', 'SCO' => 'Scosmos', 'ZG' => 'Schok', 'G8' => 'SEG', 'SD' => 'Sega', '0U' => 'Selecline', '9G' => 'Selenga', 'SV' => 'Selevision', 'SL' => 'Selfix', '0S' => 'SEMP TCL', 'S1' => 'Sencor', 'SN' => 'Sendo', '01' => 'Senkatel', 'S6' => 'Senseit', 'EW' => 'Senwa', '24' => 'Seeken', '61' => 'Seuic', 'SX' => 'SFR', 'SGI' => 'SGIN', 'SH' => 'Sharp', 'JU' => 'Shanling', '7S' => 'Shift Phones', '78' => 'Shivaki', 'RS' => 'Shtrikh-M', '3S' => 'Shuttle', '13' => 'Sico', 'SI' => 'Siemens', '1S' => 'Sigma', '70' => 'Silelis', 'SJ' => 'Silent Circle', '10' => 'Simbans', '98' => 'Simply', '52' => 'Singtech', '31' => 'Siragon', '83' => 'Sirin Labs', '5Z' => 'SK Broadband', 'GK' => 'SKG', 'SW' => 'Sky', 'SK' => 'Skyworth', 'SKY' => 'Skyline', '14' => 'Smadl', '19' => 'Smailo', 'SR' => 'Smart Electronic', 'SMA' => 'Smart Kassel', '49' => 'Smart', '47' => 'SmartBook', '3B' => 'Smartab', '80' => 'SMARTEC', 'SM1' => 'Smartex', 'SC' => 'Smartfren', 'S7' => 'Smartisan', 'JR' => 'Sylvania', 'SYH' => 'SYH', '3Y' => 'Smarty', 'HH' => 'Smooth Mobile', '1Q' => 'Smotreshka', 'SF' => 'Softbank', '9L' => 'SOLE', 'JL' => 'SOLO', 'SOS' => 'SOSH', 'SOD' => 'Soda', '16' => 'Solone', 'OI' => 'Sonim', 'SO' => 'Sony', 'SE' => 'Sony Ericsson', 'X2' => 'Soundmax', '8S' => 'Soyes', '77' => 'SONOS', '68' => 'Soho Style', 'PK' => 'Spark', 'FS' => 'SPC', '6S' => 'Spectrum', '43' => 'Spectralink', 'SP' => 'Spice', '84' => 'Sprint', 'QS' => 'SQOOL', 'S4' => 'Star', 'OL' => 'Starlight', '18' => 'Starmobile', '2S' => 'Starway', '45' => 'Starwind', 'SB' => 'STF Mobile', 'S8' => 'STK', 'GQ' => 'STG Telecom', 'S2' => 'Stonex', 'ST' => 'Storex', 'STR' => 'Stream', '71' => 'StrawBerry', '96' => 'STRONG', '69' => 'Stylo', '9S' => 'Sugar', 'SUR' => 'Surge', '06' => 'Subor', 'SZ' => 'Sumvision', '0H' => 'Sunstech', 'S3' => 'SunVan', '5S' => 'Sunvell', '5Y' => 'Sunny', 'W8' => 'SUNWIND', 'SBX' => 'SuperBOX', 'SU' => 'SuperSonic', '79' => 'SuperTab', 'S5' => 'Supra', 'ZS' => 'Suzuki', '2J' => 'Sunmi', 'SUN' => 'Sunmax', '0W' => 'Swipe', 'SWI' => 'Switel', 'SS' => 'SWISSMOBILITY', '1W' => 'Swisstone', 'W7' => 'SWTV', 'SSK' => 'SSKY', 'SYC' => 'Syco', 'SM' => 'Symphony', '4S' => 'Syrox', 'TM' => 'T-Mobile', 'T96' => 'T96', 'TK' => 'Takara', '73' => 'Tambo', '9N' => 'Tanix', 'U5' => 'Taiga System', 'TAL' => 'Talius', '7G' => 'TAG Tech', 'T5' => 'TB Touch', 'TC' => 'TCL', 'T0' => 'TD Systems', 'YY' => 'TD Tech', 'H4' => 'Technicolor', 'TEA' => 'TeachTouch', 'Z5' => 'Technika', 'TX' => 'TechniSat', 'TT' => 'TechnoTrend', 'TP' => 'TechPad', '9E' => 'Techwood', '7F' => 'Technopc', 'T7' => 'Teclast', 'TB' => 'Tecno Mobile', 'TEC' => 'TecToy', '91' => 'TEENO', '2L' => 'Tele2', 'TL' => 'Telefunken', 'TG' => 'Telego', 'T2' => 'Telenor', 'TE' => 'Telit', '65' => 'Telia', 'TEL' => 'Telma', 'PW' => 'Telpo', 'TLS' => 'TeloSystems', 'TER' => 'Teracube', 'TD' => 'Tesco', 'TA' => 'Tesla', '9T' => 'Tetratab', 'TET' => 'TETC', 'TZ' => 'teXet', '29' => 'Teknosa', 'JZ' => 'TJC', 'JC' => 'TENPLUS', 'T4' => 'ThL', 'TN' => 'Thomson', 'O0' => 'Thuraya', 'TI' => 'TIANYU', 'JY' => 'Tigers', '8T' => 'Time2', 'TQ' => 'Timovi', 'TIM' => 'TIMvision', '2T' => 'Tinai', 'TF' => 'Tinmo', 'TH' => 'TiPhone', 'YV' => 'TiVo', 'TIB' => 'Tibuta', 'Y3' => 'TOKYO', 'TOX' => 'TOX', 'T1' => 'Tolino', '0T' => 'Tone', 'TY' => 'Tooky', 'T9' => 'Top House', 'DK' => 'Topelotek', '42' => 'Topway', 'TO' => 'Toplux', 'TOD' => 'TOPDON', '7T' => 'Torex', 'TOR' => 'Torque', '6O' => 'TOSCIDO', 'TO1' => 'Topsion', 'TS' => 'Toshiba', 'T8' => 'Touchmate', 'TOU' => 'Touch Plus', '5R' => 'Transpeed', 'T6' => 'TrekStor', 'T3' => 'Trevi', 'TJ' => 'Trifone', 'Q5' => 'Trident', '4T' => 'Tronsmart', '11' => 'True', 'JT' => 'True Slim', 'J1' => 'Trio', '5C' => 'TTEC', 'TTK' => 'TTK-TV', 'TU' => 'Tunisie Telecom', '1T' => 'Turbo', 'TR' => 'Turbo-X', '5X' => 'TurboPad', '5T' => 'TurboKids', 'UR' => 'Turkcell', '4U' => 'TuCEL', '2U' => 'Türk Telekom', 'TV' => 'TVC', 'TW' => 'TWM', 'Z1' => 'TWZ', '6T' => 'Twoe', '15' => 'Tymes', 'UC' => 'U.S. Cellular', 'UG' => 'Ugoos', 'U1' => 'Uhans', 'UH' => 'Uhappy', 'UL' => 'Ulefone', 'UA' => 'Umax', 'UM' => 'UMIDIGI', 'UNT' => 'Unitech', 'UZ' => 'Unihertz', '3Z' => 'UZ Mobile', 'UX' => 'Unimax', 'UNQ' => 'Uniqcell', 'US' => 'Uniscope', 'UNI' => 'Unistrong', 'U2' => 'UNIWA', 'UND' => 'Uniden', 'UO' => 'Unnecto', 'UNN' => 'Unnion Technologies', 'UU' => 'Unonu', 'UN' => 'Unowhy', 'UY' => 'UNNO', 'UNB' => 'Unblock Tech', 'UK' => 'UTOK', '3U' => 'IUNI', 'UT' => 'UTStarcom', '6U' => 'UTime', '9U' => 'Urovo', 'UW' => 'U-Magic', '5V' => 'VAIO', 'WV' => 'VAVA', 'VA' => 'Vastking', 'VP' => 'Vargo', 'VC' => 'Vankyo', 'VAL' => 'VALEM', 'VAT' => 'VALTECH', 'VB' => 'VC', 'VN' => 'Venso', 'VEN' => 'Venstar', 'UV' => 'Venturer', 'VQ' => 'Vega', 'WC' => 'VEON', '4V' => 'Verico', 'V4' => 'Verizon', 'VR' => 'Vernee', 'VX' => 'Vertex', 'VE' => 'Vertu', 'VL' => 'Verykool', 'QV' => 'Verssed', 'VER' => 'Versus', 'V8' => 'Vesta', 'VT' => 'Vestel', '48' => 'Vexia', 'V6' => 'VGO TEL', 'QJ' => 'VDVD', 'VIC' => 'Victurio', 'VD' => 'Videocon', 'VW' => 'Videoweb', 'VS' => 'ViewSonic', 'V7' => 'Vinga', 'V3' => 'Vinsoc', 'XD' => 'Vinabox', 'FV' => 'Vios', '0V' => 'Vipro', 'ZV' => 'Virzo', 'VI' => 'Vitelcom', 'VIB' => 'ViBox', '8V' => 'Viumee', 'V5' => 'Vivax', 'VIV' => 'VIVIMAGE', 'VV' => 'Vivo', '6V' => 'VIWA', 'VID' => 'VIDA', 'VZ' => 'Vizio', 'VIZ' => 'Vizmo', '9V' => 'Vision Touch', 'VK' => 'VK Mobile', 'JM' => 'v-mobile', 'VHO' => 'V-HOPE', 'VHM' => 'V-HOME', 'VGE' => 'V-Gen', 'V0' => 'VKworld', 'VM' => 'Vodacom', 'VF' => 'Vodafone', '7W' => 'VOGA', 'V2' => 'Vonino', '1V' => 'Vontar', 'VG' => 'Vorago', '2V' => 'Vorke', '8U' => 'Vorcom', 'JW' => 'Vortex', 'VOR' => 'Vormor', 'V1' => 'Voto', 'Z7' => 'VOX', 'VO' => 'Voxtel', 'VY' => 'Voyo', 'VOL' => 'Völfen', 'VO1' => 'Volt', 'VH' => 'Vsmart', 'V9' => 'Vsun', 'VU' => 'Vulcan', '3V' => 'VVETIME', 'ZC' => 'VUCATIMES', 'VUE' => 'Vue Micro', 'WA' => 'Walton', 'WAL' => 'Waltter', 'WHI' => 'White Mobile', 'WM' => 'Weimei', 'WE' => 'WellcoM', 'W6' => 'WELLINGTON', 'WD' => 'Western Digital', 'WT' => 'Westpoint', 'WAN' => 'Wanmukang', 'WA1' => 'WANSA', 'WY' => 'Wexler', '3W' => 'WE', 'WEC' => 'Wecool', 'WEE' => 'Weelikeit', 'WP' => 'Wieppo', 'W2' => 'Wigor', 'WI' => 'Wiko', 'WF' => 'Wileyfox', 'WS' => 'Winds', 'WN' => 'Wink', '9W' => 'Winmax', 'W5' => 'Winnovo', 'WU' => 'Wintouch', 'WIS' => 'Winstar', 'W0' => 'Wiseasy', '2W' => 'Wizz', 'W4' => 'WIWA', 'WIZ' => 'WizarPos', 'WL' => 'Wolder', 'WG' => 'Wolfgang', 'WQ' => 'Wolki', 'WO' => 'Wonu', 'W1' => 'Woo', 'WR' => 'Wortmann', 'WX' => 'Woxter', 'XQ' => 'X-AGE', 'XEL' => 'XElectron', 'X3' => 'X-BO', 'XMO' => 'X-Mobile', 'XT' => 'X-TIGI', 'XV' => 'X-View', 'X4' => 'X.Vision', 'X88' => 'X88', 'X96' => 'X96', '96Q' => 'X96Q', 'XG' => 'Xgody', 'QX' => 'XGIMI', 'XL' => 'Xiaolajiao', 'XI' => 'Xiaomi', 'XW' => 'Xiaodu', 'XN' => 'Xion', 'XO' => 'Xolo', 'XR' => 'Xoro', 'XS' => 'Xshitou', '4X' => 'Xtouch', 'X8' => 'Xtratech', 'XCR' => 'Xcruiser', 'XCO' => 'XCOM', 'XWA' => 'Xwave', 'YD' => 'Yandex', 'YA' => 'Yarvik', 'Y2' => 'Yes', 'YES' => 'Yestel', 'YE' => 'Yezz', 'YG' => 'YEPEN', 'YEL' => 'YELLYOUTH', 'YK' => 'Yoka TV', 'YO' => 'Yota', 'YOU' => 'Youin', 'YO1' => 'Youwei', 'YOO' => 'Yooz', 'YT' => 'Ytone', 'Y9' => 'YOTOPT', 'Y1' => 'Yu', 'YF' => 'YU Fly', 'Y0' => 'YUHO', 'YN' => 'Yuno', 'YUN' => 'YUNDOO', 'YUS' => 'YunSong', 'YUM' => 'YUMKEM', 'YU' => 'Yuandao', 'YS' => 'Yusun', 'YJ' => 'YASIN', 'YX' => 'Yxtel', '0Z' => 'Zatec', '2Z' => 'Zaith', 'ZAM' => 'Zamolxe', 'ZEA' => 'Zealot', 'PZ' => 'Zebra', 'ZE' => 'Zeemi', 'WZ' => 'Zeeker', 'ZN' => 'Zen', 'ZK' => 'Zenek', 'ZL' => 'Zentality', 'ZF' => 'Zfiner', 'ZI' => 'Zidoo', 'FZ' => 'ZIFRO', 'ZX' => 'Ziox', 'ZIK' => 'ZIK', 'ZIN' => 'Zinox', 'ZO' => 'Zonda', 'ZW' => 'Zonko', 'ZP' => 'Zopo', 'ZOO' => 'ZoomSmart', 'ZT' => 'ZTE', 'ZU' => 'Zuum', 'ZY' => 'Zync', 'ZR' => 'Zyrex', 'ZQ' => 'ZYQ', 'Z4' => 'ZH&K', 'OW' => 'öwn', // legacy brands, might be removed in future versions 'WB' => 'Web TV', 'XX' => 'Unknown', ]; /** * Returns the device type represented by one of the DEVICE_TYPE_* constants * * @return int|null */ public function getDeviceType(): ?int { return $this->deviceType; } /** * Returns available device types * * @see $deviceTypes * * @return array */ public static function getAvailableDeviceTypes(): array { return self::$deviceTypes; } /** * Returns names of all available device types * * @return array */ public static function getAvailableDeviceTypeNames(): array { return \array_keys(self::$deviceTypes); } /** * Returns the name of the given device type * * @param int $deviceType one of the DEVICE_TYPE_* constants * * @return mixed */ public static function getDeviceName(int $deviceType) { return \array_search($deviceType, self::$deviceTypes); } /** * Returns the detected device model * * @return string */ public function getModel(): string { return $this->model; } /** * Returns the detected device brand * * @return string */ public function getBrand(): string { return $this->brand; } /** * Returns the full brand name for the given short name * * @param string $brandId short brand name * * @return string */ public static function getFullName(string $brandId): string { if (\array_key_exists($brandId, self::$deviceBrands)) { return self::$deviceBrands[$brandId]; } return ''; } /** * Returns the brand short code for the given name * * @param string $brand brand name * * @return string * * @deprecated since 4.0 - short codes might be removed in next major release */ public static function getShortCode(string $brand): string { return (string) \array_search($brand, self::$deviceBrands) ?: ''; } /** * Sets the useragent to be parsed * * @param string $userAgent */ public function setUserAgent(string $userAgent): void { $this->reset(); parent::setUserAgent($userAgent); } /** * @inheritdoc */ public function parse(): ?array { $resultClientHint = $this->parseClientHints(); $deviceModel = $resultClientHint['model'] ?? ''; if ('' === $deviceModel && $this->hasDesktopFragment()) { return $this->getResult(); } $brand = ''; $regexes = $this->getRegexes(); foreach ($regexes as $brand => $regex) { $matches = $this->matchUserAgent($regex['regex']); if ($matches) { break; } } if (empty($matches)) { return $resultClientHint; } if ('Unknown' !== $brand) { if (!\in_array($brand, self::$deviceBrands)) { // This Exception should never be thrown. If so a defined brand name is missing in $deviceBrands throw new \Exception(\sprintf( "The brand with name '%s' should be listed in deviceBrands array. Tried to parse user agent: %s", $brand, $this->userAgent )); // @codeCoverageIgnore } $this->brand = (string) $brand; } if (isset($regex['device']) && \array_key_exists($regex['device'], self::$deviceTypes)) { $this->deviceType = self::$deviceTypes[$regex['device']]; } $this->model = ''; if (isset($regex['model'])) { $this->model = $this->buildModel($regex['model'], $matches); } if (isset($regex['models'])) { $modelRegex = ''; foreach ($regex['models'] as $modelRegex) { $modelMatches = $this->matchUserAgent($modelRegex['regex']); if ($modelMatches) { break; } } if (empty($modelMatches)) { return $this->getResult(); } $this->model = $this->buildModel($modelRegex['model'], $modelMatches); if (isset($modelRegex['brand']) && \in_array($modelRegex['brand'], self::$deviceBrands)) { $this->brand = (string) $modelRegex['brand']; } if (isset($modelRegex['device']) && \array_key_exists($modelRegex['device'], self::$deviceTypes)) { $this->deviceType = self::$deviceTypes[$modelRegex['device']]; } } return $this->getResult(); } /** * @param string $model * @param array $matches * * @return string */ protected function buildModel(string $model, array $matches): string { $model = $this->buildByMatch($model, $matches); $model = \str_replace('_', ' ', $model); $model = \preg_replace('/ TD$/i', '', $model); if ('Build' === $model || empty($model)) { return ''; } return \trim($model); } /** * @return array|null */ protected function parseClientHints(): ?array { if ($this->clientHints && $this->clientHints->getModel()) { return [ 'deviceType' => null, 'model' => $this->clientHints->getModel(), 'brand' => '', ]; } return null; } /** * Returns if the parsed UA contains the 'Windows NT;' or 'X11; Linux x86_64' fragments * * @return bool */ protected function hasDesktopFragment(): bool { return $this->matchUserAgent('(?:Windows (?:NT|IoT)|X11; Linux x86_64)') && !$this->matchUserAgent(' Mozilla/|Andr[o0]id|Tablet|Mobile|iPhone|Windows Phone|ricoh|OculusBrowser') && !$this->matchUserAgent('Lenovo|compatible; MSIE|Trident/|Tesla/|XBOX|FBMD/|ARM; ?([^)]+)'); } /** * Resets the stored values */ protected function reset(): void { $this->deviceType = null; $this->model = ''; $this->brand = ''; } /** * @return array */ protected function getResult(): array { return [ 'deviceType' => $this->deviceType, 'model' => $this->model, 'brand' => $this->brand, ]; } } device-detector-6.1.1/Parser/Device/Camera.php000066400000000000000000000013441440455040400211230ustar00rootroot00000000000000preMatchOverall()) { return null; } return parent::parse(); } } device-detector-6.1.1/Parser/Device/CarBrowser.php000066400000000000000000000013731440455040400220060ustar00rootroot00000000000000preMatchOverall()) { return null; } return parent::parse(); } } device-detector-6.1.1/Parser/Device/Console.php000066400000000000000000000013511440455040400213330ustar00rootroot00000000000000preMatchOverall()) { return null; } return parent::parse(); } } device-detector-6.1.1/Parser/Device/HbbTv.php000066400000000000000000000026201440455040400207360ustar00rootroot00000000000000isHbbTv()) { return null; } parent::parse(); // always set device type to tv, even if no model/brand could be found $this->deviceType = self::DEVICE_TYPE_TV; return $this->getResult(); } /** * Returns if the parsed UA was identified as a HbbTV device * * @return string|null */ public function isHbbTv(): ?string { $regex = 'HbbTV/([1-9]{1}(?:\.[0-9]{1}){1,2})'; $match = $this->matchUserAgent($regex); return $match[1] ?? null; } } device-detector-6.1.1/Parser/Device/Mobile.php000066400000000000000000000010431440455040400211360ustar00rootroot00000000000000matchUserAgent('FBMD/')) { return null; } return parent::parse(); } } device-detector-6.1.1/Parser/Device/PortableMediaPlayer.php000066400000000000000000000014501440455040400236160ustar00rootroot00000000000000preMatchOverall()) { return null; } return parent::parse(); } } device-detector-6.1.1/Parser/Device/ShellTv.php000066400000000000000000000026321440455040400213150ustar00rootroot00000000000000matchUserAgent($regex); return null !== $match; } /** * Parses the current UA and checks whether it contains ShellTv information * * @see shell_tv.yml for list of detected televisions * * @return array|null */ public function parse(): ?array { // only parse user agents containing fragments: {brand} shell if (false === $this->isShellTv()) { return null; } parent::parse(); // always set device type to tv, even if no model/brand could be found $this->deviceType = self::DEVICE_TYPE_TV; return $this->getResult(); } } device-detector-6.1.1/Parser/OperatingSystem.php000066400000000000000000000414461440455040400217000ustar00rootroot00000000000000 'AIX', 'AND' => 'Android', 'ADR' => 'Android TV', 'AMZ' => 'Amazon Linux', 'AMG' => 'AmigaOS', 'ATV' => 'tvOS', 'ARL' => 'Arch Linux', 'BTR' => 'BackTrack', 'SBA' => 'Bada', 'BEO' => 'BeOS', 'BLB' => 'BlackBerry OS', 'QNX' => 'BlackBerry Tablet OS', 'BOS' => 'Bliss OS', 'BMP' => 'Brew', 'CAI' => 'Caixa Mágica', 'CES' => 'CentOS', 'CST' => 'CentOS Stream', 'CLR' => 'ClearOS Mobile', 'COS' => 'Chrome OS', 'CRS' => 'Chromium OS', 'CHN' => 'China OS', 'CYN' => 'CyanogenMod', 'DEB' => 'Debian', 'DEE' => 'Deepin', 'DFB' => 'DragonFly', 'DVK' => 'DVKBuntu', 'FED' => 'Fedora', 'FEN' => 'Fenix', 'FOS' => 'Firefox OS', 'FIR' => 'Fire OS', 'FOR' => 'Foresight Linux', 'FRE' => 'Freebox', 'BSD' => 'FreeBSD', 'FYD' => 'FydeOS', 'FUC' => 'Fuchsia', 'GNT' => 'Gentoo', 'GRI' => 'GridOS', 'GTV' => 'Google TV', 'HPX' => 'HP-UX', 'HAI' => 'Haiku OS', 'IPA' => 'iPadOS', 'HAR' => 'HarmonyOS', 'HAS' => 'HasCodingOS', 'IRI' => 'IRIX', 'INF' => 'Inferno', 'JME' => 'Java ME', 'KOS' => 'KaiOS', 'KAN' => 'Kanotix', 'KNO' => 'Knoppix', 'KTV' => 'KreaTV', 'KBT' => 'Kubuntu', 'LIN' => 'GNU/Linux', 'LND' => 'LindowsOS', 'LNS' => 'Linspire', 'LEN' => 'Lineage OS', 'LBT' => 'Lubuntu', 'LOS' => 'Lumin OS', 'VLN' => 'VectorLinux', 'MAC' => 'Mac', 'MAE' => 'Maemo', 'MAG' => 'Mageia', 'MDR' => 'Mandriva', 'SMG' => 'MeeGo', 'MCD' => 'MocorDroid', 'MON' => 'moonOS', 'MIN' => 'Mint', 'MLD' => 'MildWild', 'MOR' => 'MorphOS', 'NBS' => 'NetBSD', 'MTK' => 'MTK / Nucleus', 'MRE' => 'MRE', 'WII' => 'Nintendo', 'NDS' => 'Nintendo Mobile', 'NOV' => 'Nova', 'OS2' => 'OS/2', 'T64' => 'OSF1', 'OBS' => 'OpenBSD', 'OWR' => 'OpenWrt', 'OTV' => 'Opera TV', 'ORD' => 'Ordissimo', 'PAR' => 'Pardus', 'PCL' => 'PCLinuxOS', 'PLA' => 'Plasma Mobile', 'PSP' => 'PlayStation Portable', 'PS3' => 'PlayStation', 'PUR' => 'PureOS', 'RHT' => 'Red Hat', 'RED' => 'RedOS', 'REV' => 'Revenge OS', 'ROS' => 'RISC OS', 'ROK' => 'Roku OS', 'RSO' => 'Rosa', 'ROU' => 'RouterOS', 'REM' => 'Remix OS', 'RRS' => 'Resurrection Remix OS', 'REX' => 'REX', 'RZD' => 'RazoDroiD', 'SAB' => 'Sabayon', 'SSE' => 'SUSE', 'SAF' => 'Sailfish OS', 'SEE' => 'SeewoOS', 'SIR' => 'Sirin OS', 'SLW' => 'Slackware', 'SOS' => 'Solaris', 'SYL' => 'Syllable', 'SYM' => 'Symbian', 'SYS' => 'Symbian OS', 'S40' => 'Symbian OS Series 40', 'S60' => 'Symbian OS Series 60', 'SY3' => 'Symbian^3', 'TEN' => 'TencentOS', 'TDX' => 'ThreadX', 'TIZ' => 'Tizen', 'TOS' => 'TmaxOS', 'UBT' => 'Ubuntu', 'WAS' => 'watchOS', 'WTV' => 'WebTV', 'WHS' => 'Whale OS', 'WIN' => 'Windows', 'WCE' => 'Windows CE', 'WIO' => 'Windows IoT', 'WMO' => 'Windows Mobile', 'WPH' => 'Windows Phone', 'WRT' => 'Windows RT', 'XBX' => 'Xbox', 'XBT' => 'Xubuntu', 'YNS' => 'YunOS', 'ZEN' => 'Zenwalk', 'ZOR' => 'ZorinOS', 'IOS' => 'iOS', 'POS' => 'palmOS', 'WOS' => 'webOS', ]; /** * Operating system families mapped to the short codes of the associated operating systems * * @var array */ protected static $osFamilies = [ 'Android' => [ 'AND', 'CYN', 'FIR', 'REM', 'RZD', 'MLD', 'MCD', 'YNS', 'GRI', 'HAR', 'ADR', 'CLR', 'BOS', 'REV', 'LEN', 'SIR', 'RRS', ], 'AmigaOS' => ['AMG', 'MOR'], 'BlackBerry' => ['BLB', 'QNX'], 'Brew' => ['BMP'], 'BeOS' => ['BEO', 'HAI'], 'Chrome OS' => ['COS', 'CRS', 'FYD', 'SEE'], 'Firefox OS' => ['FOS', 'KOS'], 'Gaming Console' => ['WII', 'PS3'], 'Google TV' => ['GTV'], 'IBM' => ['OS2'], 'iOS' => ['IOS', 'ATV', 'WAS', 'IPA'], 'RISC OS' => ['ROS'], 'GNU/Linux' => [ 'LIN', 'ARL', 'DEB', 'KNO', 'MIN', 'UBT', 'KBT', 'XBT', 'LBT', 'FED', 'RHT', 'VLN', 'MDR', 'GNT', 'SAB', 'SLW', 'SSE', 'CES', 'BTR', 'SAF', 'ORD', 'TOS', 'RSO', 'DEE', 'FRE', 'MAG', 'FEN', 'CAI', 'PCL', 'HAS', 'LOS', 'DVK', 'ROK', 'OWR', 'OTV', 'KTV', 'PUR', 'PLA', 'FUC', 'PAR', 'FOR', 'MON', 'KAN', 'ZEN', 'LND', 'LNS', 'CHN', 'AMZ', 'TEN', 'CST', 'NOV', 'ROU', 'ZOR', 'RED', ], 'Mac' => ['MAC'], 'Mobile Gaming Console' => ['PSP', 'NDS', 'XBX'], 'Real-time OS' => ['MTK', 'TDX', 'MRE', 'JME', 'REX'], 'Other Mobile' => ['WOS', 'POS', 'SBA', 'TIZ', 'SMG', 'MAE'], 'Symbian' => ['SYM', 'SYS', 'SY3', 'S60', 'S40'], 'Unix' => ['SOS', 'AIX', 'HPX', 'BSD', 'NBS', 'OBS', 'DFB', 'SYL', 'IRI', 'T64', 'INF'], 'WebTV' => ['WTV'], 'Windows' => ['WIN'], 'Windows Mobile' => ['WPH', 'WMO', 'WCE', 'WRT', 'WIO'], 'Other Smart TV' => ['WHS'], ]; /** * Contains a list of mappings from OS names we use to known client hint values * * @var array> */ protected static $clientHintMapping = [ 'GNU/Linux' => ['Linux'], 'Mac' => ['MacOS'], ]; /** * Operating system families that are known as desktop only * * @var array */ protected static $desktopOsArray = [ 'AmigaOS', 'IBM', 'GNU/Linux', 'Mac', 'Unix', 'Windows', 'BeOS', 'Chrome OS', 'Chromium OS', ]; /** * Returns all available operating systems * * @return array */ public static function getAvailableOperatingSystems(): array { return self::$operatingSystems; } /** * Returns all available operating system families * * @return array */ public static function getAvailableOperatingSystemFamilies(): array { return self::$osFamilies; } /** * Returns the os name and shot name * * @param string $name * * @return array */ public static function getShortOsData(string $name): array { $short = 'UNK'; foreach (self::$operatingSystems as $osShort => $osName) { if (\strtolower($name) !== \strtolower($osName)) { continue; } $name = $osName; $short = $osShort; break; } return \compact('short', 'name'); } /** * @inheritdoc */ public function parse(): ?array { $osFromClientHints = $this->parseOsFromClientHints(); $osFromUserAgent = $this->parseOsFromUserAgent(); if (!empty($osFromClientHints['name'])) { $name = $osFromClientHints['name']; $version = $osFromClientHints['version']; // use version from user agent if non was provided in client hints, but os family from useragent matches if (empty($version) && self::getOsFamily($name) === self::getOsFamily($osFromUserAgent['name']) ) { $version = $osFromUserAgent['version']; } // If the OS name detected from client hints matches the OS family from user agent // but the os name is another, we use the one from user agent, as it might be more detailed if (self::getOsFamily($osFromUserAgent['name']) === $name && $osFromUserAgent['name'] !== $name) { $name = $osFromUserAgent['name']; if ('HarmonyOS' === $name) { $version = ''; } } $short = $osFromClientHints['short_name']; // Chrome OS is in some cases reported as Linux in client hints, we fix this only if the version matches if ('GNU/Linux' === $name && 'Chrome OS' === $osFromUserAgent['name'] && $osFromClientHints['version'] === $osFromUserAgent['version'] ) { $name = $osFromUserAgent['name']; $short = $osFromUserAgent['short_name']; } } elseif (!empty($osFromUserAgent['name'])) { $name = $osFromUserAgent['name']; $version = $osFromUserAgent['version']; $short = $osFromUserAgent['short_name']; } else { return []; } $platform = $this->parsePlatform(); $family = self::getOsFamily($short); $androidApps = ['com.hisense.odinbrowser', 'com.seraphic.openinet.pre', 'com.appssppa.idesktoppcbrowser']; if (null !== $this->clientHints) { if (\in_array($this->clientHints->getApp(), $androidApps) && 'Android' !== $name) { $name = 'Android'; $family = 'Android'; $short = 'ADR'; $version = ''; } } $return = [ 'name' => $name, 'short_name' => $short, 'version' => $version, 'platform' => $platform, 'family' => $family, ]; if (\in_array($return['name'], self::$operatingSystems)) { $return['short_name'] = \array_search($return['name'], self::$operatingSystems); } return $return; } /** * Returns the operating system family for the given operating system * * @param string $osLabel name or short name * * @return string|null If null, "Unknown" */ public static function getOsFamily(string $osLabel): ?string { if (\in_array($osLabel, self::$operatingSystems)) { $osLabel = \array_search($osLabel, self::$operatingSystems); } foreach (self::$osFamilies as $family => $labels) { if (\in_array($osLabel, $labels)) { return (string) $family; } } return null; } /** * Returns true if OS is desktop * * @param string $osName OS short name * * @return bool */ public static function isDesktopOs(string $osName): bool { $osFamily = self::getOsFamily($osName); return \in_array($osFamily, self::$desktopOsArray); } /** * Returns the full name for the given short name * * @param string $os * @param string|null $ver * * @return ?string */ public static function getNameFromId(string $os, ?string $ver = null): ?string { if (\array_key_exists($os, self::$operatingSystems)) { $osFullName = self::$operatingSystems[$os]; return \trim($osFullName . ' ' . $ver); } return null; } /** * Returns the OS that can be safely detected from client hints * * @return array */ protected function parseOsFromClientHints(): array { $name = $version = $short = ''; if ($this->clientHints instanceof ClientHints && $this->clientHints->getOperatingSystem()) { $hintName = $this->applyClientHintMapping($this->clientHints->getOperatingSystem()); foreach (self::$operatingSystems as $osShort => $osName) { if ($this->fuzzyCompare($hintName, $osName)) { $name = $osName; $short = $osShort; break; } } $version = $this->clientHints->getOperatingSystemVersion(); if ('Windows' === $name) { $majorVersion = (int) (\explode('.', $version, 1)[0] ?? '0'); if ($majorVersion > 0 && $majorVersion < 11) { $version = '10'; } elseif ($majorVersion > 10) { $version = '11'; } } if (0 === (int) $version) { $version = ''; } } return [ 'name' => $name, 'short_name' => $short, 'version' => $this->buildVersion($version, []), ]; } /** * Returns the OS that can be detected from useragent * * @return array * * @throws \Exception */ protected function parseOsFromUserAgent(): array { $osRegex = $matches = []; $name = $version = $short = ''; foreach ($this->getRegexes() as $osRegex) { $matches = $this->matchUserAgent($osRegex['regex']); if ($matches) { break; } } if (!empty($matches)) { $name = $this->buildByMatch($osRegex['name'], $matches); ['name' => $name, 'short' => $short] = self::getShortOsData($name); $version = \array_key_exists('version', $osRegex) ? $this->buildVersion((string) $osRegex['version'], $matches) : ''; foreach ($osRegex['versions'] ?? [] as $regex) { $matches = $this->matchUserAgent($regex['regex']); if (!$matches) { continue; } if (\array_key_exists('name', $regex)) { $name = $this->buildByMatch($regex['name'], $matches); ['name' => $name, 'short' => $short] = self::getShortOsData($name); } if (\array_key_exists('version', $regex)) { $version = $this->buildVersion((string) $regex['version'], $matches); } break; } } return [ 'name' => $name, 'short_name' => $short, 'version' => $version, ]; } /** * Parse current UserAgent string for the operating system platform * * @return string */ protected function parsePlatform(): string { // Use architecture from client hints if available if ($this->clientHints instanceof ClientHints && $this->clientHints->getArchitecture()) { $arch = \strtolower($this->clientHints->getArchitecture()); if (false !== \strpos($arch, 'arm')) { return 'ARM'; } if (false !== \strpos($arch, 'mips')) { return 'MIPS'; } if (false !== \strpos($arch, 'sh4')) { return 'SuperH'; } if (false !== \strpos($arch, 'x64') || (false !== \strpos($arch, 'x86') && '64' === $this->clientHints->getBitness()) ) { return 'x64'; } if (false !== \strpos($arch, 'x86')) { return 'x86'; } } if ($this->matchUserAgent('arm|aarch64|Apple ?TV|Watch ?OS|Watch1,[12]')) { return 'ARM'; } if ($this->matchUserAgent('mips')) { return 'MIPS'; } if ($this->matchUserAgent('sh4')) { return 'SuperH'; } if ($this->matchUserAgent('64-?bit|WOW64|(?:Intel)?x64|WINDOWS_64|win64|amd64|x86_?64')) { return 'x64'; } if ($this->matchUserAgent('.+32bit|.+win32|(?:i[0-9]|x)86|i86pc')) { return 'x86'; } return ''; } } device-detector-6.1.1/Parser/VendorFragment.php000066400000000000000000000022611440455040400214540ustar00rootroot00000000000000getRegexes() as $brand => $regexes) { foreach ($regexes as $regex) { if ($this->matchUserAgent($regex . '[^a-z0-9]+')) { $this->matchedRegex = $regex; return ['brand' => $brand]; } } } return null; } /** * @return string|null */ public function getMatchedRegex(): ?string { return $this->matchedRegex; } } device-detector-6.1.1/README.md000066400000000000000000001240321440455040400160460ustar00rootroot00000000000000DeviceDetector ============== [![Latest Stable Version](https://poser.pugx.org/matomo/device-detector/v/stable)](https://packagist.org/packages/matomo/device-detector) [![Total Downloads](https://poser.pugx.org/matomo/device-detector/downloads)](https://packagist.org/packages/matomo/device-detector) [![License](https://poser.pugx.org/matomo/device-detector/license)](https://packagist.org/packages/matomo/device-detector) ## Code Status ![PHPUnit](https://github.com/matomo-org/device-detector/workflows/PHPUnit/badge.svg?branch=master) ![PHPStan](https://github.com/matomo-org/device-detector/workflows/PHPStan%20check/badge.svg?branch=master) ![PHPCS](https://github.com/matomo-org/device-detector/workflows/PHPCS%20check/badge.svg?branch=master) ![YAML Lint](https://github.com/matomo-org/device-detector/workflows/YAML%20Lint/badge.svg?branch=master) [![Validate regular Expressions](https://github.com/matomo-org/device-detector/actions/workflows/regular_expressions.yml/badge.svg)](https://github.com/matomo-org/device-detector/actions/workflows/regular_expressions.yml) [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/matomo-org/device-detector.svg)](http://isitmaintained.com/project/matomo-org/device-detector "Average time to resolve an issue") [![Percentage of issues still open](http://isitmaintained.com/badge/open/matomo-org/device-detector.svg)](http://isitmaintained.com/project/matomo-org/device-detector "Percentage of issues still open") ## Description The Universal Device Detection library that parses User Agents and Browser Client Hints to detect devices (desktop, tablet, mobile, tv, cars, console, etc.), clients (browsers, feed readers, media players, PIMs, ...), operating systems, brands and models. ## Usage Using DeviceDetector with composer is quite easy. Just add `matomo/device-detector` to your projects requirements. ``` composer require matomo/device-detector ``` And use some code like this one: ```php require_once 'vendor/autoload.php'; use DeviceDetector\ClientHints; use DeviceDetector\DeviceDetector; use DeviceDetector\Parser\Device\AbstractDeviceParser; // OPTIONAL: Set version truncation to none, so full versions will be returned // By default only minor versions will be returned (e.g. X.Y) // for other options see VERSION_TRUNCATION_* constants in DeviceParserAbstract class AbstractDeviceParser::setVersionTruncation(AbstractDeviceParser::VERSION_TRUNCATION_NONE); $userAgent = $_SERVER['HTTP_USER_AGENT']; // change this to the useragent you want to parse $clientHints = ClientHints::factory($_SERVER); // client hints are optional $dd = new DeviceDetector($userAgent, $clientHints); // OPTIONAL: Set caching method // By default static cache is used, which works best within one php process (memory array caching) // To cache across requests use caching in files or memcache // $dd->setCache(new Doctrine\Common\Cache\PhpFileCache('./tmp/')); // OPTIONAL: Set custom yaml parser // By default Spyc will be used for parsing yaml files. You can also use another yaml parser. // You may need to implement the Yaml Parser facade if you want to use another parser than Spyc or [Symfony](https://github.com/symfony/yaml) // $dd->setYamlParser(new DeviceDetector\Yaml\Symfony()); // OPTIONAL: If called, getBot() will only return true if a bot was detected (speeds up detection a bit) // $dd->discardBotInformation(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) // $dd->skipBotDetection(); $dd->parse(); if ($dd->isBot()) { // handle bots,spiders,crawlers,... $botInfo = $dd->getBot(); } else { $clientInfo = $dd->getClient(); // holds information about browser, feed reader, media player, ... $osInfo = $dd->getOs(); $device = $dd->getDeviceName(); $brand = $dd->getBrandName(); $model = $dd->getModel(); } ``` Methods check device type: ```php $dd->isSmartphone(); $dd->isFeaturePhone(); $dd->isTablet(); $dd->isPhablet(); $dd->isConsole(); $dd->isPortableMediaPlayer(); $dd->isCarBrowser(); $dd->isTV(); $dd->isSmartDisplay(); $dd->isSmartSpeaker(); $dd->isCamera(); $dd->isWearable(); $dd->isPeripheral(); ``` Methods check client type: ```php $dd->isBrowser(); $dd->isFeedReader(); $dd->isMobileApp(); $dd->isPIM(); $dd->isLibrary(); $dd->isMediaPlayer(); ``` Get OS family: ```php use DeviceDetector\Parser\OperatingSystem; $osFamily = OperatingSystem::getOsFamily($dd->getOs('name')); ``` Get browser family: ```php use DeviceDetector\Parser\Client\Browser; $browserFamily = Browser::getBrowserFamily($dd->getClient('name')); ``` Instead of using the full power of DeviceDetector it might in some cases be better to use only specific parsers. If you aim to check if a given useragent is a bot and don't require any of the other information, you can directly use the bot parser. ```php require_once 'vendor/autoload.php'; use DeviceDetector\Parser\Bot AS BotParser; $botParser = new BotParser(); $botParser->setUserAgent($userAgent); // OPTIONAL: discard bot information. parse() will then return true instead of information $botParser->discardDetails(); $result = $botParser->parse(); if (!is_null($result)) { // do not do anything if a bot is detected return; } // handle non-bot requests ``` ## Using without composer Alternatively to using composer you can also use the included `autoload.php`. This script will register an autoloader to dynamically load all classes in `DeviceDetector` namespace. Device Detector requires a YAML parser. By default `Spyc` parser is used. As this library is not included you need to include it manually or use another YAML parser. ```php setCache( new \DeviceDetector\Cache\PSR6Bridge($cache) ); // Example with PSR-16 and ScrapBook $cache = new \MatthiasMullie\Scrapbook\Psr16\SimpleCache( new \MatthiasMullie\Scrapbook\Adapters\Apc() ); $dd->setCache( new \DeviceDetector\Cache\PSR16Bridge($cache) ); // Example with Doctrine $cache = new \Doctrine\Common\Cache\ApcuCache(); $dd->setCache( new \DeviceDetector\Cache\DoctrineBridge($cache) ); // Example with Laravel $dd->setCache( new \DeviceDetector\Cache\LaravelCache() ); ``` ## Contributing ### Hacking the library This is a free/libre library under license LGPL v3 or later. Your pull requests and/or feedback is very welcome! ### Listing all user agents from your logs Sometimes it may be useful to generate the list of most used user agents on your website, extracting this list from your access logs using the following command: ``` zcat ~/path/to/access/logs* | awk -F'"' '{print $6}' | sort | uniq -c | sort -rn | head -n20000 > /home/matomo/top-user-agents.txt ``` ### Contributors Created by the [Matomo team](http://matomo.org/team/), Stefan Giehl, Matthieu Aubry, Michał Gaździk, Tomasz Majczak, Grzegorz Kaszuba, Piotr Banaszczyk and contributors. Together we can build the best Device Detection library. We are looking forward to your contributions and pull requests! ## Tests See also: [QA at Matomo](http://matomo.org/qa/) ### Running tests ``` cd /path/to/device-detector curl -sS https://getcomposer.org/installer | php php composer.phar install ./vendor/bin/phpunit ``` ## Device Detector for other languages There are already a few ports of this tool to other languages: - **.NET** https://github.com/totpero/DeviceDetector.NET - **Ruby** https://github.com/podigee/device_detector - **JavaScript/TypeScript/NodeJS** https://github.com/etienne-martin/device-detector-js - **NodeJS** https://github.com/sanchezzzhak/node-device-detector - **Python 3** https://github.com/thinkwelltwd/device_detector - **Crystal** https://github.com/creadone/device_detector - **Elixir** https://github.com/elixir-inspector/ua_inspector - **Java** https://github.com/mngsk/device-detector ## What Device Detector is able to detect The lists below are auto generated and updated from time to time. Some of them might not be complete. *Last update: 2023/03/12* ### List of detected operating systems: AIX, Android, Android TV, Amazon Linux, AmigaOS, tvOS, Arch Linux, BackTrack, Bada, BeOS, BlackBerry OS, BlackBerry Tablet OS, Bliss OS, Brew, Caixa Mágica, CentOS, CentOS Stream, ClearOS Mobile, Chrome OS, Chromium OS, China OS, CyanogenMod, Debian, Deepin, DragonFly, DVKBuntu, Fedora, Fenix, Firefox OS, Fire OS, Foresight Linux, Freebox, FreeBSD, FydeOS, Fuchsia, Gentoo, GridOS, Google TV, HP-UX, Haiku OS, iPadOS, HarmonyOS, HasCodingOS, IRIX, Inferno, Java ME, KaiOS, Kanotix, Knoppix, KreaTV, Kubuntu, GNU/Linux, LindowsOS, Linspire, Lineage OS, Lubuntu, Lumin OS, VectorLinux, Mac, Maemo, Mageia, Mandriva, MeeGo, MocorDroid, moonOS, Mint, MildWild, MorphOS, NetBSD, MTK / Nucleus, MRE, Nintendo, Nintendo Mobile, Nova, OS/2, OSF1, OpenBSD, OpenWrt, Opera TV, Ordissimo, Pardus, PCLinuxOS, Plasma Mobile, PlayStation Portable, PlayStation, PureOS, Red Hat, RedOS, Revenge OS, RISC OS, Roku OS, Rosa, RouterOS, Remix OS, Resurrection Remix OS, REX, RazoDroiD, Sabayon, SUSE, Sailfish OS, SeewoOS, Sirin OS, Slackware, Solaris, Syllable, Symbian, Symbian OS, Symbian OS Series 40, Symbian OS Series 60, Symbian^3, TencentOS, ThreadX, Tizen, TmaxOS, Ubuntu, watchOS, WebTV, Whale OS, Windows, Windows CE, Windows IoT, Windows Mobile, Windows Phone, Windows RT, Xbox, Xubuntu, YunOS, Zenwalk, ZorinOS, iOS, palmOS, webOS ### List of detected browsers: Via, Pure Mini Browser, Pure Lite Browser, Raise Fast Browser, Rabbit Private Browser, Fast Browser UC Lite, Fast Explorer, Lightning Browser, Cake Browser, IE Browser Fast, Vegas Browser, OH Browser, OH Private Browser, XBrowser Mini, Sharkee Browser, Lark Browser, Pluma, Anka Browser, Azka Browser, Dragon Browser, Easy Browser, Dark Web Browser, 18+ Privacy Browser, 115 Browser, 1DM Browser, 1DM+ Browser, 2345 Browser, 360 Browser, 360 Phone Browser, 7654 Browser, Avant Browser, ABrowse, AdBlock Browser, Adult Browser, ANT Fresco, ANTGalio, Aloha Browser, Aloha Browser Lite, Amaya, Amaze Browser, Amerigo, Amigo, Android Browser, AOL Desktop, AOL Shield, AOL Shield Pro, AppBrowzer, APUS Browser, Arora, Arctic Fox, Amiga Voyager, Amiga Aweb, APN Browser, Arvin, Ask.com, Asus Browser, Atom, Atomic Web Browser, Atlas, Avast Secure Browser, AVG Secure Browser, Avira Scout, AwoX, Beaker Browser, Beamrise, BlackBerry Browser, BrowseHere, Browser Hup Pro, Baidu Browser, Baidu Spark, Bangla Browser, Basilisk, Belva Browser, Beyond Private Browser, Beonex, Berry Browser, Bitchute Browser, BlackHawk, Bloket, Bunjalloo, B-Line, Black Lion Browser, Blue Browser, Bonsai, Borealis Navigator, Brave, BriskBard, Browspeed Browser, BrowseX, Browzar, Browlser, Biyubi, Byffox, BF Browser, Camino, CCleaner, CG Browser, ChanjetCloud, Chedot, Cherry Browser, Centaury, Coc Coc, CoolBrowser, Colibri, Comodo Dragon, Coast, Charon, CM Browser, CM Mini, Chrome Frame, Headless Chrome, Chrome, Chrome Mobile iOS, Conkeror, Chrome Mobile, Chowbo, CoolNovo, CometBird, Comfort Browser, COS Browser, Cornowser, Chim Lac, ChromePlus, Chromium, Chromium GOST, Cyberfox, Cheshire, Crusta, Craving Explorer, Crazy Browser, Cunaguaro, Chrome Webview, CyBrowser, dbrowser, Peeps dBrowser, Debuggable Browser, Decentr, Deepnet Explorer, deg-degan, Deledao, Delta Browser, Desi Browser, DeskBrowse, Dolphin, Dolphin Zero, Dorado, Dot Browser, Dooble, Dillo, DUC Browser, DuckDuckGo Privacy Browser, Ecosia, Edge WebView, Epic, Elinks, EinkBro, Element Browser, Elements Browser, Explore Browser, eZ Browser, EUI Browser, GNOME Web, G Browser, Espial TV Browser, Falkon, Faux Browser, Fiery Browser, Firefox Mobile iOS, Firebird, Fluid, Fennec, Firefox, Firefox Focus, Firefox Reality, Firefox Rocket, Firefox Klar, Float Browser, Flock, Floorp, Flow, Flow Browser, Firefox Mobile, Fireweb, Fireweb Navigator, Flash Browser, Flast, Flyperlink, FreeU, Frost+, Fulldive, Galeon, Gener8, Ghostery Privacy Browser, GinxDroid Browser, Glass Browser, Google Earth, Google Earth Pro, GOG Galaxy, GoBrowser, Harman Browser, HasBrowser, Hawk Turbo Browser, Hawk Quick Browser, Helio, Hexa Web Browser, Hi Browser, hola! Browser, HotJava, HTC Browser, Huawei Browser Mobile, Huawei Browser, HUB Browser, iBrowser, iBrowser Mini, IBrowse, iDesktop PC Browser, iCab, iCab Mobile, Iridium, Iron Mobile, IceCat, IceDragon, Isivioo, Iceweasel, Inspect Browser, Internet Explorer, Internet Browser Secure, Indian UC Mini Browser, IE Mobile, Iron, Japan Browser, Jasmine, JavaFX, Jelly, Jig Browser, Jig Browser Plus, Jio Browser, JioPages, K.Browser, Keepsafe Browser, Kids Safe Browser, Kindle Browser, K-meleon, Konqueror, Kapiko, Kinza, Kiwi, Kode Browser, KUTO Mini Browser, Kylo, Kazehakase, Cheetah Browser, Lagatos Browser, Lexi Browser, Lenovo Browser, LieBaoFast, LG Browser, Light, Lilo, Links, Lolifox, Lovense Browser, LT Browser, LuaKit, Lulumi, Lunascape, Lunascape Lite, Lynx, Lynket Browser, Mandarin, mCent, MicroB, NCSA Mosaic, Meizu Browser, Mercury, Me Browser, Mobile Safari, Midori, Midori Lite, Mobicip, MIUI Browser, Mobile Silk, Minimo, Mint Browser, Maxthon, MaxTube Browser, Maelstrom, Mmx Browser, MxNitro, Mypal, Monument Browser, MAUI WAP Browser, Navigateur Web, Naked Browser, Naked Browser Pro, NFS Browser, Nokia Browser, Nokia OSS Browser, Nokia Ovi Browser, Nox Browser, NetSurf, NetFront, NetFront Life, NetPositive, Netscape, NextWord Browser, NTENT Browser, Oculus Browser, Opera Mini iOS, Obigo, Odin, Odin Browser, OceanHero, Odyssey Web Browser, Off By One, Office Browser, OhHai Browser, ONE Browser, Opera Crypto, Opera GX, Opera Neon, Opera Devices, Opera Mini, Opera Mobile, Opera, Opera Next, Opera Touch, Orca, Ordissimo, Oregano, Origin In-Game Overlay, Origyn Web Browser, Openwave Mobile Browser, OpenFin, Open Browser, Open Browser 4U, Open Browser fast 5G, OmniWeb, Otter Browser, Palm Blazer, Pale Moon, Polypane, Oppo Browser, Palm Pre, Puffin, Puffin Web Browser, Palm WebPro, Palmscape, Perfect Browser, Phantom.me, Phantom Browser, Phoenix, Phoenix Browser, PlayFree Browser, PocketBook Browser, Polaris, Polarity, PolyBrowser, PrivacyWall, Privacy Explorer Fast Safe, Pi Browser, PronHub Browser, PSI Secure Browser, Reqwireless WebViewer, Microsoft Edge, Qazweb, QQ Browser Lite, QQ Browser Mini, QQ Browser, Quick Browser, Qutebrowser, Quark, QupZilla, Qwant Mobile, QtWebEngine, Realme Browser, Rekonq, RockMelt, Samsung Browser, Sailfish Browser, Seewo Browser, SEMC-Browser, Sogou Explorer, Sogou Mobile Browser, SOTI Surf, Soul Browser, Safari, Safari Technology Preview, Safe Exam Browser, SalamWeb, Savannah Browser, SavySoda, Secure Browser, SFive, Shiira, Sidekick, SimpleBrowser, SilverMob US, Sizzy, Skyfire, Seraphic Sraf, SiteKiosk, Sleipnir, Slimjet, SP Browser, Sony Small Browser, Secure Private Browser, Stampy Browser, 7Star, Smart Browser, Smart Search & Web Browser, Smart Lenovo Browser, Smooz, Snowshoe, Spectre Browser, Splash, Sputnik Browser, Sunrise, SuperBird, Super Fast Browser, SuperFast Browser, Sushi Browser, surf, Surf Browser, Stargon, START Internet Browser, Steam In-Game Overlay, Streamy, Swiftfox, Seznam Browser, Sweet Browser, SX Browser, T+Browser, T-Browser, t-online.de Browser, Tao Browser, TenFourFox, Tenta Browser, Tesla Browser, Tizen Browser, Tint Browser, TUC Mini Browser, Tungsten, ToGate, TweakStyle, TV Bro, U Browser, UBrowser, UC Browser, UC Browser HD, UC Browser Mini, UC Browser Turbo, Ui Browser Mini, UR Browser, Uzbl, Ume Browser, vBrowser, Vast Browser, Venus Browser, Nova Video Downloader Pro, Viasat Browser, Vivaldi, vivo Browser, Vivid Browser Mini, Vision Mobile Browser, VMware AirWatch, Wear Internet Browser, Web Explorer, Web Browser & Explorer, WebPositive, Waterfox, Wave Browser, Whale Browser, wOSBrowser, WeTab Browser, Wolvic, YAGI, Yahoo! Japan Browser, Yandex Browser, Yandex Browser Lite, Yaani Browser, Yo Browser, Yolo Browser, YouCare, Yuzu Browser, xBrowser, X Browser Lite, X-VPN, xBrowser Pro Super Fast, XNX Browser, XtremeCast, xStand, Xiino, Xooloo Internet, Xvast, Zetakey, Zvu, Zirco Browser ### List of detected browser engines: WebKit, Blink, Trident, Text-based, Dillo, iCab, Elektra, Presto, Gecko, KHTML, NetFront, Edge, NetSurf, Servo, Goanna, EkiohFlow ### List of detected libraries: aiohttp, Akka HTTP, AnyEvent HTTP, Apache HTTP Client, Aria2, Artifactory, Axios, Azure Data Factory, Buildah, BuildKit, C++ REST SDK, Containerd, containers, cPanel HTTP Client, cri-o, curl, Dart, docker, Embarcadero URI Client, Faraday, fasthttp, GeoIP Update, go-container registry, Go-http-client, Google HTTP Java Client, got, GRequests, gRPC-Java, Guzzle (PHP HTTP Client), gvfs, hackney, Harbor registry client, Helm, HTTPie, httplib2, HTTPX, HTTP_Request2, Insomnia REST Client, Jakarta Commons HttpClient, Java, Java HTTP Client, jsdom, libdnf, libpod, LUA OpenResty NGINX, Mechanize, Mikrotik Fetch, Node Fetch, OkHttp, Open Build Service, Pa11y, Perl, Perl REST::Client, PHP cURL Class, Postman Desktop, Python Requests, Python urllib, quic-go, r-curl, ReactorNetty, req, REST Client for Ruby, RestSharp, Resty, ScalaJ HTTP, Skopeo, SlimerJS, Typhoeus, uclient-fetch, Ultimate Sitemap Parser, Unirest for Java, urlgrabber (yum), uTorrent, Wget, Windows HTTP, WinHttp WinHttpRequest, WWW-Mechanize ### List of detected media players: Audacious, Banshee, Boxee, Clementine, Deezer, Downcast, FlyCast, Foobar2000, foobar2000, Google Podcasts, HTC Streaming Player, iTunes, Kodi, MediaMonkey, Miro, MPlayer, mpv, Music Player Daemon, NexPlayer, Nightingale, QuickTime, Songbird, SONOS, Sony Media Go, Stagefright, SubStream, VLC, Winamp, Windows Media Player, XBMC ### List of detected mobile apps: 1Password, 2tch, Adobe Creative Cloud, Adobe IPM, Adobe NGL, Adobe Synchronizer, Aha Radio 2, AIDA64, Alexa Media Player, AliExpress, Alipay, Amazon Music, Amazon Shopping, AndroidDownloadManager, AntennaPod, AntiBrowserSpy, Apple News, ASUS Updater, Audible, Avid Link, Background Intelligent Transfer Service, Baidu Box App, Baidu Input, Ballz, Bank Millenium, Battle.net, BB2C, BBC News, Be Focused, BetBull, BeyondPod, Bible KJV, Binance, Bing iPad, BingWebApp, Bitcoin Core, Bitsboard, Blackboard, Blitz, Blue Proxy, BlueStacks, BonPrix, Bookshelf, Bose Music, bPod, CastBox, Castro, Castro 2, CCleaner, CGN, ChMate, Chrome Update, Ciisaa, Citrix Workspace, Clovia, COAF SMART Citizen, Copied, Cortana, Covenant Eyes, CPU-Z, CrosswalkApp, Daum, DevCasts, DeviantArt, DingTalk, DIRECTV, Discord, DoggCatcher, Don't Waste My Time!, douban App, Downcast, Dr. Watson, DStream Air, Edge Update, Emby Theater, Epic Games Launcher, ESET Remote Administrator, eToro, Evolve Podcast, Expedia, F-Secure SAFE, Facebook, Facebook Audience Network, Facebook Groups, Facebook Lite, Facebook Messenger, Facebook Messenger Lite, FeedR, Flipboard App, Flipp, Focus Keeper, Focus Matrix, Gaana, GBWhatsApp, Git, GitHub Desktop, GlobalProtect, GoNative, Google Drive, Google Fiber TV, Google Go, Google Photos, Google Play Newsstand, Google Plus, Google Podcasts, Google Search App, Google Tag Manager, GroupMe, Hago, HandBrake, HeyTapBrowser, Hik-Connect, HiSearch, HisThumbnail, HP Smart, HTTP request maker, iCatcher, IMO HD Video Calls & Chat, IMO International Calls & Chat, Instabridge, Instacast, Instagram App, Instapaper, JaneStyle, Jitsi Meet, JJ2GO, Jungle Disk, KakaoTalk, Keeper Password Manager, Kik, Klarna, Landis+Gyr AIM Browser, Lazada, Line, LinkedIn, Logi Options+, Macrium Reflect, MBolsa, MEmpresas, Mercantile Bank of Michigan, Meta Business Suite, MetaTrader, Microsoft Bing Search, Microsoft Lync, Microsoft Office, Microsoft Office $1, Microsoft Office Mobile, Microsoft OneDrive, Microsoft Start, Microsoft Store, mobile.de, My Bentley, My World, Naver, NET.mede, Netflix, NewsArticle App, Nextcloud, NPR One, NTV Mobil, NuMuKi Browser, Odnoklassniki, OfferUp, Opal Travel, Opera News, Opera Updater, Orange Radio, Overcast, Paint by Number, Pandora, Papers, Petal Search App, Pic Collage, Pinterest, Player FM, Plex Media Server, Pocket Casts, Podbean, Podcast & Radio Addict, Podcaster, Podcast Republic, Podcasts, Podcat, Podcatcher Deluxe, Podimo, Podkicker$1, PowerShell, Procast, Q-municate, qBittorrent, QQMusic, QuickCast, Quick Search TV, Quora, R, RadioApp, Radio Italiane, RadioPublic, Rave Social, Razer Synapse, RDDocuments, Reddit, rekordbox, RNPS Action Cards, Roblox, RoboForm, Rocket Chat, RSSRadio, Safari Search Helper, SafeIP, Samsung Magician, Shopee, ShowMe, Sina Weibo, Siri, Skyeng, Skyeng Teachers, Skype, Skype for Business, Slack, Snapchat, SogouSearch App, SohuNews, Soldier, SPORT1, Spotify, Startsiden, Streamlabs OBS, Strimio, Surfshark, Swoot, Taobao, Teams, The Wall Street Journal, Theyub, Thunder, tieba, TikTok, TopBuzz, TradingView, TuneIn Radio, TuneIn Radio Pro, Tuya Smart Life, TVirl, twinkle, Twitter, Twitterrific, U-Cursos, Uconnect LIVE, Unibox, UnityPlayer, Viber, Visual Studio Code, Vuhuv, Vuze, Wattpad, Wayback Machine, WebDAV, WeChat, WeChat Share Extension, WhatsApp, WhatsApp+2, Whisper, WH Questions, Windows Antivirus, Windows CryptoAPI, Windows Delivery Optimization, Windows Push Notification Services, Windows Update Agent, Wireshark, Wirtschafts Woche, Word Cookies!, WPS Office, Y8 Browser, Yahoo! Japan, Yahoo OneSearch, YakYak, Yandex, Yelp Mobile, YouTube, Zalo, ZEPETO, Zoho Chat and *mobile apps using [AFNetworking](https://github.com/AFNetworking/AFNetworking)* ### List of detected PIMs (personal information manager): Airmail, Barca, Basecamp, BathyScaphe, DAVdroid, eM Client, Evernote, Franz, JaneView, Live5ch, Lotus Notes, MailBar, Mailbird, Mailspring, Microsoft Outlook, NAVER Mail, Notion, Outlook Express, Postbox, Raindrop.io, Rambox Pro, SeaMonkey, The Bat!, Thunderbird, Windows Mail, Yahoo Mail ### List of detected feed readers: Akregator, Apple PubSub, BashPodder, Breaker, FeedDemon, Feeddler RSS Reader, gPodder, JetBrains Omea Reader, Liferea, NetNewsWire, Newsbeuter, NewsBlur, NewsBlur Mobile App, PritTorrent, Pulp, QuiteRSS, ReadKit, Reeder, RSS Bandit, RSS Junkie, RSSOwl, Stringer ### List of brands with detected devices: 2E, 3GNET, 3GO, 3Q, 4Good, 4ife, 7 Mobile, 10moons, 360, 8848, A1, Accent, Ace, Acer, Acteck, actiMirror, Adronix, Advan, Advance, AFFIX, AfriOne, AGM, AG Mobile, AIDATA, Ainol, Airis, Airness, AIRON, Airpha, Airtel, Airties, AIS, Aiuto, Aiwa, Akai, AKIRA, Alba, Alcatel, Alcor, ALDI NORD, ALDI SÜD, Alfawise, Aligator, AllCall, AllDocube, ALLINmobile, Allview, Allwinner, Alps, Altech UEC, Altice, altron, AMA, Amazon, AMCV, AMGOO, Amigoo, Amino, Amoi, Andowl, Angelcare, Anker, Anry, ANS, AOC, Aocos, AOpen, Aoro, Aoson, AOYODKG, Apple, Aquarius, Archos, Arian Space, Ark, ArmPhone, Arnova, ARRIS, Artel, Artizlee, ArtLine, Asano, Asanzo, Ask, Aspera, Assistant, Astro, Asus, AT&T, Athesi, Atmaca Elektronik, ATMAN, ATOL, Atom, Attila, Atvio, Audiovox, AURIS, Autan, AUX, Avaya, Avenzo, AVH, Avvio, Awow, Axioo, AXXA, Axxion, AYYA, Azumi Mobile, b2m, Backcell, BangOlufsen, Barnes & Noble, BBK, BB Mobile, BDF, BDQ, BDsharing, Beafon, Becker, Beeline, Beelink, Beetel, Beista, Bellphone, Benco, Benesse, BenQ, BenQ-Siemens, BenWee, Benzo, Beyond, Bezkam, BGH, Bigben, BIHEE, BilimLand, Billion, Billow, BioRugged, Bird, Bitel, Bitmore, Bittium, Bkav, Black Bear, Black Fox, Blackpcs, Blackview, Blaupunkt, Bleck, BLISS, Blloc, Blow, Blu, Bluboo, Bluebird, Bluedot, Bluegood, BlueSky, Bluewave, BluSlate, BMAX, Bmobile, BMXC, Bobarry, bogo, Bookeen, Boost, Boway, bq, BrandCode, Brandt, BRAVE, Bravis, BrightSign, Brigmton, Brondi, BROR, BS Mobile, Bubblegum, Bundy, Bush, BuzzTV, C5 Mobile, CAGI, Camfone, Canal Digital, Capitel, Captiva, Carbon Mobile, Carrefour, Casio, Casper, Cat, Cavion, Ceibal, Celcus, Celkon, Cell-C, Cellacom, CellAllure, Cellution, Centric, CG Mobile, CGV, Chainway, Changhong, Cherry Mobile, Chico Mobile, ChiliGreen, China Mobile, China Telecom, Chuwi, CipherLab, Citycall, Claresta, Clarmin, ClearPHONE, Clementoni, Cloud, Cloudfone, Cloudpad, Clout, CnM, Cobalt, Coby Kyros, Colors, Comio, Compal, Compaq, COMPUMAX, ComTrade Tesla, Concord, ConCorde, Condor, Connectce, Connex, Conquest, Contixo, Coolpad, Coopers, CORN, Cosmote, Covia, Cowon, COYOTE, CreNova, Crescent, Cricket, Crius Mea, Crony, Crosscall, Crown, Cube, CUBOT, CVTE, Cwowdefu, Cyrus, D-Link, D-Tech, Daewoo, Danew, Dany, DASS, Datalogic, Datamini, Datang, Datawind, Datsun, Dazen, DbPhone, Dbtel, Dcode, DEALDIG, Dell, Denali, Denver, Desay, DeWalt, DEXP, DEYI, DF, DGTEC, Dialog, Dicam, Digi, Digicel, DIGICOM, Digidragon, DIGIFORS, Digihome, Digiland, Digit4G, Digma, DIMO, Dinax, DING DING, DISH, Ditecma, Diva, DiverMax, Divisat, DIXON, DL, DMM, DNS, DoCoMo, Doffler, Dolamee, Dom.ru, Doogee, Doopro, Doov, Dopod, Doppio, DORLAND, Doro, DRAGON, Dragon Touch, Dreamgate, DreamStar, DreamTab, Droxio, DSIC, Dtac, Dune HD, DUNNS Mobile, Duubee, E-Boda, E-Ceros, E-tel, Eagle, Easypix, EBEN, EBEST, Echo Mobiles, ecom, ECON, ECOO, ECS, EE, EFT, EGL, Einstein, EKO, Eks Mobility, EKT, ELARI, Elecson, Electroneum, ELECTRONIA, Elekta, Element, Elenberg, Elephone, Elevate, Elong Mobile, Eltex, Ematic, Emporia, ENACOM, Energizer, Energy Sistem, Engel, ENIE, Enot, eNOVA, Entity, Envizen, Ephone, Epik One, Epson, Equator, Ergo, Ericsson, Ericy, Erisson, Essential, Essentielb, eSTAR, Eton, eTouch, Etuline, Eurocase, Eurostar, Evercoss, Everest, Everex, Evertek, Evolio, Evolveo, Evoo, EVPAD, EvroMedia, EWIS, EXCEED, Exmart, ExMobile, EXO, Explay, Extrem, EYU, Ezio, Ezze, F&U, F2 Mobile, F150, Facebook, Facetel, Facime, Fairphone, Famoco, Famous, Fantec, FaRao Pro, Farassoo, FarEasTone, Fengxiang, FEONAL, Fero, FFF SmartLife, Figgers, FiGi, FiGO, FiiO, FILIX, FinePower, Finlux, FireFly Mobile, FISE, Fluo, Fly, FLYCAT, FMT, FNB, FNF, Fondi, Fonos, FOODO, FORME, Formuler, Forstar, Fortis, Fourel, Four Mobile, Foxconn, FoxxD, FPT, Freetel, Frunsi, Fuego, Fujitsu, Funai, Fusion5, Future Mobile Technology, Fxtec, G-TiDE, G-Touch, Galaxy Innovations, Garmin-Asus, Gateway, Gazer, Geanee, Gear Mobile, Gemini, General Mobile, Genesis, GEOFOX, Geotel, Geotex, GEOZON, GFive, Ghia, Ghong, Ghost, Gigabyte, Gigaset, Gini, Ginzzu, Gionee, GIRASOLE, Globex, Glofiish, GLONYX, GLX, GOCLEVER, Gocomma, GoGEN, GoldMaster, Gol Mobile, Goly, Gome, GoMobile, Google, Goophone, Gooweel, Gplus, Gradiente, Grape, Great Asia, Gree, Greentel, Gresso, Gretel, Grundig, Gtel, GTMEDIA, Guophone, H96, H133, Hafury, Haier, Haipai, Hamlet, Hammer, Handheld, HannSpree, HAOVM, Hardkernel, Harper, Hartens, Hasee, HDC, HeadWolf, Helio, HERO, HexaByte, Hezire, Hi, Hi-Level, Hiberg, High Q, Highscreen, HiHi, HiKing, HiMax, Hi Nova, Hipstreet, Hisense, Hitachi, Hitech, HKPro, HLLO, Hoffmann, Hometech, Homtom, Honeywell, Hoozo, Horizon, Horizont, Hosin, Hotel, Hot Pepper, HOTREALS, Hotwav, How, HP, HTC, Huadoo, Huagan, Huavi, Huawei, Hugerock, Humax, Hurricane, Huskee, Hykker, Hyrican, Hytera, Hyundai, Hyve, i-Cherry, I-INN, i-Joy, i-mate, i-mobile, iBall, iBerry, iBrit, IconBIT, iData, iDroid, iGet, iHunt, Ikea, IKI Mobile, iKoMo, iKon, IKU Mobile, iLA, iLife, iMan, iMars, iMI, IMO Mobile, Imose, Impression, iMuz, iNavi, INCAR, Inch, Inco, iNew, Infiniton, Infinix, InFocus, InfoKit, InFone, Inkti, InnJoo, Innos, Innostream, Inoi, iNo Mobile, iNOVA, INQ, Insignia, INSYS, Intek, Intel, Intex, Invens, Inverto, Invin, iOcean, iOutdoor, iPEGTOP, iPro, iQ&T, IQM, IRA, Irbis, iReplace, Iris, iRobot, iRola, iRulu, iSafe Mobile, iStar, iSWAG, IT, iTel, iTruck, IUNI, iVA, iView, iVooMi, ivvi, iWaylink, iXTech, iYou, iZotron, JAY-Tech, Jedi, Jeka, Jesy, JFone, Jiake, Jiayu, Jinga, Jio, Jivi, JKL, Jolla, Joy, JoySurf, JPay, JREN, Jumper, Juniper Systems, Just5, JVC, JXD, K-Lite, K-Touch, Kaan, Kaiomy, Kalley, Kanji, Kapsys, Karbonn, Kata, KATV1, Kazam, Kazuna, KDDI, Kempler & Strauss, Kenbo, Keneksi, Kenxinda, Khadas, Kiano, Kingbox, Kingstar, Kingsun, KINGZONE, Kiowa, Kivi, Klipad, Kocaso, Kodak, Kogan, Komu, Konka, Konrow, Koobee, Koolnee, Kooper, KOPO, Koridy, Koslam, KREZ, KRIP, KRONO, Krüger&Matz, KT-Tech, KUBO, Kuliao, Kult, Kumai, Kurio, Kvant, Kyocera, Kyowon, Kzen, KZG, L-Max, LAIQ, Land Rover, Landvo, Lanin, Lanix, Lark, Laurus, Lava, LCT, Leader Phone, Leagoo, Leben, LeBest, Lectrus, Ledstar, LeEco, Leelbox, Leff, Legend, Leke, LEMFO, Lemhoov, Lenco, Lenovo, Leotec, Le Pan, Lephone, Lesia, Lexand, Lexibook, LG, Liberton, Lifemaxx, Lingwin, Linnex, Linsar, Listo, LNMBBS, Loewe, Logic, Logic Instrument, Logicom, LOKMAT, Loview, Lovme, LPX-G, LT Mobile, Lumigon, Lumitel, Lumus, Luna, Luxor, LYF, M-Horse, M-Tech, M.T.T., M3 Mobile, M4tel, MAC AUDIO, Macoox, Mafe, Magicsee, Magnus, Majestic, Malata, Mango, Manhattan, Mann, Manta Multimedia, Mantra, Mara, Marshal, Mascom, Massgo, Masstel, Master-G, Mastertech, Matrix, Maxcom, Maxfone, Maximus, Maxtron, MAXVI, Maxwest, MAXX, Maze, Maze Speed, MBI, MBOX, MDC Store, MDTV, meanIT, Mecer, Mecool, Mediacom, MediaTek, Medion, MEEG, MegaFon, Meitu, Meizu, Melrose, Memup, Meta, Metz, MEU, MicroMax, Microsoft, Microtech, Minix, Mint, Mintt, Mio, Mione, Miray, Mito, Mitsubishi, Mitsui, MIVO, MIWANG, MIXC, MiXzo, MLLED, MLS, MMI, Mobell, Mobicel, MobiIoT, Mobiistar, Mobile Kingdom, Mobiola, Mobistel, MobiWire, Mobo, Modecom, Mofut, Mosimosi, Motorola, Movic, MOVISUN, Movitel, Moxee, mPhone, Mpman, MSI, MStar, MTC, MTN, Multilaser, MwalimuPlus, MYFON, MyGica, MygPad, Mymaga, MyMobile, MyPhone, Myria, Myros, Mystery, MyTab, MyWigo, Nabi, Naomi Phone, NASCO, National, Navcity, Navitech, Navitel, Navon, NavRoad, NEC, Necnot, Nedaphone, Neffos, NEKO, Neo, neoCore, Neolix, Neomi, Neon IQ, Netgear, Netmak, NeuImage, New Balance, New Bridge, Newgen, Newland, Newman, Newsday, NewsMy, Nexa, NEXBOX, Nexian, NEXON, NEXT, Nextbit, NextBook, NextTab, NGM, NG Optics, Nikon, NINETEC, Nintendo, nJoy, NOA, Noain, Nobby, Noblex, NOBUX, NOGA, Nokia, Nomi, Nomu, Noontec, Nordmende, NorthTech, Nos, Nothing Phone, Nous, Novex, Novey, NOVO, NTT West, NuAns, Nubia, NUU Mobile, NuVision, Nuvo, Nvidia, NYX Mobile, O+, O2, Oale, Oangcc, OASYS, Obabox, Obi, Odotpad, Odys, OINOM, Ok, Okapia, OKSI, OKWU, Olax, Ollee, OLTO, Olympia, OMIX, Onda, OneClick, OnePlus, Onix, Onkyo, ONN, ONYX BOOX, Ookee, OpelMobile, Openbox, Ophone, OPPO, Opsson, Optoma, Orange, Orbic, Orbita, Orbsmart, Ordissimo, Orion, OSCAL, OTTO, OUJIA, Ouki, Oukitel, OUYA, Overmax, Ovvi, Owwo, OYSIN, Oysters, Oyyu, OzoneHD, P-UP, Packard Bell, Paladin, Palm, Panacom, Panasonic, Pano, Pantech, PAPYRE, Parrot Mobile, Partner Mobile, PCBOX, PCD, PCD Argentina, PC Smart, PEAQ, Pelitt, Pendoo, Pentagram, Perfeo, Phicomm, Philco, Philips, Phonemax, phoneOne, Pico, PINE, Pioneer, Pioneer Computers, PiPO, PIRANHA, Pixela, Pixelphone, Pixus, Planet Computers, Ployer, Plum, PlusStyle, Pluzz, PocketBook, POCO, Point Mobile, Point of View, Polar, PolarLine, Polaroid, Polestar, PolyPad, Polytron, Pomp, Poppox, POPTEL, Porsche, Positivo, Positivo BGH, PPTV, Premio, Prestigio, PRIME, Primepad, Primux, Pritom, Prixton, PROFiLO, Proline, Prology, ProScan, Protruly, ProVision, PULID, Purism, Q-Box, Q-Touch, Q.Bell, QFX, Qilive, QLink, QMobile, Qnet Mobile, QTECH, Qtek, Quantum, Quatro, Qubo, Quechua, Quest, Quipus, Qumo, Qware, R-TV, Rakuten, Ramos, Raspberry, Ravoz, Raylandz, Razer, RCA Tablets, Reach, Readboy, Realme, RED, Redbean, Redfox, Redway, Reeder, REGAL, RelNAT, Remdun, Retroid Pocket, Revo, Revomovil, Ricoh, Rikomagic, RIM, Rinno, Ritmix, Ritzviva, Riviera, Rivo, Rizzen, ROADMAX, Roadrover, Roam Cat, Rokit, Roku, Rombica, Ross&Moor, Rover, RoverPad, Royole, RoyQueen, RT Project, RugGear, Ruggex, Ruio, Runbo, Ryte, S-TELL, S2Tel, Saba, Safaricom, Sagem, Saiet, Salora, Samsung, Samtech, Sanei, Sankey, Sansui, Santin, SANY, Sanyo, Savio, SCBC, Schneider, Schok, Scosmos, Seatel, Seeken, SEG, Sega, Selecline, Selenga, Selevision, Selfix, SEMP TCL, Sencor, Sendo, Senkatel, Senseit, Senwa, Seuic, Sewoo, SFR, SGIN, Shanling, Sharp, Shift Phones, Shivaki, Shtrikh-M, Shuttle, Sico, Siemens, Sigma, Silelis, Silent Circle, Simbans, Simply, Singtech, Siragon, Sirin Labs, SK Broadband, SKG, Sky, Skyline, Skyworth, Smadl, Smailo, Smart, Smartab, SmartBook, SMARTEC, Smart Electronic, Smartex, Smartfren, Smartisan, Smart Kassel, Smarty, Smooth Mobile, Smotreshka, SNAMI, Soda, Softbank, Soho Style, SOLE, SOLO, Solone, Sonim, SONOS, Sony, Sony Ericsson, SOSH, Soundmax, Soyes, Spark, SPC, Spectralink, Spectrum, Spice, Sprint, SQOOL, SSKY, Star, Starlight, Starmobile, Starway, Starwind, STF Mobile, STG Telecom, STK, Stonex, Storex, StrawBerry, Stream, STRONG, Stylo, Subor, Sugar, Sumvision, Sunmax, Sunmi, Sunny, Sunstech, SunVan, Sunvell, SUNWIND, SuperBOX, SuperSonic, SuperTab, Supra, Surge, Suzuki, Swipe, SWISSMOBILITY, Swisstone, Switel, SWTV, Syco, SYH, Sylvania, Symphony, Syrox, T-Mobile, T96, TAG Tech, Taiga System, Takara, Talius, Tambo, Tanix, TB Touch, TCL, TD Systems, TD Tech, TeachTouch, Technicolor, Technika, TechniSat, Technopc, TechnoTrend, TechPad, Techwood, Teclast, Tecno Mobile, TecToy, TEENO, Teknosa, Tele2, Telefunken, Telego, Telenor, Telia, Telit, Telma, TeloSystems, Telpo, TENPLUS, Teracube, Tesco, Tesla, TETC, Tetratab, teXet, ThL, Thomson, Thuraya, TIANYU, Tibuta, Tigers, Time2, Timovi, TIMvision, Tinai, Tinmo, TiPhone, TiVo, TJC, TOKYO, Tolino, Tone, Tooky, TOPDON, Topelotek, Top House, Toplux, Topsion, Topway, Torex, Torque, TOSCIDO, Toshiba, Touchmate, Touch Plus, TOX, Transpeed, TrekStor, Trevi, Trident, Trifone, Trio, Tronsmart, True, True Slim, TTEC, TTK-TV, TuCEL, Tunisie Telecom, Turbo, Turbo-X, TurboKids, TurboPad, Turkcell, TVC, TWM, Twoe, TWZ, Tymes, Türk Telekom, U-Magic, U.S. Cellular, UE, Ugoos, Uhans, Uhappy, Ulefone, Umax, UMIDIGI, Unblock Tech, Uniden, Unihertz, Unimax, Uniqcell, Uniscope, Unistrong, Unitech, UNIWA, Unknown, Unnecto, Unnion Technologies, UNNO, Unonu, Unowhy, Urovo, UTime, UTOK, UTStarcom, UZ Mobile, V-Gen, V-HOME, V-HOPE, v-mobile, VAIO, VALEM, VALTECH, Vankyo, Vargo, Vastking, VAVA, VC, VDVD, Vega, Venso, Venstar, Venturer, VEON, Verico, Verizon, Vernee, Verssed, Versus, Vertex, Vertu, Verykool, Vesta, Vestel, Vexia, VGO TEL, ViBox, Victurio, VIDA, Videocon, Videoweb, ViewSonic, Vinabox, Vinga, Vinsoc, Vios, Vipro, Virzo, Vision Touch, Vitelcom, Viumee, Vivax, VIVIMAGE, Vivo, VIWA, Vizio, Vizmo, VK Mobile, VKworld, Vodacom, Vodafone, VOGA, Volt, Vonino, Vontar, Vorago, Vorcom, Vorke, Vormor, Vortex, Voto, VOX, Voxtel, Voyo, Vsmart, Vsun, VUCATIMES, Vue Micro, Vulcan, VVETIME, Völfen, Walton, Waltter, Wanmukang, WANSA, WE, Web TV, Wecool, Weelikeit, Weimei, WellcoM, WELLINGTON, Western Digital, Westpoint, Wexler, White Mobile, Wieppo, Wigor, Wiko, Wileyfox, Winds, Wink, Winmax, Winnovo, Winstar, Wintouch, Wiseasy, WIWA, WizarPos, Wizz, Wolder, Wolfgang, Wolki, Wonu, Woo, Wortmann, Woxter, X-AGE, X-BO, X-Mobile, X-TIGI, X-View, X.Vision, X88, X96, X96Q, XCOM, Xcruiser, XElectron, XGIMI, Xgody, Xiaodu, Xiaolajiao, Xiaomi, Xion, Xolo, Xoro, Xshitou, Xtouch, Xtratech, Xwave, Yandex, Yarvik, YASIN, YELLYOUTH, YEPEN, Yes, Yestel, Yezz, Yoka TV, Yooz, Yota, YOTOPT, Youin, Youwei, Ytone, Yu, Yuandao, YU Fly, YUHO, YUMKEM, YUNDOO, Yuno, YunSong, Yusun, Yxtel, Zaith, Zamolxe, Zatec, Zealot, Zebra, Zeeker, Zeemi, Zen, Zenek, Zentality, Zfiner, ZH&K, Zidoo, ZIFRO, ZIK, Zinox, Ziox, Zonda, Zonko, ZoomSmart, Zopo, ZTE, Zuum, Zync, ZYQ, Zyrex, öwn ### List of detected bots: 2ip, 360 Monitoring, 360Spider, Abonti, Aboundexbot, Acoon, AdAuth, Adbeat, AddThis.com, ADMantX, ADmantX Service Fetcher, Adsbot, AdsTxtCrawler, adstxtlab.com, aHrefs Bot, AhrefsSiteAudit, aiHitBot, Alexa Crawler, Alexa Site Audit, Allloadin Favicon Bot, Amazon Bot, Amazon ELB, Amazon Route53 Health Check, Amorank Spider, Analytics SEO Crawler, ApacheBench, Applebot, AppSignalBot, Arachni, archive.org bot, ArchiveBox, Asana, Ask Jeeves, AspiegelBot, Awario, Awario, Backlink-Check.de, BacklinkCrawler, Baidu Spider, Barkrowler, BazQux Reader, BDCbot, Better Uptime Bot, BingBot, Birdcrawlerbot, BitlyBot, BitSight, Blekkobot, BLEXBot Crawler, Bloglovin, Blogtrottr, BoardReader, BoardReader Blog Indexer, Bountii Bot, BrandVerity, Browsershots, BUbiNG, Buck, BuiltWith, Butterfly Robot, Bytespider, CareerBot, Castro 2, Catchpoint, CATExplorador, ccBot crawler, CensysInspect, Charlotte, Choosito, Chrome Privacy Preserving Prefetch Proxy, Cincraw, CISPA Web Analyzer, Cliqzbot, CloudFlare Always Online, CloudFlare AMP Fetcher, Cloudflare Diagnostics, Cloudflare Health Checks, Cocolyzebot, Collectd, colly, CommaFeed, COMODO DCV, Comscore, ContentKing, Cookiebot, Crawldad, Crawlson, CriteoBot, CrowdTangle, CSS Certificate Spider, Cốc Cốc Bot, Datadog Agent, DataForSeoBot, datagnionbot, Datanyze, Dataprovider, DataXu, Daum, Dazoobot, deepnoc, Diffbot, Discobot, Discord Bot, Disqus, DNSResearchBot, DomainAppender, DomainCrawler, Domain Re-Animator Bot, Domains Project, DomainStatsBot, DotBot, Dotcom Monitor, DuckDuckGo Bot, Easou Spider, eCairn-Grabber, EFF Do Not Track Verifier, EMail Exractor, EmailWolf, Embedly, Entfer, evc-batch, Everyfeed, ExaBot, ExactSeek Crawler, Exchange check, Expanse, Ezgif, Ezooms, eZ Publish Link Validator, Facebook External Hit, Faveeo, Feedbin, FeedBurner, Feedly, Feedspot, Feed Wrangler, Femtosearch, Fever, Findxbot, Flipboard, FreeWebMonitoring, FreshRSS, GDNP, Generic Bot, Generic Bot, Genieo Web filter, Gigablast, Gigabot, GitCrawlerBot, Gluten Free Crawler, Gmail Image Proxy, Gobuster, Goo, Googlebot, Google Cloud Scheduler, Google Favicon, Google PageSpeed Insights, Google Partner Monitoring, Google Search Console, Google Stackdriver Monitoring, Google StoreBot, Google Structured Data Testing Tool, Gowikibot, Grammarly, Grapeshot, Gregarius, GTmetrix, GumGum Verity, hackermention, Hatena Favicon, Headline, Heart Rails Capture, Heritrix, Heureka Feed, HTTPMon, httpx, HuaweiWebCatBot, HubPages, HubSpot, ICC-Crawler, ichiro, IDG/IT, Iframely, IIS Site Analysis, Inetdex Bot, Infegy, InfoTigerBot, Inktomi Slurp, inoreader, Intelligence X, InternetMeasurement, IONOS Crawler, IP-Guide Crawler, IPIP, IPS Agent, JobboerseBot, JungleKeyThumbnail, K6, Kaspersky, KlarnaBot, KomodiaBot, Kouio, Kozmonavt, l9explore, l9tcpid, Larbin web crawler, LastMod Bot, LCC, LeakIX, Let's Encrypt Validation, Lighthouse, Linespider, Linkdex Bot, LinkedIn Bot, LinkpadBot, LinkPreview, LinkWalker, LTX71, LumtelBot, Lycos, MaCoCu, Magpie-Crawler, MagpieRSS, Mail.Ru Bot, masscan, masscan-ng, Mastodon Bot, Meanpath Bot, Mediatoolkit Bot, MegaIndex, MetaInspector, MetaJobBot, MicroAdBot, Mixrank Bot, MJ12 Bot, Mnogosearch, MojeekBot, Monitor.Us, Morningscore Bot, MTRobot, Munin, MuscatFerret, Nagios check_http, NalezenCzBot, nbertaupete95, Neevabot, Netcraft Survey Bot, netEstate, NetLyzer FastProbe, NetResearchServer, NetSystemsResearch, Netvibes, NETZZAPPEN, NewsBlur, NewsGator, Newslitbot, NiceCrawler, Nimbostratus Bot, NLCrawler, Nmap, Notify Ninja, Nutch-based Bot, Nuzzel, oBot, Octopus, Odnoklassniki Bot, Omgili bot, Onalytica, Openindex Spider, OpenLinkProfiler, OpenWebSpider, Orange Bot, Outbrain, Overcast Podcast Sync, Pageburst, Page Modified Pinger, PagePeeker, PageThing, Panscient, PaperLiBot, parse.ly, PATHspider, PayPal IPN, PDR Labs, Petal Bot, Phantomas, PHP Server Monitor, Picsearch bot, PingAdmin.Ru, Pingdom Bot, Pinterest, PiplBot, Plukkie, Pocket, Pompos, PritTorrent, Project Patchwatch, Project Resonance, PRTG Network Monitor, QuerySeekerSpider, Quora Bot, Quora Link Preview, Qwantify, Rainmeter, RamblerMail Image Proxy, Reddit Bot, RenovateBot, Repo Lookout, ReqBin, Riddler, Robozilla, RocketMonitorBot, Rogerbot, ROI Hunter, RSSRadio Bot, Ryowl, SabsimBot, SafeDNSBot, Scamadviser External Hit, Scooter, ScoutJet, Scrapy, Screaming Frog SEO Spider, ScreenerBot, Sectigo DCV, security.txt scanserver, Seekport, Sellers.Guide, Semantic Scholar Bot, Semrush Bot, SEMrush Reputation Management, Sensika Bot, Sentry Bot, Seobility, SEOENGBot, SEOkicks, SEOkicks-Robot, seolyt, Seolyt Bot, Seoscanners.net, Serendeputy Bot, serpstatbot, Server Density, Seznam Bot, Seznam Email Proxy, Seznam Zbozi.cz, ShopAlike, Shopify Partner, ShopWiki, SilverReader, SimplePie, SISTRIX Crawler, SISTRIX Optimizer, Site24x7 Website Monitoring, Siteimprove, SitemapParser-VIPnytt, SiteSucker, Sixy.ch, Skype URI Preview, Slackbot, SMTBot, Snapchat Proxy, Snap URL Preview Service, Sogou Spider, Soso Spider, Sparkler, Speedy, Spinn3r, Spotify, Sprinklr, Sputnik Bot, Sputnik Favicon Bot, Sputnik Image Bot, sqlmap, SSL Labs, start.me, Startpagina Linkchecker, StatusCake, Sublinq, Superfeedr Bot, SurdotlyBot, Survey Bot, t3versions, Taboolabot, Tag Inspector, Tarmot Gezgin, tchelebi, TelegramBot, TestCrawler, The Knowledge AI, theoldreader, ThinkChaos, TigerBot, TinEye Crawler, Tiny Tiny RSS, TLSProbe, TraceMyFile, Trendiction Bot, Turnitin, TurnitinBot, TweetedTimes Bot, Tweetmeme Bot, Twingly Recon, Twitterbot, UkrNet Mail Proxy, uMBot, UniversalFeedParser, Uptimebot, Uptime Robot, URLAppendBot, URLinspector, Vagabondo, Velen Public Web Crawler, Vercel Bot, VeryHip, Visual Site Mapper Crawler, VK Share Button, W3C CSS Validator, W3C I18N Checker, W3C Link Checker, W3C Markup Validation Service, W3C MobileOK Checker, W3C Unified Validator, Wappalyzer, WebbCrawler, WebDataStats, Weborama, WebPageTest, WebPros, WebSitePulse, WebThumbnail, WellKnownBot, WeSEE:Search, WeViKaBot, WhatCMS, WhereGoes, WikiDo, Willow Internet Crawler, WooRank, WooRank, WordPress, Wotbox, XenForo, XoviBot, YaCy, Yahoo! Cache System, Yahoo! Japan BRW, Yahoo! Link Preview, Yahoo! Mail Proxy, Yahoo! Slurp, Yahoo Gemini, YaK, Yandex Bot, Yeti/Naverbot, Yottaa Site Monitor, Youdao Bot, Yourls, Yunyun Bot, Zaldamo, Zao, Ze List, zgrab, Zookabot, ZoominfoBot, ZumBot device-detector-6.1.1/Yaml/000077500000000000000000000000001440455040400154675ustar00rootroot00000000000000device-detector-6.1.1/Yaml/ParserInterface.php000066400000000000000000000007551440455040400212640ustar00rootroot00000000000000class. * * @param string $class * the name of the class to load * * @return void */ function dd_autoload(string $class): void { if (false === strpos($class, 'DeviceDetector\\')) { return; } $namespaceMap = ['DeviceDetector\\' => __DIR__ . '/']; foreach ($namespaceMap as $prefix => $dir) { /* First swap out the namespace prefix with a directory... */ $path = str_replace($prefix, $dir, $class); /* replace the namespace separator with a directory separator... */ $path = str_replace('\\', '/', $path); /* and finally, add the PHP file extension to the result. */ $path .= '.php'; /* $path should now contain the path to a PHP file defining $class */ @include $path; } } spl_autoload_register('dd_autoload'); device-detector-6.1.1/composer.json000066400000000000000000000035401440455040400173110ustar00rootroot00000000000000{ "name": "matomo/device-detector", "type": "library", "description": "The Universal Device Detection library, that parses User Agents and detects devices (desktop, tablet, mobile, tv, cars, console, etc.), clients (browsers, media players, mobile apps, feed readers, libraries, etc), operating systems, devices, brands and models.", "keywords": ["useragent","parser","devicedetection"], "homepage": "https://matomo.org", "license": "LGPL-3.0-or-later", "authors": [ { "name": "The Matomo Team", "email": "hello@matomo.org", "homepage": "https://matomo.org/team/" } ], "support": { "forum": "https://forum.matomo.org/", "issues": "https://github.com/matomo-org/device-detector/issues", "wiki": "https://dev.matomo.org/", "source": "https://github.com/matomo-org/matomo" }, "autoload": { "psr-4": { "DeviceDetector\\": "" }, "exclude-from-classmap": ["Tests/"] }, "require": { "php": "^7.2|^8.0", "mustangostang/spyc": "*" }, "require-dev": { "phpunit/phpunit": "^8.5.8", "psr/cache": "^1.0.1", "psr/simple-cache": "^1.0.1", "matthiasmullie/scrapbook": "^1.4.7", "phpstan/phpstan": "^0.12.52", "mayflower/mo4-coding-standard": "^v8.0.0", "symfony/yaml": "^5.1.7" }, "suggest": { "doctrine/cache": "Can directly be used for caching purpose", "ext-yaml": "Necessary for using the Pecl YAML parser" }, "scripts": { "php-cs-fixed": "php vendor/bin/phpcbf" }, "archive": { "exclude": ["/autoload.php"] }, "replace": { "piwik/device-detector":"self.version" }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } device-detector-6.1.1/phpcs.xml000066400000000000000000000074501440455040400164320ustar00rootroot00000000000000 Device Detector Coding Standard . vendor/ misc/ Tests/ misc/ autoload.php misc/ autoload.php misc/ Tests/ device-detector-6.1.1/regexes/000077500000000000000000000000001440455040400162275ustar00rootroot00000000000000device-detector-6.1.1/regexes/bots.yml000066400000000000000000002310521440455040400177240ustar00rootroot00000000000000############### # Device Detector - The Universal Device Detection library for parsing User Agents # # @link https://matomo.org # @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later ############### - regex: 'monitoring360bot' name: '360 Monitoring' category: 'Site Monitor' url: 'https://www.360monitoring.io' producer: name: 'Plesk International GmbH' url: 'https://www.plesk.com' - regex: 'Cloudflare-Healthchecks' name: 'Cloudflare Health Checks' category: 'Service Agent' url: 'https://developers.cloudflare.com/health-checks/' producer: name: 'CloudFlare' url: 'http://www.cloudflare.com' - regex: '360Spider' name: '360Spider' category: 'Search bot' url: 'https://www.so.com/help/help_3_2.html' producer: name: 'Online Media Group, Inc.' url: '' - regex: 'Aboundex' name: 'Aboundexbot' category: 'Search bot' url: 'http://www.aboundex.com/crawler/' producer: name: 'Aboundex.com' url: 'http://www.aboundex.com' - regex: 'AcoonBot' name: 'Acoon' category: 'Search bot' url: 'http://www.acoon.de/robot.asp' producer: name: 'Acoon GmbH' url: 'http://www.acoon.de' - regex: 'AddThis\.com' name: 'AddThis.com' category: 'Social Media Agent' url: '' producer: name: 'Clearspring Technologies, Inc.' url: 'http://www.clearspring.com' - regex: 'AhrefsBot' name: 'aHrefs Bot' category: 'Crawler' url: 'https://ahrefs.com/robot' producer: name: 'Ahrefs Pte Ltd' url: 'https://ahrefs.com/robot' - regex: 'AhrefsSiteAudit/([\d+.]+)' name: 'AhrefsSiteAudit' category: 'Site Monitor' url: 'https://ahrefs.com/robot/site-audit' producer: name: 'Ahrefs Pte Ltd' url: 'https://ahrefs.com/' - regex: 'ia_archiver|alexabot|verifybot' name: 'Alexa Crawler' category: 'Search bot' url: 'https://support.alexa.com/hc/en-us/sections/200100794-Crawlers' producer: name: 'Alexa Internet' url: 'https://www.alexa.com' - regex: 'alexa site audit' name: 'Alexa Site Audit' category: 'Site Monitor' url: 'https://support.alexa.com/hc/en-us/articles/200450194' producer: name: 'Alexa Internet' url: 'https://www.alexa.com' - regex: 'Amazonbot' name: 'Amazon Bot' category: 'Crawler' url: 'https://developer.amazon.com/support/amazonbot' producer: name: 'Amazon.com, Inc.' url: 'https://www.amazon.com/' - regex: 'Amazon[ -]Route ?53[ -]Health[ -]Check[ -]Service' name: 'Amazon Route53 Health Check' category: 'Service Agent' producer: name: 'Amazon Web Services' url: 'https://aws.amazon.com/' - regex: 'AmorankSpider' name: 'Amorank Spider' category: 'Crawler' url: 'http://amorank.com/webcrawler.html' producer: name: 'Amorank' url: 'http://www.amorank.com' - regex: 'ApacheBench' name: 'ApacheBench' category: 'Benchmark' url: 'https://httpd.apache.org/docs/2.4/programs/ab.html' producer: name: 'The Apache Software Foundation' url: 'https://www.apache.org/foundation/' - regex: 'Applebot' name: 'Applebot' category: 'Crawler' url: 'https://support.apple.com/en-us/HT204683' producer: name: 'Apple Inc' url: 'https://www.apple.com' - regex: 'AppSignalBot' name: 'AppSignalBot' category: 'Site Monitor' url: 'https://docs.appsignal.com/uptime-monitoring/' producer: name: 'AppSignal' url: 'https://appsignal.com/' - regex: 'Arachni' name: 'Arachni' category: 'Security Checker' url: 'https://www.arachni-scanner.com/' producer: name: 'Sarosys LLC' url: 'https://www.sarosys.com/' - regex: 'AspiegelBot' name: 'AspiegelBot' category: 'Crawler' url: 'https://aspiegel.com/' producer: name: 'Huawei' url: 'https://www.huawei.com/' - regex: 'Castro 2, Episode Duration Lookup' name: 'Castro 2' category: 'Service Agent' url: 'http://supertop.co/castro/' producer: name: 'Supertop' url: 'http://supertop.co' - regex: 'Curious George' name: 'Analytics SEO Crawler' category: 'Crawler' url: 'http://www.analyticsseo.com/crawler' producer: name: 'Analytics SEO' url: 'http://www.analyticsseo.com' - regex: 'archive\.org_bot|special_archiver' name: 'archive.org bot' category: 'Crawler' url: 'https://archive.org/details/archive.org_bot' producer: name: 'The Internet Archive' url: 'https://archive.org' - regex: 'Ask Jeeves/Teoma' name: 'Ask Jeeves' category: 'Search bot' url: '' producer: name: 'Ask Jeeves Inc.' url: 'http://www.ask.com' - regex: 'Backlink-Check\.de' name: 'Backlink-Check.de' category: 'Crawler' url: 'http://www.backlink-check.de/bot.html' producer: name: 'Mediagreen Medienservice' url: 'http://www.backlink-check.de' - regex: 'BacklinkCrawler' name: 'BacklinkCrawler' category: 'Crawler' url: 'http://www.backlinktest.com/crawler.html' producer: name: '2.0Promotion GbR' url: 'http://www.backlinktest.com' - regex: 'Baidu.*spider|baidu Transcoder' name: 'Baidu Spider' category: 'Search bot' url: 'http://www.baidu.com/search/spider.htm' producer: name: 'Baidu' url: 'http://www.baidu.com' - regex: 'BazQux' name: 'BazQux Reader' url: 'https://bazqux.com/fetcher' category: 'Feed Fetcher' producer: name: '' url: '' - regex: 'Better Uptime Bot' name: 'Better Uptime Bot' category: 'Site Monitor' url: 'https://betteruptime.com/faq' producer: name: 'Better Uptime' url: 'https://betteruptime.com/' - regex: 'MSNBot|msrbot|bingbot|BingPreview|msnbot-(UDiscovery|NewsBlogs)|adidxbot' name: 'BingBot' category: 'Search bot' url: 'http://search.msn.com/msnbot.htmn' producer: name: 'Microsoft Corporation' url: 'http://www.microsoft.com' - regex: 'Blekkobot' name: 'Blekkobot' category: 'Search bot' url: 'http://blekko.com/about/blekkobot' producer: name: 'Blekko' url: 'http://blekko.com' - regex: 'BLEXBot' name: 'BLEXBot Crawler' category: 'Crawler' url: 'http://webmeup-crawler.com' producer: name: 'WebMeUp' url: 'http://webmeup.com' - regex: 'Bloglovin' name: 'Bloglovin' url: 'http://www.bloglovin.com' category: 'Feed Fetcher' producer: name: '' url: '' - regex: 'Blogtrottr' name: 'Blogtrottr' url: '' category: 'Feed Fetcher' producer: name: 'Blogtrottr Ltd' url: 'https://blogtrottr.com/' - regex: 'BoardReader Blog Indexer' name: 'BoardReader Blog Indexer' category: 'Crawler' producer: name: 'BoardReader' url: 'https://boardreader.com/' - regex: 'BountiiBot' name: 'Bountii Bot' category: 'Search bot' url: 'http://bountii.com/contact.php' producer: name: 'Bountii Inc.' url: 'http://bountii.com' - regex: 'Browsershots' name: 'Browsershots' category: 'Service Agent' url: 'http://browsershots.org/faq' producer: name: 'Browsershots.org' url: 'http://browsershots.org' - regex: 'BUbiNG' name: 'BUbiNG' category: 'Crawler' url: 'http://law.di.unimi.it/BUbiNG.html' producer: name: 'The Laboratory for Web Algorithmics (LAW)' url: 'http://law.di.unimi.it/software.php#buging' - regex: '(?