Skip to content

Commit

Permalink
Improved RDAP parser and common UI
Browse files Browse the repository at this point in the history
  • Loading branch information
getpinga committed Feb 21, 2025
1 parent 91c726b commit f84293a
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 91 deletions.
278 changes: 188 additions & 90 deletions whois/templates/clientarea.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@
const moduleLink = "{$modulelink}";
const contactLinkBase = "{$contactLink}";
const domainInput = document.getElementById('domainInput');
const errorMessage = document.getElementById('errorMessage');
function validateInput() {
const domain = domainInput.value.trim();
const resultContainer = document.getElementById('result');
const bottomContainer = document.getElementById('bottom');
if (!domain) {
resultContainer.innerHTML = '<span style="color: #d9534f;">Please enter a valid domain name.</span>';
bottomContainer.style.display = 'block'; // Ensure the container is visible
domainInput.focus(); // Focus back on the input field
return false;
}
resultContainer.innerText = ''; // Clear previous messages
bottomContainer.style.display = 'none'; // Hide the container
return true;
}
// Function to update the contact link
function updateContactLink(domain) {
var contactElement = document.getElementById("contact");
Expand All @@ -64,13 +84,8 @@
});
document.getElementById('whoisButton').addEventListener('click', function() {
document.getElementById("contact").innerHTML = ''; // Clear the contact link
if (!validateInput()) return;
var domain = document.getElementById('domainInput').value.trim();
if (!domain) {
alert('Please enter a domain name.');
return;
}
var captcha = '';
fetch(moduleLink + "&action=check", {
Expand All @@ -83,23 +98,20 @@
.then(response => response.text())
.then(data => {
document.getElementById('result').innerText = data;
const rowCount = data.split('\n').length;
if (rowCount >= 5) {
document.getElementById('bottom').style.display = 'block';
if (data.toLowerCase().includes(domain.toLowerCase())) {
updateContactLink(domain);
}
document.getElementById('bottom').style.display = 'block';
})
.catch(error => console.error('Error:', error));
.catch(error => {
console.error('Error:', error); // Log the error to the console
document.getElementById('result').innerText = 'Error: ' + error.message; // Display the error message on the page
});
});
document.getElementById('rdapButton').addEventListener('click', function() {
document.getElementById("contact").innerHTML = ''; // Clear the contact link
if (!validateInput()) return;
var domain = document.getElementById('domainInput').value.trim();
if (!domain) {
alert('Please enter a domain name.');
return;
}
var captcha = '';
fetch(moduleLink + "&action=check", {
Expand All @@ -114,100 +126,186 @@
if (data.error) {
console.error('Error:', data.error);
document.getElementById('result').innerText = 'Error: ' + data.error;
document.getElementById('bottom').style.display = 'block';
} else {
let output = parseRdapResponse(data);
let output = parseRDAP(data);
document.getElementById('result').innerText = output;
if (!output.includes("Domain Name: N/A")) {
document.getElementById('bottom').style.display = 'block';
if (output.toUpperCase().includes(domain.toUpperCase())) {
updateContactLink(domain);
}
document.getElementById('bottom').style.display = 'block';
}
})
.catch(error => console.error('Error:', error));
.catch(error => {
console.error('Error:', error); // Log the error to the console
document.getElementById('result').innerText = 'Error: ' + error.message;
document.getElementById('bottom').style.display = 'block';
});
});
});
function parseRdapResponse(data) {
let output = '';
/**
* Flattens the "entities" field.
* The RDAP JSON sometimes nests arrays of entities.
*/
function flattenEntities(entities) {
let flat = [];
entities.forEach(item => {
if (Array.isArray(item)) {
flat = flat.concat(item);
} else if (typeof item === "object" && item !== null) {
flat.push(item);
// If an entity contains a nested entities array (for example, abuse contacts inside registrar)
if (item.entities && Array.isArray(item.entities)) {
flat = flat.concat(flattenEntities(item.entities));
}
}
});
return flat;
}
// Domain Name and Status
output += 'Domain Name: ' + (data.ldhName || 'N/A') + '\n';
output += 'Status: ' + (data.status ? data.status.join(', ') : 'N/A') + '\n\n';
/**
* Helper to extract a vCard field value by key from a vcardArray.
*/
function getVCardValue(vcardArray, key) {
if (!vcardArray || vcardArray.length < 2) return null;
const props = vcardArray[1];
const field = props.find(item => item[0] === key);
return field ? field[3] : null;
}
// Parsing entities for specific roles like registrar and registrant
if (data.entities && data.entities.length > 0) {
data.entities.forEach(entity => {
if (entity.roles) {
output += entity.roles.join(', ').toUpperCase() + ' Contact:\n';
if (entity.vcardArray && entity.vcardArray.length > 1) {
output += parseVcard(entity.vcardArray[1]);
}
if (entity.roles.includes('registrar') && entity.publicIds) {
output += ' IANA ID: ' + entity.publicIds.map(id => id.identifier).join(', ') + '\n';
}
output += '\n';
}
});
}
/**
* Main parser: Takes the RDAP JSON object and returns a WHOIS-style text output.
*/
function parseRDAP(data) {
let output = "";
// Nameservers
if (data.nameservers && data.nameservers.length > 0) {
output += 'Nameservers:\n';
data.nameservers.forEach(ns => {
output += ' - ' + ns.ldhName + '\n';
});
output += '\n';
}
// Domain basic details
output += `Domain Name: ${(data.ldhName || "N/A").toUpperCase()}\n`;
output += `Domain ID: ${data.handle || "N/A"}\n\n`;
// SecureDNS Details
if (data.secureDNS) {
output += 'SecureDNS:\n';
output += ' - Delegation Signed: ' + (data.secureDNS.delegationSigned ? 'Yes' : 'No') + '\n';
output += ' - Zone Signed: ' + (data.secureDNS.zoneSigned ? 'Yes' : 'No') + '\n\n';
}
// Domain status
if (data.status && data.status.length) {
output += "Status:\n";
data.status.forEach(s => {
output += ` - ${s}\n`;
});
output += "\n";
}
// Events (like registration, expiration dates)
if (data.events && data.events.length > 0) {
output += 'Events:\n';
data.events.forEach(event => {
output += ' - ' + event.eventAction + ': ' + new Date(event.eventDate).toLocaleString() + '\n';
});
output += '\n';
}
// Events (e.g., registration, expiration, last update)
if (data.events && data.events.length) {
output += "Events:\n";
data.events.forEach(event => {
// Capitalize event action for display
const action = event.eventAction.charAt(0).toUpperCase() + event.eventAction.slice(1);
output += ` ${action}: ${event.eventDate}\n`;
});
output += "\n";
}
// Notices
if (data.notices && data.notices.length > 0) {
output += 'Notices:\n';
data.notices.forEach(notice => {
output += ' - ' + (notice.title || 'Notice') + ': ' + notice.description.join(' ') + '\n';
});
// Nameservers
if (data.nameservers && data.nameservers.length) {
output += "Nameservers:\n";
data.nameservers.forEach(ns => {
output += ` - ${ns.ldhName || "N/A"}\n`;
});
output += "\n";
}
// Secure DNS info
if (data.secureDNS) {
output += "Secure DNS:\n";
output += ` Zone Signed: ${data.secureDNS.zoneSigned}\n`;
output += ` Delegation Signed: ${data.secureDNS.delegationSigned}\n\n`;
}
// Flatten all entities (registrar, registrant, admin, tech, billing, etc.)
let allEntities = data.entities ? flattenEntities(data.entities) : [];
// Registrar
const registrar = allEntities.find(ent => ent.roles && ent.roles.includes("registrar"));
if (registrar) {
const regName = getVCardValue(registrar.vcardArray, "fn") || "N/A";
output += `Registrar: ${regName}\n`;
let ianaId = "";
if (registrar.publicIds && Array.isArray(registrar.publicIds)) {
const ianaObj = registrar.publicIds.find(pub => pub.type === "IANA Registrar ID");
if (ianaObj) {
ianaId = ianaObj.identifier;
}
}
output += `IANA ID: ${ianaId}\n\n`;
return output;
}
// Look for nested abuse contact within the registrar entity
if (registrar.entities && Array.isArray(registrar.entities)) {
const abuseContact = flattenEntities(registrar.entities).find(ent => ent.roles && ent.roles.includes("abuse"));
if (abuseContact) {
const abuseName = getVCardValue(abuseContact.vcardArray, "fn") || "N/A";
const abuseEmail = getVCardValue(abuseContact.vcardArray, "email") || "N/A";
const abuseTel = getVCardValue(abuseContact.vcardArray, "tel") || "N/A";
output += "Registrar Abuse Contact:\n";
output += ` Name: ${abuseName}\n`;
output += ` Email: ${abuseEmail}\n`;
output += ` Phone: ${abuseTel}\n`;
}
}
output += "\n";
}
function parseVcard(vcard) {
let vcardOutput = '';
vcard.forEach(entry => {
switch (entry[0]) {
case 'fn':
vcardOutput += ' Name: ' + entry[3] + '\n';
break;
case 'adr':
if (Array.isArray(entry[3]) && entry[3].length > 0) {
const addressParts = entry[3];
vcardOutput += ' Address: ' + addressParts.join(', ') + '\n';
}
break;
case 'email':
vcardOutput += ' Email: ' + entry[3] + '\n';
break;
case 'tel':
vcardOutput += ' Phone: ' + entry[3] + '\n';
break;
// Process other roles: registrant, admin, tech, billing
const rolesToShow = ["registrant", "admin", "tech", "billing"];
rolesToShow.forEach(role => {
// Filter entities by role
const ents = allEntities.filter(ent => ent.roles && ent.roles.includes(role));
if (ents.length) {
ents.forEach(ent => {
const name = getVCardValue(ent.vcardArray, "fn") || "N/A";
output += `${role.charAt(0).toUpperCase() + role.slice(1)} Contact: ${name}\n`;
output += ` Handle: ${ent.handle || "N/A"}\n`;
// Optionally, include organization and address if available
const org = getVCardValue(ent.vcardArray, "org");
if (org) {
output += ` Organization: ${org}\n`;
}
// You can add more fields as needed (e.g., email, phone)
const email = getVCardValue(ent.vcardArray, "email");
if (email) {
output += ` Email: ${email}\n`;
}
const tel = getVCardValue(ent.vcardArray, "tel");
if (tel) {
output += ` Phone: ${tel}\n`;
}
const address = getVCardValue(ent.vcardArray, "adr");
if (address) {
// Since the address is an array, filter out any empty parts and join them
const addrStr = Array.isArray(address) ? address.filter(part => part && part.trim()).join(', ') : address;
output += ` Address: ${addrStr}\n`;
}
output += "\n";
});
}
});
// Notices
if (data.notices && data.notices.length) {
output += "Notices:\n";
data.notices.forEach(notice => {
if (notice.title) {
output += ` ${notice.title}\n`;
}
if (notice.description && Array.isArray(notice.description)) {
notice.description.forEach(desc => {
output += ` ${desc}\n`;
});
}
output += "\n";
});
return vcardOutput;
}
return output;
}
</script>
</div>
Expand Down
2 changes: 1 addition & 1 deletion whois/whmcs.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schema": "1.0",
"type": "whmcs-addons",
"name": "whois",
"version": "1.1",
"version": "1.1.1",
"license": "MIT",
"category": "utilities",
"description": {
Expand Down

0 comments on commit f84293a

Please sign in to comment.