mirror of
https://github.com/Xevion/rdap.git
synced 2025-12-06 01:16:00 -06:00
Prototype commit
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.2.2",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"ipaddr.js": "^2.0.1",
|
||||
"next": "13.1.1",
|
||||
"react": "18.2.0",
|
||||
|
||||
2
public/shortcut-icon.svg
Normal file
2
public/shortcut-icon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.9 KiB |
46
src/components/Domain.tsx
Normal file
46
src/components/Domain.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React, {Fragment, FunctionComponent, ReactNode} from "react";
|
||||
import {DomainType} from "./DomainType";
|
||||
import {rdapStatusInfo} from "../constants";
|
||||
|
||||
export type DomainProps = {
|
||||
data: DomainType;
|
||||
};
|
||||
|
||||
const Domain: FunctionComponent<DomainProps> = ({data}: DomainProps) => {
|
||||
const properties: [string | ReactNode, string | ReactNode][] = [];
|
||||
|
||||
if (data.unicodeName) {
|
||||
properties.push(["Name", data.unicodeName]);
|
||||
properties.push(["ASCII Name", data.ldhName]);
|
||||
} else {
|
||||
properties.push(["Name", data.ldhName])
|
||||
}
|
||||
|
||||
if (data.handle) properties.push(["Handle", data.handle]);
|
||||
// if (data.events) properties.push
|
||||
if (data.status) properties.push([
|
||||
"Status",
|
||||
data.status.map((statusKey, index) =>
|
||||
<span title={rdapStatusInfo[statusKey]!} key={index}>
|
||||
{statusKey}
|
||||
</span>)
|
||||
])
|
||||
|
||||
return <div className="card">
|
||||
<div className="card-header">{data.name} ({data.handle})</div>
|
||||
<div className="card-body">
|
||||
<dl>
|
||||
{
|
||||
properties.map(([name, value], index) => {
|
||||
return <Fragment key={index}>
|
||||
<dt>{name}:</dt>
|
||||
<dd className="mt-2 mb-2 ml-6">{value}</dd>
|
||||
</Fragment>
|
||||
}
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Domain;
|
||||
62
src/components/DomainType.tsx
Normal file
62
src/components/DomainType.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import type {FunctionComponent} from "react";
|
||||
import {useMemo} from "react";
|
||||
import Domain from "./Domain";
|
||||
|
||||
export type Link = {
|
||||
value: string;
|
||||
rel: string;
|
||||
href: string;
|
||||
type: string
|
||||
}
|
||||
export type ObjectTypes = 'domain' | 'nameserver' | 'entity' | 'autnum' | 'ip network';
|
||||
|
||||
export type DomainType = {
|
||||
objectClassName: 'domain';
|
||||
handle: string;
|
||||
unicodeName: string;
|
||||
ldhName: string;
|
||||
links: Link[];
|
||||
nameservers: NameserverType[];
|
||||
entities: EntityType[];
|
||||
status: string[]
|
||||
}
|
||||
|
||||
export type NameserverType = {
|
||||
objectClassName: 'nameserver';
|
||||
};
|
||||
export type EntityType = {
|
||||
objectClassName: 'entity';
|
||||
};
|
||||
export type AutnumType = {
|
||||
objectClassName: 'autnum';
|
||||
};
|
||||
export type IpNetworkType = {
|
||||
objectClassName: 'ip network';
|
||||
};
|
||||
|
||||
export type ObjectProps = {
|
||||
data: DomainType | NameserverType | EntityType | AutnumType | IpNetworkType;
|
||||
};
|
||||
|
||||
const GenericObject: FunctionComponent<ObjectProps> = ({data}: ObjectProps) => {
|
||||
switch (data.objectClassName) {
|
||||
case "domain":
|
||||
return <Domain data={data}/>
|
||||
case "autnum":
|
||||
case "entity":
|
||||
case "ip network":
|
||||
case "nameserver":
|
||||
default:
|
||||
return <div className="card my-2">
|
||||
<div className="card-header">Not implemented</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
// const title: string = (data.unicodeName ?? data.ldhName ?? data.handle)?.toUpperCase() ?? "Response";
|
||||
// return <div className="card">
|
||||
// <div className="card-header">{title}</div>
|
||||
// {objectFragment}
|
||||
// </div>
|
||||
}
|
||||
|
||||
export default GenericObject;
|
||||
9
src/components/Events.tsx
Normal file
9
src/components/Events.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import {FunctionComponent} from "react";
|
||||
|
||||
export type Event = {
|
||||
eventAction: string;
|
||||
eventDate: string;
|
||||
}
|
||||
const Events: FunctionComponent<EventsProps> = () => {
|
||||
|
||||
}
|
||||
59
src/constants.ts
Normal file
59
src/constants.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
// see https://www.iana.org/assignments/rdap-json-values
|
||||
import type {ExtendedUri, RdapStatusType, Uri} from "./types";
|
||||
|
||||
export const rdapStatusInfo: Record<RdapStatusType, string> = {
|
||||
"validated": "Signifies that the data of the object instance has been found to be accurate. This type of status is usually found on entity object instances to note the validity of identifying contact information.",
|
||||
"renew prohibited": "Renewal or reregistration of the object instance is forbidden.",
|
||||
"update prohibited": "Updates to the object instance are forbidden.",
|
||||
"transfer prohibited": "Transfers of the registration from one registrar to another are forbidden. This type of status normally applies to DNR domain names.",
|
||||
"delete prohibited": "Deletion of the registration of the object instance is forbidden. This type of status normally applies to DNR domain names.",
|
||||
"proxy": "The registration of the object instance has been performed by a third party. This is most commonly applied to entities.",
|
||||
"private": "The information of the object instance is not designated for public consumption. This is most commonly applied to entities.",
|
||||
"removed": "Some of the information of the object instance has not been made available and has been removed. This is most commonly applied to entities.",
|
||||
"obscured": "Some of the information of the object instance has been altered for the purposes of not readily revealing the actual information of the object instance. This is most commonly applied to entities.",
|
||||
"associated": "The object instance is associated with other object instances in the registry. This is most commonly used to signify that a nameserver is associated with a domain or that an entity is associated with a network resource or domain.",
|
||||
"active": "The object instance is in use. For domain names, it signifies that the domain name is published in DNS. For network and autnum registrations it signifies that they are allocated or assigned for use in operational networks. This maps to the Extensible Provisioning Protocol (EPP) [RFC5730] 'OK' status.",
|
||||
"inactive": "The object instance is not in use. See 'active'.",
|
||||
"locked": "Changes to the object instance cannot be made, including the association of other object instances.",
|
||||
"pending create": "A request has been received for the creation of the object instance but this action is not yet complete.",
|
||||
"pending renew": "A request has been received for the renewal of the object instance but this action is not yet complete.",
|
||||
"pending transfer": "A request has been received for the transfer of the object instance but this action is not yet complete.",
|
||||
"pending update": "A request has been received for the update or modification of the object instance but this action is not yet complete.",
|
||||
"pending delete": "A request has been received for the deletion or removal of the object instance but this action is not yet complete. For domains, this might mean that the name is no longer published in DNS but has not yet been purged from the registry database.",
|
||||
"add period": "This grace period is provided after the initial registration of the object. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the registration. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'addPeriod' status.",
|
||||
"auto renew period": "This grace period is provided after an object registration period expires and is extended (renewed) automatically by the server. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the auto renewal. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'autoRenewPeriod' status.",
|
||||
"client delete prohibited": "The client requested that requests to delete the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'clientDeleteProhibited' status.",
|
||||
"client hold": "The client requested that the DNS delegation information MUST NOT be published for the object. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'clientHold' status.",
|
||||
"client renew prohibited": "The client requested that requests to renew the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'clientRenewProhibited' status.",
|
||||
"client transfer prohibited": "The client requested that requests to transfer the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'clientTransferProhibited' status.",
|
||||
"client update prohibited": "The client requested that requests to update the object (other than to remove this status) MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'clientUpdateProhibited' status.",
|
||||
"pending restore": "An object is in the process of being restored after being in the redemption period state. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'pendingRestore' status.",
|
||||
"redemption period": "A delete has been received, but the object has not yet been purged because an opportunity exists to restore the object and abort the deletion process. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'redemptionPeriod' status.",
|
||||
"renew period": "This grace period is provided after an object registration period is explicitly extended (renewed) by the client. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the renewal. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'renewPeriod' status.",
|
||||
"server delete prohibited": "The server set the status so that requests to delete the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'serverDeleteProhibited' status.",
|
||||
"server renew prohibited": "The server set the status so that requests to renew the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'serverRenewProhibited' status.",
|
||||
"server transfer prohibited": "The server set the status so that requests to transfer the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'serverTransferProhibited' status.",
|
||||
"server update prohibited": "The server set the status so that requests to update the object (other than to remove this status) MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'serverUpdateProhibited' status.",
|
||||
"server hold": "The server set the status so that DNS delegation information MUST NOT be published for the object. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'serverHold' status.",
|
||||
"transfer period": "This grace period is provided after the successful transfer of object registration sponsorship from one client to another client. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the transfer. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'transferPeriod' status."
|
||||
};
|
||||
|
||||
// list of RDAP bootstrap registry URLs
|
||||
export const registryURLs: Record<string, ExtendedUri> = {
|
||||
"https://data.iana.org/rdap/asn.json": "autnum",
|
||||
"https://data.iana.org/rdap/dns.json": "domain",
|
||||
"https://data.iana.org/rdap/ipv4.json": "ip4",
|
||||
"https://data.iana.org/rdap/ipv6.json": "ip6",
|
||||
"https://data.iana.org/rdap/object-tags.json": "entity",
|
||||
};
|
||||
|
||||
export const placeholders: Record<Uri, string> = {
|
||||
'ip': '192.168.0.1/16',
|
||||
'autnum': '65535',
|
||||
'entity': 'ABC123-EXAMPLE',
|
||||
'url': 'https://rdap.org/domain/example.com',
|
||||
'tld': 'example',
|
||||
'registrar': '9999',
|
||||
'json': '{ (paste JSON) }',
|
||||
'domain': 'example.com'
|
||||
}
|
||||
5
src/helpers.ts
Normal file
5
src/helpers.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function truthy(value: string | null | undefined) {
|
||||
if (value == undefined) return false;
|
||||
return value.toLowerCase() == 'true' || value == '1';
|
||||
}
|
||||
|
||||
@@ -1,11 +1,351 @@
|
||||
import {NextPage} from "next";
|
||||
import {type NextPage} from "next";
|
||||
import Head from "next/head";
|
||||
import type {Uri} from "../types";
|
||||
import {placeholders} from "../constants";
|
||||
import {asnMatch, domainMatch, entityMatch, getBestURL, getRDAPURL, getType, ipMatch, showSpinner} from "../rdap";
|
||||
import {useEffect, useState} from "react";
|
||||
import {truthy} from "../helpers";
|
||||
import {registryURLs} from "../constants";
|
||||
import axios, {AxiosResponse} from "axios";
|
||||
import update from "immutability-helper";
|
||||
import GenericObject, {Link} from "../components/DomainType";
|
||||
|
||||
const Index: NextPage = () => {
|
||||
return <main className="flex bg-black items-center justify-center w-screen h-screen">
|
||||
<div className="w-screen text-white text-center">
|
||||
This project is a work-in-progress. Check back soon.
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
const [uriType, setUriType] = useState<Uri>('domain');
|
||||
|
||||
export default Index;
|
||||
|
||||
const [requestJSContact, setRequestJSContact] = useState(false);
|
||||
const [followReferral, setFollowReferral] = useState(false);
|
||||
const [object, setObject] = useState<string>("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [response, setResponse] = useState<any | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [registryData, setRegistryData] = useState<Record<string, Any>>({});
|
||||
|
||||
// Change the selected type automatically
|
||||
useEffect(function () {
|
||||
const new_type = getType(object);
|
||||
if (new_type != null && new_type != uriType)
|
||||
setUriType(new_type)
|
||||
}, [object]);
|
||||
|
||||
async function loadRegistryData() {
|
||||
setLoading(true);
|
||||
const responses = await Promise.all(Object.entries(registryURLs).map(async ([url, registryType]) => {
|
||||
const response = await axios.get(url);
|
||||
return {
|
||||
registryType,
|
||||
response: response.data
|
||||
};
|
||||
}))
|
||||
|
||||
setRegistryData(() => {
|
||||
return Object.fromEntries(
|
||||
responses.map(({registryType, response}) => [registryType, response.services])
|
||||
)
|
||||
})
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
// construct an RDAP URL for the given object
|
||||
function getRDAPURL(object: string): string | null {
|
||||
let urls: string[] = [];
|
||||
|
||||
const service: [string[], string[]][] | [string[], string[], string[]][] = registryData[uriType];
|
||||
services:
|
||||
for (const serviceItem of service) {
|
||||
// special case for object tags, since the registrant email address is in the 0th position
|
||||
const [rangeIndex, urlIndex] = uriType == 'entity' ? [1, 2] : [0, 1];
|
||||
|
||||
for (const tld of serviceItem[rangeIndex]!) {
|
||||
let match = false;
|
||||
|
||||
switch (uriType) {
|
||||
case 'domain':
|
||||
match = domainMatch(tld, object);
|
||||
break;
|
||||
// case "autnum":
|
||||
// match = asnMatch(range, object);
|
||||
// break;
|
||||
// case "entity":
|
||||
// match = entityMatch(range, object);
|
||||
// break;
|
||||
// case "ip":
|
||||
// match = ipMatch(range, object);
|
||||
// break;
|
||||
}
|
||||
|
||||
if (match) {
|
||||
urls = serviceItem[urlIndex]!;
|
||||
break services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// no match
|
||||
if (urls.length == 0) return null;
|
||||
|
||||
let url = getBestURL(urls);
|
||||
// some bootstrap entries have a trailing slash, some don't
|
||||
if (!url.endsWith('/')) url += '/';
|
||||
return `${url + uriType}/${object}`;
|
||||
}
|
||||
|
||||
|
||||
function submit(e) {
|
||||
e?.preventDefault();
|
||||
|
||||
const queryParams = requestJSContact ? '?jscard=1' : '';
|
||||
|
||||
const url = (function () {
|
||||
switch (uriType) {
|
||||
case 'url':
|
||||
return object;
|
||||
case 'tld':
|
||||
return `https://root.rdap.org/domain/${object}${queryParams}`;
|
||||
case 'registrar':
|
||||
return `https://registrars.rdap.org/entity/${object}-IANA${queryParams}`;
|
||||
case 'json':
|
||||
return `json://${object}`
|
||||
case 'domain':
|
||||
const temp = getRDAPURL(object);
|
||||
if (temp) return `${temp}${queryParams}`
|
||||
return null;
|
||||
default:
|
||||
setError(`No RDAP URL available for ${uriType} ${object}.`);
|
||||
return null;
|
||||
}
|
||||
})()
|
||||
|
||||
if (url) sendQuery(url, followReferral);
|
||||
}
|
||||
|
||||
async function sendQuery(url: string, followReferral = false) {
|
||||
setLoading(true);
|
||||
|
||||
if (url.startsWith('json://')) {
|
||||
// run the callback with a mock XHR
|
||||
await handleResponse(JSON.parse(url.substring(7)))
|
||||
} else {
|
||||
try {
|
||||
const response = await axios.get(url, {responseType: "json"})
|
||||
if (response.status == 404)
|
||||
setError('This object does not exist.');
|
||||
else if (response.status != 200)
|
||||
setError(`Error ${response.status}: ${response.statusText}`)
|
||||
await handleResponse(response, followReferral)
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
setError(e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// callback executed when a response is received
|
||||
async function handleResponse(data: { links: Link[] }, followReferral = false) {
|
||||
setLoading(false);
|
||||
|
||||
if (followReferral && data.links != null) {
|
||||
for (const link of data.links) {
|
||||
if ('related' == link.rel && 'application/rdap+json' == link.type && link.href.match(/^(https?:|)\/\//i)) {
|
||||
await sendQuery(link.href, false)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// div.appendChild(processObject(xhr.response, true));
|
||||
setResponse(data);
|
||||
const url = `${window.location.href}?type=${encodeURIComponent(uriType)}&object=${object}&request-jscontact=${requestJSContact ? 1 : 0}&follow-referral=${followReferral ? 1 : 0}`
|
||||
window.history.pushState(null, document.title, url);
|
||||
|
||||
} catch (e) {
|
||||
setError(`Exception: ${e.message} (line ${e.lineNumber})`);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Load parameters from URL query string on page load
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.has('type'))
|
||||
setUriType(params.get('type') as Uri);
|
||||
else if (params.has('object'))
|
||||
setObject(params.get('object')!);
|
||||
|
||||
if (params.has('request-jscontact') && truthy(params.get('request-jscontact')))
|
||||
setRequestJSContact(true);
|
||||
|
||||
if (params.has('follow-referral') && truthy(params.get('follow-referral')))
|
||||
setFollowReferral(true);
|
||||
|
||||
if (params.has('object') && (params.get('object')?.length ?? 0) > 0) {
|
||||
setObject(params.get('object')!);
|
||||
submit(null);
|
||||
}
|
||||
|
||||
|
||||
loadRegistryData().catch(console.error);
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && registryData.domain != undefined)
|
||||
console.log(registryData);
|
||||
}, [loading])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>rdap.xevion.dev</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<link rel="shortcut icon" href="/shortcut-icon.svg"/>
|
||||
<meta name="description" content=""/>
|
||||
<meta name="keywords" content="xevion, rdap, whois, rdap, domain name, dns, ip address"/>
|
||||
</Head>
|
||||
<>
|
||||
<style jsx>{`
|
||||
dd {
|
||||
margin: 0.5em 0 1em 2em;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.rdap-status-code, .rdap-event-time {
|
||||
border-bottom: 1px dashed silver;
|
||||
}
|
||||
|
||||
#object {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
#spinner-msg {
|
||||
height: 2em;
|
||||
display: inline-block;
|
||||
margin: -0.25em 0 0 0;
|
||||
padding: 0.25em 0 0 0;
|
||||
}
|
||||
|
||||
`}</style>
|
||||
<nav className="navbar navbar-expand-lg navbar-dark bg-dark shadow-sm">
|
||||
<span className="text-white" style={{fontSize: 'larger'}}>
|
||||
<a className="navbar-brand" href="#">rdap.xevion.dev</a>
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
<br/>
|
||||
|
||||
<div className="container">
|
||||
<form onSubmit={submit} className="form-inline">
|
||||
<div className="col p-0">
|
||||
<div className="input-group">
|
||||
|
||||
<div className="input-group-prepend">
|
||||
<select className="custom-select" id="type" name="type"
|
||||
value={uriType}
|
||||
onChange={(e) => {
|
||||
setUriType(e.target.value as Uri);
|
||||
}}>
|
||||
<option value="domain">Domain</option>
|
||||
<option value="tld">TLD</option>
|
||||
<option value="ip">IP/CIDR</option>
|
||||
<option value="autnum">AS Number</option>
|
||||
<option value="entity">Entity</option>
|
||||
<option value="registrar">Registrar</option>
|
||||
<option value="url">URL</option>
|
||||
<option value="json">JSON</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<input className="form-control"
|
||||
type="text"
|
||||
placeholder={placeholders[uriType]}
|
||||
disabled={loading}
|
||||
onChange={(e) => {
|
||||
setObject(e.target.value);
|
||||
}} required/>
|
||||
|
||||
<div className="input-group-append">
|
||||
<input id="button" type="button" value="Submit" onClick={submit}
|
||||
className="btn btn-primary"
|
||||
disabled={loading}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="container p-0 italic text-[#aaa]" style={{fontSize: "small"}}>
|
||||
<div className="col pt-3 pb-1">
|
||||
Options:
|
||||
<label htmlFor="request-jscontact">
|
||||
<input name="request-jscontact" id="request-jscontact" type="checkbox"/>
|
||||
Request JSContact
|
||||
</label>
|
||||
|
||||
<label htmlFor="follow-referral">
|
||||
<input name="follow-referral" id="follow-referral" type="checkbox"/>
|
||||
Follow referral to registrar's RDAP record
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="output-div">
|
||||
{response != null ? <GenericObject data={response.data}/> : null}
|
||||
</div>
|
||||
|
||||
<p>This page implements a <em>completely private lookup tool</em> for domain names, IP addresses and
|
||||
Autonymous System Numbers (ASNs). Only the relevant registry sees your query: your browser will
|
||||
directly
|
||||
connect to the registry's RDAP server using an encrypted HTTPS connection to protect the
|
||||
confidentiality of
|
||||
your query. If you click the "Follow referral to registrar's RDAP
|
||||
record" checkbox, then the
|
||||
sponsoring
|
||||
registrar may also see your query.</p>
|
||||
<ul>
|
||||
<li><a href="https://about.rdap.org" target="_new">Click here</a> for more information about
|
||||
what RDAP is
|
||||
and how it differs from traditional Whois.
|
||||
</li>
|
||||
<li>Most generic TLDs now support RDAP, but only a few ccTLDs have deployed RDAP so far. To see
|
||||
which TLDs
|
||||
support RDAP, <a href="https://deployment.rdap.org" target="_new">click here</a>.
|
||||
</li>
|
||||
<li>There is no bootstrap registry for top-level domains or ICANN-accredited registrars; instead
|
||||
these queries are sent to the
|
||||
<a href="https://about.rdap.org/#additional"
|
||||
target="_new">
|
||||
{"{"}root,registrars{"}"}.rdap.org servers
|
||||
</a>.
|
||||
</li>
|
||||
<li>To submit feedback, <a href="mailto:feedback@rdap.org">click here</a>. Please contact the
|
||||
relevant
|
||||
registry or registrar if you have an issue with the content of an RDAP response.
|
||||
</li>
|
||||
<li>This tool is Free Software; for the license, <a href="LICENSE">click here</a>. To fork a
|
||||
copy of the git
|
||||
repository, <a rel="noopener" target="_new"
|
||||
href="https://gitlab.centralnic.com/centralnic/rdap-web-client">click
|
||||
here</a>.
|
||||
</li>
|
||||
<li>This page uses <a rel="noopener" target="_new"
|
||||
href="https://github.com/whitequark/ipaddr.js/">ipaddr.js</a> by <a
|
||||
rel="noopener"
|
||||
target="_new"
|
||||
href="https://whitequark.org/">whitequark</a>.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
||||
|
||||
740
src/rdap.ts
Normal file
740
src/rdap.ts
Normal file
@@ -0,0 +1,740 @@
|
||||
import ipaddr from "ipaddr.js";
|
||||
import {registryURLs, rdapStatusInfo, placeholders} from "./constants";
|
||||
import type {Uri} from "./types";
|
||||
|
||||
// keeps track of how many registries we've loaded
|
||||
let loadedRegistries = 0;
|
||||
|
||||
// registry data is stored in this
|
||||
let registryData = {};
|
||||
|
||||
// keeps track of the elements we've created so we can assign a unique ID
|
||||
let elementCounter = 123456;
|
||||
|
||||
const cardTitles = {
|
||||
"domain": "Domain Name",
|
||||
"ip network": "IP Network",
|
||||
"nameserver": "Nameserver",
|
||||
"entity": "Entity",
|
||||
"autnum": "AS Number",
|
||||
};
|
||||
|
||||
export function domainMatch(tld: string, domain: string): boolean {
|
||||
return domain.toUpperCase().endsWith(`.${tld.toUpperCase()}`);
|
||||
}
|
||||
|
||||
export function asnMatch(range, asn) {
|
||||
var [min, max] = range.split('-', 2);
|
||||
min = parseInt(min);
|
||||
max = parseInt(max);
|
||||
|
||||
return (asn >= min && asn <= max);
|
||||
}
|
||||
|
||||
export function entityMatch(tag: string, handle: string) {
|
||||
return handle.toUpperCase().endsWith('-' + tag.toUpperCase());
|
||||
}
|
||||
|
||||
export function ipMatch(prefix: string, ip: string) {
|
||||
var parsedIp = ipaddr.parse(ip);
|
||||
let cidr = ipaddr.parseCIDR(prefix);
|
||||
return (parsedIp.kind() == cidr[0].kind() && parsedIp.match(cidr));
|
||||
}
|
||||
|
||||
// return the first HTTPS url, or the first URL
|
||||
export function getBestURL(urls: string[]): string {
|
||||
urls.forEach((url) => {
|
||||
if (url.startsWith('https://'))
|
||||
return url;
|
||||
})
|
||||
return urls[0]!;
|
||||
}
|
||||
|
||||
// given a URL, injects that URL into the query input,
|
||||
// and initiates an RDAP query
|
||||
export function runQuery(url) {
|
||||
var type = document.getElementById('type');
|
||||
|
||||
for (var i = 0; i < type.options.length; i++) if ('url' == type.options[i].value) type.selectedIndex = i;
|
||||
document.getElementById('object').value = url;
|
||||
doQuery();
|
||||
}
|
||||
|
||||
export function showSpinner(msg) {
|
||||
msg = msg ? msg : 'Loading...';
|
||||
|
||||
var div = document.getElementById('output-div');
|
||||
div.innerHTML = '';
|
||||
|
||||
var spinner = document.createElement('div');
|
||||
spinner.classList.add('spinner-border');
|
||||
spinner.role = 'status';
|
||||
var span = spinner.appendChild(document.createElement('span'));
|
||||
span.classList.add('sr-only');
|
||||
span.appendChild(document.createTextNode(msg));
|
||||
|
||||
div.appendChild(spinner);
|
||||
|
||||
var msgDiv = document.createElement('div');
|
||||
msgDiv.id = 'spinner-msg';
|
||||
msgDiv.appendChild(document.createTextNode(msg));
|
||||
div.appendChild(msgDiv);
|
||||
}
|
||||
|
||||
// export function handleError(error) {
|
||||
// var div = document.getElementById('output-div');
|
||||
// div.innerHTML = '';
|
||||
// div.appendChild(createErrorNode(error));
|
||||
// }
|
||||
|
||||
export function createErrorNode(error) {
|
||||
el = document.createElement('p');
|
||||
el.classList.add('error', 'alert', 'alert-warning');
|
||||
el.appendChild(document.createTextNode(error));
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
// process an RDAP object. Argument is a JSON object, return
|
||||
// value is an element that can be inserted into the page
|
||||
export function processObject(object, toplevel, followReferral = true) {
|
||||
if (!object) {
|
||||
console.log(object);
|
||||
return false;
|
||||
}
|
||||
|
||||
var dl = document.createElement('dl');
|
||||
|
||||
switch (object.objectClassName) {
|
||||
case 'domain':
|
||||
processDomain(object, dl, toplevel);
|
||||
break;
|
||||
|
||||
case 'nameserver':
|
||||
processNameserver(object, dl, toplevel);
|
||||
break;
|
||||
|
||||
case 'entity':
|
||||
processEntity(object, dl, toplevel);
|
||||
break;
|
||||
|
||||
case 'autnum':
|
||||
processAutnum(object, dl, toplevel);
|
||||
break;
|
||||
|
||||
case 'ip network':
|
||||
processIp(object, dl, toplevel);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (object.errorCode) {
|
||||
return createErrorNode(object.errorCode + ' error: ' + object.title);
|
||||
|
||||
} else {
|
||||
processUnknown(object, dl, toplevel);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var card = document.createElement('div');
|
||||
card.classList.add('card');
|
||||
|
||||
var titleText = '';
|
||||
if (object.unicodeName) {
|
||||
titleText = object.unicodeName.toUpperCase();
|
||||
|
||||
} else if (object.ldhName) {
|
||||
titleText = object.ldhName.toUpperCase();
|
||||
|
||||
} else if (object.handle) {
|
||||
titleText = object.handle.toUpperCase();
|
||||
|
||||
}
|
||||
|
||||
if (object.handle && object.handle != titleText) titleText += ' (' + object.handle + ')';
|
||||
|
||||
if (titleText.length > 0) {
|
||||
titleText = cardTitles[object.objectClassName] + ' ' + titleText;
|
||||
|
||||
} else if (!toplevel) {
|
||||
titleText = cardTitles[object.objectClassName];
|
||||
|
||||
} else {
|
||||
titleText = 'Response';
|
||||
|
||||
}
|
||||
|
||||
var title = document.createElement('div');
|
||||
title.classList.add('card-header', 'font-weight-bold');
|
||||
title.appendChild(document.createTextNode(titleText));
|
||||
card.appendChild(title);
|
||||
|
||||
var body = document.createElement('div');
|
||||
body.classList.add('card-body');
|
||||
|
||||
body.appendChild(dl);
|
||||
|
||||
card.appendChild(body);
|
||||
return card;
|
||||
}
|
||||
|
||||
// simplify the process of adding a name => value to a definition list
|
||||
export function addProperty(dl, name, value) {
|
||||
|
||||
var dt = document.createElement('dt');
|
||||
dt.classList.add('rdap-property-name');
|
||||
dt.appendChild(document.createTextNode(name));
|
||||
dl.appendChild(dt);
|
||||
|
||||
var dd = document.createElement('dd');
|
||||
dd.classList.add('rdap-property-value');
|
||||
if (value instanceof Node) {
|
||||
dd.appendChild(value);
|
||||
|
||||
} else {
|
||||
dd.appendChild(document.createTextNode(String(value)));
|
||||
|
||||
}
|
||||
dl.appendChild(dd);
|
||||
}
|
||||
|
||||
// called by the individual object processors, since all RDAP objects have a similar set of
|
||||
// properties. the first argument is the RDAP object and the second is the <dl> element
|
||||
// being used to display that object.
|
||||
export function processCommonObjectProperties(object, dl) {
|
||||
// if (object.objectClassName) addProperty(dl, 'Object Type:', object.objectClassName);
|
||||
// if (object.handle) addProperty(dl, 'Handle:', object.handle);
|
||||
if (object.status) processStatus(object.status, dl);
|
||||
if (object.events) processEvents(object.events, dl);
|
||||
if (object.entities) processEntities(object.entities, dl);
|
||||
if (object.remarks) processRemarks(object.remarks, dl);
|
||||
if (object.notices) processNotices(object.notices, dl);
|
||||
if (object.links) processLinks(object.links, dl);
|
||||
if (object.lang) addProperty(dl, 'Language:', object.lang);
|
||||
if (object.port43) addProperty(dl, 'Whois Server:', object.port43);
|
||||
if (object.rdapConformance) processrdapConformance(object.rdapConformance, dl);
|
||||
|
||||
var div = document.createElement('div');
|
||||
div.id = 'element-' + ++elementCounter;
|
||||
|
||||
var button = document.createElement('button');
|
||||
button.classList.add('btn', 'btn-secondary');
|
||||
button.appendChild(document.createTextNode('Show'));
|
||||
button.onclick = new Function('showRawData("' + div.id + '");return false');
|
||||
div.appendChild(button);
|
||||
|
||||
var pre = document.createElement('pre');
|
||||
pre.style = 'display:none;visibility:hidden';
|
||||
pre.appendChild(document.createTextNode(JSON.stringify(object, null, 2)));
|
||||
div.appendChild(pre);
|
||||
|
||||
addProperty(dl, 'Raw Data:', div);
|
||||
}
|
||||
|
||||
// call back for "Show Raw Data" button
|
||||
export function showRawData(id) {
|
||||
var div = document.getElementById(id);
|
||||
div.childNodes[0].style = 'display:none;visibility:hidden';
|
||||
div.childNodes[1].style = 'display:block;visibility:visible';
|
||||
}
|
||||
|
||||
// convert an array into a bulleted list
|
||||
export function createList(list) {
|
||||
var ul = document.createElement('ul');
|
||||
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var li = document.createElement('li');
|
||||
if (list[i] instanceof Node) {
|
||||
li.appendChild(list[i]);
|
||||
|
||||
} else {
|
||||
li.appendChild(document.createTextNode(list[i]));
|
||||
|
||||
}
|
||||
ul.appendChild(li);
|
||||
}
|
||||
|
||||
return ul;
|
||||
}
|
||||
|
||||
// add the RDAP conformance of the response
|
||||
export function processrdapConformance(rdapConformance, dl) {
|
||||
addProperty(dl, 'Conformance:', createList(rdapConformance));
|
||||
}
|
||||
|
||||
// add the object's status codes
|
||||
export function processStatus(status, dl) {
|
||||
var s = new Array;
|
||||
for (var i = 0; i < status.length; i++) {
|
||||
var span = document.createElement('span');
|
||||
span.classList.add('rdap-status-code');
|
||||
span.appendChild(document.createTextNode(status[i]));
|
||||
span.setAttribute("title", rdapStatusInfo[status[i]]);
|
||||
s.push(span);
|
||||
}
|
||||
addProperty(dl, 'Status:', createList(s));
|
||||
}
|
||||
|
||||
// add the object's events
|
||||
export function processEvents(events, dl) {
|
||||
var sdl = document.createElement('dl');
|
||||
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
var span1 = document.createElement('span');
|
||||
span1.appendChild(document.createTextNode(new Date(events[i].eventDate).toLocaleString()));
|
||||
span1.classList.add('rdap-event-time');
|
||||
span1.setAttribute('title', events[i].eventDate);
|
||||
|
||||
var span2 = document.createElement('span');
|
||||
span2.appendChild(span1);
|
||||
|
||||
if (events[i].eventActor) {
|
||||
span2.appendChild(document.createTextNode(' (by ' + events[i].eventActor + ')'));
|
||||
}
|
||||
addProperty(sdl, events[i].eventAction + ':', span2);
|
||||
}
|
||||
|
||||
addProperty(dl, 'Events:', sdl);
|
||||
}
|
||||
|
||||
// add the object's links
|
||||
export function processLinks(links, dl) {
|
||||
var ul = document.createElement('ul');
|
||||
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
li = document.createElement('li');
|
||||
|
||||
var title = (links[i].title ? links[i].title : links[i].href);
|
||||
|
||||
var link;
|
||||
if (links[i].type && 0 == links[i].type.indexOf('application/rdap+json')) {
|
||||
link = createRDAPLink(links[i].href, title);
|
||||
|
||||
} else {
|
||||
link = document.createElement('a');
|
||||
link.rel = 'noopener';
|
||||
link.title = link.href = links[i].href;
|
||||
link.target = '_new';
|
||||
link.appendChild(document.createTextNode(title));
|
||||
|
||||
}
|
||||
|
||||
li.appendChild(link);
|
||||
|
||||
if (links[i].rel) li.appendChild(document.createTextNode(' (' + links[i].rel + ')'));
|
||||
|
||||
ul.appendChild(li);
|
||||
}
|
||||
|
||||
addProperty(dl, 'Links:', ul);
|
||||
}
|
||||
|
||||
// add the object's entities
|
||||
export function processEntities(entities, dl) {
|
||||
var div = document.createElement('div');
|
||||
|
||||
for (var i = 0; i < entities.length; i++) div.appendChild(processObject(entities[i]));
|
||||
|
||||
addProperty(dl, 'Entities:', div);
|
||||
}
|
||||
|
||||
// add the object's remarks
|
||||
export function processRemarks(remarks, dl) {
|
||||
addProperty(dl, 'Remarks:', processRemarksOrNotices(remarks));
|
||||
|
||||
}
|
||||
|
||||
// add the responses's notices
|
||||
export function processNotices(notices, dl) {
|
||||
addProperty(dl, 'Notices:', processRemarksOrNotices(notices));
|
||||
}
|
||||
|
||||
// command handler for remarks and notices
|
||||
export function processRemarksOrNotices(things) {
|
||||
var div = document.createElement('div');
|
||||
|
||||
for (var i = 0; i < things.length; i++) {
|
||||
var section = document.createElement('section');
|
||||
section.classList.add('card');
|
||||
div.appendChild(section);
|
||||
|
||||
var title = document.createElement('header');
|
||||
title.classList.add('card-header', 'font-weight-bold');
|
||||
title.appendChild(document.createTextNode(things[i].title));
|
||||
section.appendChild(title);
|
||||
|
||||
var body = document.createElement('div');
|
||||
body.classList.add('card-body');
|
||||
section.appendChild(body);
|
||||
|
||||
if (things[i].description) for (var j = 0; j < things[i].description.length; j++) {
|
||||
var p = document.createElement('p');
|
||||
p.innerHTML = convertURLstoLinks(things[i].description[j]);
|
||||
body.appendChild(p);
|
||||
}
|
||||
|
||||
if (things[i].links) {
|
||||
var ldl = document.createElement('dl');
|
||||
processLinks(things[i].links, ldl);
|
||||
body.appendChild(ldl);
|
||||
}
|
||||
}
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
// naively match URLs in plain text and convert to links
|
||||
export function convertURLstoLinks(str) {
|
||||
return str.replace(
|
||||
/(https?:\/\/[^\s]+[^\.])/g,
|
||||
'<a href="$1" target="_new" rel="noopener">$1</a>'
|
||||
);
|
||||
}
|
||||
|
||||
// process a domain
|
||||
export function processDomain(object, dl, toplevel = false) {
|
||||
|
||||
if (toplevel) document.title = 'Domain ' + (object.unicodeName ? object.unicodeName : object.ldhName).toUpperCase() + ' - RDAP Lookup';
|
||||
|
||||
if (object.unicodeName) {
|
||||
addProperty(dl, 'Name:', object.unicodeName);
|
||||
addProperty(dl, 'ASCII Name:', object.ldhName);
|
||||
|
||||
} else {
|
||||
addProperty(dl, 'Name:', object.ldhName);
|
||||
|
||||
}
|
||||
|
||||
if (object.handle) addProperty(dl, 'Handle:', object.handle);
|
||||
|
||||
// process events, status and entities here, then set them to null so processCommonObjectProperties()
|
||||
// doesn't process them again. this makes the output look more like a traditional whois record:
|
||||
if (object.events) processEvents(object.events, dl);
|
||||
if (object.status) processStatus(object.status, dl);
|
||||
if (object.entities) processEntities(object.entities, dl);
|
||||
|
||||
object.events = object.status = object.entities = null;
|
||||
|
||||
if (object.nameservers) {
|
||||
var div = document.createElement('div');
|
||||
|
||||
for (var i = 0; i < object.nameservers.length; i++) div.appendChild(processObject(object.nameservers[i]));
|
||||
|
||||
addProperty(dl, 'Nameservers:', div);
|
||||
}
|
||||
|
||||
addProperty(dl, 'DNSSEC:', object.secureDNS && object.secureDNS.delegationSigned ? 'Secure' : 'Insecure');
|
||||
|
||||
processCommonObjectProperties(object, dl);
|
||||
}
|
||||
|
||||
// process a nameserver
|
||||
export function processNameserver(object, dl, toplevel = false) {
|
||||
|
||||
if (toplevel) document.title = 'Nameserver ' + object.ldhName + ' - RDAP Lookup';
|
||||
|
||||
addProperty(dl, 'Host Name:', object.ldhName);
|
||||
if (object.unicodeName) addProperty(dl, 'Internationalised Domain Name:', object.unicodeName);
|
||||
if (object.handle) addProperty(dl, 'Handle:', object.handle);
|
||||
|
||||
if (object.ipAddresses) {
|
||||
if (object.ipAddresses.v4) {
|
||||
for (var i = 0; i < object.ipAddresses.v4.length; i++) {
|
||||
addProperty(dl, 'IP Address:', createRDAPLink('https://rdap.org/ip/' + object.ipAddresses.v4[i], object.ipAddresses.v4[i]));
|
||||
}
|
||||
}
|
||||
|
||||
if (object.ipAddresses.v6) {
|
||||
for (var i = 0; i < object.ipAddresses.v6.length; i++) {
|
||||
addProperty(dl, 'IP Address:', createRDAPLink('https://rdap.org/ip/' + object.ipAddresses.v6[i], object.ipAddresses.v6[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processCommonObjectProperties(object, dl);
|
||||
}
|
||||
|
||||
// process an entity
|
||||
export function processEntity(object, dl, toplevel = false) {
|
||||
|
||||
if (toplevel) document.title = 'Entity ' + object.handle + ' - RDAP Lookup';
|
||||
|
||||
if (object.handle) addProperty(dl, 'Handle:', object.handle);
|
||||
|
||||
if (object.publicIds) {
|
||||
for (var i = 0; i < object.publicIds.length; i++) addProperty(dl, object.publicIds[i].type + ':', object.publicIds[i].identifier);
|
||||
}
|
||||
|
||||
if (object.roles) addProperty(dl, 'Roles:', createList(object.roles));
|
||||
|
||||
if (object.jscard) {
|
||||
addProperty(dl, 'Contact Information:', processJSCard(object.jscard));
|
||||
|
||||
} else if (object.jscard_0) {
|
||||
addProperty(dl, 'Contact Information:', processJSCard(object.jscard_0));
|
||||
|
||||
} else if (object.vcardArray && object.vcardArray[1]) {
|
||||
addProperty(dl, 'Contact Information:', processVCardArray(object.vcardArray[1]));
|
||||
}
|
||||
|
||||
processCommonObjectProperties(object, dl);
|
||||
}
|
||||
|
||||
// process an entity's vCard
|
||||
export function processVCardArray(vcard) {
|
||||
var vdl = document.createElement('dl');
|
||||
|
||||
for (var i = 0; i < vcard.length; i++) {
|
||||
var node = vcard[i];
|
||||
|
||||
var type = node[0];
|
||||
var value = node[3];
|
||||
|
||||
if ('version' == type) {
|
||||
continue;
|
||||
|
||||
} else if ('fn' == type) {
|
||||
type = 'Name';
|
||||
|
||||
} else if ('n' == type) {
|
||||
continue;
|
||||
|
||||
} else if ('org' == type) {
|
||||
type = 'Organization';
|
||||
|
||||
} else if ('tel' == type) {
|
||||
type = 'Phone';
|
||||
|
||||
if (node[1].type) for (var j = 0; j < node[1].type; j++) if ('fax' == node[1].type[j]) {
|
||||
type = 'Fax';
|
||||
break;
|
||||
}
|
||||
|
||||
var link = document.createElement('a');
|
||||
link.href = (0 == value.indexOf('tel:') ? '' : 'tel:') + value;
|
||||
link.appendChild(document.createTextNode(value));
|
||||
|
||||
value = link;
|
||||
|
||||
} else if ('adr' == type) {
|
||||
type = 'Address';
|
||||
|
||||
if (node[1].label) {
|
||||
var div = document.createElement('div');
|
||||
strings = node[1].label.split("\n");
|
||||
for (var j = 0; j < strings.length; j++) {
|
||||
div.appendChild(document.createTextNode(strings[j]));
|
||||
if (j < strings.length - 1) div.appendChild(document.createElement('br'));
|
||||
}
|
||||
|
||||
value = div;
|
||||
|
||||
} else if (value) {
|
||||
var div = document.createElement('div');
|
||||
|
||||
for (var j = 0; j < value.length; j++) {
|
||||
if (value[j] && value[j].length > 0) {
|
||||
div.appendChild(document.createTextNode(value[j]));
|
||||
div.appendChild(document.createElement('br'));
|
||||
}
|
||||
}
|
||||
|
||||
value = div;
|
||||
}
|
||||
|
||||
} else if ('email' == type) {
|
||||
type = 'Email';
|
||||
|
||||
var link = document.createElement('a');
|
||||
link.href = 'mailto:' + value;
|
||||
link.appendChild(document.createTextNode(value));
|
||||
|
||||
value = link;
|
||||
|
||||
} else if ('contact-uri' == type) {
|
||||
type = 'Contact URL';
|
||||
|
||||
var link = document.createElement('a');
|
||||
link.href = value;
|
||||
link.appendChild(document.createTextNode(value));
|
||||
|
||||
value = link;
|
||||
}
|
||||
|
||||
if (value) addProperty(vdl, type + ':', value);
|
||||
}
|
||||
|
||||
addProperty(vdl, 'Contact format:', 'jCard');
|
||||
|
||||
return vdl;
|
||||
}
|
||||
|
||||
export function processJSCard(jscard) {
|
||||
var vdl = document.createElement('dl');
|
||||
|
||||
if (jscard.fullName) addProperty(vdl, 'Name:', jscard.fullName);
|
||||
|
||||
if (jscard.organizations) {
|
||||
for (const k in jscard.organizations) {
|
||||
addProperty(vdl, 'Organization:', jscard.organizations[k].name);
|
||||
}
|
||||
}
|
||||
|
||||
if (jscard.addresses) {
|
||||
for (const k in jscard.addresses) {
|
||||
addProperty(vdl, 'Address:', processJSCardAddress(jscard.addresses[k]));
|
||||
}
|
||||
}
|
||||
|
||||
if (jscard.emails) {
|
||||
for (const k in jscard.emails) {
|
||||
var link = document.createElement('a');
|
||||
link.href = 'mailto:' + jscard.emails[k].email;
|
||||
link.appendChild(document.createTextNode(jscard.emails[k].email));
|
||||
|
||||
addProperty(vdl, 'Email Address:', link);
|
||||
}
|
||||
}
|
||||
|
||||
if (jscard.phones) {
|
||||
for (const k in jscard.phones) {
|
||||
var link = document.createElement('a');
|
||||
link.href = jscard.phones[k].phone;
|
||||
link.appendChild(document.createTextNode(jscard.phones[k].phone));
|
||||
|
||||
addProperty(vdl, (jscard.phones[k].features.fax ? 'Fax:' : 'Phone:'), link);
|
||||
}
|
||||
}
|
||||
|
||||
addProperty(vdl, 'Contact format:', 'JSContact');
|
||||
|
||||
return vdl;
|
||||
}
|
||||
|
||||
export function processJSCardAddress(address) {
|
||||
var dl = document.createElement('dl');
|
||||
for (k in address) {
|
||||
v = address[k];
|
||||
if ('street' == k) {
|
||||
var addr = document.createElement('span');
|
||||
for (var i = 0; i < v.length; i++) {
|
||||
if (i > 1) addr.appendChild(document.createElement('br'));
|
||||
addr.appendChild(document.createTextNode(v[i]));
|
||||
}
|
||||
addProperty(dl, 'Street:', addr);
|
||||
|
||||
} else if ('locality' == k) {
|
||||
addProperty(dl, 'City:', v);
|
||||
|
||||
} else if ('region' == k) {
|
||||
addProperty(dl, 'State/Province:', v);
|
||||
|
||||
} else if ('postcode' == k) {
|
||||
addProperty(dl, 'Postal Code:', v);
|
||||
|
||||
} else if ('countryCode' == k) {
|
||||
addProperty(dl, 'Country:', v);
|
||||
|
||||
}
|
||||
}
|
||||
return dl;
|
||||
}
|
||||
|
||||
// process an AS number
|
||||
export function processAutnum(object, dl, toplevel = false) {
|
||||
|
||||
if (toplevel) document.title = 'AS Number ' + object.handle + ' - RDAP Lookup';
|
||||
|
||||
if (object.name) addProperty(dl, 'Network Name:', object.name);
|
||||
if (object.type) addProperty(dl, 'Network Type:', object.type);
|
||||
|
||||
processCommonObjectProperties(object, dl);
|
||||
}
|
||||
|
||||
// process an IP or IP block
|
||||
export function processIp(object, dl, toplevel = false) {
|
||||
|
||||
if (toplevel) document.title = 'IP Network ' + object.handle + ' - RDAP Lookup';
|
||||
|
||||
if (object.ipVersion) addProperty(dl, 'IP Version:', object.ipVersion);
|
||||
if (object.startAddress && object.endAddress) addProperty(dl, 'Address Range:', object.startAddress + ' - ' + object.endAddress);
|
||||
if (object.name) addProperty(dl, 'Network Name:', object.name);
|
||||
if (object.type) addProperty(dl, 'Network Type:', object.type);
|
||||
if (object.country) addProperty(dl, 'Country:', object.country);
|
||||
if (object.parentHandle) addProperty(dl, 'Parent Network:', object.parentHandle);
|
||||
if (object.cidr0_cidrs) addProperty(dl, 'CIDR Prefix(es):', processCIDRs(object.cidr0_cidrs));
|
||||
|
||||
processCommonObjectProperties(object, dl);
|
||||
}
|
||||
|
||||
export function processCIDRs(cidrs) {
|
||||
var list = document.createElement('ul');
|
||||
for (i = 0; i < cidrs.length; i++) {
|
||||
var cidr = (cidrs[i].v6prefix ? cidrs[i].v6prefix : cidrs[i].v4prefix) + '/' + cidrs[i].length;
|
||||
list.appendChild(document.createElement('li')).appendChild(createRDAPLink('https://rdap.org/ip/' + cidr, cidr));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
export function processUnknown(object, dl, toplevel = false) {
|
||||
processCommonObjectProperties(object, dl);
|
||||
}
|
||||
|
||||
// given an object, return the "self" URL (if any)
|
||||
export function getSelfLink(object) {
|
||||
if (object.links) for (var i = 0; i < object.links.length; i++) if ('self' == object.links[i].rel) return object.links[i].href;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// create an RDAP link: a link pointing to an RDAP URL
|
||||
// that when clicked, causes an RDAP query to be made
|
||||
export function createRDAPLink(url, title) {
|
||||
var link = document.createElement('a');
|
||||
|
||||
link.href = 'javascript:void(0)';
|
||||
link.title = url;
|
||||
link.onclick = new Function("runQuery('" + url + "')");
|
||||
link.appendChild(document.createTextNode(title));
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
const URIPatterns: [RegExp, Uri][] = [
|
||||
[/^\d+$/, "autnum"],
|
||||
[/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/?\d*$/, "ip"],
|
||||
[/^[0-9a-f:]{2,}\/?\d*$/, "ip"],
|
||||
[/^https?:/, "url"],
|
||||
[/^{/, "json"],
|
||||
[/./, "domain"],
|
||||
];
|
||||
|
||||
// guess the type from the input value
|
||||
export function getType(value: string): Uri | null {
|
||||
for (let i = 0; i < URIPatterns.length; i++)
|
||||
if (URIPatterns[i]![0].test(value)) {
|
||||
return URIPatterns[i]![1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function loadRegistries(callback) {
|
||||
showSpinner('Loading bootstrap registries...');
|
||||
for (const url in registryURLs) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url);
|
||||
|
||||
xhr.timeout = 25000;
|
||||
xhr.responseType = 'json';
|
||||
xhr.onload = () => handleRegistryResponse(xhr);
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// event handler for when the submit button is pressed, or
|
||||
// when the user clicks on a link to an RDAP URL
|
||||
export function doQuery() {
|
||||
|
||||
}
|
||||
@@ -2,4 +2,8 @@
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind utilities;
|
||||
|
||||
[title]:not(.no-title) {
|
||||
border-bottom: 1px dashed silver;
|
||||
}
|
||||
9
src/types.ts
Normal file
9
src/types.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export type Uri = 'ip' | 'autnum' | 'entity' | 'url' | 'tld' | 'registrar' | 'json' | 'domain';
|
||||
export type ExtendedUri = Omit<Uri, 'ip'> | 'ip4' | 'ip6';
|
||||
export type RdapStatusType = "validated" | "renew prohibited" | "update prohibited" | "transfer prohibited"
|
||||
| "delete prohibited" | "proxy" | "private" | "removed" | "obscured" | "associated" | "active" | "inactive"
|
||||
| "locked" | "pending create" | "pending renew" | "pending transfer" | "pending update" | "pending delete"
|
||||
| "add period" | "auto renew period" | "client delete prohibited" | "client hold" | "client renew prohibited"
|
||||
| "client transfer prohibited" | "client update prohibited" | "pending restore" | "redemption period"
|
||||
| "renew period" | "server delete prohibited" | "server renew prohibited" | "server transfer prohibited"
|
||||
| "server update prohibited" | "server hold" | "transfer period";
|
||||
@@ -1342,6 +1342,11 @@ ignore@^5.2.0:
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
|
||||
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
|
||||
|
||||
immutability-helper@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-3.1.1.tgz#2b86b2286ed3b1241c9e23b7b21e0444f52f77b7"
|
||||
integrity sha512-Q0QaXjPjwIju/28TsugCHNEASwoCcJSyJV3uO1sOIQGI0jKgm9f41Lvz0DZj3n46cNCyAZTsEYoY4C2bVRUzyQ==
|
||||
|
||||
import-fresh@^3.0.0, import-fresh@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||
|
||||
Reference in New Issue
Block a user