├── src ├── Exceptions │ ├── RdapException.php │ ├── InvalidIpException.php │ ├── InvalidRdapResponse.php │ └── RdapRequestTimedOut.php ├── Enums │ ├── IpVersion.php │ ├── EventAction.php │ └── DomainStatus.php ├── Facades │ └── Rdap.php ├── RdapServiceProvider.php ├── CouldNotFindRdapServer.php ├── Responses │ ├── IpResponse.php │ └── DomainResponse.php ├── RdapIp.php ├── RdapDns.php └── Rdap.php ├── LICENSE.md ├── composer.json ├── config └── rdap.php ├── CHANGELOG.md └── README.md /src/Exceptions/RdapException.php: -------------------------------------------------------------------------------- 1 | name('laravel-rdap') 14 | ->hasConfigFile(); 15 | 16 | $this->app->bind('rdap', Rdap::class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Enums/DomainStatus.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Responses/IpResponse.php: -------------------------------------------------------------------------------- 1 | $responseProperties 13 | */ 14 | public function __construct(protected array $responseProperties) 15 | { 16 | } 17 | 18 | /** 19 | * @return array 20 | */ 21 | public function all(): array 22 | { 23 | return $this->responseProperties; 24 | } 25 | 26 | public function get(string $key): mixed 27 | { 28 | return Arr::get($this->responseProperties, $key); 29 | } 30 | 31 | public function registrationDate(): ?Carbon 32 | { 33 | return $this->getEventDate(EventAction::Registration); 34 | } 35 | 36 | public function expirationDate(): ?Carbon 37 | { 38 | return $this->getEventDate(EventAction::Expiration); 39 | } 40 | 41 | public function lastChangedDate(): ?Carbon 42 | { 43 | return $this->getEventDate(EventAction::LastChanged); 44 | } 45 | 46 | public function lastUpdateOfRdapDb(): ?Carbon 47 | { 48 | return $this->getEventDate(EventAction::LastUpdateOfRdapDb); 49 | } 50 | 51 | protected function getEventDate(EventAction $eventAction): ?Carbon 52 | { 53 | $events = $this->get("events"); 54 | 55 | foreach ($events as $event) { 56 | if ($event["eventAction"] === $eventAction->value) { 57 | $dateString = $event["eventDate"]; 58 | 59 | return Carbon::createFromTimeString($dateString); 60 | } 61 | } 62 | 63 | return null; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/RdapIp.php: -------------------------------------------------------------------------------- 1 | ipVersion ??= config('rdap.default_ip_version'); 17 | $this->cacheStoreName ??= config('rdap.' . $this->ipVersion->value . '_servers_cache.store_name') ?? config('cache.default'); 18 | 19 | $this->cacheTtl ??= config('rdap.' . $this->ipVersion->value . '_servers_cache.duration_in_seconds'); 20 | } 21 | 22 | public function getServerForIp(string $ip): ?string 23 | { 24 | $ipServerProperties = collect($this->getAllIpServers())->first( 25 | function ($registries) use ($ip) { 26 | return IpUtils::checkIp($ip, $registries[0]); 27 | } 28 | ); 29 | 30 | if (! $ipServerProperties) { 31 | return null; 32 | } 33 | 34 | $servers = $ipServerProperties[1]; 35 | 36 | return $servers[0] ?? null; 37 | } 38 | 39 | public function getAllIpServers(): array 40 | { 41 | return Cache::store($this->cacheStoreName)->remember( 42 | "laravel-rdap-{$this->ipVersion->value}-servers", 43 | $this->cacheTtl, 44 | function () { 45 | return retry( 46 | times: 3, 47 | callback: fn () => Http::get( 48 | $this->serverJson . $this->ipVersion->value . '.json' 49 | )->json("services"), 50 | sleepMilliseconds: 1000 51 | ); 52 | } 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Responses/DomainResponse.php: -------------------------------------------------------------------------------- 1 | responseProperties; 19 | } 20 | 21 | public function get(string $key): mixed 22 | { 23 | return Arr::get($this->responseProperties, $key); 24 | } 25 | 26 | public function registrationDate(): ?Carbon 27 | { 28 | return $this->getEventDate(EventAction::Registration); 29 | } 30 | 31 | public function expirationDate(): ?Carbon 32 | { 33 | return $this->getEventDate(EventAction::Expiration); 34 | } 35 | 36 | public function lastChangedDate(): ?Carbon 37 | { 38 | return $this->getEventDate(EventAction::LastChanged); 39 | } 40 | 41 | public function lastUpdateOfRdapDb(): ?Carbon 42 | { 43 | return $this->getEventDate(EventAction::LastUpdateOfRdapDb); 44 | } 45 | 46 | public function hasStatus(string|DomainStatus $domainStatus): bool 47 | { 48 | if ($domainStatus instanceof DomainStatus) { 49 | $domainStatus = $domainStatus->value; 50 | } 51 | 52 | $allStatuses = $this->get('status') ?? []; 53 | 54 | return in_array($domainStatus, $allStatuses); 55 | } 56 | 57 | protected function getEventDate(EventAction $eventAction): ?Carbon 58 | { 59 | $events = $this->get('events'); 60 | 61 | foreach ($events as $event) { 62 | if ($event['eventAction'] === $eventAction->value) { 63 | $dateString = $event['eventDate']; 64 | 65 | return Carbon::createFromTimeString($dateString); 66 | } 67 | } 68 | 69 | return null; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-rdap", 3 | "description": "Perform RDAP queries in a Laravel app", 4 | "keywords": [ 5 | "spatie", 6 | "laravel", 7 | "laravel-rdap" 8 | ], 9 | "homepage": "https://github.com/spatie/laravel-rdap", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Freek Van der Herten", 14 | "email": "freek@spatie.be", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.2", 20 | "guzzlehttp/guzzle": "^7.8.1", 21 | "illuminate/cache": "^10.0|^11.0|^12.0", 22 | "illuminate/contracts": "^10.0|^11.0|^12.0", 23 | "illuminate/http": "^10.0|^11.0|^12.0", 24 | "spatie/laravel-package-tools": "^1.16.4" 25 | }, 26 | "require-dev": { 27 | "nunomaduro/collision": "^6.0|^7.10|^8.9", 28 | "larastan/larastan": "^2.9.2|^3.0", 29 | "orchestra/testbench": "^8.22.2|^9.0|^10.0", 30 | "pestphp/pest": "^2.34.6|^3.7", 31 | "pestphp/pest-plugin-laravel": "^2.3|^3.0", 32 | "phpstan/extension-installer": "^1.3.1", 33 | "phpstan/phpstan-deprecation-rules": "^1.1.4|^2.0", 34 | "spatie/laravel-ray": "^1.36" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "Spatie\\Rdap\\": "src", 39 | "Spatie\\Rdap\\Database\\Factories\\": "database/factories" 40 | } 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "Spatie\\Rdap\\Tests\\": "tests" 45 | } 46 | }, 47 | "scripts": { 48 | "analyse": "vendor/bin/phpstan analyse", 49 | "test": "vendor/bin/pest", 50 | "test-coverage": "vendor/bin/pest --coverage", 51 | "baseline": "vendor/bin/phpstan --generate-baseline" 52 | }, 53 | "config": { 54 | "sort-packages": true, 55 | "allow-plugins": { 56 | "phpstan/extension-installer": true, 57 | "pestphp/pest-plugin": true 58 | } 59 | }, 60 | "extra": { 61 | "laravel": { 62 | "providers": [ 63 | "Spatie\\Rdap\\RdapServiceProvider" 64 | ], 65 | "aliases": { 66 | "Rdap": "Spatie\\Rdap\\Facades\\Rdap" 67 | } 68 | } 69 | }, 70 | "minimum-stability": "dev", 71 | "prefer-stable": true 72 | } 73 | -------------------------------------------------------------------------------- /config/rdap.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'store_name' => null, 15 | 'duration_in_seconds' => CarbonInterval::week()->totalSeconds, 16 | ], 17 | 'ipv4_servers_cache' => [ 18 | 'store_name' => null, 19 | 'duration_in_seconds' => CarbonInterval::week()->totalSeconds, 20 | ], 21 | 'ipv6_servers_cache' => [ 22 | 'store_name' => null, 23 | 'duration_in_seconds' => CarbonInterval::week()->totalSeconds, 24 | ], 25 | 26 | /* 27 | * RDAP seem to be a bit unreliable when responding to domain queries. 28 | * We solve this by attempting a request to RDAP a couple of times 29 | * until we get a response. 30 | */ 31 | 'domain_queries' => [ 32 | /* 33 | * How long we should wait per attempt to get a response 34 | */ 35 | 'timeout_in_seconds' => 5, 36 | /* 37 | * How many times we should attempt getting a response 38 | */ 39 | 'retry_times' => 3, 40 | /* 41 | * The time between attempts 42 | */ 43 | 'sleep_in_milliseconds_between_retries' => 1000, 44 | /* 45 | * Configure caching for domain query responses. Set the duration to null to disable it. 46 | */ 47 | 'cache' => [ 48 | 'store_name' => null, 49 | 'duration_in_seconds' => null, 50 | ], 51 | ], 52 | "ip_queries" => [ 53 | /* 54 | * How long we should wait per attempt to get a response 55 | */ 56 | "timeout_in_seconds" => 5, 57 | /* 58 | * How many times we should attempt getting a response 59 | */ 60 | "retry_times" => 3, 61 | /* 62 | * The time between attempts 63 | */ 64 | "sleep_in_milliseconds_between_retries" => 1000, 65 | /* 66 | * Configure caching for IP query responses. Set the duration to null to disable it. 67 | */ 68 | 'cache' => [ 69 | 'store_name' => null, 70 | 'duration_in_seconds' => null, 71 | ], 72 | ], 73 | 74 | /** 75 | * The default IP version to use when no IpVersion is specified for RdapIp. 76 | */ 77 | "default_ip_version" => IpVersion::IpV4, 78 | ]; 79 | -------------------------------------------------------------------------------- /src/RdapDns.php: -------------------------------------------------------------------------------- 1 | cacheStoreName ??= config('rdap.tld_servers_cache.store_name') ?? config('cache.default'); 17 | 18 | $this->cacheTtl ??= config('rdap.tld_servers_cache.duration_in_seconds'); 19 | } 20 | 21 | public function getServerForDomain(string $domain): ?string 22 | { 23 | $tldServerProperties = collect($this->getAllServers()) 24 | ->first(function (array $tldServerProperties) use ($domain) { 25 | $tlds = $tldServerProperties[0]; 26 | 27 | foreach ($tlds as $tld) { 28 | if (str_ends_with($domain, ".{$tld}")) { 29 | return true; 30 | } 31 | } 32 | 33 | return false; 34 | }); 35 | 36 | if (! $tldServerProperties) { 37 | return null; 38 | } 39 | 40 | $servers = $tldServerProperties[1]; 41 | 42 | return $servers[0] ?? null; 43 | } 44 | 45 | public function getServerForTld(string $searchingTld): ?string 46 | { 47 | $tldServerProperties = collect($this->getAllServers()) 48 | ->first(function (array $tldServerProperties) use ($searchingTld) { 49 | return in_array($searchingTld, $tldServerProperties[0]); 50 | }); 51 | 52 | if (! $tldServerProperties) { 53 | return null; 54 | } 55 | 56 | $servers = $tldServerProperties[1]; 57 | 58 | return $servers[0] ?? null; 59 | } 60 | 61 | public function supportedTlds(): array 62 | { 63 | return collect($this->getAllServers()) 64 | ->flatMap(function (array $tldProperties) { 65 | return $tldProperties[0]; 66 | }) 67 | ->unique() 68 | ->sort() 69 | ->values() 70 | ->toArray(); 71 | } 72 | 73 | public function getAllServers(): array 74 | { 75 | return Cache::store($this->cacheStoreName)->remember( 76 | 'laravel-rdap-servers', 77 | $this->cacheTtl, 78 | function () { 79 | return retry( 80 | times: 3, 81 | callback: fn () => Http::get($this->serverJson)->json('services'), 82 | sleepMilliseconds: 1000 83 | ); 84 | } 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-rdap` will be documented in this file. 4 | 5 | ## 1.3.1 - 2025-10-22 6 | 7 | ### What's Changed 8 | 9 | * Allow to set custom RDAP/DNS server by @adumskis in https://github.com/spatie/laravel-rdap/pull/49 10 | 11 | ### New Contributors 12 | 13 | * @adumskis made their first contribution in https://github.com/spatie/laravel-rdap/pull/49 14 | 15 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/1.3.0...1.3.1 16 | 17 | ## 1.3.0 - 2025-09-19 18 | 19 | ### What's Changed 20 | 21 | * Update issue template by @AlexVanderbist in https://github.com/spatie/laravel-rdap/pull/45 22 | * Cache domain responses by @allnetru in https://github.com/spatie/laravel-rdap/pull/47 23 | * Bump actions/checkout from 3 to 5 by @dependabot[bot] in https://github.com/spatie/laravel-rdap/pull/44 24 | * Bump stefanzweifel/git-auto-commit-action from 4 to 6 by @dependabot[bot] in https://github.com/spatie/laravel-rdap/pull/43 25 | 26 | ### New Contributors 27 | 28 | * @AlexVanderbist made their first contribution in https://github.com/spatie/laravel-rdap/pull/45 29 | * @allnetru made their first contribution in https://github.com/spatie/laravel-rdap/pull/47 30 | 31 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/1.2.1...1.3.0 32 | 33 | ## 1.2.1 - 2025-05-25 34 | 35 | - fix breaking change 36 | 37 | ## 1.2.0 - 2025-05-22 38 | 39 | ### What's Changed 40 | 41 | * Bump dependabot/fetch-metadata from 2.3.0 to 2.4.0 by @dependabot in https://github.com/spatie/laravel-rdap/pull/42 42 | * Add IP lookup functionality by @bso-AL in https://github.com/spatie/laravel-rdap/pull/41 43 | 44 | ### New Contributors 45 | 46 | * @bso-AL made their first contribution in https://github.com/spatie/laravel-rdap/pull/41 47 | 48 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/1.1.2...1.2.0 49 | 50 | ## 1.1.2 - 2025-05-04 51 | 52 | ### What's Changed 53 | 54 | * Fix: PHP 8.4 deprecation by @estevao-simoes in https://github.com/spatie/laravel-rdap/pull/40 55 | 56 | ### New Contributors 57 | 58 | * @estevao-simoes made their first contribution in https://github.com/spatie/laravel-rdap/pull/40 59 | 60 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/1.1.1...1.1.2 61 | 62 | ## 1.1.1 - 2025-02-25 63 | 64 | ### What's Changed 65 | 66 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.1.0 by @dependabot in https://github.com/spatie/laravel-rdap/pull/36 67 | * Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/spatie/laravel-rdap/pull/37 68 | * Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/spatie/laravel-rdap/pull/38 69 | * Laravel 12 support by @mbardelmeijer in https://github.com/spatie/laravel-rdap/pull/39 70 | 71 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/1.1.0...1.1.1 72 | 73 | ## 1.1.0 - 2024-03-11 74 | 75 | ### What's Changed 76 | 77 | * Bump ramsey/composer-install from 2 to 3 by @dependabot in https://github.com/spatie/laravel-rdap/pull/34 78 | 79 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/1.0.3...1.1.0 80 | 81 | ## 1.0.3 - 2023-10-31 82 | 83 | ### What's Changed 84 | 85 | - Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/spatie/laravel-rdap/pull/25 86 | - Bump dependabot/fetch-metadata from 1.4.0 to 1.5.0 by @dependabot in https://github.com/spatie/laravel-rdap/pull/26 87 | - Bump dependabot/fetch-metadata from 1.5.0 to 1.5.1 by @dependabot in https://github.com/spatie/laravel-rdap/pull/27 88 | - Bump dependabot/fetch-metadata from 1.5.1 to 1.6.0 by @dependabot in https://github.com/spatie/laravel-rdap/pull/28 89 | - Fix typo in domainIsSupported by @murdercode in https://github.com/spatie/laravel-rdap/pull/29 90 | - English grammar typo by @danswiser in https://github.com/spatie/laravel-rdap/pull/32 91 | - Handle invalid RDAP response by @mbardelmeijer in https://github.com/spatie/laravel-rdap/pull/33 92 | 93 | ### New Contributors 94 | 95 | - @murdercode made their first contribution in https://github.com/spatie/laravel-rdap/pull/29 96 | - @danswiser made their first contribution in https://github.com/spatie/laravel-rdap/pull/32 97 | - @mbardelmeijer made their first contribution in https://github.com/spatie/laravel-rdap/pull/33 98 | 99 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/1.0.2...1.0.3 100 | 101 | ## 1.0.2 - 2023-03-14 102 | 103 | ### What's Changed 104 | 105 | - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/laravel-rdap/pull/23 106 | - Prevent eager matching of tld by @resohead in https://github.com/spatie/laravel-rdap/pull/24 107 | 108 | ### New Contributors 109 | 110 | - @resohead made their first contribution in https://github.com/spatie/laravel-rdap/pull/24 111 | 112 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/1.0.1...1.0.2 113 | 114 | ## 1.0.1 - 2023-01-25 115 | 116 | ### What's Changed 117 | 118 | - Fix typo in readme by @kapersoft in https://github.com/spatie/laravel-rdap/pull/17 119 | - Bump dependabot/fetch-metadata from 1.3.1 to 1.3.3 by @dependabot in https://github.com/spatie/laravel-rdap/pull/19 120 | - Bump dependabot/fetch-metadata from 1.3.3 to 1.3.4 by @dependabot in https://github.com/spatie/laravel-rdap/pull/20 121 | - Bump dependabot/fetch-metadata from 1.3.4 to 1.3.5 by @dependabot in https://github.com/spatie/laravel-rdap/pull/21 122 | - Bump ramsey/composer-install from 1 to 2 by @dependabot in https://github.com/spatie/laravel-rdap/pull/22 123 | 124 | ### New Contributors 125 | 126 | - @kapersoft made their first contribution in https://github.com/spatie/laravel-rdap/pull/17 127 | - @dependabot made their first contribution in https://github.com/spatie/laravel-rdap/pull/19 128 | 129 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/1.0.0...1.0.1 130 | 131 | ## 1.0.0 - 2022-06-14 132 | 133 | - initial release 134 | 135 | ## 0.0.7 - 2022-06-11 136 | 137 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/0.0.6...0.0.7 138 | 139 | ## 0.0.5 - 2022-06-02 140 | 141 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/0.0.4...0.0.5 142 | 143 | ## 0.0.4 - 2022-06-01 144 | 145 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/0.0.2...0.0.4 146 | 147 | ## 0.0.3 - 2022-06-01 148 | 149 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/0.0.2...0.0.3 150 | 151 | ## 0.0.2 - 2022-05-27 152 | 153 | ## What's Changed 154 | 155 | - Small typo by @tplaner in https://github.com/spatie/laravel-rdap/pull/1 156 | 157 | ## New Contributors 158 | 159 | - @tplaner made their first contribution in https://github.com/spatie/laravel-rdap/pull/1 160 | 161 | **Full Changelog**: https://github.com/spatie/laravel-rdap/compare/0.0.1...0.0.2 162 | 163 | ## 0.0.1 - 2022-05-25 164 | 165 | **Full Changelog**: https://github.com/spatie/laravel-rdap/commits/0.0.1 166 | -------------------------------------------------------------------------------- /src/Rdap.php: -------------------------------------------------------------------------------- 1 | domainCacheStoreName ??= config('rdap.domain_queries.cache.store_name') ?? config('cache.default'); 27 | $this->domainCacheTtl ??= config('rdap.domain_queries.cache.duration_in_seconds'); 28 | 29 | $this->ipCacheStoreName ??= config('rdap.ip_queries.cache.store_name') ?? config('cache.default'); 30 | $this->ipCacheTtl ??= config('rdap.ip_queries.cache.duration_in_seconds'); 31 | } 32 | 33 | public function domain( 34 | string $domain, 35 | ?int $timeoutInSeconds = null, 36 | ?int $retryTimes = null, 37 | ?int $sleepInMillisecondsBetweenRetries = null, 38 | ?string $dnsServer = null, 39 | ): ?DomainResponse { 40 | if ($dnsServer === null) { 41 | $dnsServer = $this->rdapDns->getServerForDomain($domain); 42 | } 43 | 44 | if (! $dnsServer) { 45 | throw CouldNotFindRdapServer::forDomain($domain); 46 | } 47 | 48 | $dnsServer = rtrim($dnsServer, '/') . '/'; 49 | 50 | $url = "{$dnsServer}domain/{$domain}"; 51 | 52 | $timeoutInSeconds ??= config('rdap.domain_queries.timeout_in_seconds'); 53 | $retryTimes ??= config('rdap.domain_queries.retry_times'); 54 | $sleepInMillisecondsBetweenRetries ??= config('rdap.domain_queries.sleep_in_milliseconds_between_retries'); 55 | 56 | if (! $this->isDomainCachingEnabled()) { 57 | return $this->performDomainQuery( 58 | $url, 59 | $domain, 60 | $timeoutInSeconds, 61 | $retryTimes, 62 | $sleepInMillisecondsBetweenRetries 63 | ); 64 | } 65 | 66 | return Cache::store($this->domainCacheStoreName) 67 | ->remember( 68 | $this->domainCacheKey($domain, $url), 69 | $this->domainCacheTtl, 70 | fn () => $this->performDomainQuery( 71 | $url, 72 | $domain, 73 | $timeoutInSeconds, 74 | $retryTimes, 75 | $sleepInMillisecondsBetweenRetries 76 | ) 77 | ); 78 | } 79 | 80 | public function ip( 81 | string $ip, 82 | ?int $timeoutInSeconds = null, 83 | ?int $retryTimes = null, 84 | ?int $sleepInMillisecondsBetweenRetries = null 85 | ): ?IpResponse { 86 | $ipVersion = $this->getIpAndVersion($ip); 87 | if (! $ipVersion) { 88 | throw InvalidIpException::make($ip); 89 | } 90 | 91 | if (! isset($this->rdapIp)) { 92 | $this->rdapIp = new RdapIp($ipVersion); 93 | } 94 | 95 | $ipServer = $this->rdapIp->getServerForIp($ip); 96 | if (! $ipServer) { 97 | throw CouldNotFindRdapServer::forIp($ip); 98 | } 99 | 100 | $url = "{$ipServer}ip/{$ip}"; 101 | 102 | $timeoutInSeconds ??= config("rdap.ip_queries.timeout_in_seconds"); 103 | $retryTimes ??= config("rdap.ip_queries.retry_times"); 104 | $sleepInMillisecondsBetweenRetries ??= config( 105 | "rdap.ip_queries.sleep_in_milliseconds_between_retries" 106 | ); 107 | 108 | if (! $this->isIpCachingEnabled()) { 109 | return $this->performIpQuery( 110 | $url, 111 | $ip, 112 | $timeoutInSeconds, 113 | $retryTimes, 114 | $sleepInMillisecondsBetweenRetries 115 | ); 116 | } 117 | 118 | return Cache::store($this->ipCacheStoreName) 119 | ->remember( 120 | $this->ipCacheKey($ip), 121 | $this->ipCacheTtl, 122 | fn () => $this->performIpQuery( 123 | $url, 124 | $ip, 125 | $timeoutInSeconds, 126 | $retryTimes, 127 | $sleepInMillisecondsBetweenRetries 128 | ) 129 | ); 130 | } 131 | 132 | public function domainIsSupported(string $domain): bool 133 | { 134 | return $this->dns()->getServerForDomain($domain) !== null; 135 | } 136 | 137 | public function dns(): RdapDns 138 | { 139 | return $this->rdapDns; 140 | } 141 | 142 | public function rdapIp(): RdapIp 143 | { 144 | return $this->rdapIp; 145 | } 146 | 147 | public function supportedTlds(): array 148 | { 149 | return $this->dns()->supportedTlds(); 150 | } 151 | 152 | protected function getIpAndVersion(string $ip): ?IpVersion 153 | { 154 | $ipV4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); 155 | if ($ipV4) { 156 | return IpVersion::IpV4; 157 | } 158 | 159 | $ipV6 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); 160 | if ($ipV6) { 161 | return IpVersion::IpV6; 162 | } 163 | 164 | return null; 165 | } 166 | 167 | protected function performDomainQuery( 168 | string $url, 169 | string $domain, 170 | int $timeoutInSeconds, 171 | int $retryTimes, 172 | int $sleepInMillisecondsBetweenRetries, 173 | ): ?DomainResponse { 174 | try { 175 | $response = Http::timeout($timeoutInSeconds) 176 | ->retry(times: $retryTimes, sleepMilliseconds: $sleepInMillisecondsBetweenRetries) 177 | ->get($url) 178 | ->json(); 179 | } catch (RequestException $exception) { 180 | if ($exception->getCode() === 404) { 181 | return null; 182 | } 183 | 184 | throw $exception; 185 | } catch (ConnectionException $exception) { 186 | throw RdapRequestTimedOut::make($domain, $exception); 187 | } 188 | 189 | if (empty($response)) { 190 | // Some misconfigured RDAP servers might return (invalid) HTML responses. 191 | // The JSON conversion will return an empty array in that case. 192 | throw InvalidRdapResponse::make($domain); 193 | } 194 | 195 | return new DomainResponse($response); 196 | } 197 | 198 | protected function isDomainCachingEnabled(): bool 199 | { 200 | return $this->domainCacheTtl !== null && $this->domainCacheTtl > 0; 201 | } 202 | 203 | protected function domainCacheKey(string $domain, string $url): string 204 | { 205 | return sprintf('laravel-rdap-domain-%s-%s', $domain, md5($url)); 206 | } 207 | 208 | protected function performIpQuery( 209 | string $url, 210 | string $ip, 211 | int $timeoutInSeconds, 212 | int $retryTimes, 213 | int $sleepInMillisecondsBetweenRetries, 214 | ): ?IpResponse { 215 | try { 216 | $response = Http::timeout($timeoutInSeconds) 217 | ->retry( 218 | times: $retryTimes, 219 | sleepMilliseconds: $sleepInMillisecondsBetweenRetries 220 | ) 221 | ->get($url) 222 | ->json(); 223 | } catch (RequestException $exception) { 224 | if ($exception->getCode() === 404) { 225 | return null; 226 | } 227 | 228 | throw $exception; 229 | } catch (ConnectionException $exception) { 230 | throw RdapRequestTimedOut::make($ip, $exception); 231 | } 232 | 233 | if (empty($response)) { 234 | // Some misconfigured RDAP servers might return (invalid) HTML responses. 235 | // The JSON conversion will return an empty array in that case. 236 | throw InvalidRdapResponse::make($ip); 237 | } 238 | 239 | return new IpResponse($response); 240 | } 241 | 242 | protected function isIpCachingEnabled(): bool 243 | { 244 | return $this->ipCacheTtl !== null && $this->ipCacheTtl > 0; 245 | } 246 | 247 | protected function ipCacheKey(string $ip): string 248 | { 249 | return "laravel-rdap-ip-{$ip}"; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perform RDAP queries in a Laravel app 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-rdap.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-rdap) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-rdap.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-rdap) 5 | 6 | RDAP is a protocol to query domain registration data. It is seen as the successor to WHOIS. The main advantage over WHOIS is that the returned data is standardized and structured as JSON. A downside of RDAP is that, at the moment of writing, not all TLDs are supported. 7 | 8 | This package contains a few classes to query basic data from RDAP. It also provides caching of the responses out of the box. 9 | 10 | ## Support us 11 | 12 | [](https://spatie.be/github-ad-click/laravel-rdap) 13 | 14 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 15 | 16 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 17 | 18 | ## Installation 19 | 20 | You can install the package via composer: 21 | 22 | ```bash 23 | composer require spatie/laravel-rdap 24 | ``` 25 | 26 | 27 | You can publish the config file with: 28 | 29 | ```bash 30 | php artisan vendor:publish --tag="rdap-config" 31 | ``` 32 | 33 | This is the contents of the published config file: 34 | 35 | ```php 36 | [ 48 | 'store_name' => null, 49 | 'duration_in_seconds' => CarbonInterval::week()->totalSeconds, 50 | ], 51 | 52 | /* 53 | * RDAP seem to be a bit unreliable when responding to domain queries. 54 | * We solve this by attempting a request to RDAP a couple of times 55 | * until we get a response. 56 | */ 57 | 'domain_queries' => [ 58 | /* 59 | * How long we should wait per attempt to get a response 60 | */ 61 | 'timeout_in_seconds' => 5, 62 | /* 63 | * How many times we should attempt getting a response 64 | */ 65 | 'retry_times' => 3, 66 | /* 67 | * The time between attempts 68 | */ 69 | 'sleep_in_milliseconds_between_retries' => 1000, 70 | ], 71 | "ip_queries" => [ 72 | /* 73 | * How long we should wait per attempt to get a response 74 | */ 75 | "timeout_in_seconds" => 5, 76 | /* 77 | * How many times we should attempt getting a response 78 | */ 79 | "retry_times" => 3, 80 | /* 81 | * The time between attempts 82 | */ 83 | "sleep_in_milliseconds_between_retries" => 1000, 84 | ], 85 | ]; 86 | ``` 87 | 88 | ## Usage 89 | 90 | 91 | ## Perform a domain query 92 | 93 | To get information about a domain, call `domain()`. 94 | 95 | ```php 96 | use Spatie\Rdap\Facades\Rdap; 97 | 98 | $domain = Rdap::domain('google.com'); // returns an instance of `Spatie\Rdap\Responses\DomainResponse` 99 | ``` 100 | 101 | If you pass a non-existing domain, then the `domain()` function will return `null`. 102 | 103 | ### Retrying requests 104 | 105 | RDAP seem to be a bit unreliable when responding to domain requests. We solve this by attempting a request to RDAP a couple of times until we get a response. In the `rdap` config file, you can set the defaults for the retry mechanism. 106 | 107 | You can override those defaults, by passing extra parameters to `domain`. 108 | 109 | ```php 110 | $domain = Rdap::domain( 111 | 'google.com' 112 | timeoutInSecons: 10, 113 | retryTimes: 4, 114 | sleepInMillisecondsBetweenRetries: 2000, 115 | ); 116 | ```` 117 | 118 | ### Using custom RDAP servers 119 | 120 | You can specify a custom RDAP server instead of the default one by passing the dnsServer parameter. This is useful if you want to query a specific registry’s RDAP endpoint or test against a private server. 121 | 122 | ```php 123 | $domain = Rdap::domain( 124 | 'google.com' 125 | dnsServer: 'https://rdap.verisign.com/com/v1/', 126 | ); 127 | ```` 128 | 129 | ### Get various dates 130 | 131 | On an instance of `DomainResponse` you can call various methods to fetch various dates. All of these methods return an instance of `Carbon\Carbon`. 132 | 133 | ``` 134 | $domain->registrationDate(); 135 | $domain->expirationDate(); 136 | $domain->lastChangedDate(); 137 | $domain->lastUpdateOfRdapDb(); 138 | ``` 139 | 140 | ### Getting all domain properties 141 | 142 | You can get all properties of a `DomainResponse` using `all()`. 143 | 144 | ```php 145 | $properties = $domain->all(); // returns an array 146 | ``` 147 | 148 | To know which properties get returned, take a look at [this json containing the response for google.com](https://github.com/spatie/laravel-rdap/blob/b37a323a2743d7ae21c397367446160900aad517/tests/TestSupport/stubs/google-domain.json). 149 | 150 | ### Getting a specific domain property 151 | 152 | Use `get()` to get a specific domain property. 153 | 154 | ```php 155 | $domain->get('objectClassName'); // returns 'domain' 156 | ``` 157 | 158 | You can use dot notation to reach deeper in the properties. 159 | 160 | ```php 161 | $domain->get('links.0.value'); // returns 'https://rdap.verisign.com/com/v1/domain/GOOGLE.COM' 162 | ``` 163 | 164 | ### Check if a domain as a specific status 165 | 166 | You can check if a domain has a specific status, such as "client transfer prohibited", using the `hasStatus` method. 167 | 168 | ```php 169 | use Spatie\Rdap\Enums\DomainStatus; 170 | 171 | $domain->hasStatus(DomainStatus::ClientTransferProhibited); // returns a boolean 172 | ``` 173 | 174 | ### Check if domain is supported by Rdap 175 | 176 | You can check if Rdap has info about your domain using `domainIsSupported` 177 | 178 | ```php 179 | use Spatie\Rdap\Facades\Rdap; 180 | 181 | Rdap::domainIsSupported('freek.dev'); // returns true; 182 | Rdap::domainIsSupported('spatie.be'); // returns false because 'be' isn't currently a supported tld; 183 | ``` 184 | 185 | ```php 186 | use Spatie\Rdap\Facades\Rdap; 187 | 188 | $domain = Rdap::domain('google.com'); // returns an instance of `Spatie\Rdap\Responses\DomainResponse` 189 | ``` 190 | 191 | If you pass a non-existing domain, then the `domain()` function will return `null`. 192 | 193 | ## Handling errors 194 | 195 | Sometimes RDAP is slow in responding. If a response isn't returned in a timely manner, a `Spatie\Rdap\Exceptions\RdapRequestTimedOut` exception will be thrown. 196 | 197 | Sometimes RDAP servers return with an invalid response. If that happens, a `Spatie\Rdap\Exceptions\InvalidRdapResponse` exception will be thrown. 198 | 199 | Both exceptions implement `Spatie\Rdap\Exceptions\RdapException`. You can catch that exception to handle both cases. 200 | 201 | ## Perform an IP query 202 | 203 | To get information about an IP, call ip(). 204 | 205 | ```php 206 | use Spatie\Rdap\Facades\Rdap; 207 | $ip = Rdap::ip("127.0.0.1"); // returns an instance of `Spatie\Rdap\Responses\IpResponse 208 | ```` 209 | 210 | ### Get various dates from the IpRespones 211 | 212 | On an instance of `IpResponse` you can call various methods to fetch various dates. All of these methods return an instance of `Carbon\Carbon`. 213 | 214 | ```php 215 | $ip->registrationDate(); 216 | $ip->expirationDate(); 217 | $ip->lastChangedDate(); 218 | $ip->lastUpdateOfRdapDb(); 219 | ``` 220 | ## Getting a specific IP property 221 | 222 | Similar to domain, you may call ->get() to get a specific property. 223 | ```php 224 | $ip->get("objectClassName"); // returns ip network 225 | ``` 226 | 227 | ## Working with RDAP DNS 228 | 229 | For each TLD a specific server is used to respond to domain queries. Such a server is called a "DNS server". The official list of all RDAP DNS server is available as JSON [here](https://data.iana.org/rdap/dns.json). 230 | 231 | The `Spatie\Rdap\RdapDns` class can fetch information from that JSON file. Because all above domain methods need to search the approriate DNS server, we cache the list with available DNS servers. By default, the response will be cached for a week. You can configure this caching period in the `rdap` config file. 232 | 233 | You can get info on Rdap DNS via this `dns` function. 234 | 235 | ```php 236 | $rdapDns = Spatie\Rdap\Facades\Rdap::dns(); 237 | ``` 238 | 239 | ### Get the DNS server URL 240 | 241 | To get the DNS server URL for a specific domain call `getServerForDomain`: 242 | 243 | ```php 244 | $rdapDns->getServerForDomain('google.com'); // returns "https://rdap.verisign.com/com/v1/" 245 | ``` 246 | 247 | Alternatively, you can use `getServerForTld` and pass a TLD. 248 | 249 | ```php 250 | $rdapDns->getServerForTld('com'); // returns "https://rdap.verisign.com/com/v1/" 251 | ``` 252 | 253 | If you pass a domain or tld that is not supported, the above methods will return `null`. 254 | 255 | ### Get all supported TLDs 256 | 257 | To get a list of all supported TLDs, call `supportedTlds`. 258 | 259 | ```php 260 | $rdapDns->supportedTlds(); // returns an array with all supported TLDs 261 | ``` 262 | ### Working with IPv4/IPv6 registries 263 | 264 | Similar to RdapDns you may call getAllIPServers() to see all registries. 265 | ```php 266 | use Spatie\Rdap\RdapIpV4; 267 | $ipv4 = new RdapIpV4(); 268 | $ipv4->getAllIpServers(); // returns array> 269 | ``` 270 | To find the server for an IP you may call getServerForIP(). 271 | ```php 272 | use Spatie\Rdap\RdapIpV4; 273 | $ipv4 = new RdapIpV4(); 274 | $ipv4->getServerForIp("216.58.207.206"); // returns "https://rdap.arin.net/registry/" 275 | ``` 276 | ## Testing 277 | 278 | ```bash 279 | composer test 280 | ``` 281 | 282 | ## Changelog 283 | 284 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 285 | 286 | ## Contributing 287 | 288 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 289 | 290 | ## Security Vulnerabilities 291 | 292 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 293 | 294 | ## Credits 295 | 296 | - [Freek Van der Herten](https://github.com/freekmurze) 297 | - [All Contributors](../../contributors) 298 | 299 | ## License 300 | 301 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 302 | --------------------------------------------------------------------------------