Added DevicesManager

This commit is contained in:
Zi Xing 2021-12-18 19:14:55 -05:00
parent 6215c69595
commit fcbad59a59
31 changed files with 7132 additions and 1 deletions

26
database/devices.sql Normal file
View File

@ -0,0 +1,26 @@
create table devices
(
fingerprint varchar(126) not null comment 'The fingerprint of the device',
user_agent varchar(255) not null comment 'The full raw user agent string of the device',
os_family varchar(255) null comment 'The Operating System family of the user agent',
os_version varchar(126) null comment 'The version of the operating system',
device_family varchar(255) null comment 'The family of the device',
device_brand varchar(255) null comment 'The brand of the device',
device_model varchar(255) null comment 'The model of the device',
browser_family varchar(255) null comment 'The family of the web browser that''s being used',
browser_version varchar(255) null comment 'The version of the browser that''s being used',
mobile_browser bool null comment 'Indicates if the browser is on a mobile platform',
mobile_device bool null comment 'Indicates if the device is a mobile device',
last_seen_timestamp int null comment 'The Unix Timestamp for when this device was last seen',
created_timestamp int null comment 'The Unix Timestamp for when this record was first registered into the database',
constraint devices_pk
primary key (fingerprint)
)
comment 'Table for housing known devices and their properties';
create unique index devices_fingerprint_uindex
on devices (fingerprint);
create unique index devices_user_agent_uindex
on devices (user_agent);

View File

@ -0,0 +1,23 @@
create table if not exists known_devices
(
ip_address varchar(126) null comment 'The IP address of the host',
ip_version int null comment 'The version of the IP Address',
device_fingerprint varchar(126) null comment 'The fingerprint of the device',
properties blob null comment 'ZiProto encoded blob of properties associated with this device',
last_seen_timestamp int null comment 'The Unix Timestamp for when this device and host was last seen',
created_timestamp int null comment 'The Unix Timestamp for when this record was first registered',
constraint known_devices_ip_address_device_fingerprint_uindex
unique (ip_address, device_fingerprint),
constraint known_devices_devices_fingerprint_fk
foreign key (device_fingerprint) references devices (fingerprint),
constraint known_devices_known_hosts_ip_address_fk
foreign key (ip_address) references known_hosts (ip_address)
)
comment 'A table for housing the relationship between a host and a device to detect if a device is using multiple hosts';
create index known_devices_device_fingerprint_index
on known_devices (device_fingerprint);
create index known_devices_ip_address_index
on known_devices (ip_address);

12
database/known_hosts.sql Normal file
View File

@ -0,0 +1,12 @@
create table if not exists known_hosts
(
ip_address varchar(126) not null comment 'The IP address of the known host'
primary key,
properties blob null comment 'ZiProto encoded blob for this hosts properties',
last_seen_timestamp int null comment 'The Unix Timestamp for when this host was last seen',
created_timestamp int null comment 'The Unix Timestamp for when this host was first seen',
constraint known_hosts_ip_address_uindex
unique (ip_address)
)
comment 'Table for housing known hosts and their properties';

View File

@ -0,0 +1,19 @@
<?php
namespace khm\Abstracts;
abstract class AbstractClient
{
/**
* @return string
*/
abstract public function toString(): string;
/**
* @return string
*/
public function __toString()
{
return $this->toString();
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace khm\Abstracts;
abstract class AbstractParser
{
/**
* @var array
*/
protected $regexes = [];
public function __construct(array $regexes)
{
$this->regexes = $regexes;
}
/**
* @param array $regexes
* @param string $userAgent
* @return array
*/
protected static function tryMatch(array $regexes, string $userAgent): array
{
foreach ($regexes as $regex) {
if (preg_match($regex['regex'], $userAgent, $matches)) {
$defaults = [
1 => 'Other',
2 => null,
3 => null,
4 => null,
5 => null,
];
return [$regex, $matches + $defaults];
}
}
return [null, null];
}
/**
* @param array $regex
* @param string $key
* @param string|null $default
* @param array $matches
* @return string|null
*/
protected static function multiReplace(array $regex, string $key, ?string $default, array $matches): ?string
{
if (!isset($regex[$key])) {
return self::emptyStringToNull($default);
}
$replacement = preg_replace_callback(
'|\$(?P<key>\d)|',
static function ($m) use ($matches) {
return $matches[$m['key']] ?? '';
},
$regex[$key]
);
return self::emptyStringToNull($replacement);
}
/**
* @param string|null $string
* @return string|null
*/
private static function emptyStringToNull(?string $string): ?string
{
$string = trim($string ?? '');
return $string === '' ? null : $string;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace khm\Abstracts;
abstract class AbstractSoftware extends AbstractClient
{
/**
* @var string
*/
public $family = 'Other';
/**
* @return string
*/
public function toString(): string
{
return $this->family;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace khm\Abstracts;
use DynamicalWeb\Abstracts\AbstractSoftware;
abstract class AbstractVersionedSoftware extends AbstractSoftware
{
/**
* @return string
*/
abstract public function toVersion(): string;
/**
* @return string
*/
public function toString(): string
{
return implode(' ', array_filter([$this->family, $this->toVersion()]));
}
/**
* @param string|null ...$args
* @return string
* @noinspection PhpUnused
*/
protected function formatVersion(?string ...$args): string
{
return implode('.', array_filter($args, 'is_numeric'));
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace khm\Abstracts;
abstract class HostFlags
{
/**
* Indicates that this IP address has a history of reported
*/
const BadUser = 'BAD_USER';
/**
* Indicates that the IP address is a part of the Tor network acting as a relay
*/
const TorRelay = 'TOR_RELAY';
/**
* Indicates that the IP address is an exit node
*/
const TorExit = 'TOR_EXIT';
}

View File

@ -0,0 +1,10 @@
<?php
namespace khm\Abstracts\SearchMethods;
abstract class DeviceSearchMethod
{
const ByFingerprint = 'fingerprint';
const ByUserAgent = 'user_agent';
}

View File

@ -0,0 +1,119 @@
<?php
namespace khm\Classes;
use khm\Classes\UserAgentParser\Parser;
use khm\Exceptions\NoUserAgentProvidedException;
use khm\Objects\Device;
class DeviceDetection
{
/**
* Detects the client's device if applicable
*
* @return Device
* @throws NoUserAgentProvidedException
*/
public static function detectDevice(): Device
{
if(isset($_SERVER['HTTP_USER_AGENT']) == false)
throw new NoUserAgentProvidedException('Cannot detect the device because no user agent was provided');
$parser = new Parser();
$parsed_ua = $parser->parse($_SERVER['HTTP_USER_AGENT']);
$MobileBrowserRegex = RegexLoader::getMobile1();
$MobileDeviceRegex = RegexLoader::getMobile2();
$Device = new Device();
$Device->Fingerprint = hash('sha1', $_SERVER['HTTP_USER_AGENT']);
$Device->UserAgent = $_SERVER['HTTP_USER_AGENT'];
$Device->OperatingSystemFamily = ($parsed_ua->os->family == null ? null : $parsed_ua->os->family);
$Device->OperatingSystemVersion = ($parsed_ua->os->toVersion() == null ? null : $parsed_ua->os->toVersion());
$Device->DeviceFamily = ($parsed_ua->device->family == null ? null : $parsed_ua->device->family);
$Device->DeviceBrand = ($parsed_ua->device->brand == null ? null : $parsed_ua->device->brand);
$Device->DeviceModel = ($parsed_ua->device->model == null ? null : $parsed_ua->device->model);
$Device->BrowserFamily = ($parsed_ua->ua->family == null ? null : $parsed_ua->ua->family);
$Device->BrowserVersion = ($parsed_ua->ua->toVersion() == null ? null : $parsed_ua->ua->toVersion());
$Device->MobileBrowser = (bool)preg_match($MobileBrowserRegex, $_SERVER['HTTP_USER_AGENT']);
$Device->MobileDevice = (bool)preg_match($MobileDeviceRegex, $_SERVER['HTTP_USER_AGENT']);
return $Device;
}
/**
* Returns the IP address of the client
*
* @return string
*/
public static function getClientIP(): string
{
if(isset($_SERVER['HTTP_CF_CONNECTING_IP']))
{
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
if(isset($_SERVER['HTTP_CLIENT_IP']))
{
return $_SERVER['HTTP_CLIENT_IP'];
}
if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
{
return $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if(isset($_SERVER['HTTP_X_FORWARDED']))
{
return $_SERVER['HTTP_X_FORWARDED'];
}
if(isset($_SERVER['HTTP_FORWARDED_FOR']))
{
return $_SERVER['HTTP_FORWARDED_FOR'];
}
if(isset($_SERVER['HTTP_FORWARDED']))
{
return $_SERVER['HTTP_FORWARDED'];
}
if(isset($_SERVER['REMOTE_ADDR']))
{
return $_SERVER['REMOTE_ADDR'];
}
if(getenv('HTTP_CLIENT_IP') !== False)
{
return getenv('HTTP_CLIENT_IP');
}
if(getenv('HTTP_X_FORWARDED_FOR'))
{
return getenv('HTTP_X_FORWARDED_FOR');
}
if(getenv('HTTP_X_FORWARDED'))
{
return getenv('HTTP_X_FORWARDED');
}
if(getenv('HTTP_FORWARDED_FOR'))
{
return getenv('HTTP_FORWARDED_FOR');
}
if(getenv('HTTP_FORWARDED'))
{
return getenv('HTTP_FORWARDED');
}
if(getenv('REMOTE_ADDR'))
{
return getenv('REMOTE_ADDR');
}
return '127.0.0.1';
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace khm\Classes;
class RegexLoader
{
/**
* Loads the mobile1.regex file
*
* @return string
*/
public static function getMobile1(): string
{
$path = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'mobile1.regex';
return file_get_contents($path);
}
/**
* Loads the mobile1.regex file
*
* @return string
*/
public static function getMobile2(): string
{
$path = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'mobile2.regex';
return file_get_contents($path);
}
/**
* Loads the regexes.json
*
* @return array
*/
public static function getRegex(): array
{
$path = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'regexes.json';
return json_decode(file_get_contents($path), true);
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace khm\Classes;
use khm\Abstracts\AbstractParser;
use khm\Objects\UserAgent;
class UserAgentParser extends AbstractParser
{
/**
* Attempts to see if the user agent matches a user agents regex
* @noinspection PhpStrFunctionsInspection
* @noinspection RedundantSuppression
*/
public function parseUserAgent(string $userAgent, array $jsParseBits = []): UserAgent
{
$ua = new UserAgent();
if (isset($jsParseBits['js_user_agent_family']) && $jsParseBits['js_user_agent_family'])
{
$ua->family = $jsParseBits['js_user_agent_family'];
$ua->major = $jsParseBits['js_user_agent_v1'];
$ua->minor = $jsParseBits['js_user_agent_v2'];
$ua->patch = $jsParseBits['js_user_agent_v3'];
}
else
{
[$regex, $matches] = self::tryMatch($this->regexes['user_agent_parsers'], $userAgent);
if ($matches)
{
$ua->family = self::multiReplace($regex, 'family_replacement', $matches[1], $matches) ?? $ua->family;
$ua->major = self::multiReplace($regex, 'v1_replacement', $matches[2], $matches);
$ua->minor = self::multiReplace($regex, 'v2_replacement', $matches[3], $matches);
$ua->patch = self::multiReplace($regex, 'v3_replacement', $matches[4], $matches);
}
}
if (isset($jsParseBits['js_user_agent_string']))
{
$jsUserAgentString = $jsParseBits['js_user_agent_string'];
if (strpos($jsUserAgentString, 'Chrome/') !== false && strpos($userAgent, 'chromeframe') !== false)
{
$override = $this->parseUserAgent($jsUserAgentString);
$family = $ua->family;
$ua->family = 'Chrome Frame';
if ($family !== null && $ua->major !== null)
$ua->family .= sprintf(' (%s %s)', $family, $ua->major);
$ua->major = $override->major;
$ua->minor = $override->minor;
$ua->patch = $override->patch;
}
}
return $ua;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace khm\Classes\UserAgentParser;
use khm\Abstracts\AbstractParser;
use khm\Objects\UserAgentDevice;
class DeviceParser extends AbstractParser
{
/**
* Attempts to see if the user agent matches a device regex
*
* @param string $userAgent
* @return UserAgentDevice
*/
public function parseDevice(string $userAgent): UserAgentDevice
{
$device = new UserAgentDevice();
[$regex, $matches] = self::tryMatch($this->regexes['device_parsers'], $userAgent);
if ($matches) {
$device->family = self::multiReplace($regex, 'device_replacement', $matches[1], $matches) ?? $device->family;
$device->brand = self::multiReplace($regex, 'brand_replacement', null, $matches);
$deviceModelDefault = $matches[1] !== 'Other' ? $matches[1] : null;
$device->model = self::multiReplace($regex, 'model_replacement', $deviceModelDefault, $matches);
}
return $device;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace khm\Classes\UserAgentParser;
use khm\Abstracts\AbstractParser;
use khm\Objects\UserAgentOperatingSystem;
class OperatingSystemParser extends AbstractParser
{
/**
* Attempts to see if the user agent matches an operating system regex
*
* @param string $userAgent
* @return UserAgentOperatingSystem
*/
public function parseOperatingSystem(string $userAgent): UserAgentOperatingSystem
{
$os = new UserAgentOperatingSystem();
[$regex, $matches] = self::tryMatch($this->regexes['os_parsers'], $userAgent);
if ($matches) {
$os->family = self::multiReplace($regex, 'os_replacement', $matches[1], $matches) ?? $os->family;
$os->major = self::multiReplace($regex, 'os_v1_replacement', $matches[2], $matches);
$os->minor = self::multiReplace($regex, 'os_v2_replacement', $matches[3], $matches);
$os->patch = self::multiReplace($regex, 'os_v3_replacement', $matches[4], $matches);
$os->patchMinor = self::multiReplace($regex, 'os_v4_replacement', $matches[5], $matches);
}
return $os;
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace khm\Classes\UserAgentParser;
class Parser extends AbstractParser
{
/**
* @var DeviceParser
*/
private $deviceParser;
/**
* @var OperatingSystemParser
*/
private $operatingSystemParser;
/**
* @var UserAgentParser
*/
private $userAgentParser;
/**
* Start up the parser by importing the data file to $this->regexes
* @throws FileNotFoundException
*/
public function __construct()
{
parent::__construct(Utilities::getUserAgentRegexes());
$this->deviceParser = new DeviceParser($this->regexes);
$this->operatingSystemParser = new OperatingSystemParser($this->regexes);
$this->userAgentParser = new UserAgentParser($this->regexes);
}
/**
* Sets up some standard variables as well as starts the user agent parsing process
*
* @param string $userAgent
* @param array $jsParseBits
* @return UserAgentClient
*/
public function parse(string $userAgent, array $jsParseBits = []): UserAgentClient
{
$client = new UserAgentClient($userAgent);
$client->ua = $this->userAgentParser->parseUserAgent($userAgent, $jsParseBits);
$client->os = $this->operatingSystemParser->parseOperatingSystem($userAgent);
$client->device = $this->deviceParser->parseDevice($userAgent);
return $client;
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace khm\Exceptions;
use Exception;
use Throwable;
class DeviceRecordNotFoundException extends Exception
{
/**
* @param $message
* @param $code
* @param Throwable|null $previous
* @noinspection PhpMissingParamTypeInspection
*/
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->message = $message;
$this->code = $code;
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace khm\Exceptions;
use Exception;
use Throwable;
class InvalidSearchMethodException extends Exception
{
/**
* @param $message
* @param $code
* @param Throwable|null $previous
* @noinspection PhpMissingParamTypeInspection
*/
public function __construct($message="", $code=0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->message = $message;
$this->code = $code;
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace khm\Exceptions;
use Exception;
use Throwable;
class NoUserAgentProvidedException extends Exception
{
/**
* @param $message
* @param $code
* @param Throwable|null $previous
* @noinspection PhpMissingParamTypeInspection
*/
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->message = $message;
$this->code = $code;
}
}

View File

@ -0,0 +1,173 @@
<?php
namespace khm\Managers;
use khm\Abstracts\SearchMethods\DeviceSearchMethod;
use khm\Exceptions\DatabaseException;
use khm\Exceptions\DeviceRecordNotFoundException;
use khm\Exceptions\InvalidSearchMethodException;
use khm\khm;
use khm\Objects\Device;
use msqg\QueryBuilder;
use ZiProto\ZiProto;
class DevicesManager
{
/**
* @var khm
*/
private $khm;
/**
* @param khm $khm
*/
public function __construct(khm $khm)
{
$this->khm = $khm;
}
/**
* Registers a new record into the database
*
* @param Device $device
* @return Device
* @throws DatabaseException
* @noinspection PhpCastIsUnnecessaryInspection
*/
public function registerRecord(Device $device): Device
{
$device->LastSeenTimestamp = time();
$device->CreatedTimestamp = time();
if($device->Properties == null)
$device->Properties = new Device\Properties();
$Query = QueryBuilder::insert_into('devices', [
'fingerprint' => $this->khm->getDatabase()->real_escape_string($device->Fingerprint),
'user_agent' => $this->khm->getDatabase()->real_escape_string($device->UserAgent),
'os_family' => ($device->OperatingSystemFamily == null ? null : $this->khm->getDatabase()->real_escape_string($device->OperatingSystemFamily)),
'os_version' => ($device->OperatingSystemVersion == null ? null : $this->khm->getDatabase()->real_escape_string($device->OperatingSystemVersion)),
'device_family' => ($device->DeviceFamily == null ? null : $this->khm->getDatabase()->real_escape_string($device->DeviceFamily)),
'device_brand' => ($device->DeviceBrand == null ? null : $this->khm->getDatabase()->real_escape_string($device->DeviceBrand)),
'device_model' => ($device->DeviceModel == null ? null : $this->khm->getDatabase()->real_escape_string($device->DeviceModel)),
'browser_family' => ($device->BrowserFamily == null ? null : $this->khm->getDatabase()->real_escape_string($device->BrowserFamily)),
'browser_version' => ($device->BrowserVersion == null ? null : $this->khm->getDatabase()->real_escape_string($device->BrowserVersion)),
'mobile_browser' => ($device->MobileBrowser == null ? null : $this->khm->getDatabase()->real_escape_string($device->MobileBrowser)),
'mobile_device' => ($device->MobileDevice == null ? null : $this->khm->getDatabase()->real_escape_string($device->MobileDevice)),
'properties' => $this->khm->getDatabase()->real_escape_string(ZiProto::encode($device->Properties->toArray())),
'last_seen_timestamp' => (int)$device->LastSeenTimestamp,
'created_timestamp' => (int)$device->CreatedTimestamp
]);
$QueryResults = $this->khm->getDatabase()->query($Query);
if($QueryResults == false)
{
throw new DatabaseException($Query, $this->khm->getDatabase()->error);
}
return $device;
}
/**
* Returns an existing record from the database
*
* @throws InvalidSearchMethodException
* @throws DeviceRecordNotFoundException
* @throws DatabaseException
*/
public function getRecord(string $search_method, string $value): Device
{
switch($search_method)
{
case DeviceSearchMethod::ByFingerprint:
case DeviceSearchMethod::ByUserAgent:
$search_method = $this->khm->getDatabase()->real_escape_string($search_method);
$value = $this->khm->getDatabase()->real_escape_string($value);
break;
default:
throw new InvalidSearchMethodException('The given search method \'' . $search_method . '\' is not affected');
}
$Query = QueryBuilder::select('devices', [
'fingerprint',
'user_agent',
'os_family',
'device_family',
'device_brand',
'device_model',
'browser_family',
'browser_version',
'mobile_browser',
'mobile_device',
'properties',
'last_seen_timestamp',
'created_timestamp'
], $search_method, $value);
$QueryResults = $this->khm->getDatabase()->query($Query);
if($QueryResults == false)
{
throw new DatabaseException($Query, $this->khm->getDatabase()->error);
}
$Row = $QueryResults->fetch_array(MYSQLI_ASSOC);
if ($Row == False)
{
throw new DeviceRecordNotFoundException('The device record wa was not found');
}
$Row['properties'] = ZiProto::decode($Row['properties']);
return Device::fromArray($Row);
}
/**
* Updates the properties of a device
*
* @param Device $device
* @return void
* @throws DatabaseException
*/
public function updateDeviceProperties(Device $device)
{
$Query = QueryBuilder::update('devices', [
'properties' => $this->khm->getDatabase()->real_escape_string(ZiProto::encode($device->toArray()))
], 'fingerprint', $this->khm->getDatabase()->real_escape_string($device->Fingerprint));
$QueryResults = $this->khm->getDatabase()->query($Query);
if($QueryResults == false)
{
throw new DatabaseException($Query, $this->khm->getDatabase()->error);
}
}
/**
* Updates the last seen of a device
*
* @param Device $device
* @return void
* @throws DatabaseException
*/
public function updateLastSeen(Device $device): Device
{
$device->LastSeenTimestamp = time();
$Query = QueryBuilder::update('devices', [
'last_seen_timestamp' => $device->LastSeenTimestamp
], 'fingerprint', $this->khm->getDatabase()->real_escape_string($device->Fingerprint));
$QueryResults = $this->khm->getDatabase()->query($Query);
if($QueryResults == false)
{
throw new DatabaseException($Query, $this->khm->getDatabase()->error);
}
return $device;
}
}

188
src/khm/Objects/Device.php Normal file
View File

@ -0,0 +1,188 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace khm\Objects;
use khm\Objects\Device\Properties;
class Device
{
/**
* The unique fingerprint for the device
*
* @var string
*/
public $Fingerprint;
/**
* The raw user agent string returned by the client
*
* @var string
*/
public $UserAgent;
/**
* The detected operating system family
*
* @var string|null
*/
public $OperatingSystemFamily;
/**
* The detected operating system version
*
* @var string|null
*/
public $OperatingSystemVersion;
/**
* The detected device family
*
* @var string|null
*/
public $DeviceFamily;
/**
* The detected device brand
*
* @var string|null
*/
public $DeviceBrand;
/**
* The detected device model
*
* @var string|null
*/
public $DeviceModel;
/**
* The detected version of the browser family
*
* @var string|null
*/
public $BrowserFamily;
/**
* The detected version of the browser
*
* @var string|null
*/
public $BrowserVersion;
/**
* Indicates if the browser is a mobile browser
*
* @var bool
*/
public $MobileBrowser;
/**
* Indicates if the device is a mobile device
*
* @var bool
*/
public $MobileDevice;
/**
* Properties associated with this device
*
* @var Properties
*/
public $Properties;
/**
* The Unix Timestamp for when this device was last seen
*
* @var int
*/
public $LastSeenTimestamp;
/**
* The Unix Timestamp for when this record was first created
*
* @var int
*/
public $CreatedTimestamp;
/**
* Returns an array representation of the object
*
* @return array
*/
public function toArray(): array
{
return [
'fingerprint' => $this->Fingerprint,
'user_agent' => $this->UserAgent,
'os_family' => $this->OperatingSystemFamily,
'os_version' => $this->OperatingSystemVersion,
'device_family' => $this->DeviceFamily,
'device_brand' => $this->DeviceBrand,
'device_model' => $this->DeviceModel,
'browser_family' => $this->BrowserFamily,
'browser_version' => $this->BrowserVersion,
'mobile_browser' => $this->MobileBrowser,
'mobile_device' => $this->MobileDevice,
'properties' => $this->Properties->toArray(),
'last_seen_timestamp' => $this->LastSeenTimestamp,
'created_timestamp' => $this->CreatedTimestamp
];
}
/**
* Constructs object from an array representation
*
* @param array $data
* @return Device
*/
public static function fromArray(array $data): Device
{
$DeviceObject = new Device();
if(isset($data['fingerprint']))
$DeviceObject->Fingerprint = $data['fingerprint'];
if(isset($data['user_agent']))
$DeviceObject->UserAgent = $data['user_agent'];
if(isset($data['os_family']))
$DeviceObject->OperatingSystemFamily = $data['os_version'];
if(isset($data['os_version']))
$DeviceObject->OperatingSystemVersion = $data['os_version'];
if(isset($data['device_family']))
$DeviceObject->DeviceFamily = $data['device_family'];
if(isset($data['device_brand']))
$DeviceObject->DeviceBrand = $data['device_brand'];
if(isset($data['device_model']))
$DeviceObject->DeviceModel = $data['device_model'];
if(isset($data['browser_family']))
$DeviceObject->BrowserFamily = $data['browser_family'];
if(isset($data['browser_version']))
$DeviceObject->BrowserVersion = $data['browser_version'];
if(isset($data['mobile_browser']))
$DeviceObject->MobileBrowser = $data['mobile_browser'];
if(isset($data['mobile_device']))
$DeviceObject->MobileDevice = $data['mobile_device'];
if(isset($data['properties']))
$DeviceObject->Properties = Properties::fromArray($data['properties']);
if(isset($data['last_seen_timestamp']))
$DeviceObject->LastSeenTimestamp = (int)$data['last_seen_timestamp'];
if(isset($data['created_timestamp']))
$DeviceObject->CreatedTimestamp = (int)$data['created_timestamp'];
return $DeviceObject;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace khm\Objects\Device;
class Properties
{
/**
* Returns an array representation of the object
*
* @return array
*/
public function toArray(): array
{
return [];
}
/**
* Constructs object from an array representation
*
* @param array $data
* @return Properties
*/
public static function fromArray(array $data): Properties
{
$PropertiesObject = new Properties();
return $PropertiesObject;
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace khm\Objects;
use khm\Abstracts\HostFlags;
class QueryResults
{
/**
* The queried IP address
*
* @var string
*/
public $IPAddress;
/**
* The version of the IP address
*
* @var int
*/
public $IPVersion;
/**
* Information about the IP addresses onion relay if available, otherwise null.
*
* @var OnionRelay|null
*/
public $Onion;
/**
* Information about the IP addresses geolocation if available, otherwise null.
*
* @var GeoLookup|null
*/
public $Geo;
/**
* Information about the abuse status of the IP address
*
* @var AbuseCheck|null
*/
public $Abuse;
/**
* Flags associated with this IP address query
*
* @var string[]|HostFlags[]
*/
public $Flags;
}

View File

@ -0,0 +1,37 @@
<?php
namespace khm\Objects;
use khm\Abstracts\AbstractVersionedSoftware;
class UserAgent extends AbstractVersionedSoftware
{
/**
* @var string|null
*/
public $family;
/**
* @var string|null
*/
public $major;
/**
* @var string|null
*/
public $minor;
/**
* @var string|null
*/
public $patch;
/**
* @return string
*/
public function toVersion(): string
{
return $this->formatVersion($this->major, $this->minor, $this->patch);
}
}

View File

@ -0,0 +1,46 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace khm\Objects;
use khm\Abstracts\AbstractClient;
class UserAgentClient extends AbstractClient
{
/**
* @var UserAgent
*/
public $ua;
/**
* @var UserAgentOperatingSystem
*/
public $os;
/**
* @var UserAgentDevice
*/
public $device;
/**
* @var string
*/
public $originalUserAgent;
/**
* @param string $originalUserAgent
*/
public function __construct(string $originalUserAgent)
{
$this->originalUserAgent = $originalUserAgent;
}
/**
* @return string
*/
public function toString(): string
{
return $this->ua->toString().'/'.$this->os->toString();
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace khm\Objects;
use khm\Abstracts\AbstractSoftware;
class UserAgentDevice extends AbstractSoftware
{
/**
* @var string|null
*/
public $brand;
/**
* @var string|null
*/
public $model;
}

View File

@ -0,0 +1,38 @@
<?php
namespace khm\Objects;
use khm\Abstracts\AbstractVersionedSoftware;
class UserAgentOperatingSystem extends AbstractVersionedSoftware
{
/**
* @var string|null
*/
public $major;
/**
* @var string|null
*/
public $minor;
/**
* @var string|null
*/
public $patch;
/**
* @var string|null
*/
public $patchMinor;
/**
* @return string
*/
public function toVersion(): string
{
return $this->formatVersion($this->major, $this->minor, $this->patch, $this->patchMinor);
}
}

View File

@ -0,0 +1 @@
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i

View File

@ -0,0 +1 @@
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i

5801
src/khm/data/regexes.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@
use khm\Exceptions\BadGeoSourceException;
use khm\Exceptions\GeoRecordNotFoundException;
use khm\Managers\AbuseManager;
use khm\Managers\DevicesManager;
use khm\Managers\GeoManager;
use khm\Managers\OnionManager;
use khm\Objects\AbuseCheck;
@ -66,6 +67,11 @@
*/
private $OnionManager;
/**
* @var DevicesManager
*/
private $DevicesManager;
/**
* @throws ConfigurationNotDefinedException
*/
@ -101,6 +107,7 @@
$this->AbuseManager = new AbuseManager($this);
$this->GeoManager = new GeoManager($this);
$this->OnionManager = new OnionManager($this);
$this->DevicesManager = new DevicesManager($this);
}
/**
@ -338,4 +345,12 @@
}
}
}
/**
* @return DevicesManager
*/
public function getDevicesManager(): DevicesManager
{
return $this->DevicesManager;
}
}

View File

@ -41,6 +41,18 @@
}
},
"components": [
{
"required": true,
"file": "Abstracts/AbstractSoftware.php"
},
{
"required": true,
"file": "Abstracts/SearchMethods/DeviceSearchMethod.php"
},
{
"required": true,
"file": "Abstracts/AbstractParser.php"
},
{
"required": true,
"file": "Abstracts/GeoLookupSource.php"
@ -49,14 +61,54 @@
"required": true,
"file": "Abstracts/OnionVersionStatus.php"
},
{
"required": true,
"file": "Abstracts/AbstractClient.php"
},
{
"required": true,
"file": "Abstracts/AbstractVersionedSoftware.php"
},
{
"required": true,
"file": "Abstracts/HostFlags.php"
},
{
"required": true,
"file": "Classes/UserAgentParser.php"
},
{
"required": true,
"file": "Classes/DeviceDetection.php"
},
{
"required": true,
"file": "Classes/Curl.php"
},
{
"required": true,
"file": "Classes/RegexLoader.php"
},
{
"required": true,
"file": "Classes/UserAgentParser/DeviceParser.php"
},
{
"required": true,
"file": "Classes/UserAgentParser/OperatingSystemParser.php"
},
{
"required": true,
"file": "Classes/UserAgentParser/Parser.php"
},
{
"required": true,
"file": "khm.php"
},
{
"required": true,
"file": "Exceptions/NoUserAgentProvidedException.php"
},
{
"required": true,
"file": "Exceptions/AbuseRecordNotFoundException.php"
@ -73,6 +125,10 @@
"required": true,
"file": "Exceptions/KhmResolutionException.php"
},
{
"required": true,
"file": "Exceptions/DeviceRecordNotFoundException.php"
},
{
"required": true,
"file": "Exceptions/OnionRecordNotFoundException.php"
@ -81,6 +137,10 @@
"required": true,
"file": "Exceptions/BadGeoSourceException.php"
},
{
"required": true,
"file": "Exceptions/InvalidSearchMethodException.php"
},
{
"required": true,
"file": "ThirdParty/TorProject.php"
@ -97,10 +157,38 @@
"required": true,
"file": "ThirdParty/IpAPIco.php"
},
{
"required": true,
"file": "Objects/UserAgentDevice.php"
},
{
"required": true,
"file": "Objects/UserAgent.php"
},
{
"required": true,
"file": "Objects/UserAgentClient.php"
},
{
"required": true,
"file": "Objects/QueryResults.php"
},
{
"required": true,
"file": "Objects/Device.php"
},
{
"required": true,
"file": "Objects/AbuseCheck.php"
},
{
"required": true,
"file": "Objects/Device/Properties.php"
},
{
"required": true,
"file": "Objects/UserAgentOperatingSystem.php"
},
{
"required": true,
"file": "Objects/GeoLookup.php"
@ -120,10 +208,17 @@
{
"required": true,
"file": "Managers/OnionManager.php"
},
{
"required": true,
"file": "Managers/DevicesManager.php"
}
],
"files": [
"package.json",
"data/asn.json"
"data/asn.json",
"data/mobile2.regex",
"data/regexes.json",
"data/mobile1.regex"
]
}