Add ability to show full WHOIS output in nomulus command

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=197731992
This commit is contained in:
jianglai 2018-05-23 08:47:56 -07:00
parent ac500652ac
commit 86dd6bd59e
15 changed files with 181 additions and 62 deletions

View file

@ -36,11 +36,14 @@ final class WhoisQueryCommand implements RemoteApiCommand {
description = "When set, output will be Unicode") description = "When set, output will be Unicode")
private boolean unicode; private boolean unicode;
@Parameter(names = "--full_output", description = "When set, the full output will be displayed")
private boolean fullOutput;
@Inject @Inject
Whois whois; Whois whois;
@Override @Override
public void run() { public void run() {
System.out.println(whois.lookup(Joiner.on(' ').join(mainParameters), unicode)); System.out.println(whois.lookup(Joiner.on(' ').join(mainParameters), unicode, fullOutput));
} }
} }

View file

@ -24,8 +24,11 @@ import org.joda.time.DateTime;
/** Represents a WHOIS lookup on a domain name (i.e. SLD). */ /** Represents a WHOIS lookup on a domain name (i.e. SLD). */
public class DomainLookupCommand extends DomainOrHostLookupCommand { public class DomainLookupCommand extends DomainOrHostLookupCommand {
public DomainLookupCommand(InternetDomainName domainName) { private final boolean fullOutput;
public DomainLookupCommand(InternetDomainName domainName, boolean fullOutput) {
super(domainName, "Domain"); super(domainName, "Domain");
this.fullOutput = fullOutput;
} }
@Override @Override
@ -33,6 +36,6 @@ public class DomainLookupCommand extends DomainOrHostLookupCommand {
final DomainResource domainResource = final DomainResource domainResource =
loadByForeignKeyCached(DomainResource.class, domainName.toString(), now); loadByForeignKeyCached(DomainResource.class, domainName.toString(), now);
return Optional.ofNullable( return Optional.ofNullable(
domainResource == null ? null : new DomainWhoisResponse(domainResource, now)); domainResource == null ? null : new DomainWhoisResponse(domainResource, fullOutput, now));
} }
} }

View file

@ -26,6 +26,8 @@ import google.registry.model.EppResource;
import google.registry.model.contact.ContactPhoneNumber; import google.registry.model.contact.ContactPhoneNumber;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.contact.PostalInfo; import google.registry.model.contact.PostalInfo;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.DomainResource; import google.registry.model.domain.DomainResource;
import google.registry.model.domain.GracePeriod; import google.registry.model.domain.GracePeriod;
import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppcommon.StatusValue;
@ -54,10 +56,14 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
/** Domain which was the target of this WHOIS command. */ /** Domain which was the target of this WHOIS command. */
private final DomainResource domain; private final DomainResource domain;
/** Whether the full WHOIS output is to be displayed. */
private final boolean fullOutput;
/** Creates new WHOIS domain response on the given domain. */ /** Creates new WHOIS domain response on the given domain. */
DomainWhoisResponse(DomainResource domain, DateTime timestamp) { DomainWhoisResponse(DomainResource domain, boolean fullOutput, DateTime timestamp) {
super(timestamp); super(timestamp);
this.domain = checkNotNull(domain, "domain"); this.domain = checkNotNull(domain, "domain");
this.fullOutput = fullOutput;
} }
@Override @Override
@ -75,7 +81,7 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
.stream() .stream()
.filter(RegistrarContact::getVisibleInDomainWhoisAsAbuse) .filter(RegistrarContact::getVisibleInDomainWhoisAsAbuse)
.findFirst(); .findFirst();
String plaintext = DomainEmitter domainEmitter =
new DomainEmitter() new DomainEmitter()
.emitField( .emitField(
"Domain Name", "Domain Name",
@ -98,19 +104,32 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
"Registrar Abuse Contact Phone", "Registrar Abuse Contact Phone",
abuseContact.map(RegistrarContact::getPhoneNumber).orElse(null)) abuseContact.map(RegistrarContact::getPhoneNumber).orElse(null))
.emitStatusValues(domain.getStatusValues(), domain.getGracePeriods()) .emitStatusValues(domain.getStatusValues(), domain.getGracePeriods())
.emitContact("Registrant", domain.getRegistrant(), preferUnicode) .emitContact(
"Registrant", Optional.of(domain.getRegistrant()), preferUnicode, fullOutput);
if (fullOutput) {
domainEmitter
.emitContact("Admin", getContactReference(Type.ADMIN), preferUnicode, fullOutput)
.emitContact("Tech", getContactReference(Type.TECH), preferUnicode, fullOutput)
.emitContact("Billing", getContactReference(Type.BILLING), preferUnicode, fullOutput);
}
domainEmitter
.emitSet( .emitSet(
"Name Server", "Name Server",
domain.loadNameserverFullyQualifiedHostNames(), domain.loadNameserverFullyQualifiedHostNames(),
hostName -> maybeFormatHostname(hostName, preferUnicode)) hostName -> maybeFormatHostname(hostName, preferUnicode))
.emitField( .emitField("DNSSEC", isNullOrEmpty(domain.getDsData()) ? "unsigned" : "signedDelegation")
"DNSSEC", isNullOrEmpty(domain.getDsData()) ? "unsigned" : "signedDelegation")
.emitWicfLink() .emitWicfLink()
.emitLastUpdated(getTimestamp()) .emitLastUpdated(getTimestamp())
.emitAwipMessage() .emitAwipMessage()
.emitFooter(disclaimer) .emitFooter(disclaimer);
.toString(); return WhoisResponseResults.create(domainEmitter.toString(), 1);
return WhoisResponseResults.create(plaintext, 1); }
/** Returns the contact of the given type. */
private Optional<Key<ContactResource>> getContactReference(Type type) {
Optional<DesignatedContact> contactOfType =
domain.getContacts().stream().filter(d -> d.getType() == type).findFirst();
return contactOfType.map(DesignatedContact::getContactKey);
} }
/** Output emitter with logic for domains. */ /** Output emitter with logic for domains. */
@ -127,14 +146,17 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
/** Emit the contact entry of the given type. */ /** Emit the contact entry of the given type. */
DomainEmitter emitContact( DomainEmitter emitContact(
String contactType, @Nullable Key<ContactResource> contact, boolean preferUnicode) { String contactType,
if (contact == null) { Optional<Key<ContactResource>> contact,
boolean preferUnicode,
boolean fullOutput) {
if (!contact.isPresent()) {
return this; return this;
} }
// If we refer to a contact that doesn't exist, that's a bug. It means referential integrity // If we refer to a contact that doesn't exist, that's a bug. It means referential integrity
// has somehow been broken. We skip the rest of this contact, but log it to hopefully bring it // has somehow been broken. We skip the rest of this contact, but log it to hopefully bring it
// someone's attention. // someone's attention.
ContactResource contactResource = EppResource.loadCached(contact); ContactResource contactResource = EppResource.loadCached(contact.get());
if (contactResource == null) { if (contactResource == null) {
logger.severefmt( logger.severefmt(
"(BUG) Broken reference found from domain %s to contact %s", "(BUG) Broken reference found from domain %s to contact %s",
@ -146,9 +168,23 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
preferUnicode, preferUnicode,
contactResource.getLocalizedPostalInfo(), contactResource.getLocalizedPostalInfo(),
contactResource.getInternationalizedPostalInfo()); contactResource.getInternationalizedPostalInfo());
if (fullOutput) {
// If the full output is to be displayed, show all fields for all contact types.
// ICANN Consistent Labeling & Display policy requires that this be the ROID.
emitField(ImmutableList.of("Registry", contactType, "ID"), contactResource.getRepoId());
if (postalInfo != null) {
emitFieldIfDefined(ImmutableList.of(contactType, "Name"), postalInfo.getName());
emitFieldIfDefined(ImmutableList.of(contactType, "Organization"), postalInfo.getOrg());
emitAddress(contactType, postalInfo.getAddress(), fullOutput);
}
emitPhone(contactType, "Phone", contactResource.getVoiceNumber());
emitPhone(contactType, "Fax", contactResource.getFaxNumber());
emitField(ImmutableList.of(contactType, "Email"), contactResource.getEmailAddress());
} else {
if (postalInfo != null) { if (postalInfo != null) {
emitFieldIfDefined(ImmutableList.of(contactType, "Organization"), postalInfo.getOrg()); emitFieldIfDefined(ImmutableList.of(contactType, "Organization"), postalInfo.getOrg());
emitRegistrantAddress(contactType, postalInfo.getAddress()); emitAddress(contactType, postalInfo.getAddress(), fullOutput);
}
} }
return this; return this;
} }

View file

@ -48,12 +48,13 @@ class RegistrarWhoisResponse extends WhoisResponseImpl {
String plaintext = String plaintext =
new RegistrarEmitter() new RegistrarEmitter()
.emitField("Registrar", registrar.getRegistrarName()) .emitField("Registrar", registrar.getRegistrarName())
.emitRegistrarAddress( .emitAddress(
null, null,
chooseByUnicodePreference( chooseByUnicodePreference(
preferUnicode, preferUnicode,
registrar.getLocalizedAddress(), registrar.getLocalizedAddress(),
registrar.getInternationalizedAddress())) registrar.getInternationalizedAddress()),
true)
.emitPhonesAndEmail( .emitPhonesAndEmail(
registrar.getPhoneNumber(), registrar.getFaxNumber(), registrar.getEmailAddress()) registrar.getPhoneNumber(), registrar.getFaxNumber(), registrar.getEmailAddress())
.emitField("Registrar WHOIS Server", registrar.getWhoisServer()) .emitField("Registrar WHOIS Server", registrar.getWhoisServer())

View file

@ -36,11 +36,11 @@ public final class Whois {
} }
/** Performs a WHOIS lookup on a plaintext query string. */ /** Performs a WHOIS lookup on a plaintext query string. */
public String lookup(String query, boolean preferUnicode) { public String lookup(String query, boolean preferUnicode, boolean fullOutput) {
DateTime now = clock.nowUtc(); DateTime now = clock.nowUtc();
try { try {
return whoisReader return whoisReader
.readCommand(new StringReader(query), now) .readCommand(new StringReader(query), fullOutput, now)
.executeQuery(now) .executeQuery(now)
.getResponse(preferUnicode, disclaimer) .getResponse(preferUnicode, disclaimer)
.plainTextOutput(); .plainTextOutput();

View file

@ -80,7 +80,7 @@ public class WhoisAction implements Runnable {
String responseText; String responseText;
final DateTime now = clock.nowUtc(); final DateTime now = clock.nowUtc();
try { try {
final WhoisCommand command = whoisReader.readCommand(input, now); final WhoisCommand command = whoisReader.readCommand(input, false, now);
metricBuilder.setCommand(command); metricBuilder.setCommand(command);
WhoisResponseResults results = WhoisResponseResults results =
retrier.callWithRetry( retrier.callWithRetry(

View file

@ -27,8 +27,8 @@ import java.net.InetAddress;
public class WhoisCommandFactory { public class WhoisCommandFactory {
/** Returns a new {@link WhoisCommand} to perform a domain lookup on the specified domain name. */ /** Returns a new {@link WhoisCommand} to perform a domain lookup on the specified domain name. */
public WhoisCommand domainLookup(InternetDomainName domainName) { public WhoisCommand domainLookup(InternetDomainName domainName, boolean fullOutput) {
return new DomainLookupCommand(domainName); return new DomainLookupCommand(domainName, fullOutput);
} }
/** /**

View file

@ -151,7 +151,7 @@ public final class WhoisHttpAction implements Runnable {
String commandText = String commandText =
decode(JOINER.join(SLASHER.split(path.substring(PATH.length())))) + "\r\n"; decode(JOINER.join(SLASHER.split(path.substring(PATH.length())))) + "\r\n";
DateTime now = clock.nowUtc(); DateTime now = clock.nowUtc();
WhoisCommand command = whoisReader.readCommand(new StringReader(commandText), now); WhoisCommand command = whoisReader.readCommand(new StringReader(commandText), false, now);
metricBuilder.setCommand(command); metricBuilder.setCommand(command);
sendResponse(SC_OK, command.executeQuery(now)); sendResponse(SC_OK, command.executeQuery(now));
} catch (WhoisException e) { } catch (WhoisException e) {

View file

@ -82,21 +82,22 @@ class WhoisReader {
} }
/** /**
* Read a command from some source to produce a new instance of * Read a command from some source to produce a new instance of WhoisCommand.
* WhoisCommand.
* *
* @throws IOException If the command could not be read from the reader. * @throws IOException If the command could not be read from the reader.
* @throws WhoisException If the command could not be parsed as a WhoisCommand. * @throws WhoisException If the command could not be parsed as a WhoisCommand.
*/ */
WhoisCommand readCommand(Reader reader, DateTime now) throws IOException, WhoisException { WhoisCommand readCommand(Reader reader, boolean fullOutput, DateTime now)
return parseCommand(CharStreams.toString(checkNotNull(reader, "reader")), now); throws IOException, WhoisException {
return parseCommand(CharStreams.toString(checkNotNull(reader, "reader")), fullOutput, now);
} }
/** /**
* Given a WHOIS command string, parse it into its command type and target string. See class level * Given a WHOIS command string, parse it into its command type and target string. See class level
* comments for a full description of the command syntax accepted. * comments for a full description of the command syntax accepted.
*/ */
private WhoisCommand parseCommand(String command, DateTime now) throws WhoisException { private WhoisCommand parseCommand(String command, boolean fullOutput, DateTime now)
throws WhoisException {
// Split the string into tokens based on whitespace. // Split the string into tokens based on whitespace.
List<String> tokens = filterEmptyStrings(command.split("\\s")); List<String> tokens = filterEmptyStrings(command.split("\\s"));
if (tokens.isEmpty()) { if (tokens.isEmpty()) {
@ -115,8 +116,8 @@ class WhoisReader {
// Try to parse the argument as a domain name. // Try to parse the argument as a domain name.
try { try {
logger.infofmt("Attempting domain lookup command using domain name %s", tokens.get(1)); logger.infofmt("Attempting domain lookup command using domain name %s", tokens.get(1));
return commandFactory.domainLookup(InternetDomainName.from( return commandFactory.domainLookup(
canonicalizeDomainName(tokens.get(1)))); InternetDomainName.from(canonicalizeDomainName(tokens.get(1))), fullOutput);
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
// If we can't interpret the argument as a host name, then return an error. // If we can't interpret the argument as a host name, then return an error.
throw new WhoisException(now, SC_BAD_REQUEST, String.format( throw new WhoisException(now, SC_BAD_REQUEST, String.format(
@ -194,7 +195,7 @@ class WhoisReader {
// (SLD) and we should do a domain lookup on it. // (SLD) and we should do a domain lookup on it.
if (targetName.parent().equals(tld.get())) { if (targetName.parent().equals(tld.get())) {
logger.infofmt("Attempting domain lookup using %s as a domain name", targetName); logger.infofmt("Attempting domain lookup using %s as a domain name", targetName);
return commandFactory.domainLookup(targetName); return commandFactory.domainLookup(targetName, fullOutput);
} }
// The target is more than one level above the TLD, so we'll assume it's a nameserver. // The target is more than one level above the TLD, so we'll assume it's a nameserver.

View file

@ -134,24 +134,18 @@ abstract class WhoisResponseImpl implements WhoisResponse {
return emitField(Joiner.on(' ').join(nameParts), value); return emitField(Joiner.on(' ').join(nameParts), value);
} }
/** Emit registrar address. */ /** Emit a contact address. */
E emitRegistrarAddress(@Nullable String prefix, @Nullable Address address) { E emitAddress(@Nullable String prefix, @Nullable Address address, boolean fullOutput) {
prefix = isNullOrEmpty(prefix) ? "" : prefix + " "; prefix = isNullOrEmpty(prefix) ? "" : prefix + " ";
if (address != null) { if (address != null) {
if (fullOutput) {
emitList(prefix + "Street", address.getStreet()); emitList(prefix + "Street", address.getStreet());
emitField(prefix + "City", address.getCity()); emitField(prefix + "City", address.getCity());
}
emitField(prefix + "State/Province", address.getState()); emitField(prefix + "State/Province", address.getState());
if (fullOutput) {
emitField(prefix + "Postal Code", address.getZip()); emitField(prefix + "Postal Code", address.getZip());
emitField(prefix + "Country", address.getCountryCode());
} }
return thisCastToDerived();
}
/** Emit registrant address. */
E emitRegistrantAddress(@Nullable String prefix, @Nullable Address address) {
prefix = isNullOrEmpty(prefix) ? "" : prefix + " ";
if (address != null) {
emitField(prefix + "State/Province", address.getState());
emitField(prefix + "Country", address.getCountryCode()); emitField(prefix + "Country", address.getCountryCode());
} }
return thisCastToDerived(); return thisCastToDerived();

View file

@ -255,7 +255,7 @@ public class DomainWhoisResponseTest {
@Test @Test
public void getPlainTextOutputTest() { public void getPlainTextOutputTest() {
DomainWhoisResponse domainWhoisResponse = DomainWhoisResponse domainWhoisResponse =
new DomainWhoisResponse(domainResource, clock.nowUtc()); new DomainWhoisResponse(domainResource, false, clock.nowUtc());
assertThat( assertThat(
domainWhoisResponse.getResponse( domainWhoisResponse.getResponse(
false, false,
@ -263,11 +263,22 @@ public class DomainWhoisResponseTest {
.isEqualTo(WhoisResponseResults.create(loadFile("whois_domain.txt"), 1)); .isEqualTo(WhoisResponseResults.create(loadFile("whois_domain.txt"), 1));
} }
@Test
public void getPlainTextOutputTest_fullOutput() {
DomainWhoisResponse domainWhoisResponse =
new DomainWhoisResponse(domainResource, true, clock.nowUtc());
assertThat(
domainWhoisResponse.getResponse(
false,
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
.isEqualTo(WhoisResponseResults.create(loadFile("whois_domain_full_output.txt"), 1));
}
@Test @Test
public void addImplicitOkStatusTest() { public void addImplicitOkStatusTest() {
DomainWhoisResponse domainWhoisResponse = DomainWhoisResponse domainWhoisResponse =
new DomainWhoisResponse( new DomainWhoisResponse(
domainResource.asBuilder().setStatusValues(null).build(), clock.nowUtc()); domainResource.asBuilder().setStatusValues(null).build(), false, clock.nowUtc());
assertThat( assertThat(
domainWhoisResponse domainWhoisResponse
.getResponse( .getResponse(

View file

@ -554,7 +554,7 @@ public class WhoisActionTest {
persistResource(makeHostResource("ns1.cat.lol", "1.2.3.4")); persistResource(makeHostResource("ns1.cat.lol", "1.2.3.4"));
WhoisAction action = newWhoisAction("ns1.cat.lol"); WhoisAction action = newWhoisAction("ns1.cat.lol");
action.whoisReader = mock(WhoisReader.class); action.whoisReader = mock(WhoisReader.class);
when(action.whoisReader.readCommand(any(Reader.class), any(DateTime.class))) when(action.whoisReader.readCommand(any(Reader.class), eq(false), any(DateTime.class)))
.thenThrow(new IOException("missing cat interface")); .thenThrow(new IOException("missing cat interface"));
action.whoisMetrics = mock(WhoisMetrics.class); action.whoisMetrics = mock(WhoisMetrics.class);
@ -574,11 +574,15 @@ public class WhoisActionTest {
persistResource(makeHostResource("ns1.cat.lol", "1.2.3.4")); persistResource(makeHostResource("ns1.cat.lol", "1.2.3.4"));
WhoisAction action = newWhoisAction("ns1.cat.lol"); WhoisAction action = newWhoisAction("ns1.cat.lol");
WhoisResponse expectedResponse = WhoisResponse expectedResponse =
action.whoisReader.readCommand(action.input, clock.nowUtc()).executeQuery(clock.nowUtc()); action
.whoisReader
.readCommand(action.input, false, clock.nowUtc())
.executeQuery(clock.nowUtc());
WhoisReader mockReader = mock(WhoisReader.class); WhoisReader mockReader = mock(WhoisReader.class);
WhoisCommand mockCommand = mock(WhoisCommand.class); WhoisCommand mockCommand = mock(WhoisCommand.class);
when(mockReader.readCommand(any(Reader.class), any(DateTime.class))).thenReturn(mockCommand); when(mockReader.readCommand(any(Reader.class), eq(false), any(DateTime.class)))
.thenReturn(mockCommand);
when(mockCommand.executeQuery(any(DateTime.class))) when(mockCommand.executeQuery(any(DateTime.class)))
.thenThrow(new DatastoreFailureException("Expected transient exception #1")) .thenThrow(new DatastoreFailureException("Expected transient exception #1"))
.thenThrow(new DatastoreTimeoutException("Expected transient exception #2")) .thenThrow(new DatastoreTimeoutException("Expected transient exception #2"))

View file

@ -358,7 +358,7 @@ public class WhoisHttpActionTest {
persistResource(makeHostResource("ns1.cat.lol", "1.2.3.4")); persistResource(makeHostResource("ns1.cat.lol", "1.2.3.4"));
WhoisHttpAction action = newWhoisHttpAction("ns1.cat.lol"); WhoisHttpAction action = newWhoisHttpAction("ns1.cat.lol");
action.whoisReader = mock(WhoisReader.class); action.whoisReader = mock(WhoisReader.class);
when(action.whoisReader.readCommand(any(Reader.class), any(DateTime.class))) when(action.whoisReader.readCommand(any(Reader.class), eq(false), any(DateTime.class)))
.thenThrow(new IOException("missing cat interface")); .thenThrow(new IOException("missing cat interface"));
action.whoisMetrics = mock(WhoisMetrics.class); action.whoisMetrics = mock(WhoisMetrics.class);

View file

@ -48,7 +48,7 @@ public class WhoisReaderTest {
<T> T readCommand(String commandStr) throws Exception { <T> T readCommand(String commandStr) throws Exception {
return (T) return (T)
new WhoisReader(new WhoisCommandFactory()) new WhoisReader(new WhoisCommandFactory())
.readCommand(new StringReader(commandStr), clock.nowUtc()); .readCommand(new StringReader(commandStr), false, clock.nowUtc());
} }
void assertLoadsExampleTld(String commandString) throws Exception { void assertLoadsExampleTld(String commandString) throws Exception {

View file

@ -0,0 +1,66 @@
Domain Name: example.tld
Registry Domain ID: 3-TLD
Registrar WHOIS Server: whois.nic.fakewhois.example
Registrar URL: http://my.fake.url
Updated Date: 2009-05-29T20:13:00Z
Creation Date: 2000-10-08T00:45:00Z
Registry Expiry Date: 2010-10-08T00:44:59Z
Registrar: New Registrar
Registrar IANA ID: 5555555
Registrar Abuse Contact Email: jakedoe@theregistrar.com
Registrar Abuse Contact Phone: +1.2125551216
Domain Status: addPeriod https://icann.org/epp#addPeriod
Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited
Domain Status: transferPeriod https://icann.org/epp#transferPeriod
Registry Registrant ID: 4-TLD
Registrant Name: EXAMPLE REGISTRANT
Registrant Organization: Tom & Jerry Corp.
Registrant Street: 123 EXAMPLE STREET
Registrant City: ANYTOWN
Registrant State/Province: AP
Registrant Postal Code: A1A1A1
Registrant Country: EX
Registrant Phone: +1.5555551212
Registrant Phone Ext: 1234
Registrant Fax: +1.5555551213
Registrant Fax Ext: 4321
Registrant Email: EMAIL@EXAMPLE.tld
Registry Admin ID: 5-TLD
Admin Name: EXAMPLE REGISTRANT ADMINISTRATIVE
Admin Organization: EXAMPLE REGISTRANT ORGANIZATION
Admin Street: 123 EXAMPLE STREET
Admin City: ANYTOWN
Admin State/Province: AP
Admin Postal Code: A1A1A1
Admin Country: EX
Admin Phone: +1.5555551212
Admin Phone Ext: 1234
Admin Fax: +1.5555551213
Admin Email: EMAIL@EXAMPLE.tld
Registry Tech ID: 6-TLD
Tech Name: EXAMPLE REGISTRAR TECHNICAL
Tech Organization: EXAMPLE REGISTRAR LLC
Tech Street: 123 EXAMPLE STREET
Tech City: ANYTOWN
Tech State/Province: AP
Tech Postal Code: A1A1A1
Tech Country: EX
Tech Phone: +1.1235551234
Tech Phone Ext: 1234
Tech Fax: +1.5555551213
Tech Fax Ext: 93
Tech Email: EMAIL@EXAMPLE.tld
Name Server: ns01.exampleregistrar.tld
Name Server: ns02.exampleregistrar.tld
DNSSEC: signedDelegation
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
For more information on Whois status codes, please visit https://icann.org/epp
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.