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")
private boolean unicode;
@Parameter(names = "--full_output", description = "When set, the full output will be displayed")
private boolean fullOutput;
@Inject
Whois whois;
@Override
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). */
public class DomainLookupCommand extends DomainOrHostLookupCommand {
public DomainLookupCommand(InternetDomainName domainName) {
private final boolean fullOutput;
public DomainLookupCommand(InternetDomainName domainName, boolean fullOutput) {
super(domainName, "Domain");
this.fullOutput = fullOutput;
}
@Override
@ -33,6 +36,6 @@ public class DomainLookupCommand extends DomainOrHostLookupCommand {
final DomainResource domainResource =
loadByForeignKeyCached(DomainResource.class, domainName.toString(), now);
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.ContactResource;
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.GracePeriod;
import google.registry.model.eppcommon.StatusValue;
@ -54,10 +56,14 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
/** Domain which was the target of this WHOIS command. */
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. */
DomainWhoisResponse(DomainResource domain, DateTime timestamp) {
DomainWhoisResponse(DomainResource domain, boolean fullOutput, DateTime timestamp) {
super(timestamp);
this.domain = checkNotNull(domain, "domain");
this.fullOutput = fullOutput;
}
@Override
@ -75,7 +81,7 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
.stream()
.filter(RegistrarContact::getVisibleInDomainWhoisAsAbuse)
.findFirst();
String plaintext =
DomainEmitter domainEmitter =
new DomainEmitter()
.emitField(
"Domain Name",
@ -98,19 +104,32 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
"Registrar Abuse Contact Phone",
abuseContact.map(RegistrarContact::getPhoneNumber).orElse(null))
.emitStatusValues(domain.getStatusValues(), domain.getGracePeriods())
.emitContact("Registrant", domain.getRegistrant(), preferUnicode)
.emitSet(
"Name Server",
domain.loadNameserverFullyQualifiedHostNames(),
hostName -> maybeFormatHostname(hostName, preferUnicode))
.emitField(
"DNSSEC", isNullOrEmpty(domain.getDsData()) ? "unsigned" : "signedDelegation")
.emitWicfLink()
.emitLastUpdated(getTimestamp())
.emitAwipMessage()
.emitFooter(disclaimer)
.toString();
return WhoisResponseResults.create(plaintext, 1);
.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(
"Name Server",
domain.loadNameserverFullyQualifiedHostNames(),
hostName -> maybeFormatHostname(hostName, preferUnicode))
.emitField("DNSSEC", isNullOrEmpty(domain.getDsData()) ? "unsigned" : "signedDelegation")
.emitWicfLink()
.emitLastUpdated(getTimestamp())
.emitAwipMessage()
.emitFooter(disclaimer);
return WhoisResponseResults.create(domainEmitter.toString(), 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. */
@ -127,14 +146,17 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
/** Emit the contact entry of the given type. */
DomainEmitter emitContact(
String contactType, @Nullable Key<ContactResource> contact, boolean preferUnicode) {
if (contact == null) {
String contactType,
Optional<Key<ContactResource>> contact,
boolean preferUnicode,
boolean fullOutput) {
if (!contact.isPresent()) {
return this;
}
// 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
// someone's attention.
ContactResource contactResource = EppResource.loadCached(contact);
ContactResource contactResource = EppResource.loadCached(contact.get());
if (contactResource == null) {
logger.severefmt(
"(BUG) Broken reference found from domain %s to contact %s",
@ -146,9 +168,23 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
preferUnicode,
contactResource.getLocalizedPostalInfo(),
contactResource.getInternationalizedPostalInfo());
if (postalInfo != null) {
emitFieldIfDefined(ImmutableList.of(contactType, "Organization"), postalInfo.getOrg());
emitRegistrantAddress(contactType, postalInfo.getAddress());
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) {
emitFieldIfDefined(ImmutableList.of(contactType, "Organization"), postalInfo.getOrg());
emitAddress(contactType, postalInfo.getAddress(), fullOutput);
}
}
return this;
}

View file

@ -48,12 +48,13 @@ class RegistrarWhoisResponse extends WhoisResponseImpl {
String plaintext =
new RegistrarEmitter()
.emitField("Registrar", registrar.getRegistrarName())
.emitRegistrarAddress(
.emitAddress(
null,
chooseByUnicodePreference(
preferUnicode,
registrar.getLocalizedAddress(),
registrar.getInternationalizedAddress()))
registrar.getInternationalizedAddress()),
true)
.emitPhonesAndEmail(
registrar.getPhoneNumber(), registrar.getFaxNumber(), registrar.getEmailAddress())
.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. */
public String lookup(String query, boolean preferUnicode) {
public String lookup(String query, boolean preferUnicode, boolean fullOutput) {
DateTime now = clock.nowUtc();
try {
return whoisReader
.readCommand(new StringReader(query), now)
.readCommand(new StringReader(query), fullOutput, now)
.executeQuery(now)
.getResponse(preferUnicode, disclaimer)
.plainTextOutput();

View file

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

View file

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

View file

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

View file

@ -82,21 +82,22 @@ class WhoisReader {
}
/**
* Read a command from some source to produce a new instance of
* WhoisCommand.
* Read a command from some source to produce a new instance of WhoisCommand.
*
* @throws IOException If the command could not be read from the reader.
* @throws WhoisException If the command could not be parsed as a WhoisCommand.
*/
WhoisCommand readCommand(Reader reader, DateTime now) throws IOException, WhoisException {
return parseCommand(CharStreams.toString(checkNotNull(reader, "reader")), now);
WhoisCommand readCommand(Reader reader, boolean fullOutput, DateTime 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
* 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.
List<String> tokens = filterEmptyStrings(command.split("\\s"));
if (tokens.isEmpty()) {
@ -115,8 +116,8 @@ class WhoisReader {
// Try to parse the argument as a domain name.
try {
logger.infofmt("Attempting domain lookup command using domain name %s", tokens.get(1));
return commandFactory.domainLookup(InternetDomainName.from(
canonicalizeDomainName(tokens.get(1))));
return commandFactory.domainLookup(
InternetDomainName.from(canonicalizeDomainName(tokens.get(1))), fullOutput);
} catch (IllegalArgumentException iae) {
// If we can't interpret the argument as a host name, then return an error.
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.
if (targetName.parent().equals(tld.get())) {
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.

View file

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

View file

@ -255,7 +255,7 @@ public class DomainWhoisResponseTest {
@Test
public void getPlainTextOutputTest() {
DomainWhoisResponse domainWhoisResponse =
new DomainWhoisResponse(domainResource, clock.nowUtc());
new DomainWhoisResponse(domainResource, false, clock.nowUtc());
assertThat(
domainWhoisResponse.getResponse(
false,
@ -263,11 +263,22 @@ public class DomainWhoisResponseTest {
.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
public void addImplicitOkStatusTest() {
DomainWhoisResponse domainWhoisResponse =
new DomainWhoisResponse(
domainResource.asBuilder().setStatusValues(null).build(), clock.nowUtc());
domainResource.asBuilder().setStatusValues(null).build(), false, clock.nowUtc());
assertThat(
domainWhoisResponse
.getResponse(

View file

@ -554,7 +554,7 @@ public class WhoisActionTest {
persistResource(makeHostResource("ns1.cat.lol", "1.2.3.4"));
WhoisAction action = newWhoisAction("ns1.cat.lol");
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"));
action.whoisMetrics = mock(WhoisMetrics.class);
@ -574,11 +574,15 @@ public class WhoisActionTest {
persistResource(makeHostResource("ns1.cat.lol", "1.2.3.4"));
WhoisAction action = newWhoisAction("ns1.cat.lol");
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);
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)))
.thenThrow(new DatastoreFailureException("Expected transient exception #1"))
.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"));
WhoisHttpAction action = newWhoisHttpAction("ns1.cat.lol");
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"));
action.whoisMetrics = mock(WhoisMetrics.class);

View file

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