diff --git a/ClientHints.php b/ClientHints.php index 0aece93236..2eefcc7842 100644 --- a/ClientHints.php +++ b/ClientHints.php @@ -76,6 +76,12 @@ class ClientHints */ protected $app = ''; + /** + * Represents `x-client` header field: additional header from applications/bots + * @var string + */ + protected $xClient = ''; + /** * Constructor * @@ -88,8 +94,9 @@ class ClientHints * @param string $architecture `Sec-CH-UA-Arch` header field * @param string $bitness `Sec-CH-UA-Bitness` * @param string $app `HTTP_X-REQUESTED-WITH` + * @param string $xClient `HTTP_X_CLIENT` */ - public function __construct(string $model = '', string $platform = '', string $platformVersion = '', string $uaFullVersion = '', array $fullVersionList = [], bool $mobile = false, string $architecture = '', string $bitness = '', string $app = '') // phpcs:ignore Generic.Files.LineLength + public function __construct(string $model = '', string $platform = '', string $platformVersion = '', string $uaFullVersion = '', array $fullVersionList = [], bool $mobile = false, string $architecture = '', string $bitness = '', string $app = '', string $xClient = '') // phpcs:ignore Generic.Files.LineLength { $this->model = $model; $this->platform = $platform; @@ -100,6 +107,7 @@ public function __construct(string $model = '', string $platform = '', string $p $this->architecture = $architecture; $this->bitness = $bitness; $this->app = $app; + $this->xClient = $xClient; } /** @@ -221,6 +229,16 @@ public function getApp(): string return $this->app; } + /** + * Returns the xClient string + * + * @return string + */ + public function getXClient(): string + { + return $this->xClient; + } + /** * Factory method to easily instantiate this class using an array containing all available (client hint) headers * @@ -231,7 +249,7 @@ public function getApp(): string public static function factory(array $headers): ClientHints { $model = $platform = $platformVersion = $uaFullVersion = $architecture = $bitness = ''; - $app = ''; + $app = $xClient = ''; $mobile = false; $fullVersionList = []; @@ -294,6 +312,7 @@ public static function factory(array $headers): ClientHints 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': @@ -316,6 +335,11 @@ public static function factory(array $headers): ClientHints $app = $value; } + break; + case 'http-x-client': + case 'x-client': + $xClient = $value; + break; } } @@ -329,7 +353,8 @@ public static function factory(array $headers): ClientHints $mobile, $architecture, $bitness, - $app + $app, + $xClient ); } } diff --git a/Parser/AbstractParser.php b/Parser/AbstractParser.php index e33fdbc488..ea43dbd96c 100644 --- a/Parser/AbstractParser.php +++ b/Parser/AbstractParser.php @@ -53,7 +53,7 @@ abstract class AbstractParser * Contains a list of mappings from names we use to known client hint values * @var array> */ - protected static $clientHintMapping = []; + protected static $xClientMapping = []; /** * Holds an array with method that should be available global @@ -269,7 +269,7 @@ protected function getRegexes(): array */ protected function applyClientHintMapping(string $name): string { - foreach (static::$clientHintMapping as $mappedName => $clientHints) { + foreach (static::$xClientMapping as $mappedName => $clientHints) { foreach ($clientHints as $clientHint) { if (\strtolower($name) === \strtolower($clientHint)) { return $mappedName; diff --git a/Parser/Bot.php b/Parser/Bot.php index 49348e93ae..9ae51dd3f3 100644 --- a/Parser/Bot.php +++ b/Parser/Bot.php @@ -12,6 +12,8 @@ namespace DeviceDetector\Parser; +use DeviceDetector\ClientHints; + /** * Class Bot * @@ -44,6 +46,19 @@ public function discardDetails(): void $this->discardDetails = true; } + /** + * Contains a list of mappings from xClient names we use to known x-client values of bots + * + * @var array> + */ + protected static $xClientMapping = [ + 'Collabim' => [ + 'name' => 'Collabim', + 'category' => 'Crawler', + 'url' => 'https://www.collabim.com/', + ], + ]; + /** * Parses the current UA and checks whether it contains bot information * @@ -64,6 +79,18 @@ public function discardDetails(): void */ public function parse(): ?array { + if ($this->clientHints instanceof ClientHints && $this->clientHints->getXClient()) { + foreach (self::$xClientMapping as $key => $result) { + if ($this->fuzzyCompare($key, $this->clientHints->getXClient())) { + if ($this->discardDetails) { + return [true]; + } + + return $result; + } + } + } + $result = null; if ($this->preMatchOverall()) { diff --git a/Parser/OperatingSystem.php b/Parser/OperatingSystem.php index c1ceab4d2e..72431e3503 100644 --- a/Parser/OperatingSystem.php +++ b/Parser/OperatingSystem.php @@ -219,7 +219,7 @@ class OperatingSystem extends AbstractParser * * @var array> */ - protected static $clientHintMapping = [ + protected static $xClientMapping = [ 'GNU/Linux' => ['Linux'], 'Mac' => ['MacOS'], ]; diff --git a/Tests/DeviceDetectorTest.php b/Tests/DeviceDetectorTest.php index 3d8359890a..3e025c8fc8 100644 --- a/Tests/DeviceDetectorTest.php +++ b/Tests/DeviceDetectorTest.php @@ -385,8 +385,12 @@ public function testVersionTruncationForClientHints(): void */ public function testParseBots(array $fixtureData): void { - $ua = $fixtureData['user_agent']; - $dd = new DeviceDetector($ua); + $ua = $fixtureData['user_agent']; + $headers = $fixtureData['headers'] ?? []; + $clientHints = ClientHints::factory($headers); + $dd = new DeviceDetector($ua); + + $dd->setClientHints($clientHints); $dd->parse(); $this->assertTrue($dd->isBot()); $botData = $dd->getBot(); diff --git a/Tests/fixtures/bots.yml b/Tests/fixtures/bots.yml index 3513bf3d57..fd146ca356 100644 --- a/Tests/fixtures/bots.yml +++ b/Tests/fixtures/bots.yml @@ -5452,3 +5452,11 @@ name: ReqBin category: Crawler url: https://reqbin.com/curl +- + user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15 + headers: + http-x-client: Collabim + bot: + name: Collabim + category: Crawler + url: https://www.collabim.com/