diff --git a/java/google/registry/whois/DomainWhoisResponse.java b/java/google/registry/whois/DomainWhoisResponse.java index 63d9234dd..0e73e388c 100644 --- a/java/google/registry/whois/DomainWhoisResponse.java +++ b/java/google/registry/whois/DomainWhoisResponse.java @@ -81,7 +81,7 @@ final class DomainWhoisResponse extends WhoisResponseImpl { .stream() .filter(RegistrarContact::getVisibleInDomainWhoisAsAbuse) .findFirst(); - DomainEmitter domainEmitter = + return WhoisResponseResults.create( new DomainEmitter() .emitField( "Domain Name", @@ -104,25 +104,22 @@ final class DomainWhoisResponse extends WhoisResponseImpl { "Registrar Abuse Contact Phone", abuseContact.map(RegistrarContact::getPhoneNumber).orElse(null)) .emitStatusValues(domain.getStatusValues(), domain.getGracePeriods()) - .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); + .emitContact("Registrant", Optional.of(domain.getRegistrant()), preferUnicode) + .emitContact("Admin", getContactReference(Type.ADMIN), preferUnicode) + .emitContact("Tech", getContactReference(Type.TECH), preferUnicode) + .emitContact("Billing", getContactReference(Type.BILLING), preferUnicode) + .emitSet( + "Name Server", + domain.loadNameserverFullyQualifiedHostNames(), + hostName -> maybeFormatHostname(hostName, preferUnicode)) + .emitField( + "DNSSEC", isNullOrEmpty(domain.getDsData()) ? "unsigned" : "signedDelegation") + .emitWicfLink() + .emitLastUpdated(getTimestamp()) + .emitAwipMessage() + .emitFooter(disclaimer) + .toString(), + 1); } /** Returns the contact of the given type. */ @@ -139,17 +136,15 @@ final class DomainWhoisResponse extends WhoisResponseImpl { if (phoneNumber == null) { return this; } - return emitFieldIfDefined(ImmutableList.of(contactType, title), phoneNumber.getPhoneNumber()) + return emitFieldIfDefined( + ImmutableList.of(contactType, title), phoneNumber.getPhoneNumber(), fullOutput) .emitFieldIfDefined( - ImmutableList.of(contactType, title, "Ext"), phoneNumber.getExtension()); + ImmutableList.of(contactType, title, "Ext"), phoneNumber.getExtension(), fullOutput); } /** Emit the contact entry of the given type. */ DomainEmitter emitContact( - String contactType, - Optional> contact, - boolean preferUnicode, - boolean fullOutput) { + String contactType, Optional> contact, boolean preferUnicode) { if (!contact.isPresent()) { return this; } @@ -168,24 +163,21 @@ final class DomainWhoisResponse extends WhoisResponseImpl { preferUnicode, contactResource.getLocalizedPostalInfo(), 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) { - emitFieldIfDefined(ImmutableList.of(contactType, "Organization"), postalInfo.getOrg()); - emitAddress(contactType, postalInfo.getAddress(), fullOutput); - } + // ICANN Consistent Labeling & Display policy requires that this be the ROID. + emitField( + ImmutableList.of("Registry", contactType, "ID"), contactResource.getRepoId(), fullOutput); + if (postalInfo != null) { + emitFieldIfDefined(ImmutableList.of(contactType, "Name"), postalInfo.getName(), fullOutput); + emitFieldIfDefined( + ImmutableList.of(contactType, "Organization"), + postalInfo.getOrg(), + fullOutput || contactType.equals("Registrant")); + emitAddress(contactType, postalInfo.getAddress(), fullOutput); } + emitPhone(contactType, "Phone", contactResource.getVoiceNumber()); + emitPhone(contactType, "Fax", contactResource.getFaxNumber()); + emitField( + ImmutableList.of(contactType, "Email"), contactResource.getEmailAddress(), fullOutput); return this; } diff --git a/java/google/registry/whois/WhoisResponseImpl.java b/java/google/registry/whois/WhoisResponseImpl.java index b6dca066f..ef0aae94e 100644 --- a/java/google/registry/whois/WhoisResponseImpl.java +++ b/java/google/registry/whois/WhoisResponseImpl.java @@ -39,6 +39,9 @@ abstract class WhoisResponseImpl implements WhoisResponse { /** ICANN problem reporting URL appended to all WHOIS responses. */ private static final String ICANN_REPORTING_URL = "https://www.icann.org/wicf/"; + /** Text to display when the field is redacted for privacy. */ + static final String REDACT_TEXT = "REDACTED FOR PRIVACY"; + /** The time at which this response was created. */ private final DateTime timestamp; @@ -94,59 +97,105 @@ abstract class WhoisResponseImpl implements WhoisResponse { return emitList(title, values.stream().map(transform).sorted().collect(toImmutableList())); } - /** Helper method that loops over a list of values and calls {@link #emitField}. */ - E emitList(String title, Iterable values) { + /** + * Helper method that loops over a list of values and calls {@link #emitField}. + * + *

This method redacts the output unless {@code fullOutput} is {@code true}. + */ + E emitList(String title, Iterable values, boolean fullOutput) { for (String value : values) { - emitField(title, value); + emitField(title, value, fullOutput); } return thisCastToDerived(); } - /** Emit the field name and value followed by a newline, but only if a value exists. */ - E emitFieldIfDefined(String name, @Nullable String value) { + /** Helper method that loops over a list of values and calls {@link #emitField}. */ + E emitList(String title, Iterable values) { + return emitList(title, values, true); + } + + /** + * Emit the field name and value followed by a newline, but only if a value exists. + * + *

This method redacts the output unless {@code fullOutput} is {@code true}. + */ + E emitFieldIfDefined(String name, @Nullable String value, boolean fullOutput) { if (isNullOrEmpty(value)) { return thisCastToDerived(); } stringBuilder.append(cleanse(name)).append(':'); - stringBuilder.append(' ').append(cleanse(value)); + stringBuilder.append(' ').append(fullOutput ? cleanse(value) : REDACT_TEXT); + return emitNewline(); + } + + /** Emit the field name and value followed by a newline, but only if a value exists. */ + E emitFieldIfDefined(String name, @Nullable String value) { + return emitFieldIfDefined(name, value, true); + } + + /** + * Emit a multi-part field name and value followed by a newline, but only if a value exists. + * + *

This method redacts the output unless {@code fullOutput} is {@code true}. + */ + E emitFieldIfDefined(List nameParts, String value, boolean fullOutput) { + if (isNullOrEmpty(value)) { + return thisCastToDerived(); + } + return emitField(nameParts, value, fullOutput); + } + + /** Emit a multi-part field name and value followed by a newline, but only if a value exists. */ + E emitFieldIfDefined(List nameParts, String value) { + return emitFieldIfDefined(nameParts, value, true); + } + /** + * Emit the field name and value followed by a newline. /* + * + *

This method redacts the output unless {@code fullOutput} is {@code true}. + */ + E emitField(String name, @Nullable String value, boolean fullOutput) { + stringBuilder.append(cleanse(name)).append(':'); + if (!isNullOrEmpty(value)) { + stringBuilder.append(' ').append(fullOutput ? cleanse(value) : REDACT_TEXT); + } return emitNewline(); } /** Emit the field name and value followed by a newline. */ E emitField(String name, @Nullable String value) { - stringBuilder.append(cleanse(name)).append(':'); - if (!isNullOrEmpty(value)) { - stringBuilder.append(' ').append(cleanse(value)); - } - return emitNewline(); + return emitField(name, value, true); } - /** Emit a multi-part field name and value followed by a newline, but only if a value exists. */ - E emitFieldIfDefined(List nameParts, String value) { - if (isNullOrEmpty(value)) { - return thisCastToDerived(); - } - return emitField(nameParts, value); + /** + * Emit a multi-part field name and value followed by a newline. + * + *

This method redacts the output unless {@code fullOutput} is {@code true}. + */ + E emitField(List nameParts, String value, boolean fullOutput) { + return emitField(Joiner.on(' ').join(nameParts), value, fullOutput); } /** Emit a multi-part field name and value followed by a newline. */ E emitField(List nameParts, String value) { - return emitField(Joiner.on(' ').join(nameParts), value); + return emitField(nameParts, value, true); } /** 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()); + emitList(prefix + "Street", address.getStreet(), fullOutput); + emitField(prefix + "City", address.getCity(), fullOutput); + emitField( + prefix + "State/Province", + address.getState(), + (fullOutput || prefix.equals("Registrant "))); + emitField(prefix + "Postal Code", address.getZip(), fullOutput); + emitField( + prefix + "Country", + address.getCountryCode(), + fullOutput || prefix.equals("Registrant ")); } return thisCastToDerived(); } diff --git a/javatests/google/registry/whois/testdata/whois_action_domain.txt b/javatests/google/registry/whois/testdata/whois_action_domain.txt index 3a857e564..5304f7c79 100644 --- a/javatests/google/registry/whois/testdata/whois_action_domain.txt +++ b/javatests/google/registry/whois/testdata/whois_action_domain.txt @@ -13,9 +13,39 @@ Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibit Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited Domain Status: serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited +Registry Registrant ID: REDACTED FOR PRIVACY +Registrant Name: REDACTED FOR PRIVACY Registrant Organization: GOOGLE INCORPORATED