Updates to RDAP structure

This commit is contained in:
Pinga 2025-04-23 12:59:09 +03:00
parent db00aaffeb
commit 2847d04942
3 changed files with 145 additions and 70 deletions

View file

@ -92,25 +92,18 @@ function mapContactToVCard($contactDetails, $role, $c) {
'remarks' => [ 'remarks' => [
[ [
"description" => [ "description" => [
"This object's data has been partially omitted for privacy.", "REDACTED FOR PRIVACY",
"Only the registrar managing the record can view personal contact data." "Visit www.icann.org/privacy for details."
],
"links" => [
[
"href" => "https://namingo.org",
"rel" => "alternate",
"type" => "text/html"
]
], ],
"title" => "REDACTED FOR PRIVACY", "title" => "REDACTED FOR PRIVACY",
"type" => "Details are withheld due to privacy restrictions." "type" => "object redacted due to authorization"
], ],
[ [
"description" => [ "description" => [
"To obtain contact information for the domain registrant, please refer to the Registrar of Record's RDDS service as indicated in this report." "To obtain contact information for the domain registrant, please refer to the Registrar of Record's RDDS service as indicated in this report."
], ],
"title" => "EMAIL REDACTED FOR PRIVACY", "title" => "EMAIL REDACTED FOR PRIVACY",
"type" => "Details are withheld due to privacy restrictions." "type" => "object truncated due to authorization"
], ],
], ],
'vcardArray' => [ 'vcardArray' => [
@ -119,7 +112,7 @@ function mapContactToVCard($contactDetails, $role, $c) {
['version', new stdClass(), 'text', '4.0'], ['version', new stdClass(), 'text', '4.0'],
["fn", new stdClass(), 'text', $disclose_name ? $contactDetails['name'] : "REDACTED FOR PRIVACY"], ["fn", new stdClass(), 'text', $disclose_name ? $contactDetails['name'] : "REDACTED FOR PRIVACY"],
["org", new stdClass(), 'text', $disclose_org ? $contactDetails['org'] : "REDACTED FOR PRIVACY"], ["org", new stdClass(), 'text', $disclose_org ? $contactDetails['org'] : "REDACTED FOR PRIVACY"],
["adr", ["CC" => strtoupper($contactDetails['cc'])], 'text', [ // specify "CC" parameter for country code ["adr", ["cc" => strtoupper($contactDetails['cc'])], 'text', [ // specify "cc" parameter for country code
$disclose_addr ? $contactDetails['street1'] : "REDACTED FOR PRIVACY", // Extended address $disclose_addr ? $contactDetails['street1'] : "REDACTED FOR PRIVACY", // Extended address
$disclose_addr ? $contactDetails['street2'] : "REDACTED FOR PRIVACY", // Street address $disclose_addr ? $contactDetails['street2'] : "REDACTED FOR PRIVACY", // Street address
$disclose_addr ? $contactDetails['street3'] : "REDACTED FOR PRIVACY", // Additional street address $disclose_addr ? $contactDetails['street3'] : "REDACTED FOR PRIVACY", // Additional street address
@ -128,8 +121,8 @@ function mapContactToVCard($contactDetails, $role, $c) {
$disclose_addr ? $contactDetails['pc'] : "REDACTED FOR PRIVACY", // Postal code $disclose_addr ? $contactDetails['pc'] : "REDACTED FOR PRIVACY", // Postal code
"" // Add empty last element as required for ADR structure "" // Add empty last element as required for ADR structure
]], ]],
["tel", ["type" => "voice"], 'uri', $disclose_voice ? "tel:" . $contactDetails['voice'] : "REDACTED FOR PRIVACY"], ["tel", ["type" => "voice"], $disclose_voice ? 'uri' : 'text', $disclose_voice ? "tel:" . $contactDetails['voice'] : "REDACTED FOR PRIVACY"],
["tel", ["type" => "fax"], 'uri', $disclose_fax ? "tel:" . $contactDetails['fax'] : "REDACTED FOR PRIVACY"], ["tel", ["type" => "fax"], $disclose_fax ? 'uri' : 'text', $disclose_fax ? "tel:" . $contactDetails['fax'] : "REDACTED FOR PRIVACY"],
["email", new stdClass(), 'text', $disclose_email ? $contactDetails['email'] : "REDACTED FOR PRIVACY"], ["email", new stdClass(), 'text', $disclose_email ? $contactDetails['email'] : "REDACTED FOR PRIVACY"],
] ]
], ],
@ -194,3 +187,43 @@ function mapStatuses(array $statuses): array {
return $statusMap[$status] ?? $status; // Return mapped value or original if not found return $statusMap[$status] ?? $status; // Return mapped value or original if not found
}, $statuses); }, $statuses);
} }
function isValidHostname($hostname) {
$hostname = trim($hostname);
// Convert IDN (Unicode) to ASCII if necessary
if (mb_detect_encoding($hostname, 'ASCII', true) === false) {
$hostname = idn_to_ascii($hostname, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
if ($hostname === false) {
return false; // Invalid IDN conversion
}
}
// Ensure there is at least **one dot** (to prevent single-segment hostnames)
if (substr_count($hostname, '.') < 1) {
return false;
}
// Regular expression for validating a hostname
$pattern = '/^((xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.)*([a-zA-Z0-9-]{1,63}|xn--[a-zA-Z0-9-]{2,63})$/';
// Ensure it matches the hostname pattern
if (!preg_match($pattern, $hostname)) {
return false;
}
// Ensure no label exceeds 63 characters
$labels = explode('.', $hostname);
foreach ($labels as $label) {
if (strlen($label) > 63) {
return false;
}
}
// Ensure full hostname is not longer than 255 characters
if (strlen($hostname) > 255) {
return false;
}
return true;
}

View file

@ -514,12 +514,15 @@ function handleDomainQuery($request, $response, $pdo, $domainName, $c, $log) {
} }
$abuseContactName = ($registrarAbuseDetails) ? $registrarAbuseDetails['first_name'] . ' ' . $registrarAbuseDetails['last_name'] : ''; $abuseContactName = ($registrarAbuseDetails) ? $registrarAbuseDetails['first_name'] . ' ' . $registrarAbuseDetails['last_name'] : '';
$rdapClean = rtrim(preg_replace('#^.*?//#', '', $registrarDetails['rdap_server'] ?? ''), '/');
// Construct the RDAP response in JSON format // Construct the RDAP response in JSON format
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'objectClassName' => 'domain', 'objectClassName' => 'domain',
@ -601,8 +604,8 @@ function handleDomainQuery($request, $response, $pdo, $domainName, $c, $log) {
'type' => 'application/rdap+json', 'type' => 'application/rdap+json',
], ],
[ [
'href' => $registrarDetails['rdap_server'] . 'domain/' . $domain, 'href' => 'https://' . $rdapClean . '/domain/' . $domain,
'value' => $registrarDetails['rdap_server'] . 'domain/' . $domain, 'value' => 'https://' . $rdapClean . '/domain/' . $domain,
'rel' => 'related', 'rel' => 'related',
'type' => 'application/rdap+json', 'type' => 'application/rdap+json',
] ]
@ -918,7 +921,7 @@ function handleEntityQuery($request, $response, $pdo, $entityHandle, $c, $log) {
// Initialize an array to hold entity blocks // Initialize an array to hold entity blocks
$entityBlocks = []; $entityBlocks = [];
// Define an array of allowed contact types // Define an array of allowed contact types
$allowedTypes = ['owner', 'tech', 'abuse']; $allowedTypes = ['owner', 'billing', 'abuse'];
foreach ($contacts as $contact) { foreach ($contacts as $contact) {
// Check if the contact type is one of the allowed types // Check if the contact type is one of the allowed types
@ -929,14 +932,14 @@ function handleEntityQuery($request, $response, $pdo, $entityHandle, $c, $log) {
// Create an entity block for each allowed contact type // Create an entity block for each allowed contact type
$entityBlock = [ $entityBlock = [
'objectClassName' => 'entity', 'objectClassName' => 'entity',
'roles' => [$contact['type']], 'roles' => [($contact['type'] === 'owner') ? 'administrative' : $contact['type']],
"status" => ["active"], "status" => ["active"],
"vcardArray" => [ "vcardArray" => [
"vcard", "vcard",
[ [
['version', new stdClass(), 'text', '4.0'], ['version', new stdClass(), 'text', '4.0'],
["fn", new stdClass(), "text", $fullName], ["fn", new stdClass(), "text", $fullName],
["tel", ["type" => ["voice"]], "uri", "tel:" . $contact['voice']], ["tel", ["type" => ["voice"]], $contact['voice'] ? "uri" : "text", $contact['voice'] ? "tel:" . $contact['voice'] : ""],
["email", new stdClass(), "text", $contact['email']] ["email", new stdClass(), "text", $contact['email']]
] ]
], ],
@ -951,7 +954,9 @@ function handleEntityQuery($request, $response, $pdo, $entityHandle, $c, $log) {
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'objectClassName' => 'entity', 'objectClassName' => 'entity',
@ -1129,11 +1134,11 @@ function handleNameserverQuery($request, $response, $pdo, $nameserverHandle, $c,
} }
} }
// Check for prohibited patterns in nameserver // Validate nameserver
if (!preg_match('/^((xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){2,}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $ns)) { if (!isValidHostname($ns)) {
$response->header('Content-Type', 'application/json'); $response->header('Content-Type', 'application/json');
$response->status(400); // Bad Request $response->status(400); // Bad Request
$response->end(json_encode(['error' => 'Nameserver invalid format'])); $response->end(json_encode(['error' => 'Nameserver format is invalid. Expected a fully qualified domain name (FQDN), punycode supported (e.g., ns1.example.com)']));
return; return;
} }
@ -1298,12 +1303,15 @@ function handleNameserverQuery($request, $response, $pdo, $nameserverHandle, $c,
if (!empty($associated)) { if (!empty($associated)) {
$statuses[] = 'associated'; $statuses[] = 'associated';
} }
$statuses = array_unique(array_map(fn($s) => $s === 'ok' ? 'active' : $s, $statuses));
// Construct the RDAP response in JSON format // Construct the RDAP response in JSON format
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'objectClassName' => 'nameserver', 'objectClassName' => 'nameserver',
@ -1366,8 +1374,9 @@ function handleNameserverQuery($request, $response, $pdo, $nameserverHandle, $c,
'ldhName' => $hostDetails['name'], 'ldhName' => $hostDetails['name'],
'links' => [ 'links' => [
[ [
'href' => $c['rdap_url'] . '/nameserver/' . $hostDetails['name'], 'value' => $c['rdap_url'] . '/nameserver/' . $hostDetails['name'],
'rel' => 'self', 'rel' => 'self',
'href' => $c['rdap_url'] . '/nameserver/' . $hostDetails['name'],
'type' => 'application/rdap+json', 'type' => 'application/rdap+json',
] ]
], ],
@ -1855,7 +1864,9 @@ function handleDomainSearchQuery($request, $response, $pdo, $searchPattern, $c,
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'domainSearchResults' => [ 'domainSearchResults' => [
@ -2117,11 +2128,11 @@ function handleNameserverSearchQuery($request, $response, $pdo, $searchPattern,
} }
} }
// Check for prohibited patterns in nameserver // Validate nameserver
if (!preg_match('/^((xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){2,}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $ns)) { if (!isValidHostname($ns)) {
$response->header('Content-Type', 'application/json'); $response->header('Content-Type', 'application/json');
$response->status(400); // Bad Request $response->status(400); // Bad Request
$response->end(json_encode(['error' => 'Nameserver invalid format'])); $response->end(json_encode(['error' => 'Nameserver format is invalid. Expected a fully qualified domain name (FQDN), punycode supported (e.g., ns1.example.com)']));
return; return;
} }
@ -2339,7 +2350,9 @@ function handleNameserverSearchQuery($request, $response, $pdo, $searchPattern,
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'nameserverSearchResults' => $rdapResult, 'nameserverSearchResults' => $rdapResult,
@ -2466,7 +2479,9 @@ function handleNameserverSearchQuery($request, $response, $pdo, $searchPattern,
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'nameserverSearchResults' => [ 'nameserverSearchResults' => [
@ -2815,7 +2830,7 @@ function handleEntitySearchQuery($request, $response, $pdo, $searchPattern, $c,
// Initialize an array to hold entity blocks // Initialize an array to hold entity blocks
$entityBlocks = []; $entityBlocks = [];
// Define an array of allowed contact types // Define an array of allowed contact types
$allowedTypes = ['owner', 'tech', 'abuse']; $allowedTypes = ['owner', 'billing', 'abuse'];
foreach ($contacts as $contact) { foreach ($contacts as $contact) {
// Check if the contact type is one of the allowed types // Check if the contact type is one of the allowed types
@ -2826,14 +2841,14 @@ function handleEntitySearchQuery($request, $response, $pdo, $searchPattern, $c,
// Create an entity block for each allowed contact type // Create an entity block for each allowed contact type
$entityBlock = [ $entityBlock = [
'objectClassName' => 'entity', 'objectClassName' => 'entity',
'roles' => [$contact['type']], 'roles' => [($contact['type'] === 'owner') ? 'administrative' : $contact['type']],
"status" => ["active"], "status" => ["active"],
"vcardArray" => [ "vcardArray" => [
"vcard", "vcard",
[ [
['version', new stdClass(), 'text', '4.0'], ['version', new stdClass(), 'text', '4.0'],
["fn", new stdClass(), "text", $fullName], ["fn", new stdClass(), "text", $fullName],
["tel", ["type" => ["voice"]], "uri", "tel:" . $contact['voice']], ["tel", ["type" => ["voice"]], $contact['voice'] ? "uri" : "text", $contact['voice'] ? "tel:" . $contact['voice'] : ""],
["email", new stdClass(), "text", $contact['email']] ["email", new stdClass(), "text", $contact['email']]
] ]
], ],
@ -2848,7 +2863,9 @@ function handleEntitySearchQuery($request, $response, $pdo, $searchPattern, $c,
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'entitySearchResults' => [ 'entitySearchResults' => [
@ -2993,9 +3010,11 @@ function handleEntitySearchQuery($request, $response, $pdo, $searchPattern, $c,
function handleHelpQuery($request, $response, $pdo, $c) { function handleHelpQuery($request, $response, $pdo, $c) {
// Set the RDAP conformance levels // Set the RDAP conformance levels
$rdapConformance = [ $rdapConformance = [
"rdap_level_0", 'rdap_level_0',
"icann_rdap_response_profile_1", 'icann_rdap_response_profile_0',
"icann_rdap_technical_implementation_guide_1" 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1',
]; ];
// Set the descriptions and links for the help section // Set the descriptions and links for the help section
@ -3015,13 +3034,15 @@ function handleHelpQuery($request, $response, $pdo, $c) {
], ],
'links' => [ 'links' => [
[ [
'href' => $c['rdap_url'] . '/help', 'value' => $c['rdap_url'] . '/help',
'rel' => 'self', 'rel' => 'self',
'href' => $c['rdap_url'] . '/help',
'type' => 'application/rdap+json', 'type' => 'application/rdap+json',
], ],
[ [
'href' => 'https://namingo.org', 'value' => 'https://namingo.org',
'rel' => 'related', 'rel' => 'related',
'href' => 'https://namingo.org',
'type' => 'application/rdap+json', 'type' => 'application/rdap+json',
] ]
], ],

View file

@ -514,12 +514,15 @@ function handleDomainQuery($request, $response, $pdo, $domainName, $c, $log) {
} }
$abuseContactName = ($registrarAbuseDetails) ? $registrarAbuseDetails['first_name'] . ' ' . $registrarAbuseDetails['last_name'] : ''; $abuseContactName = ($registrarAbuseDetails) ? $registrarAbuseDetails['first_name'] . ' ' . $registrarAbuseDetails['last_name'] : '';
$rdapClean = rtrim(preg_replace('#^.*?//#', '', $registrarDetails['rdap_server'] ?? ''), '/');
// Construct the RDAP response in JSON format // Construct the RDAP response in JSON format
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'objectClassName' => 'domain', 'objectClassName' => 'domain',
@ -575,20 +578,20 @@ function handleDomainQuery($request, $response, $pdo, $domainName, $c, $log) {
], ],
], ],
], ],
!$c['minimum_data'] ? [ !$c['minimum_data']
[ ? array_merge(
mapContactToVCard($registrantDetails, 'registrant', $c) [mapContactToVCard($registrantDetails, 'registrant', $c)],
],
array_map(function ($contact) use ($c) { array_map(function ($contact) use ($c) {
return mapContactToVCard($contact, 'admin', $c); return mapContactToVCard($contact, 'administrative', $c);
}, $adminDetails), }, $adminDetails),
array_map(function ($contact) use ($c) { array_map(function ($contact) use ($c) {
return mapContactToVCard($contact, 'tech', $c); return mapContactToVCard($contact, 'technical', $c);
}, $techDetails), }, $techDetails),
array_map(function ($contact) use ($c) { array_map(function ($contact) use ($c) {
return mapContactToVCard($contact, 'billing', $c); return mapContactToVCard($contact, 'billing', $c);
}, $billingDetails) }, $billingDetails)
] : [] )
: []
), ),
'events' => $events, 'events' => $events,
'handle' => 'D' . $domainDetails['id'] . '-' . $c['roid'] . '', 'handle' => 'D' . $domainDetails['id'] . '-' . $c['roid'] . '',
@ -601,8 +604,8 @@ function handleDomainQuery($request, $response, $pdo, $domainName, $c, $log) {
'type' => 'application/rdap+json', 'type' => 'application/rdap+json',
], ],
[ [
'href' => $registrarDetails['rdap_server'] . 'domain/' . $domain, 'href' => 'https://' . $rdapClean . '/domain/' . $domain,
'value' => $registrarDetails['rdap_server'] . 'domain/' . $domain, 'value' => 'https://' . $rdapClean . '/domain/' . $domain,
'rel' => 'related', 'rel' => 'related',
'type' => 'application/rdap+json', 'type' => 'application/rdap+json',
] ]
@ -918,7 +921,7 @@ function handleEntityQuery($request, $response, $pdo, $entityHandle, $c, $log) {
// Initialize an array to hold entity blocks // Initialize an array to hold entity blocks
$entityBlocks = []; $entityBlocks = [];
// Define an array of allowed contact types // Define an array of allowed contact types
$allowedTypes = ['owner', 'tech', 'abuse']; $allowedTypes = ['owner', 'billing', 'abuse'];
foreach ($contacts as $contact) { foreach ($contacts as $contact) {
// Check if the contact type is one of the allowed types // Check if the contact type is one of the allowed types
@ -929,14 +932,14 @@ function handleEntityQuery($request, $response, $pdo, $entityHandle, $c, $log) {
// Create an entity block for each allowed contact type // Create an entity block for each allowed contact type
$entityBlock = [ $entityBlock = [
'objectClassName' => 'entity', 'objectClassName' => 'entity',
'roles' => [$contact['type']], 'roles' => [($contact['type'] === 'owner') ? 'administrative' : $contact['type']],
"status" => ["active"], "status" => ["active"],
"vcardArray" => [ "vcardArray" => [
"vcard", "vcard",
[ [
['version', new stdClass(), 'text', '4.0'], ['version', new stdClass(), 'text', '4.0'],
["fn", new stdClass(), "text", $fullName], ["fn", new stdClass(), "text", $fullName],
["tel", ["type" => ["voice"]], "uri", "tel:" . $contact['voice']], ["tel", ["type" => ["voice"]], $contact['voice'] ? "uri" : "text", $contact['voice'] ? "tel:" . $contact['voice'] : ""],
["email", new stdClass(), "text", $contact['email']] ["email", new stdClass(), "text", $contact['email']]
] ]
], ],
@ -951,7 +954,9 @@ function handleEntityQuery($request, $response, $pdo, $entityHandle, $c, $log) {
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'objectClassName' => 'entity', 'objectClassName' => 'entity',
@ -1129,11 +1134,11 @@ function handleNameserverQuery($request, $response, $pdo, $nameserverHandle, $c,
} }
} }
// Check for prohibited patterns in nameserver // Validate nameserver
if (!preg_match('/^((xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){2,}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $ns)) { if (!isValidHostname($ns)) {
$response->header('Content-Type', 'application/json'); $response->header('Content-Type', 'application/json');
$response->status(400); // Bad Request $response->status(400); // Bad Request
$response->end(json_encode(['error' => 'Nameserver invalid format'])); $response->end(json_encode(['error' => 'Nameserver format is invalid. Expected a fully qualified domain name (FQDN), punycode supported (e.g., ns1.example.com)']));
return; return;
} }
@ -1298,12 +1303,15 @@ function handleNameserverQuery($request, $response, $pdo, $nameserverHandle, $c,
if (!empty($associated)) { if (!empty($associated)) {
$statuses[] = 'associated'; $statuses[] = 'associated';
} }
$statuses = array_unique(array_map(fn($s) => $s === 'ok' ? 'active' : $s, $statuses));
// Construct the RDAP response in JSON format // Construct the RDAP response in JSON format
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'objectClassName' => 'nameserver', 'objectClassName' => 'nameserver',
@ -1366,8 +1374,9 @@ function handleNameserverQuery($request, $response, $pdo, $nameserverHandle, $c,
'ldhName' => $hostDetails['name'], 'ldhName' => $hostDetails['name'],
'links' => [ 'links' => [
[ [
'href' => $c['rdap_url'] . '/nameserver/' . $hostDetails['name'], 'value' => $c['rdap_url'] . '/nameserver/' . $hostDetails['name'],
'rel' => 'self', 'rel' => 'self',
'href' => $c['rdap_url'] . '/nameserver/' . $hostDetails['name'],
'type' => 'application/rdap+json', 'type' => 'application/rdap+json',
] ]
], ],
@ -1855,7 +1864,9 @@ function handleDomainSearchQuery($request, $response, $pdo, $searchPattern, $c,
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'domainSearchResults' => [ 'domainSearchResults' => [
@ -2117,11 +2128,11 @@ function handleNameserverSearchQuery($request, $response, $pdo, $searchPattern,
} }
} }
// Check for prohibited patterns in nameserver // Validate nameserver
if (!preg_match('/^((xn--[a-zA-Z0-9-]{1,63}|[a-zA-Z0-9-]{1,63})\.){2,}(xn--[a-zA-Z0-9-]{2,63}|[a-zA-Z]{2,63})$/', $ns)) { if (!isValidHostname($ns)) {
$response->header('Content-Type', 'application/json'); $response->header('Content-Type', 'application/json');
$response->status(400); // Bad Request $response->status(400); // Bad Request
$response->end(json_encode(['error' => 'Nameserver invalid format'])); $response->end(json_encode(['error' => 'Nameserver format is invalid. Expected a fully qualified domain name (FQDN), punycode supported (e.g., ns1.example.com)']));
return; return;
} }
@ -2339,7 +2350,9 @@ function handleNameserverSearchQuery($request, $response, $pdo, $searchPattern,
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'nameserverSearchResults' => $rdapResult, 'nameserverSearchResults' => $rdapResult,
@ -2466,7 +2479,9 @@ function handleNameserverSearchQuery($request, $response, $pdo, $searchPattern,
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'nameserverSearchResults' => [ 'nameserverSearchResults' => [
@ -2815,7 +2830,7 @@ function handleEntitySearchQuery($request, $response, $pdo, $searchPattern, $c,
// Initialize an array to hold entity blocks // Initialize an array to hold entity blocks
$entityBlocks = []; $entityBlocks = [];
// Define an array of allowed contact types // Define an array of allowed contact types
$allowedTypes = ['owner', 'tech', 'abuse']; $allowedTypes = ['owner', 'billing', 'abuse'];
foreach ($contacts as $contact) { foreach ($contacts as $contact) {
// Check if the contact type is one of the allowed types // Check if the contact type is one of the allowed types
@ -2826,14 +2841,14 @@ function handleEntitySearchQuery($request, $response, $pdo, $searchPattern, $c,
// Create an entity block for each allowed contact type // Create an entity block for each allowed contact type
$entityBlock = [ $entityBlock = [
'objectClassName' => 'entity', 'objectClassName' => 'entity',
'roles' => [$contact['type']], 'roles' => [($contact['type'] === 'owner') ? 'administrative' : $contact['type']],
"status" => ["active"], "status" => ["active"],
"vcardArray" => [ "vcardArray" => [
"vcard", "vcard",
[ [
['version', new stdClass(), 'text', '4.0'], ['version', new stdClass(), 'text', '4.0'],
["fn", new stdClass(), "text", $fullName], ["fn", new stdClass(), "text", $fullName],
["tel", ["type" => ["voice"]], "uri", "tel:" . $contact['voice']], ["tel", ["type" => ["voice"]], $contact['voice'] ? "uri" : "text", $contact['voice'] ? "tel:" . $contact['voice'] : ""],
["email", new stdClass(), "text", $contact['email']] ["email", new stdClass(), "text", $contact['email']]
] ]
], ],
@ -2848,7 +2863,9 @@ function handleEntitySearchQuery($request, $response, $pdo, $searchPattern, $c,
$rdapResponse = [ $rdapResponse = [
'rdapConformance' => [ 'rdapConformance' => [
'rdap_level_0', 'rdap_level_0',
'icann_rdap_response_profile_0',
'icann_rdap_response_profile_1', 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1', 'icann_rdap_technical_implementation_guide_1',
], ],
'entitySearchResults' => [ 'entitySearchResults' => [
@ -2993,9 +3010,11 @@ function handleEntitySearchQuery($request, $response, $pdo, $searchPattern, $c,
function handleHelpQuery($request, $response, $pdo, $c) { function handleHelpQuery($request, $response, $pdo, $c) {
// Set the RDAP conformance levels // Set the RDAP conformance levels
$rdapConformance = [ $rdapConformance = [
"rdap_level_0", 'rdap_level_0',
"icann_rdap_response_profile_1", 'icann_rdap_response_profile_0',
"icann_rdap_technical_implementation_guide_1" 'icann_rdap_response_profile_1',
'icann_rdap_technical_implementation_guide_0',
'icann_rdap_technical_implementation_guide_1',
]; ];
// Set the descriptions and links for the help section // Set the descriptions and links for the help section
@ -3015,13 +3034,15 @@ function handleHelpQuery($request, $response, $pdo, $c) {
], ],
'links' => [ 'links' => [
[ [
'href' => $c['rdap_url'] . '/help', 'value' => $c['rdap_url'] . '/help',
'rel' => 'self', 'rel' => 'self',
'href' => $c['rdap_url'] . '/help',
'type' => 'application/rdap+json', 'type' => 'application/rdap+json',
], ],
[ [
'href' => 'https://namingo.org', 'value' => 'https://namingo.org',
'rel' => 'related', 'rel' => 'related',
'href' => 'https://namingo.org',
'type' => 'application/rdap+json', 'type' => 'application/rdap+json',
] ]
], ],