1
0
mirror of https://github.com/google/nomulus synced 2026-06-09 08:22:59 +00:00

Remove WHOIS classes and configuration (#2859)

This is steps one and two of b/454947209

We already haven't been serving WHOIS for a while, so there's no point
in keeping the old code around. This can simplify some code paths in the
future (like, certain foreign-key-loads that are only used in WHOIS
queries).
This commit is contained in:
gbrodman
2025-10-27 14:57:25 -04:00
committed by GitHub
parent 19e03dbd2e
commit 6cd351ec7c
105 changed files with 19 additions and 8623 deletions

View File

@@ -976,17 +976,6 @@ public final class RegistryConfig {
return config.misc.transientFailureRetries;
}
/**
* Amount of time public HTTP proxies are permitted to cache our WHOIS responses.
*
* @see google.registry.whois.WhoisHttpAction
*/
@Provides
@Config("whoisHttpExpires")
public static Duration provideWhoisHttpExpires() {
return Duration.standardDays(1);
}
/**
* Maximum number of results to return for an RDAP search query
*
@@ -998,39 +987,6 @@ public final class RegistryConfig {
return 100;
}
/**
* Redaction text for email address in WHOIS
*
* @see google.registry.whois.WhoisResponse
*/
@Provides
@Config("whoisRedactedEmailText")
public static String provideWhoisRedactedEmailText(RegistryConfigSettings config) {
return config.registryPolicy.whoisRedactedEmailText;
}
/**
* Disclaimer displayed at the end of WHOIS query results.
*
* @see google.registry.whois.WhoisResponse
*/
@Provides
@Config("whoisDisclaimer")
public static String provideWhoisDisclaimer(RegistryConfigSettings config) {
return config.registryPolicy.whoisDisclaimer;
}
/**
* Message template for whois response when queried domain is blocked by BSA.
*
* @see google.registry.whois.WhoisResponse
*/
@Provides
@Config("domainBlockedByBsaTemplate")
public static String provideDomainBlockedByBsaTemplate(RegistryConfigSettings config) {
return config.registryPolicy.domainBlockedByBsaTemplate;
}
/**
* Maximum QPS for the Google Cloud Monitoring V3 (aka Stackdriver) API. The QPS limit can be
* adjusted by contacting Cloud Support.
@@ -1105,12 +1061,6 @@ public final class RegistryConfig {
return config.registryPolicy.customLogicFactoryClass;
}
@Provides
@Config("whoisCommandFactoryClass")
public static String provideWhoisCommandFactoryClass(RegistryConfigSettings config) {
return config.registryPolicy.whoisCommandFactoryClass;
}
@Provides
@Config("dnsCountQueryCoordinatorClass")
public static String dnsCountQueryCoordinatorClass(RegistryConfigSettings config) {

View File

@@ -90,7 +90,6 @@ public class RegistryConfigSettings {
public String contactAndHostRoidSuffix;
public String productName;
public String customLogicFactoryClass;
public String whoisCommandFactoryClass;
public String dnsCountQueryCoordinatorClass;
public int contactAutomaticTransferDays;
public String greetingServerId;
@@ -102,9 +101,6 @@ public class RegistryConfigSettings {
public String registryAdminClientId;
public String premiumTermsExportDisclaimer;
public String reservedTermsExportDisclaimer;
public String whoisRedactedEmailText;
public String whoisDisclaimer;
public String domainBlockedByBsaTemplate;
public String rdapTos;
public String rdapTosStaticUrl;
public String registryName;

View File

@@ -65,10 +65,6 @@ registryPolicy:
# See flows/custom/CustomLogicFactory.java
customLogicFactoryClass: google.registry.flows.custom.CustomLogicFactory
# WHOIS command factory fully-qualified class name.
# See whois/WhoisCommandFactory.java
whoisCommandFactoryClass: google.registry.whois.WhoisCommandFactory
# Custom logic class for handling DNS query count reporting for ICANN.
# See reporting/icann/DnsCountQueryCoordinator.java
dnsCountQueryCoordinatorClass: google.registry.reporting.icann.DummyDnsCountQueryCoordinator
@@ -114,31 +110,6 @@ registryPolicy:
to publish. This list is subject to change. The most up-to-date source
is always the registry itself, by sending domain check EPP commands.
# Redaction text for email address in WHOIS
whoisRedactedEmailText: |
Please query the WHOIS server of the owning registrar identified in this
output for information on how to contact the Registrant, Admin, or Tech
contact of the queried domain name.
# Disclaimer at the top of WHOIS results.
whoisDisclaimer: |
WHOIS information is provided by the registry solely for query-based,
informational purposes. Any information provided is "as is" without any
guarantee of accuracy. You may not use such information to (a) allow,
enable, or otherwise support the transmission of mass unsolicited,
commercial advertising or solicitations; (b) enable high volume, automated,
electronic processes that access the registry's systems or any
ICANN-Accredited Registrar, except as reasonably necessary to register
domain names or modify existing registrations; or (c) engage in or support
unlawful behavior. We reserve the right to restrict or deny your access to
the WHOIS database, and may modify these terms at any time.
# BSA blocked domain name template.
domainBlockedByBsaTemplate: |
Domain Name: %s
>>> This name is not available for registration.
>>> This name has been blocked by a GlobalBlock service.
# RDAP Terms of Service text displayed at the /rdap/help/tos endpoint.
rdapTos: >
By querying our Domain Database as part of the RDAP pilot program (RDAP

View File

@@ -57,11 +57,6 @@
<!-- Verbatim JavaScript sources (only visible to admins for debugging). -->
<url-pattern>/assets/sources/*</url-pattern>
<!-- TODO(b/26776367): Move these files to /assets/sources. -->
<url-pattern>/assets/js/registrar_bin.js.map</url-pattern>
<url-pattern>/assets/js/registrar_dbg.js</url-pattern>
<url-pattern>/assets/css/registrar_dbg.css</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>

View File

@@ -130,7 +130,7 @@ public final class EppResourceUtils {
* past.
*
* <p>Do not call this cached version for anything that needs transactional consistency. It should
* only be used when it's OK if the data is potentially being out of date, e.g. WHOIS.
* only be used when it's OK if the data is potentially being out of date, e.g. RDAP.
*
* @param clazz the resource type to load
* @param foreignKey id to match
@@ -181,7 +181,7 @@ public final class EppResourceUtils {
return Optional.empty();
}
// When setting status values based on a time, choose the greater of "now" and the resource's
// UpdateAutoTimestamp. For non-mutating uses (info, whois, etc.), this is equivalent to rolling
// UpdateAutoTimestamp. For non-mutating uses (info, RDAP, etc.), this is equivalent to rolling
// "now" forward to at least the last update on the resource, so that a read right after a write
// doesn't appear stale. For mutating flows, if we had to roll now forward then the flow will
// fail when it tries to save anything, since "now" is needed to be > the last update time for

View File

@@ -492,7 +492,7 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
return LIVE_STATES.contains(state);
}
/** Returns {@code true} if registrar should be visible in WHOIS results. */
/** Returns {@code true} if registrar should be visible in RDAP results. */
public boolean isLiveAndPubliclyVisible() {
return LIVE_STATES.contains(state) && PUBLICLY_VISIBLE_TYPES.contains(type);
}

View File

@@ -131,9 +131,6 @@ import google.registry.ui.server.console.domains.ConsoleBulkDomainAction;
import google.registry.ui.server.console.settings.ContactAction;
import google.registry.ui.server.console.settings.RdapRegistrarFieldsAction;
import google.registry.ui.server.console.settings.SecurityAction;
import google.registry.whois.WhoisAction;
import google.registry.whois.WhoisHttpAction;
import google.registry.whois.WhoisModule;
/** Dagger component with per-request lifetime. */
@RequestScope
@@ -161,8 +158,7 @@ import google.registry.whois.WhoisModule;
Spec11Module.class,
TmchModule.class,
ToolsServerModule.class,
WhiteboxModule.class,
WhoisModule.class,
WhiteboxModule.class
})
interface RequestComponent {
FlowComponent.Builder flowComponentBuilder();
@@ -343,10 +339,6 @@ interface RequestComponent {
VerifyOteAction verifyOteAction();
WhoisAction whoisAction();
WhoisHttpAction whoisHttpAction();
WipeOutContactHistoryPiiAction wipeOutContactHistoryPiiAction();
@Subcomponent.Builder

View File

@@ -36,9 +36,6 @@ import google.registry.rdap.RdapNameserverSearchAction;
import google.registry.request.RequestComponentBuilder;
import google.registry.request.RequestModule;
import google.registry.request.RequestScope;
import google.registry.whois.WhoisAction;
import google.registry.whois.WhoisHttpAction;
import google.registry.whois.WhoisModule;
/** Dagger component with per-request lifetime for "pubapi" App Engine module. */
@RequestScope
@@ -49,8 +46,7 @@ import google.registry.whois.WhoisModule;
EppTlsModule.class,
RdapModule.class,
RequestModule.class,
WhiteboxModule.class,
WhoisModule.class,
WhiteboxModule.class
})
public interface PubApiRequestComponent {
CheckApiAction checkApiAction();
@@ -67,9 +63,6 @@ public interface PubApiRequestComponent {
ReadinessProbeActionPubApi readinessProbeActionPubApi();
WhoisHttpAction whoisHttpAction();
WhoisAction whoisAction();
@Subcomponent.Builder
abstract class Builder implements RequestComponentBuilder<PubApiRequestComponent> {
@Override public abstract Builder requestModule(RequestModule requestModule);

View File

@@ -67,15 +67,13 @@ class RequestMetrics {
private static String truncatePath(String path) {
// We want to bucket RDAP requests by type to use less metric space,
// e.g. "/rdap/domains" rather than "/rdap/domains/foo.tld"
if (path.startsWith("/rdap")) {
List<String> splitPath = Splitter.on("/").omitEmptyStrings().splitToList(path);
return Streams.stream(Iterables.limit(splitPath, 2))
.collect(Collectors.joining("/", "/", "/"));
// Similarly, we put all web WHOIS requests under the same path because otherwise its
// cardinality is unbound, and it is possible to generate a huge amount of metrics with all
// the different paths.
} else if (path.startsWith("/whois")) {
// Though we don't serve WHOIS requests anymore, bucket any non-served WHOIS requests together
// to avoid an unbound cardinality
return "/whois";
}
return path;

View File

@@ -23,7 +23,7 @@ public enum Auth {
/**
* Allows anyone to access.
*
* <p>This is used for public HTML endpoints like RDAP, the check API, and web WHOIS.
* <p>This is used for public HTML endpoints like RDAP and the check API.
*/
AUTH_PUBLIC(AuthLevel.NONE, UserPolicy.PUBLIC),

View File

@@ -122,7 +122,6 @@ public final class RegistryTool {
.put("validate_escrow_deposit", ValidateEscrowDepositCommand.class)
.put("validate_login_credentials", ValidateLoginCredentialsCommand.class)
.put("verify_ote", VerifyOteCommand.class)
.put("whois_query", WhoisQueryCommand.class)
.build();
public static void main(String[] args) throws Exception {

View File

@@ -37,7 +37,6 @@ import google.registry.request.Modules.GsonModule;
import google.registry.request.Modules.UrlConnectionServiceModule;
import google.registry.tools.AuthModule.LocalCredentialModule;
import google.registry.util.UtilsModule;
import google.registry.whois.NonCachingWhoisModule;
import jakarta.inject.Singleton;
import javax.annotation.Nullable;
@@ -67,8 +66,7 @@ import javax.annotation.Nullable;
RequestFactoryModule.class,
SecretManagerModule.class,
UrlConnectionServiceModule.class,
UtilsModule.class,
NonCachingWhoisModule.class
UtilsModule.class
})
interface RegistryToolComponent {
void inject(AckPollMessagesCommand command);
@@ -163,8 +161,6 @@ interface RegistryToolComponent {
void inject(ValidateLoginCredentialsCommand command);
void inject(WhoisQueryCommand command);
ServiceConnection serviceConnection();
@LocalCredentialJson

View File

@@ -1,48 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.tools;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.base.Joiner;
import google.registry.whois.Whois;
import jakarta.inject.Inject;
import java.util.List;
/** Command to execute a WHOIS query. */
@Parameters(separators = " =", commandDescription = "Manually perform a WHOIS query")
final class WhoisQueryCommand implements Command {
@Parameter(
description = "WHOIS query string",
required = true)
private List<String> mainParameters;
@Parameter(
names = "--unicode",
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, fullOutput));
}
}

View File

@@ -1,371 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server;
import static com.google.common.collect.Range.atLeast;
import static com.google.common.collect.Range.atMost;
import static com.google.common.collect.Range.closed;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import com.google.common.base.Ascii;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InternetDomainName;
import com.google.re2j.Pattern;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.ui.forms.FormField;
import google.registry.ui.forms.FormFieldException;
import google.registry.ui.forms.FormFields;
import google.registry.util.CidrAddressBlock;
import google.registry.util.X509Utils;
import java.security.cert.CertificateParsingException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
/** Form fields for validating input for the {@code Registrar} class. */
public final class RegistrarFormFields {
public static final Pattern BASE64_PATTERN = Pattern.compile("[+/a-zA-Z0-9]*");
public static final Pattern ASCII_PATTERN = Pattern.compile("[[:ascii:]]*");
public static final String ASCII_ERROR = "Please only use ASCII-US characters.";
public static final FormField<String, String> NAME_FIELD =
FormFields.NAME.asBuilderNamed("registrarName")
.required()
.build();
public static final FormField<String, DateTime> LAST_UPDATE_TIME =
FormFields.LABEL
.asBuilderNamed("lastUpdateTime")
.transform(DateTime.class, RegistrarFormFields::parseDateTime)
.required()
.build();
public static final FormField<String, String> EMAIL_ADDRESS_FIELD_REQUIRED =
FormFields.EMAIL.asBuilderNamed("emailAddress")
.matches(ASCII_PATTERN, ASCII_ERROR)
.required()
.build();
public static final FormField<String, String> EMAIL_ADDRESS_FIELD_OPTIONAL =
FormFields.EMAIL.asBuilderNamed("emailAddress")
.matches(ASCII_PATTERN, ASCII_ERROR)
.build();
public static final FormField<String, String> ICANN_REFERRAL_EMAIL_FIELD =
FormFields.EMAIL.asBuilderNamed("icannReferralEmail")
.matches(ASCII_PATTERN, ASCII_ERROR)
.required()
.build();
public static final FormField<String, String> PHONE_NUMBER_FIELD =
FormFields.PHONE_NUMBER.asBuilder()
.matches(ASCII_PATTERN, ASCII_ERROR)
.build();
public static final FormField<String, String> FAX_NUMBER_FIELD =
FormFields.PHONE_NUMBER.asBuilderNamed("faxNumber")
.matches(ASCII_PATTERN, ASCII_ERROR)
.build();
public static final FormField<List<String>, Set<String>> ALLOWED_TLDS_FIELD =
FormFields.LABEL.asBuilderNamed("allowedTlds")
.asSet()
.build();
public static final FormField<String, String> WHOIS_SERVER_FIELD =
FormFields.LABEL.asBuilderNamed("whoisServer")
.transform(RegistrarFormFields::parseHostname)
.build();
public static final FormField<String, String> CLIENT_CERTIFICATE_HASH_FIELD =
FormField.named("clientCertificateHash")
.emptyToNull()
.matches(BASE64_PATTERN, "Field must contain a base64 value.")
.range(closed(1, 255))
.build();
private static final FormField<String, String> X509_PEM_CERTIFICATE =
FormField.named("certificate")
.emptyToNull()
.transform(
input -> {
if (input == null) {
return null;
}
try {
X509Utils.loadCertificate(input);
} catch (CertificateParsingException e) {
throw new FormFieldException("Invalid X.509 PEM certificate");
}
return input;
})
.build();
public static final FormField<String, String> CLIENT_CERTIFICATE_FIELD =
X509_PEM_CERTIFICATE.asBuilderNamed("clientCertificate").build();
public static final FormField<String, String> FAILOVER_CLIENT_CERTIFICATE_FIELD =
X509_PEM_CERTIFICATE.asBuilderNamed("failoverClientCertificate").build();
public static final FormField<Long, Long> BILLING_IDENTIFIER_FIELD =
FormField.named("billingIdentifier", Long.class)
.range(atLeast(1))
.build();
public static final FormField<Long, Long> IANA_IDENTIFIER_FIELD =
FormField.named("ianaIdentifier", Long.class)
.range(atLeast(1))
.build();
public static final FormField<String, String> URL_FIELD =
FormFields.MIN_TOKEN.asBuilderNamed("url")
.build();
public static final FormField<List<String>, List<CidrAddressBlock>> IP_ADDRESS_ALLOW_LIST_FIELD =
FormField.named("ipAddressAllowList")
.emptyToNull()
.transform(CidrAddressBlock.class, RegistrarFormFields::parseCidr)
.asList()
.build();
public static final FormField<String, String> PASSWORD_FIELD =
FormFields.PASSWORD.asBuilderNamed("password")
.build();
public static final FormField<String, String> PHONE_PASSCODE_FIELD =
FormField.named("phonePasscode")
.emptyToNull()
.matches(Registrar.PHONE_PASSCODE_PATTERN)
.build();
public static final FormField<String, String> CONTACT_NAME_FIELD =
FormFields.NAME.asBuilderNamed("name")
.required()
.build();
public static final FormField<String, String> CONTACT_EMAIL_ADDRESS_FIELD =
FormFields.EMAIL.asBuilderNamed("emailAddress")
.required()
.build();
public static final FormField<String, String> REGISTRY_LOCK_EMAIL_ADDRESS_FIELD =
FormFields.EMAIL.asBuilderNamed("registryLockEmailAddress").build();
public static final FormField<Boolean, Boolean> CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD =
FormField.named("visibleInWhoisAsAdmin", Boolean.class)
.build();
public static final FormField<Boolean, Boolean> CONTACT_VISIBLE_IN_WHOIS_AS_TECH_FIELD =
FormField.named("visibleInWhoisAsTech", Boolean.class)
.build();
public static final FormField<Boolean, Boolean>
PHONE_AND_EMAIL_VISIBLE_IN_DOMAIN_WHOIS_AS_ABUSE_FIELD =
FormField.named("visibleInDomainWhoisAsAbuse", Boolean.class).build();
public static final FormField<String, String> CONTACT_PHONE_NUMBER_FIELD =
FormFields.PHONE_NUMBER.asBuilder().build();
public static final FormField<String, String> CONTACT_FAX_NUMBER_FIELD =
FormFields.PHONE_NUMBER.asBuilderNamed("faxNumber").build();
public static final FormField<String, Set<RegistrarPoc.Type>> CONTACT_TYPES =
FormField.named("types")
.uppercased()
.asEnum(RegistrarPoc.Type.class)
.asSet(Splitter.on(',').omitEmptyStrings().trimResults())
.build();
public static final FormField<List<Map<String, ?>>, List<Map<String, ?>>> CONTACTS_AS_MAPS =
FormField.mapNamed("contacts").asList().build();
public static final FormField<List<String>, List<String>> I18N_STREET_FIELD =
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("street")
.range(closed(1, 255))
.matches(ASCII_PATTERN, ASCII_ERROR)
.asList()
.range(closed(1, 3))
.required()
.build();
public static final FormField<List<String>, List<String>> L10N_STREET_FIELD =
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("street")
.range(closed(1, 255))
.asList()
.range(closed(1, 3))
.required()
.build();
public static final FormField<String, String> I18N_CITY_FIELD =
FormFields.NAME.asBuilderNamed("city")
.matches(ASCII_PATTERN, ASCII_ERROR)
.required()
.build();
public static final FormField<String, String> L10N_CITY_FIELD =
FormFields.NAME.asBuilderNamed("city")
.required()
.build();
public static final FormField<String, String> I18N_STATE_FIELD =
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("state")
.range(atMost(255))
.matches(ASCII_PATTERN, ASCII_ERROR)
.build();
public static final FormField<String, String> L10N_STATE_FIELD =
FormFields.XS_NORMALIZED_STRING.asBuilderNamed("state")
.range(atMost(255))
.build();
public static final FormField<String, String> I18N_ZIP_FIELD =
FormFields.XS_TOKEN.asBuilderNamed("zip")
.range(atMost(16))
.matches(ASCII_PATTERN, ASCII_ERROR)
.build();
public static final FormField<String, String> L10N_ZIP_FIELD =
FormFields.XS_TOKEN.asBuilderNamed("zip")
.range(atMost(16))
.build();
public static final FormField<String, String> COUNTRY_CODE_FIELD =
FormFields.COUNTRY_CODE.asBuilder()
.required()
.build();
public static final FormField<Map<String, ?>, RegistrarAddress> L10N_ADDRESS_FIELD =
FormField.mapNamed("localizedAddress")
.transform(
RegistrarAddress.class,
args ->
toNewAddress(
args, L10N_STREET_FIELD, L10N_CITY_FIELD, L10N_STATE_FIELD, L10N_ZIP_FIELD))
.build();
@Nullable
private static RegistrarAddress toNewAddress(
@Nullable Map<String, ?> args,
final FormField<List<String>, List<String>> streetField,
final FormField<String, String> cityField,
final FormField<String, String> stateField,
final FormField<String, String> zipField) {
if (args == null) {
return null;
}
RegistrarAddress.Builder builder = new RegistrarAddress.Builder();
String countryCode = COUNTRY_CODE_FIELD.extractUntyped(args).get();
builder.setCountryCode(countryCode);
streetField
.extractUntyped(args)
.ifPresent(streets -> builder.setStreet(ImmutableList.copyOf(streets)));
cityField.extractUntyped(args).ifPresent(builder::setCity);
Optional<String> stateFieldValue = stateField.extractUntyped(args);
if (stateFieldValue.isPresent()) {
String state = stateFieldValue.get();
if ("US".equals(countryCode)) {
state = Ascii.toUpperCase(state);
if (!StateCode.US_MAP.containsKey(state)) {
throw new FormFieldException(stateField, "Unknown US state code.");
}
}
builder.setState(state);
}
zipField.extractUntyped(args).ifPresent(builder::setZip);
return builder.build();
}
private static CidrAddressBlock parseCidr(String input) {
try {
return input != null ? CidrAddressBlock.create(input) : null;
} catch (IllegalArgumentException e) {
throw new FormFieldException("Not a valid CIDR notation IP-address block.", e);
}
}
@Nullable
private static String parseHostname(@Nullable String input) {
if (input == null) {
return null;
}
if (!InternetDomainName.isValid(input)) {
throw new FormFieldException("Not a valid hostname.");
}
return canonicalizeHostname(input);
}
@Nullable
public static DateTime parseDateTime(@Nullable String input) {
if (input == null) {
return null;
}
try {
return DateTime.parse(input);
} catch (IllegalArgumentException e) {
throw new FormFieldException("Not a valid ISO date-time string.", e);
}
}
public static ImmutableList<RegistrarPoc.Builder> getRegistrarContactBuilders(
ImmutableSet<RegistrarPoc> existingContacts, @Nullable Map<String, ?> args) {
if (args == null) {
return ImmutableList.of();
}
Optional<List<Map<String, ?>>> contactsAsMaps = CONTACTS_AS_MAPS.extractUntyped(args);
if (contactsAsMaps.isEmpty()) {
return ImmutableList.of();
}
ImmutableList.Builder<RegistrarPoc.Builder> result = new ImmutableList.Builder<>();
for (Map<String, ?> contactAsMap : contactsAsMaps.get()) {
String emailAddress =
CONTACT_EMAIL_ADDRESS_FIELD
.extractUntyped(contactAsMap)
.orElseThrow(
() -> new IllegalArgumentException("Contacts from UI must have email addresses"));
// Start with a new builder if the contact didn't previously exist
RegistrarPoc.Builder contactBuilder =
existingContacts.stream()
.filter(rc -> rc.getEmailAddress().equals(emailAddress))
.findFirst()
.map(RegistrarPoc::asBuilder)
.orElse(new RegistrarPoc.Builder());
applyRegistrarContactArgs(contactBuilder, contactAsMap);
result.add(contactBuilder);
}
return result.build();
}
private static void applyRegistrarContactArgs(RegistrarPoc.Builder builder, Map<String, ?> args) {
builder.setName(CONTACT_NAME_FIELD.extractUntyped(args).orElse(null));
builder.setEmailAddress(CONTACT_EMAIL_ADDRESS_FIELD.extractUntyped(args).orElse(null));
builder.setVisibleInWhoisAsAdmin(
CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD.extractUntyped(args).orElse(false));
builder.setVisibleInWhoisAsTech(
CONTACT_VISIBLE_IN_WHOIS_AS_TECH_FIELD.extractUntyped(args).orElse(false));
builder.setVisibleInDomainWhoisAsAbuse(
PHONE_AND_EMAIL_VISIBLE_IN_DOMAIN_WHOIS_AS_ABUSE_FIELD.extractUntyped(args).orElse(false));
builder.setPhoneNumber(CONTACT_PHONE_NUMBER_FIELD.extractUntyped(args).orElse(null));
builder.setFaxNumber(CONTACT_FAX_NUMBER_FIELD.extractUntyped(args).orElse(null));
builder.setTypes(CONTACT_TYPES.extractUntyped(args).orElse(ImmutableSet.of()));
}
}

View File

@@ -38,10 +38,10 @@ import java.util.Optional;
import java.util.StringJoiner;
/**
* Console action for editing fields on a registrar that are visible in WHOIS/RDAP.
* Console action for editing fields on a registrar that are visible in RDAP.
*
* <p>This doesn't cover many of the registrar fields but rather only those that are visible in
* WHOIS/RDAP and don't have any other obvious means of edit.
* <p>This doesn't cover many of the registrar fields but rather only those that are visible in RDAP
* and don't have any other obvious means of edit.
*/
@Action(
service = GaeService.DEFAULT,

View File

@@ -1,52 +0,0 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import dagger.Module;
import dagger.Provides;
import google.registry.util.Clock;
import google.registry.whois.WhoisMetrics.WhoisMetric;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.Reader;
/**
* Dagger base module for the whois package.
*
* <p>Provides whois objects common to both the normal ({@link WhoisModule}) and non-caching ({@link
* NonCachingWhoisModule}) implementations.
*/
@Module
class BaseWhoisModule {
@Provides
@SuppressWarnings("CloseableProvides")
static Reader provideHttpInputReader(HttpServletRequest req) {
try {
return req.getReader();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Provides a {@link WhoisMetrics.WhoisMetric.Builder} with the startTimestamp already
* initialized.
*/
@Provides
static WhoisMetric.Builder provideEppMetricBuilder(Clock clock) {
return WhoisMetric.builderForRequest(clock);
}
}

View File

@@ -1,121 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.flows.domain.DomainFlowUtils.isBlockedByBsa;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
import static google.registry.model.tld.Tlds.findTldForName;
import static google.registry.model.tld.Tlds.getTlds;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Verify;
import com.google.common.net.InternetDomainName;
import google.registry.model.domain.Domain;
import google.registry.model.tld.Tld;
import java.util.Optional;
import org.joda.time.DateTime;
/** Represents a WHOIS lookup on a domain name (i.e. SLD). */
public class DomainLookupCommand implements WhoisCommand {
private static final String ERROR_PREFIX = "Domain";
@VisibleForTesting final InternetDomainName domainName;
private final boolean fullOutput;
private final boolean cached;
private final String whoisRedactedEmailText;
private final String domainBlockedByBsaTemplate;
public DomainLookupCommand(
InternetDomainName domainName,
boolean fullOutput,
boolean cached,
String whoisRedactedEmailText,
String domainBlockedByBsaTemplate) {
this.domainName = checkNotNull(domainName, "domainOrHostName");
this.fullOutput = fullOutput;
this.cached = cached;
this.whoisRedactedEmailText = whoisRedactedEmailText;
this.domainBlockedByBsaTemplate = domainBlockedByBsaTemplate;
}
@Override
public final WhoisResponse executeQuery(final DateTime now) throws WhoisException {
Optional<InternetDomainName> tld = findTldForName(domainName);
// Google Registry Policy: Do not return records under TLDs for which we're not
// authoritative.
if (tld.isEmpty() || !getTlds().contains(tld.get().toString())) {
throw new WhoisException(now, SC_NOT_FOUND, ERROR_PREFIX + " not found.");
}
// Include `getResponse` and `isBlockedByBsa` in one transaction to reduce latency.
// Must pass the exceptions outside to throw.
ResponseOrException result =
replicaTm()
.transact(
() -> {
final Optional<WhoisResponse> response = getResponse(domainName, now);
if (response.isPresent()) {
return ResponseOrException.of(response.get());
}
String label = domainName.parts().get(0);
String tldStr = tld.get().toString();
if (isBlockedByBsa(label, Tld.get(tldStr), now)) {
return ResponseOrException.of(
new WhoisException(
now,
SC_NOT_FOUND,
String.format(domainBlockedByBsaTemplate, domainName)));
}
return ResponseOrException.of(
new WhoisException(now, SC_NOT_FOUND, ERROR_PREFIX + " not found."));
});
return result.returnOrThrow();
}
private Optional<WhoisResponse> getResponse(InternetDomainName domainName, DateTime now) {
Optional<Domain> domainResource =
cached
? loadByForeignKeyByCache(Domain.class, domainName.toString(), now)
: loadByForeignKey(Domain.class, domainName.toString(), now);
return domainResource.map(
domain -> new DomainWhoisResponse(domain, fullOutput, whoisRedactedEmailText, now));
}
record ResponseOrException(
Optional<WhoisResponse> whoisResponse, Optional<WhoisException> exception) {
WhoisResponse returnOrThrow() throws WhoisException {
Verify.verify(
whoisResponse().isPresent() || exception().isPresent(),
"Response and exception must not both be missing.");
return whoisResponse().orElseThrow(() -> exception().get());
}
static ResponseOrException of(WhoisResponse response) {
return new ResponseOrException(Optional.of(response), Optional.empty());
}
static ResponseOrException of(WhoisException exception) {
return new ResponseOrException(Optional.empty(), Optional.of(exception));
}
}
}

View File

@@ -1,208 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.xml.UtcDateTimeAdapter.getFormattedString;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.model.EppResource;
import google.registry.model.adapters.EnumToAttributeAdapter.EppEnum;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactPhoneNumber;
import google.registry.model.contact.PostalInfo;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.Domain;
import google.registry.model.domain.GracePeriod;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.persistence.VKey;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
/** Represents a WHOIS response to a domain query. */
final class DomainWhoisResponse extends WhoisResponseImpl {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** Prefix for status value URLs. */
private static final String ICANN_STATUS_URL_PREFIX = "https://icann.org/epp#";
/** Message required to be appended to all domain WHOIS responses. */
private static final String ICANN_AWIP_INFO_MESSAGE =
"For more information on Whois status codes, please visit https://icann.org/epp\r\n";
/** Domain which was the target of this WHOIS command. */
private final Domain domain;
/** Whether the full WHOIS output is to be displayed. */
private final boolean fullOutput;
/** When fullOutput is false, the text to display for the registrant's email fields. */
private final String whoisRedactedEmailText;
/** Creates new WHOIS domain response on the given domain. */
DomainWhoisResponse(
Domain domain, boolean fullOutput, String whoisRedactedEmailText, DateTime timestamp) {
super(timestamp);
this.domain = checkNotNull(domain, "domain");
this.fullOutput = fullOutput;
this.whoisRedactedEmailText = whoisRedactedEmailText;
}
@Override
public WhoisResponseResults getResponse(final boolean preferUnicode, String disclaimer) {
Optional<Registrar> registrarOptional =
Registrar.loadByRegistrarIdCached(domain.getCurrentSponsorRegistrarId());
checkState(
registrarOptional.isPresent(),
"Could not load registrar %s",
domain.getCurrentSponsorRegistrarId());
Registrar registrar = registrarOptional.get();
Optional<RegistrarPoc> abuseContact =
registrar.getPocsFromReplica().stream()
.filter(RegistrarPoc::getVisibleInDomainWhoisAsAbuse)
.findFirst();
return WhoisResponseResults.create(
new DomainEmitter()
.emitField("Domain Name", maybeFormatHostname(domain.getDomainName(), preferUnicode))
.emitField("Registry Domain ID", domain.getRepoId())
.emitField("Registrar WHOIS Server", registrar.getWhoisServer())
.emitField("Registrar URL", registrar.getUrl())
.emitFieldIfDefined("Updated Date", getFormattedString(domain.getLastEppUpdateTime()))
.emitField("Creation Date", getFormattedString(domain.getCreationTime()))
.emitField(
"Registry Expiry Date", getFormattedString(domain.getRegistrationExpirationTime()))
.emitField("Registrar", registrar.getRegistrarName())
.emitField("Registrar IANA ID", Objects.toString(registrar.getIanaIdentifier(), ""))
// Email address is a required field for registrar contacts. Therefore as long as there
// is an abuse contact, we can get an email address from it.
.emitField(
"Registrar Abuse Contact Email",
abuseContact.map(RegistrarPoc::getEmailAddress).orElse(""))
.emitField(
"Registrar Abuse Contact Phone",
abuseContact.map(RegistrarPoc::getPhoneNumber).orElse(""))
.emitStatusValues(domain.getStatusValues(), domain.getGracePeriods())
// TODO(mcilwain): Investigate if the WHOIS spec requires us to always output REDACTED
// text in WHOIS even if the contact is not present, and if so, do so.
.emitContact("Registrant", 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.loadNameserverHostNames(),
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. */
private Optional<VKey<Contact>> 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. */
class DomainEmitter extends Emitter<DomainEmitter> {
DomainEmitter emitPhone(
String contactType, String title, @Nullable ContactPhoneNumber phoneNumber) {
if (phoneNumber == null) {
return this;
}
return emitFieldIfDefined(
ImmutableList.of(contactType, title), phoneNumber.getPhoneNumber(), fullOutput)
.emitFieldIfDefined(
ImmutableList.of(contactType, title, "Ext"), phoneNumber.getExtension(), fullOutput);
}
/** Emit the contact entry of the given type. */
DomainEmitter emitContact(
String contactType, Optional<VKey<Contact>> contact, boolean preferUnicode) {
if (contact.isEmpty()) {
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.
Contact contact1 = EppResource.loadByCache(contact.get());
if (contact1 == null) {
logger.atSevere().log(
"(BUG) Broken reference found from domain %s to contact %s.",
domain.getDomainName(), contact);
return this;
}
PostalInfo postalInfo =
chooseByUnicodePreference(
preferUnicode,
contact1.getLocalizedPostalInfo(),
contact1.getInternationalizedPostalInfo());
// ICANN Consistent Labeling & Display policy requires that this be the ROID.
emitField(ImmutableList.of("Registry", contactType, "ID"), contact1.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", contact1.getVoiceNumber());
emitPhone(contactType, "Fax", contact1.getFaxNumber());
String emailFieldContent = fullOutput ? contact1.getEmailAddress() : whoisRedactedEmailText;
emitField(ImmutableList.of(contactType, "Email"), emailFieldContent);
return this;
}
/** Emits status values and grace periods as a set, in the AWIP format. */
DomainEmitter emitStatusValues(
Set<StatusValue> statusValues, Set<GracePeriod> gracePeriods) {
ImmutableSet.Builder<EppEnum> combinedStatuses = new ImmutableSet.Builder<>();
combinedStatuses.addAll(statusValues);
for (GracePeriod gracePeriod : gracePeriods) {
combinedStatuses.add(gracePeriod.getType());
}
return emitSet(
"Domain Status",
combinedStatuses.build(),
status -> {
String xmlName = status.getXmlName();
return String.format("%s %s%s", xmlName, ICANN_STATUS_URL_PREFIX, xmlName);
});
}
/** Emits the message that AWIP requires accompany all domain WHOIS responses. */
DomainEmitter emitAwipMessage() {
return emitRawLine(ICANN_AWIP_INFO_MESSAGE);
}
}
}

View File

@@ -1,64 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
import static google.registry.model.tld.Tlds.findTldForName;
import static google.registry.model.tld.Tlds.getTlds;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.net.InternetDomainName;
import google.registry.model.host.Host;
import java.util.Optional;
import org.joda.time.DateTime;
/** Represents a WHOIS lookup on a nameserver based on its hostname. */
public class NameserverLookupByHostCommand implements WhoisCommand {
private static final String ERROR_PREFIX = "Nameserver";
@VisibleForTesting final InternetDomainName hostName;
boolean cached;
NameserverLookupByHostCommand(InternetDomainName hostName, boolean cached) {
this.hostName = checkNotNull(hostName, "hostName");
this.cached = cached;
}
@Override
public final WhoisResponse executeQuery(final DateTime now) throws WhoisException {
Optional<InternetDomainName> tld = findTldForName(hostName);
// Google Registry Policy: Do not return records under TLDs for which we're not authoritative.
if (tld.isPresent() && getTlds().contains(tld.get().toString())) {
final Optional<WhoisResponse> response = getResponse(hostName, now);
if (response.isPresent()) {
return response.get();
}
}
throw new WhoisException(now, SC_NOT_FOUND, ERROR_PREFIX + " not found.");
}
private Optional<WhoisResponse> getResponse(InternetDomainName hostName, DateTime now) {
Optional<Host> host =
cached
? loadByForeignKeyByCache(Host.class, hostName.toString(), now)
: loadByForeignKey(Host.class, hostName.toString(), now);
return host.map(h -> new NameserverWhoisResponse(h, now));
}
}

View File

@@ -1,84 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.common.net.InetAddresses;
import com.google.common.net.InternetDomainName;
import google.registry.model.host.Host;
import google.registry.model.tld.Tlds;
import java.net.InetAddress;
import org.joda.time.DateTime;
/**
* Represents a WHOIS lookup for a nameserver based on its IP.
*
* <p>Both IPv4 and IPv6 addresses are supported. Unlike other WHOIS commands, this is an eventually
* consistent query.
*
* <p><b>Note:</b> There may be multiple nameservers with the same IP.
*/
final class NameserverLookupByIpCommand implements WhoisCommand {
@VisibleForTesting
final InetAddress ipAddress;
NameserverLookupByIpCommand(InetAddress ipAddress) {
this.ipAddress = checkNotNull(ipAddress, "ipAddress");
}
@Override
@SuppressWarnings("unchecked")
public WhoisResponse executeQuery(DateTime now) throws WhoisException {
Iterable<Host> hostsFromDb;
hostsFromDb =
replicaTm()
.transact(
() ->
// We cannot query @Convert-ed fields in HQL, so we must use native Postgres.
replicaTm()
.getEntityManager()
/*
* Using array_operator <@ (contained-by) with gin index on inet_address.
* Without gin index, this is slightly slower than the alternative form of
* ':address = ANY(inet_address)'.
*/
.createNativeQuery(
"SELECT * From \"Host\" WHERE "
+ "ARRAY[ CAST(:address AS TEXT) ] <@ inet_addresses AND "
+ "deletion_time > CAST(:now AS timestamptz)",
Host.class)
.setParameter("address", InetAddresses.toAddrString(ipAddress))
.setParameter("now", now.toString())
.getResultList());
ImmutableList<Host> hosts =
Streams.stream(hostsFromDb)
.filter(
host ->
Tlds.findTldForName(InternetDomainName.from(host.getHostName())).isPresent())
.collect(toImmutableList());
if (hosts.isEmpty()) {
throw new WhoisException(now, SC_NOT_FOUND, "No nameservers found.");
}
return new NameserverWhoisResponse(hosts, now);
}
}

View File

@@ -1,89 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.net.InetAddresses;
import google.registry.model.host.Host;
import google.registry.model.registrar.Registrar;
import java.util.Optional;
import org.joda.time.DateTime;
/** Container for WHOIS responses to a nameserver lookup queries. */
final class NameserverWhoisResponse extends WhoisResponseImpl {
/** Nameserver(s) which were the target of this WHOIS command. */
private final ImmutableList<Host> hosts;
/** Creates new WHOIS nameserver response on the given host. */
NameserverWhoisResponse(Host host, DateTime timestamp) {
this(ImmutableList.of(checkNotNull(host, "host")), timestamp);
}
/** Creates new WHOIS nameserver response on the given list of hosts. */
NameserverWhoisResponse(ImmutableList<Host> hosts, DateTime timestamp) {
super(timestamp);
this.hosts = checkNotNull(hosts, "hosts");
}
@Override
public WhoisResponseResults getResponse(boolean preferUnicode, String disclaimer) {
// If we have subordinate hosts, load their registrar ids in a single transaction up-front.
ImmutableList<Host> subordinateHosts =
hosts.stream().filter(Host::isSubordinate).collect(toImmutableList());
ImmutableMap<Host, String> hostRegistrars =
subordinateHosts.isEmpty()
? ImmutableMap.of()
: replicaTm()
.transact(
() ->
Maps.toMap(
subordinateHosts.iterator(),
host ->
replicaTm()
.loadByKey(host.getSuperordinateDomain())
.cloneProjectedAtTime(getTimestamp())
.getCurrentSponsorRegistrarId()));
BasicEmitter emitter = new BasicEmitter();
for (int i = 0; i < hosts.size(); i++) {
Host host = hosts.get(i);
String registrarId =
host.isSubordinate()
? hostRegistrars.get(host)
: host.getPersistedCurrentSponsorRegistrarId();
Optional<Registrar> registrar = Registrar.loadByRegistrarIdCached(registrarId);
checkState(registrar.isPresent(), "Could not load registrar %s", registrarId);
emitter
.emitField("Server Name", maybeFormatHostname(host.getHostName(), preferUnicode))
.emitSet("IP Address", host.getInetAddresses(), InetAddresses::toAddrString)
.emitField("Registrar", registrar.get().getRegistrarName())
.emitField("Registrar WHOIS Server", registrar.get().getWhoisServer())
.emitField("Registrar URL", registrar.get().getUrl());
if (i < hosts.size() - 1) {
emitter.emitNewline();
}
}
String plaintext = emitter.emitLastUpdated(getTimestamp()).emitFooter(disclaimer).toString();
return WhoisResponseResults.create(plaintext, hosts.size());
}
}

View File

@@ -1,39 +0,0 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
/**
* Whois module for systems that require that we not cache EPP resources (e.g. the nomulus tool).
*
* <h2>Dependencies</h2>
*
* <ul>
* <li>{@link google.registry.request.RequestModule RequestModule}
* </ul>
*
* @see "google.registry.tools.RegistryToolComponent"
*/
@Module
public final class NonCachingWhoisModule extends BaseWhoisModule {
@Provides
@Config("whoisCommandFactory")
static WhoisCommandFactory provideWhoisCommandFactory() {
return WhoisCommandFactory.createNonCached();
}
}

View File

@@ -1,108 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.model.CacheUtils.memoizeWithShortExpiration;
import static google.registry.util.RegistrarUtils.normalizeRegistrarName;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import google.registry.model.registrar.Registrar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.joda.time.DateTime;
/** Represents a WHOIS lookup for a registrar by its name. */
final class RegistrarLookupCommand implements WhoisCommand {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** True if the command should return cached responses. */
private boolean cached;
/**
* Cache of a map from a stripped-down (letters and digits only) name to the registrar. This map
* includes only active, publicly visible registrars, because the others should be invisible to
* WHOIS.
*/
private static final Supplier<Map<String, Registrar>> REGISTRAR_BY_NORMALIZED_NAME_CACHE =
memoizeWithShortExpiration(RegistrarLookupCommand::loadRegistrarMap);
@VisibleForTesting
final String registrarName;
RegistrarLookupCommand(String registrarName, boolean cached) {
checkArgument(!isNullOrEmpty(registrarName), "registrarName");
this.registrarName = registrarName;
this.cached = cached;
}
static Map<String, Registrar> loadRegistrarMap() {
Map<String, Registrar> map = new HashMap<>();
// Use the normalized registrar name as a key, and ignore inactive and hidden
// registrars.
for (Registrar registrar : Registrar.loadAll()) {
if (!registrar.isLiveAndPubliclyVisible() || registrar.getRegistrarName() == null) {
continue;
}
String normalized = normalizeRegistrarName(registrar.getRegistrarName());
if (map.put(normalized, registrar) != null) {
logger.atWarning().log(
"%s appeared as a normalized registrar name for more than one registrar.", normalized);
}
}
// Use the normalized registrar name without its last word as a key, assuming there are
// multiple words in the name. This allows searches without LLC or INC, etc. Only insert
// if there isn't already a mapping for this string, so that if there's a registrar with
// a
// two word name (Go Daddy) and no business-type suffix and another registrar with just
// that first word as its name (Go), the latter will win.
for (Registrar registrar : ImmutableList.copyOf(map.values())) {
if (registrar.getRegistrarName() == null) {
continue;
}
List<String> words =
Splitter.on(CharMatcher.whitespace()).splitToList(registrar.getRegistrarName());
if (words.size() > 1) {
String normalized =
normalizeRegistrarName(Joiner.on("").join(words.subList(0, words.size() - 1)));
map.putIfAbsent(normalized, registrar);
}
}
return ImmutableMap.copyOf(map);
}
@Override
public WhoisResponse executeQuery(DateTime now) throws WhoisException {
Map<String, Registrar> registrars =
cached ? REGISTRAR_BY_NORMALIZED_NAME_CACHE.get() : loadRegistrarMap();
Registrar registrar = registrars.get(normalizeRegistrarName(registrarName));
// If a registrar is in the cache, we know it must be active and publicly visible.
if (registrar == null) {
throw new WhoisException(now, SC_NOT_FOUND, "No registrar found.");
}
return new RegistrarWhoisResponse(registrar, now);
}
}

View File

@@ -1,96 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.base.Preconditions.checkNotNull;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import java.util.Set;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
/** Container for WHOIS responses to registrar lookup queries. */
class RegistrarWhoisResponse extends WhoisResponseImpl {
/** Registrar which was the target of this WHOIS command. */
private final Registrar registrar;
/**
* Used in the emitter below to signal either admin or tech
* contacts. NB, this is purposely distinct from the
* RegistrarContact.Type.{ADMIN,TECH} as they don't carry equivalent
* meaning in our system. Sigh.
*/
private enum AdminOrTech { ADMIN, TECH }
/** Creates a new WHOIS registrar response on the given registrar object. */
RegistrarWhoisResponse(Registrar registrar, DateTime timestamp) {
super(timestamp);
this.registrar = checkNotNull(registrar, "registrar");
}
@Override
public WhoisResponseResults getResponse(boolean preferUnicode, String disclaimer) {
Set<RegistrarPoc> contacts = registrar.getPocsFromReplica();
String plaintext =
new RegistrarEmitter()
.emitField("Registrar", registrar.getRegistrarName())
.emitAddress(
null,
chooseByUnicodePreference(
preferUnicode,
registrar.getLocalizedAddress(),
registrar.getInternationalizedAddress()),
true)
.emitPhonesAndEmail(
registrar.getPhoneNumber(), registrar.getFaxNumber(), registrar.getEmailAddress())
.emitField("Registrar WHOIS Server", registrar.getWhoisServer())
.emitField("Registrar URL", registrar.getUrl())
.emitRegistrarPocs("Admin", contacts, AdminOrTech.ADMIN)
.emitRegistrarPocs("Technical", contacts, AdminOrTech.TECH)
.emitLastUpdated(getTimestamp())
.emitFooter(disclaimer)
.toString();
return WhoisResponseResults.create(plaintext, 1);
}
/** An emitter with logic for registrars. */
static class RegistrarEmitter extends Emitter<RegistrarEmitter> {
/** Emits the registrar contact of the given type. */
RegistrarEmitter emitRegistrarPocs(
String contactLabel, Iterable<RegistrarPoc> contacts, AdminOrTech type) {
for (RegistrarPoc contact : contacts) {
if ((type == AdminOrTech.ADMIN && contact.getVisibleInWhoisAsAdmin())
|| (type == AdminOrTech.TECH && contact.getVisibleInWhoisAsTech())) {
emitField(contactLabel + " Contact", contact.getName())
.emitPhonesAndEmail(
contact.getPhoneNumber(),
contact.getFaxNumber(),
contact.getEmailAddress());
}
}
return this;
}
/** Emits the registrar contact of the given type. */
RegistrarEmitter emitPhonesAndEmail(
@Nullable String phone, @Nullable String fax, @Nullable String email) {
return emitField("Phone Number", phone)
.emitField("Fax Number", fax)
.emitField("Email", email);
}
}
}

View File

@@ -1,53 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import google.registry.config.RegistryConfig.Config;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.io.IOException;
import java.io.StringReader;
import org.joda.time.DateTime;
/** High-level WHOIS API for other packages. */
public final class Whois {
private final Clock clock;
private final String disclaimer;
private final WhoisReader whoisReader;
@Inject
public Whois(Clock clock, @Config("whoisDisclaimer") String disclaimer, WhoisReader whoisReader) {
this.clock = clock;
this.disclaimer = disclaimer;
this.whoisReader = whoisReader;
}
/** Performs a WHOIS lookup on a plaintext query string. */
public String lookup(String query, boolean preferUnicode, boolean fullOutput) {
DateTime now = clock.nowUtc();
try {
return whoisReader
.readCommand(new StringReader(query), fullOutput, now)
.executeQuery(now)
.getResponse(preferUnicode, disclaimer)
.plainTextOutput();
} catch (WhoisException e) {
return e.getResponse(preferUnicode, disclaimer).plainTextOutput();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -1,121 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static google.registry.request.Action.Method.POST;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.util.Retrier;
import google.registry.whois.WhoisException.UncheckedWhoisException;
import google.registry.whois.WhoisMetrics.WhoisMetric;
import google.registry.whois.WhoisResponse.WhoisResponseResults;
import jakarta.inject.Inject;
import java.io.Reader;
import org.joda.time.DateTime;
/**
* HTTP request handler for WHOIS protocol requests sent to us by a proxy.
*
* <p>All commands and responses conform to the WHOIS spec as defined in RFC 3912. Commands must be
* sent via an HTTP POST in the request body.
*
* <p>This action is meant to serve as a low level interface for the proxy app which forwards us
* requests received on port 43. However this interface is technically higher level because it sends
* back proper HTTP error codes such as 200, 400, 500, etc. These are discarded by the proxy because
* WHOIS specifies no manner for differentiating successful and erroneous requests.
*
* @see WhoisHttpAction
* @see <a href="http://www.ietf.org/rfc/rfc3912.txt">RFC 3912: WHOIS Protocol Specification</a>
*/
@Action(service = GaeService.PUBAPI, path = "/_dr/whois", method = POST, auth = Auth.AUTH_ADMIN)
public class WhoisAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** WHOIS doesn't define an encoding, nor any way to specify an encoding in the protocol. */
static final MediaType CONTENT_TYPE = MediaType.PLAIN_TEXT_UTF_8;
/**
* As stated above, this is the low level interface intended for port 43, and as such, it
* always prefers ASCII.
*/
static final boolean PREFER_UNICODE = false;
@Inject Clock clock;
@Inject Reader input;
@Inject Response response;
@Inject Retrier retrier;
@Inject WhoisReader whoisReader;
@Inject @Config("whoisDisclaimer") String disclaimer;
@Inject WhoisMetric.Builder metricBuilder;
@Inject WhoisMetrics whoisMetrics;
@Inject
WhoisAction() {}
@Override
public void run() {
String responseText;
final DateTime now = clock.nowUtc();
try {
final WhoisCommand command = whoisReader.readCommand(input, false, now);
metricBuilder.setCommand(command);
WhoisResponseResults results;
try {
results = command.executeQuery(now).getResponse(PREFER_UNICODE, disclaimer);
} catch (WhoisException e) {
throw new UncheckedWhoisException(e);
}
responseText = results.plainTextOutput();
setWhoisMetrics(metricBuilder, results.numResults(), SC_OK);
} catch (UncheckedWhoisException u) {
WhoisException e = (WhoisException) u.getCause();
WhoisResponseResults results = e.getResponse(PREFER_UNICODE, disclaimer);
responseText = results.plainTextOutput();
setWhoisMetrics(metricBuilder, 0, e.getStatus());
} catch (WhoisException e) {
WhoisResponseResults results = e.getResponse(PREFER_UNICODE, disclaimer);
responseText = results.plainTextOutput();
setWhoisMetrics(metricBuilder, 0, e.getStatus());
} catch (Throwable t) {
logger.atSevere().withCause(t).log("WHOIS request crashed.");
responseText = "Internal Server Error";
setWhoisMetrics(metricBuilder, 0, SC_INTERNAL_SERVER_ERROR);
}
// Note that we always return 200 (OK) even if an error was hit. This is because returning an
// non-OK HTTP status code will cause the proxy server to silently close the connection. Since
// WHOIS has no way to return errors, it's better to convert any such errors into strings and
// return them directly.
response.setStatus(SC_OK);
response.setContentType(CONTENT_TYPE);
response.setPayload(responseText);
whoisMetrics.recordWhoisMetric(metricBuilder.build());
}
private static void setWhoisMetrics(
WhoisMetric.Builder metricBuilder, int numResults, int status) {
metricBuilder.setNumResults(numResults);
metricBuilder.setStatus(status);
}
}

View File

@@ -1,29 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import org.joda.time.DateTime;
/** Represents a WHOIS command request from a client. */
public interface WhoisCommand {
/**
* Executes a WHOIS query and returns the resultant data.
*
* @return An object representing the response to the WHOIS command.
* @throws WhoisException If some error occured while executing the command.
*/
WhoisResponse executeQuery(DateTime now) throws WhoisException;
}

View File

@@ -1,80 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import com.google.common.net.InternetDomainName;
import google.registry.config.RegistryConfig.ConfigModule;
import java.net.InetAddress;
/**
* A class used to configure WHOIS commands.
*
* <p>To add custom commands, extend this class, then configure it in
* {@link ConfigModule#provideWhoisCommandFactoryClass}.
*/
public class WhoisCommandFactory {
/** True if the commands should return cached responses. */
private boolean cached = true;
/** Public default constructor, needed so we can construct this from the class name. */
public WhoisCommandFactory() {}
private WhoisCommandFactory(boolean cached) {
this.cached = cached;
}
/** Create a command factory that does not rely on entity caches. */
static WhoisCommandFactory createNonCached() {
return new WhoisCommandFactory(false);
}
/** Create a command factory that may rely on entity caches. */
static WhoisCommandFactory createCached() {
return new WhoisCommandFactory(true);
}
/** Returns a new {@link WhoisCommand} to perform a domain lookup on the specified domain name. */
public WhoisCommand domainLookup(
InternetDomainName domainName,
boolean fullOutput,
String whoisRedactedEmailText,
String domainBlockedByBsaTemplate) {
return new DomainLookupCommand(
domainName, fullOutput, cached, whoisRedactedEmailText, domainBlockedByBsaTemplate);
}
/**
* Returns a new {@link WhoisCommand} to perform a nameserver lookup on the specified IP address.
*/
public WhoisCommand nameserverLookupByIp(InetAddress inetAddress) {
return new NameserverLookupByIpCommand(inetAddress);
}
/**
* Returns a new {@link WhoisCommand} to perform a nameserver lookup on the specified host name.
*/
public WhoisCommand nameserverLookupByHost(InternetDomainName hostName) {
return new NameserverLookupByHostCommand(hostName, cached);
}
/**
* Returns a new {@link WhoisCommand} to perform a registrar lookup on the specified registrar
* name.
*/
public WhoisCommand registrarLookup(String registrar) {
return new RegistrarLookupCommand(registrar, cached);
}
}

View File

@@ -1,78 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
/** Exception that gets thrown when WHOIS command isn't successful. */
public final class WhoisException extends Exception implements WhoisResponse {
private final DateTime timestamp;
private final int status;
/** @see #WhoisException(DateTime, int, String, Throwable) */
WhoisException(DateTime timestamp, int status, String message) {
this(timestamp, status, message, null);
}
/**
* Construct an exception explaining why a WHOIS request has failed.
*
* @param timestamp should be set to the time at which this request was processed.
* @param status A non-2xx HTTP status code to indicate type of failure.
* @param message is displayed to the user so you should be careful about tainted data.
* @param cause the original exception or {@code null}.
* @throws IllegalArgumentException if {@code !(300 <= status < 700)}
*/
WhoisException(DateTime timestamp, int status, String message, @Nullable Throwable cause) {
super(message, cause);
checkArgument(300 <= status && status < 700,
"WhoisException status must be a non-2xx HTTP status code: %s", status);
this.timestamp = checkNotNull(timestamp, "timestamp");
this.status = status;
}
/** Returns the time at which this WHOIS request was processed. */
@Override
public DateTime getTimestamp() {
return timestamp;
}
/** Returns a non-2xx HTTP status code to differentiate types of failure. */
public int getStatus() {
return status;
}
@Override
public WhoisResponseResults getResponse(boolean preferUnicode, String disclaimer) {
String plaintext = new WhoisResponseImpl.BasicEmitter()
.emitRawLine(getMessage())
.emitLastUpdated(getTimestamp())
.emitFooter(disclaimer)
.toString();
return WhoisResponseResults.create(plaintext, 0);
}
/** Exception that wraps WhoisExceptions returned from Retrier. */
public static final class UncheckedWhoisException extends RuntimeException {
UncheckedWhoisException(WhoisException whoisException) {
super(whoisException);
}
}
}

View File

@@ -1,198 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.base.Verify.verify;
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
import static com.google.common.net.HttpHeaders.CACHE_CONTROL;
import static com.google.common.net.HttpHeaders.EXPIRES;
import static com.google.common.net.HttpHeaders.LAST_MODIFIED;
import static com.google.common.net.HttpHeaders.X_CONTENT_TYPE_OPTIONS;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Throwables;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.request.Action;
import google.registry.request.Action.GaeService;
import google.registry.request.RequestPath;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.Clock;
import google.registry.whois.WhoisMetrics.WhoisMetric;
import google.registry.whois.WhoisResponse.WhoisResponseResults;
import jakarta.inject.Inject;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import org.joda.time.DateTime;
import org.joda.time.Duration;
/**
* Human-Friendly HTTP WHOIS API
*
* <p>This API uses easy to understand paths rather than {@link WhoisAction} which requires a POST
* request containing a WHOIS command. Because the typical WHOIS command is along the lines of
* {@code "domain google.lol"} or the equivalent {@code "google.lol}, this action is just going to
* replace the slashes with spaces and let {@link WhoisReader} figure out what to do.
*
* <p>This action accepts requests from any origin.
*
* <p>You can send AJAX requests to our WHOIS API from your <em>very own</em> website using the
* following embed code:
*
* <pre>{@code
* <input id="query-input" placeholder="Domain, Nameserver, IP, etc." autofocus>
* <button id="search-button">Lookup</button>
* <pre id="whois-results"></pre>
* <script>
* (function() {
* var WHOIS_API_URL = 'https://domain-registry-alpha.appspot.com/whois/';
* function OnKeyPressQueryInput(ev) {
* if (typeof ev == 'undefined' && window.event) {
* ev = window.event;
* }
* if (ev.keyCode == 13) {
* document.getElementById('search-button').click();
* }
* }
* function OnClickSearchButton() {
* var query = document.getElementById('query-input').value;
* var req = new XMLHttpRequest();
* req.onreadystatechange = function() {
* if (req.readyState == 4) {
* var results = document.getElementById('whois-results');
* results.textContent = req.responseText;
* }
* };
* req.open('GET', WHOIS_API_URL + escape(query), true);
* req.send();
* }
* document.getElementById('search-button').onclick = OnClickSearchButton;
* document.getElementById('query-input').onkeypress = OnKeyPressQueryInput;
* })();
* </script>
* }</pre>
*
* @see WhoisAction
*/
@Action(
service = GaeService.PUBAPI,
path = WhoisHttpAction.PATH,
isPrefix = true,
auth = Auth.AUTH_PUBLIC)
public final class WhoisHttpAction implements Runnable {
public static final String PATH = "/whois/";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/**
* Cross-origin resource sharing (CORS) allowed origins policy.
*
* <p>This field specifies the value of the {@code Access-Control-Allow-Origin} response header.
* Without this header, other domains such as charlestonroadregistry.com would not be able to
* send requests to our WHOIS interface.
*
* <p>Our policy shall be to allow requests from pretty much anywhere using a wildcard policy.
* The reason this is safe is because our WHOIS interface doesn't allow clients to modify data,
* nor does it allow them to fetch user data. Only publicly available information is returned.
*
* @see <a href="http://www.w3.org/TR/cors/#access-control-allow-origin-response-header">
* W3C CORS § 5.1 Access-Control-Allow-Origin Response Header</a>
*/
private static final String CORS_ALLOW_ORIGIN = "*";
/** We're going to let any HTTP proxy in the world cache our responses. */
private static final String CACHE_CONTROL_VALUE = "public";
/** Responses may be cached for up to a day. */
private static final String X_CONTENT_NO_SNIFF = "nosniff";
/** Splitter that turns information on HTTP into a list of tokens. */
private static final Splitter SLASHER = Splitter.on('/').trimResults().omitEmptyStrings();
/** Joiner that turns {@link #SLASHER} tokens into a normal WHOIS query. */
private static final Joiner JOINER = Joiner.on(' ');
@Inject Clock clock;
@Inject Response response;
@Inject @Config("whoisDisclaimer") String disclaimer;
@Inject @Config("whoisHttpExpires") Duration expires;
@Inject WhoisReader whoisReader;
@Inject @RequestPath String requestPath;
@Inject WhoisMetric.Builder metricBuilder;
@Inject WhoisMetrics whoisMetrics;
@Inject
WhoisHttpAction() {}
@Override
public void run() {
verify(requestPath.startsWith(PATH));
String path = nullToEmpty(requestPath);
try {
// Extremely permissive parsing that turns stuff like "/hello/world/" into "hello world".
String commandText =
decode(JOINER.join(SLASHER.split(path.substring(PATH.length())))) + "\r\n";
DateTime now = clock.nowUtc();
WhoisCommand command = whoisReader.readCommand(new StringReader(commandText), false, now);
metricBuilder.setCommand(command);
sendResponse(SC_OK, command.executeQuery(now));
} catch (WhoisException e) {
metricBuilder.setStatus(e.getStatus());
metricBuilder.setNumResults(0);
sendResponse(e.getStatus(), e);
} catch (Throwable e) {
metricBuilder.setStatus(SC_INTERNAL_SERVER_ERROR);
metricBuilder.setNumResults(0);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
} finally {
whoisMetrics.recordWhoisMetric(metricBuilder.build());
}
}
private void sendResponse(int status, WhoisResponse whoisResponse) {
response.setStatus(status);
metricBuilder.setStatus(status);
response.setDateHeader(LAST_MODIFIED, whoisResponse.getTimestamp());
response.setDateHeader(EXPIRES, whoisResponse.getTimestamp().plus(expires));
response.setHeader(CACHE_CONTROL, CACHE_CONTROL_VALUE);
response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, CORS_ALLOW_ORIGIN);
response.setHeader(X_CONTENT_TYPE_OPTIONS, X_CONTENT_NO_SNIFF);
response.setContentType(PLAIN_TEXT_UTF_8);
WhoisResponseResults results = whoisResponse.getResponse(true, disclaimer);
metricBuilder.setNumResults(results.numResults());
response.setPayload(results.plainTextOutput());
}
/** Removes {@code %xx} escape codes from request path components. */
private String decode(String pathData)
throws UnsupportedEncodingException, WhoisException {
try {
return URLDecoder.decode(pathData, "UTF-8");
} catch (IllegalArgumentException e) {
logger.atInfo().log("Malformed WHOIS request path: %s (%s)", requestPath, pathData);
throw new WhoisException(clock.nowUtc(), SC_BAD_REQUEST, "Malformed path query.");
}
}
}

View File

@@ -1,147 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.base.Preconditions.checkState;
import static com.google.monitoring.metrics.EventMetric.DEFAULT_FITTER;
import com.google.auto.value.AutoBuilder;
import com.google.common.collect.ImmutableSet;
import com.google.monitoring.metrics.EventMetric;
import com.google.monitoring.metrics.IncrementableMetric;
import com.google.monitoring.metrics.LabelDescriptor;
import com.google.monitoring.metrics.MetricRegistryImpl;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.util.Optional;
import org.joda.time.DateTime;
/**
* Instrumentation for WHOIS requests.
*
* @see WhoisAction
*/
public class WhoisMetrics {
private static final ImmutableSet<LabelDescriptor> LABEL_DESCRIPTORS =
ImmutableSet.of(
LabelDescriptor.create("command_name", "The name of the WHOIS command."),
LabelDescriptor.create(
"num_results", "The number of results returned by the WHOIS command."),
LabelDescriptor.create("status", "The return status of the WHOIS command."));
private static final IncrementableMetric whoisRequests =
MetricRegistryImpl.getDefault()
.newIncrementableMetric(
"/whois/requests", "Count of WHOIS Requests", "count", LABEL_DESCRIPTORS);
private static final EventMetric processingTime =
MetricRegistryImpl.getDefault()
.newEventMetric(
"/whois/processing_time",
"WHOIS Processing Time",
"milliseconds",
LABEL_DESCRIPTORS,
DEFAULT_FITTER);
@Inject
public WhoisMetrics() {}
/** Records the given {@link WhoisMetric} and its associated processing time. */
public void recordWhoisMetric(WhoisMetric metric) {
whoisRequests.increment(
metric.commandName().orElse(""),
Integer.toString(metric.numResults()),
Integer.toString(metric.status()));
processingTime.record(
metric.endTimestamp().getMillis() - metric.startTimestamp().getMillis(),
metric.commandName().orElse(""),
Integer.toString(metric.numResults()),
Integer.toString(metric.status()));
}
/** A value class for recording attributes of a WHOIS metric. */
public record WhoisMetric(
Optional<String> commandName,
int numResults,
int status,
DateTime startTimestamp,
DateTime endTimestamp) {
/**
* Create a {@link WhoisMetric.Builder} for a request context, with the start and end timestamps
* taken from the given clock.
*
* <p>The start timestamp is recorded now, and the end timestamp at {@code build()}.
*/
public static WhoisMetric.Builder builderForRequest(Clock clock) {
return WhoisMetric.builder().setStartTimestamp(clock.nowUtc()).setClock(clock);
}
/** Create a {@link WhoisMetric.Builder}. */
public static Builder builder() {
return new AutoBuilder_WhoisMetrics_WhoisMetric_Builder();
}
/** A builder to create instances of {@link WhoisMetric}. */
@AutoBuilder
public abstract static class Builder {
boolean wasBuilt = false;
/** Builder-only clock to support automatic recording of endTimestamp on {@link #build()}. */
private Clock clock = null;
public Builder setCommand(WhoisCommand command) {
// All WHOIS command class names share the "Command" suffix, so strip it out in order to
// have shorter labels.
return setCommandName(command.getClass().getSimpleName().replaceFirst("Command$", ""));
}
public abstract Builder setCommandName(String commandName);
public abstract Builder setNumResults(int numResults);
public abstract Builder setStatus(int status);
abstract Builder setStartTimestamp(DateTime startTimestamp);
abstract Builder setEndTimestamp(DateTime endTimestamp);
Builder setClock(Clock clock) {
this.clock = clock;
return this;
}
/**
* Build an instance of {@link WhoisMetric} using this builder.
*
* <p>If a clock was provided with {@code setClock()}, the end timestamp will be set to the
* current timestamp of the clock; otherwise end timestamp must have been previously set.
*/
public WhoisMetric build() {
checkState(
!wasBuilt, "WhoisMetric was already built; building it again is most likely an error");
if (clock != null) {
setEndTimestamp(clock.nowUtc());
}
wasBuilt = true;
return autoBuild();
}
abstract WhoisMetric autoBuild();
}
}
}

View File

@@ -1,43 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static google.registry.util.TypeUtils.getClassFromString;
import static google.registry.util.TypeUtils.instantiate;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
/**
* Dagger module for the whois package.
*
* <h2>Dependencies</h2>
*
* <ul>
* <li>{@link google.registry.request.RequestModule RequestModule}
* </ul>
*
* @see "google.registry.module.frontend.FrontendComponent"
*/
@Module
public final class WhoisModule extends BaseWhoisModule {
@Provides
@Config("whoisCommandFactory")
static WhoisCommandFactory provideWhoisCommandFactory(
@Config("whoisCommandFactoryClass") String factoryClass) {
return instantiate(getClassFromString(factoryClass, WhoisCommandFactory.class));
}
}

View File

@@ -1,241 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.model.tld.Tlds.findTldForName;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import com.google.common.base.Joiner;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.CharStreams;
import com.google.common.net.InetAddresses;
import com.google.common.net.InternetDomainName;
import google.registry.config.RegistryConfig.Config;
import jakarta.inject.Inject;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.joda.time.DateTime;
/**
* The WhoisReader class understands how to read the WHOIS command from some source, parse it, and
* produce a new WhoisCommand instance. The command syntax of WHOIS is generally undefined, so we
* adopt the following rules:
*
* <dl>
* <dt>domain &lt;FQDN&gt;<dd>
* Looks up the domain record for the fully qualified domain name.
* <dt>nameserver &lt;FQDN&gt;<dd>
* Looks up the nameserver record for the fully qualified domain name.
* <dt>nameserver &lt;IP&gt;<dd>
* Looks up the nameserver record at the given IP address.
* <dt>registrar &lt;IANA ID&gt;<dd>
* Looks up the registrar record with the given IANA ID.
* <dt>registrar &lt;NAME&gt;<dd>
* Looks up the registrar record with the given name.
* <dt>&lt;IP&gt;<dd>
* Looks up the nameserver record with the given IP address.
* <dt>&lt;FQDN&gt;<dd>
* Looks up the nameserver or domain record for the fully qualified domain name.
* <dt>&lt;IANA ID&gt;<dd>
* Looks up the registrar record with the given IANA ID.
* </dl>
*
* @see <a href="http://tools.ietf.org/html/rfc3912">RFC 3912</a>
* @see <a href="http://www.iana.org/assignments/registrar-ids">IANA Registrar IDs</a>
*/
class WhoisReader {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/**
* These are strings that will always trigger a specific query type when they are sent at
* the beginning of a command.
*/
static final String DOMAIN_LOOKUP_COMMAND = "domain";
static final String NAMESERVER_LOOKUP_COMMAND = "nameserver";
static final String REGISTRAR_LOOKUP_COMMAND = "registrar";
private final WhoisCommandFactory commandFactory;
private final String whoisRedactedEmailText;
private final String domainBlockedByBsaTemplate;
/** Creates a new WhoisReader that extracts its command from the specified Reader. */
@Inject
WhoisReader(
@Config("whoisCommandFactory") WhoisCommandFactory commandFactory,
@Config("whoisRedactedEmailText") String whoisRedactedEmailText,
@Config("domainBlockedByBsaTemplate") String domainBlockedByBsaTemplate) {
this.commandFactory = commandFactory;
this.whoisRedactedEmailText = whoisRedactedEmailText;
this.domainBlockedByBsaTemplate = domainBlockedByBsaTemplate;
}
/**
* 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, 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, boolean fullOutput, DateTime now)
throws WhoisException {
// Split the string into tokens based on whitespace.
List<String> tokens = filterEmptyStrings(command.split("\\s"));
if (tokens.isEmpty()) {
throw new WhoisException(now, SC_BAD_REQUEST, "No WHOIS command specified.");
}
final String arg1 = tokens.get(0);
// Check if the first token is equal to the domain lookup command.
if (arg1.equalsIgnoreCase(DOMAIN_LOOKUP_COMMAND)) {
if (tokens.size() != 2) {
throw new WhoisException(now, SC_BAD_REQUEST, String.format(
"Wrong number of arguments to '%s' command.", DOMAIN_LOOKUP_COMMAND));
}
// Try to parse the argument as a domain name.
try {
logger.atInfo().log(
"Attempting domain lookup command using domain name '%s'.", tokens.get(1));
return commandFactory.domainLookup(
InternetDomainName.from(canonicalizeHostname(tokens.get(1))),
fullOutput,
whoisRedactedEmailText,
domainBlockedByBsaTemplate);
} 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(
"Could not parse argument to '%s' command", DOMAIN_LOOKUP_COMMAND));
}
}
// Check if the first token is equal to the nameserver lookup command.
if (arg1.equalsIgnoreCase(NAMESERVER_LOOKUP_COMMAND)) {
if (tokens.size() != 2) {
throw new WhoisException(now, SC_BAD_REQUEST, String.format(
"Wrong number of arguments to '%s' command.", NAMESERVER_LOOKUP_COMMAND));
}
// Try to parse the argument as an IP address.
try {
logger.atInfo().log(
"Attempting nameserver lookup command using %s as an IP address.", tokens.get(1));
return commandFactory.nameserverLookupByIp(InetAddresses.forString(tokens.get(1)));
} catch (IllegalArgumentException iae) {
// Silently ignore this exception.
}
// Try to parse the argument as a host name.
try {
logger.atInfo().log(
"Attempting nameserver lookup command using %s as a hostname.", tokens.get(1));
return commandFactory.nameserverLookupByHost(
InternetDomainName.from(canonicalizeHostname(tokens.get(1))));
} catch (IllegalArgumentException iae) {
// Silently ignore this exception.
}
// If we can't interpret the argument as either a host name or IP address, return an error.
throw new WhoisException(now, SC_BAD_REQUEST, String.format(
"Could not parse argument to '%s' command", NAMESERVER_LOOKUP_COMMAND));
}
// Check if the first token is equal to the registrar lookup command.
if (arg1.equalsIgnoreCase(REGISTRAR_LOOKUP_COMMAND)) {
if (tokens.size() == 1) {
throw new WhoisException(now, SC_BAD_REQUEST, String.format(
"Too few arguments to '%s' command.", REGISTRAR_LOOKUP_COMMAND));
}
String registrarLookupArgument = Joiner.on(' ').join(tokens.subList(1, tokens.size()));
logger.atInfo().log(
"Attempting registrar lookup command using registrar %s.", registrarLookupArgument);
return commandFactory.registrarLookup(registrarLookupArgument);
}
// If we have a single token, then try to interpret that in various ways.
if (tokens.size() == 1) {
// Try to parse it as an IP address. If successful, then this is a lookup on a nameserver.
try {
logger.atInfo().log("Attempting nameserver lookup using %s as an IP address.", arg1);
return commandFactory.nameserverLookupByIp(InetAddresses.forString(arg1));
} catch (IllegalArgumentException iae) {
// Silently ignore this exception.
}
// Try to parse it as a domain name or host name.
try {
final InternetDomainName targetName = InternetDomainName.from(canonicalizeHostname(arg1));
// We don't know at this point whether we have a domain name or a host name. We have to
// search through our configured TLDs to see if there's one that prefixes the name.
Optional<InternetDomainName> tld = findTldForName(targetName);
if (tld.isEmpty()) {
// This target is not under any configured TLD, so just try it as a registrar name.
logger.atInfo().log("Attempting registrar lookup using %s as a registrar.", arg1);
return commandFactory.registrarLookup(arg1);
}
// If the target is exactly one level above the TLD, then this is a second level domain
// (SLD) and we should do a domain lookup on it.
if (targetName.parent().equals(tld.get())) {
logger.atInfo().log("Attempting domain lookup using %s as a domain name.", targetName);
return commandFactory.domainLookup(
targetName, fullOutput, whoisRedactedEmailText, domainBlockedByBsaTemplate);
}
// The target is more than one level above the TLD, so we'll assume it's a nameserver.
logger.atInfo().log("Attempting nameserver lookup using %s as a hostname.", targetName);
return commandFactory.nameserverLookupByHost(targetName);
} catch (IllegalArgumentException e) {
// Silently ignore this exception.
}
// Purposefully fall through to code below.
}
// The only case left is that there are multiple tokens with no particular command given. We'll
// assume this is a registrar lookup, since there's really nothing else it could be.
String registrarLookupArgument = Joiner.on(' ').join(tokens);
logger.atInfo().log(
"Attempting registrar lookup employing %s as a registrar.", registrarLookupArgument);
return commandFactory.registrarLookup(registrarLookupArgument);
}
/** Returns an ArrayList containing the contents of the String array minus any empty strings. */
private static List<String> filterEmptyStrings(String[] strings) {
List<String> list = new ArrayList<>(strings.length);
for (String str : strings) {
if (!isNullOrEmpty(str)) {
list.add(str);
}
}
return list;
}
}

View File

@@ -1,44 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import org.joda.time.DateTime;
/** Representation of a WHOIS query response. */
public interface WhoisResponse {
/**
* Returns the WHOIS response.
*
* @param preferUnicode if {@code false} will cause the output to be converted to ASCII whenever
* possible; for example, converting IDN hostname labels to punycode. However certain things
* (like a domain registrant name with accent marks) will be returned "as is". If the WHOIS
* client has told us they're able to receive UTF-8 (such as with HTTP) then this field should
* be set to {@code true}.
* @param disclaimer text to show at bottom of output
*/
WhoisResponseResults getResponse(boolean preferUnicode, String disclaimer);
/** Returns the time at which this response was created. */
DateTime getTimestamp();
/** A wrapper class for the plaintext response of a WHOIS command and its number of results. */
record WhoisResponseResults(String plainTextOutput, int numResults) {
static WhoisResponseResults create(String plainTextOutput, int numResults) {
return new WhoisResponseResults(plainTextOutput, numResults);
}
}
}

View File

@@ -1,245 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.base.Joiner;
import google.registry.model.eppcommon.Address;
import google.registry.util.Idn;
import google.registry.xml.UtcDateTimeAdapter;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
/** Base class for responses to WHOIS queries. */
abstract class WhoisResponseImpl implements WhoisResponse {
/** Field name for ICANN problem reporting URL appended to all WHOIS responses. */
private static final String ICANN_REPORTING_URL_FIELD =
"URL of the ICANN Whois Inaccuracy Complaint Form";
/** 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;
WhoisResponseImpl(DateTime timestamp) {
this.timestamp = checkNotNull(timestamp, "timestamp");
}
@Override
public DateTime getTimestamp() {
return timestamp;
}
/**
* Translates a hostname to its unicode representation if desired.
*
* @param hostname is assumed to be in its canonical ASCII form from the database.
*/
static String maybeFormatHostname(String hostname, boolean preferUnicode) {
return preferUnicode ? Idn.toUnicode(hostname) : hostname;
}
static <T> T chooseByUnicodePreference(
boolean preferUnicode, @Nullable T localized, @Nullable T internationalized) {
if (preferUnicode) {
return Optional.ofNullable(localized).orElse(internationalized);
} else {
return Optional.ofNullable(internationalized).orElse(localized);
}
}
/** Writer for outputting data in the WHOIS format. */
abstract static class Emitter<E extends Emitter<E>> {
private final StringBuilder stringBuilder = new StringBuilder();
@SuppressWarnings("unchecked")
private E thisCastToDerived() {
return (E) this;
}
E emitNewline() {
stringBuilder.append("\r\n");
return thisCastToDerived();
}
/**
* Helper method that loops over a set of values and calls {@link #emitField}. This method will
* turn each value into a string using the provided callback and then sort those strings so the
* textual output is deterministic (which is important for unit tests). The ideal solution would
* be to use {@link java.util.SortedSet} but that would require reworking the models.
*/
<T> E emitSet(String title, Set<T> values, Function<T, String> transform) {
return emitList(title, values.stream().map(transform).sorted().collect(toImmutableList()));
}
/**
* Helper method that loops over a list of values and calls {@link #emitField}.
*
* <p>This method redacts the output unless {@code fullOutput} is {@code true}.
*/
E emitList(String title, Iterable<String> values, boolean fullOutput) {
for (String value : values) {
emitField(title, value, fullOutput);
}
return thisCastToDerived();
}
/** Helper method that loops over a list of values and calls {@link #emitField}. */
E emitList(String title, Iterable<String> values) {
return emitList(title, values, true);
}
/**
* Emit the field name and value followed by a newline, but only if a value exists.
*
* <p>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(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.
*
* <p>This method redacts the output unless {@code fullOutput} is {@code true}.
*/
E emitFieldIfDefined(List<String> 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<String> nameParts, String value) {
return emitFieldIfDefined(nameParts, value, true);
}
/**
* Emit the field name and value followed by a newline. /*
*
* <p>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) {
return emitField(name, value, true);
}
/**
* Emit a multi-part field name and value followed by a newline.
*
* <p>This method redacts the output unless {@code fullOutput} is {@code true}.
*/
E emitField(List<String> 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<String> nameParts, String 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) {
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();
}
/** Emit Whois Inaccuracy Complaint Form link. Only used for domain queries. */
E emitWicfLink() {
emitField(ICANN_REPORTING_URL_FIELD, ICANN_REPORTING_URL);
return thisCastToDerived();
}
/** Returns raw text that should be appended to the end of ALL WHOIS responses. */
E emitLastUpdated(DateTime timestamp) {
// We are assuming that our WHOIS database is always completely up to date, since it's
// querying the live backend database.
stringBuilder
.append(">>> Last update of WHOIS database: ")
.append(UtcDateTimeAdapter.getFormattedString(timestamp))
.append(" <<<\r\n\r\n");
return thisCastToDerived();
}
/** Returns raw text that should be appended to the end of ALL WHOIS responses. */
E emitFooter(String disclaimer) {
stringBuilder.append(disclaimer.replaceAll("\r?\n", "\r\n").trim()).append("\r\n");
return thisCastToDerived();
}
/** Emits a string directly, followed by a newline. */
protected E emitRawLine(String string) {
stringBuilder.append(string);
return emitNewline();
}
/** Remove ASCII control characters like {@code \n} which could be used to forge output. */
private String cleanse(String value) {
return value.replaceAll("[\\x00-\\x1f]", " ");
}
@Override
public String toString() {
return stringBuilder.toString();
}
}
/** An emitter that needs no special logic. */
static class BasicEmitter extends Emitter<BasicEmitter> {}
}

View File

@@ -1,16 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
@javax.annotation.ParametersAreNonnullByDefault
package google.registry.whois;

View File

@@ -47,14 +47,12 @@ public final class RegistryTestServer {
public static final ImmutableList<Route> ROUTES =
ImmutableList.of(
// Frontend Services
route("/whois/*", FrontendServlet.class),
route("/rdap/*", FrontendServlet.class),
route("/check", FrontendServlet.class),
route("/console-api/*", FrontendTestServlet.class),
// Proxy Services
route("/_dr/epp", FrontendServlet.class),
route("/_dr/whois", FrontendServlet.class),
// Registry Data Escrow (RDE)
route("/_dr/cron/rdeCreate", BackendServlet.class),

View File

@@ -1,68 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server;
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.testing.CertificateSamples;
import google.registry.ui.forms.FormFieldException;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link RegistrarFormFields}. */
class RegistrarFormFieldsTest {
@Test
void testValidCertificate_doesntThrowError() {
assertThat(RegistrarFormFields.CLIENT_CERTIFICATE_FIELD.convert(CertificateSamples.SAMPLE_CERT))
.hasValue(CertificateSamples.SAMPLE_CERT);
}
@Test
void testBadCertificate_throwsFfe() {
FormFieldException thrown =
assertThrows(
FormFieldException.class,
() -> RegistrarFormFields.CLIENT_CERTIFICATE_FIELD.convert("palfun"));
assertThat(
thrown,
equalTo(
new FormFieldException("Invalid X.509 PEM certificate")
.propagate("clientCertificate")));
}
@Test
void testValidCertificateHash_doesntThrowError() {
assertThat(
RegistrarFormFields.CLIENT_CERTIFICATE_HASH_FIELD.convert(
CertificateSamples.SAMPLE_CERT_HASH))
.hasValue(CertificateSamples.SAMPLE_CERT_HASH);
}
@Test
void testBadCertificateHash_throwsFfe() {
FormFieldException thrown =
assertThrows(
FormFieldException.class,
() -> RegistrarFormFields.CLIENT_CERTIFICATE_HASH_FIELD.convert("~~~"));
assertThat(
thrown,
equalTo(
new FormFieldException("Field must contain a base64 value.")
.propagate("clientCertificateHash")));
}
}

View File

@@ -1,371 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.whois.WhoisTestData.loadFile;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactAddress;
import google.registry.model.contact.ContactPhoneNumber;
import google.registry.model.contact.PostalInfo;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.Domain;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.Host;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.FakeClock;
import google.registry.whois.WhoisResponse.WhoisResponseResults;
import java.util.Optional;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link DomainWhoisResponse}. */
class DomainWhoisResponseTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private Host host1;
private Host host2;
private RegistrarPoc abuseContact;
private Contact adminContact;
private Contact registrant;
private Contact techContact;
private Domain domain;
private final FakeClock clock = new FakeClock(DateTime.parse("2009-05-29T20:15:00Z"));
@BeforeEach
void beforeEach() {
// Update the registrar to have an IANA ID.
Registrar registrar =
persistResource(
loadRegistrar("NewRegistrar")
.asBuilder()
.setUrl("http://my.fake.url")
.setIanaIdentifier(5555555L)
.build());
abuseContact =
persistResource(
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("Jake Doe")
.setEmailAddress("jakedoe@theregistrar.com")
.setPhoneNumber("+1.2125551216")
.setVisibleInDomainWhoisAsAbuse(true)
.build());
createTld("tld");
host1 =
persistResource(
new Host.Builder()
.setHostName("ns01.exampleregistrar.tld")
.setRepoId("1-ROID")
.build());
host2 =
persistResource(
new Host.Builder()
.setHostName("ns02.exampleregistrar.tld")
.setRepoId("2-ROID")
.build());
registrant =
persistResource(
new Contact.Builder()
.setContactId("5372808-ERL")
.setRepoId("4-ROID")
.setCreationRegistrarId("NewRegistrar")
.setPersistedCurrentSponsorRegistrarId("NewRegistrar")
.setLocalizedPostalInfo(
new PostalInfo.Builder()
.setType(PostalInfo.Type.LOCALIZED)
.setName("SHOULD NOT BE USED")
.setOrg("SHOULD NOT BE USED")
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("123 EXAMPLE STREET"))
.setCity("ANYTOWN")
.setState("AP")
.setZip("A1A1A1")
.setCountryCode("EX")
.build())
.build())
.setInternationalizedPostalInfo(
new PostalInfo.Builder()
.setType(PostalInfo.Type.INTERNATIONALIZED)
.setName("EXAMPLE REGISTRANT")
.setOrg("Tom & Jerry Corp.")
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("123 EXAMPLE STREET"))
.setCity("ANYTOWN")
.setState("AP")
.setZip("A1A1A1")
.setCountryCode("EX")
.build())
.build())
.setVoiceNumber(
new ContactPhoneNumber.Builder()
.setPhoneNumber("+1.5555551212")
.setExtension("1234")
.build())
.setFaxNumber(
new ContactPhoneNumber.Builder()
.setPhoneNumber("+1.5555551213")
.setExtension("4321")
.build())
.setEmailAddress("EMAIL@EXAMPLE.tld")
.build());
adminContact =
persistResource(
new Contact.Builder()
.setContactId("5372809-ERL")
.setRepoId("5-ROID")
.setCreationRegistrarId("NewRegistrar")
.setPersistedCurrentSponsorRegistrarId("NewRegistrar")
.setLocalizedPostalInfo(
new PostalInfo.Builder()
.setType(PostalInfo.Type.LOCALIZED)
.setName("SHOULD NOT BE USED")
.setOrg("SHOULD NOT BE USED")
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("123 EXAMPLE STREET"))
.setCity("ANYTOWN")
.setState("AP")
.setZip("A1A1A1")
.setCountryCode("EX")
.build())
.build())
.setInternationalizedPostalInfo(
new PostalInfo.Builder()
.setType(PostalInfo.Type.INTERNATIONALIZED)
.setName("EXAMPLE REGISTRANT ADMINISTRATIVE")
.setOrg("EXAMPLE REGISTRANT ORGANIZATION")
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("123 EXAMPLE STREET"))
.setCity("ANYTOWN")
.setState("AP")
.setZip("A1A1A1")
.setCountryCode("EX")
.build())
.build())
.setVoiceNumber(
new ContactPhoneNumber.Builder()
.setPhoneNumber("+1.5555551212")
.setExtension("1234")
.build())
.setFaxNumber(
new ContactPhoneNumber.Builder().setPhoneNumber("+1.5555551213").build())
.setEmailAddress("EMAIL@EXAMPLE.tld")
.build());
techContact =
persistResource(
new Contact.Builder()
.setContactId("5372811-ERL")
.setRepoId("6-ROID")
.setCreationRegistrarId("NewRegistrar")
.setPersistedCurrentSponsorRegistrarId("NewRegistrar")
.setLocalizedPostalInfo(
new PostalInfo.Builder()
.setType(PostalInfo.Type.LOCALIZED)
.setName("SHOULD NOT BE USED")
.setOrg("SHOULD NOT BE USED")
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("123 EXAMPLE STREET"))
.setCity("ANYTOWN")
.setState("AP")
.setZip("A1A1A1")
.setCountryCode("EX")
.build())
.build())
.setInternationalizedPostalInfo(
new PostalInfo.Builder()
.setType(PostalInfo.Type.INTERNATIONALIZED)
.setName("EXAMPLE REGISTRAR TECHNICAL")
.setOrg("EXAMPLE REGISTRAR LLC")
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("123 EXAMPLE STREET"))
.setCity("ANYTOWN")
.setState("AP")
.setZip("A1A1A1")
.setCountryCode("EX")
.build())
.build())
.setVoiceNumber(
new ContactPhoneNumber.Builder()
.setPhoneNumber("+1.1235551234")
.setExtension("1234")
.build())
.setFaxNumber(
new ContactPhoneNumber.Builder()
.setPhoneNumber("+1.5555551213")
.setExtension("93")
.build())
.setEmailAddress("EMAIL@EXAMPLE.tld")
.build());
VKey<Host> host1VKey = host1.createVKey();
VKey<Host> host2VKey = host2.createVKey();
VKey<Contact> registrantResourceKey = registrant.createVKey();
VKey<Contact> adminResourceKey = adminContact.createVKey();
VKey<Contact> techResourceKey = techContact.createVKey();
String repoId = "3-TLD";
domain =
persistResource(
new Domain.Builder()
.setDomainName("example.tld")
.setRepoId(repoId)
.setCreationRegistrarId("NewRegistrar")
.setPersistedCurrentSponsorRegistrarId("NewRegistrar")
.setLastEppUpdateTime(DateTime.parse("2009-05-29T20:13:00Z"))
.setCreationTimeForTest(DateTime.parse("2000-10-08T00:45:00Z"))
.setRegistrationExpirationTime(DateTime.parse("2010-10-08T00:44:59Z"))
.setStatusValues(
ImmutableSet.of(
StatusValue.CLIENT_DELETE_PROHIBITED,
StatusValue.CLIENT_RENEW_PROHIBITED,
StatusValue.CLIENT_TRANSFER_PROHIBITED,
StatusValue.SERVER_UPDATE_PROHIBITED))
.setRegistrant(Optional.of(registrantResourceKey))
.setContacts(
ImmutableSet.of(
DesignatedContact.create(DesignatedContact.Type.ADMIN, adminResourceKey),
DesignatedContact.create(DesignatedContact.Type.TECH, techResourceKey)))
.setNameservers(ImmutableSet.of(host1VKey, host2VKey))
.setDsData(ImmutableSet.of(DomainDsData.create(1, 2, 3, "deadface")))
.setGracePeriods(
ImmutableSet.of(
GracePeriod.create(
GracePeriodStatus.ADD, repoId, END_OF_TIME, "NewRegistrar", null),
GracePeriod.create(
GracePeriodStatus.TRANSFER, repoId, END_OF_TIME, "NewRegistrar", null)))
.build());
}
@Test
void getPlainTextOutputTest() {
DomainWhoisResponse domainWhoisResponse =
new DomainWhoisResponse(domain, false, "Please contact registrar", 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.txt"), 1));
}
@Test
void getPlainTextOutputTest_noRegistrant() {
DomainWhoisResponse domainWhoisResponse =
new DomainWhoisResponse(
domain.asBuilder().setRegistrant(Optional.empty()).build(),
false,
"Please contact registrar",
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_no_registrant.txt"), 1));
}
@Test
void getPlainTextOutputTest_noContacts() {
DomainWhoisResponse domainWhoisResponse =
new DomainWhoisResponse(
domain
.asBuilder()
.setRegistrant(Optional.empty())
.setContacts(ImmutableSet.of())
.build(),
false,
"Please contact registrar",
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_no_contacts.txt"), 1));
}
@Test
void getPlainTextOutputTest_registrarAbuseInfoMissing() {
persistResource(abuseContact.asBuilder().setVisibleInDomainWhoisAsAbuse(false).build());
DomainWhoisResponse domainWhoisResponse =
new DomainWhoisResponse(domain, false, "Please contact registrar", clock.nowUtc());
assertThat(
domainWhoisResponse.getResponse(false, "Footer"))
.isEqualTo(
WhoisResponseResults.create(
loadFile("whois_domain_registrar_abuse_info_missing.txt"), 1));
}
@Test
void getPlainTextOutputTest_fullOutput() {
DomainWhoisResponse domainWhoisResponse =
new DomainWhoisResponse(domain, true, "Please contact registrar", 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
void addImplicitOkStatusTest() {
DomainWhoisResponse domainWhoisResponse =
new DomainWhoisResponse(
domain.asBuilder().setStatusValues(null).build(),
false,
"Contact the registrar",
clock.nowUtc());
assertThat(
domainWhoisResponse
.getResponse(
false,
"""
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.""")
.plainTextOutput())
.contains("Domain Status: ok");
}
}

View File

@@ -1,127 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.whois.WhoisTestData.loadFile;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.model.registrar.Registrar;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.whois.WhoisResponse.WhoisResponseResults;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link NameserverWhoisResponse}. */
class NameserverWhoisResponseTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private Host host1;
private Host host2;
private Host host3;
private final FakeClock clock = new FakeClock(DateTime.parse("2009-05-29T20:15:00Z"));
@BeforeEach
void beforeEach() {
persistNewRegistrar("example", "Hänsel & Gretel Registrar, Inc.", Registrar.Type.REAL, 8L);
persistResource(loadRegistrar("example").asBuilder().setUrl("http://my.fake.url").build());
createTld("tld");
Domain domain = persistResource(DatabaseHelper.newDomain("zobo.tld"));
host1 =
new Host.Builder()
.setHostName("ns1.example.tld")
.setPersistedCurrentSponsorRegistrarId("example")
.setInetAddresses(
ImmutableSet.of(
InetAddresses.forString("192.0.2.123"),
InetAddresses.forString("2001:0DB8::1")))
.setRepoId("1-EXAMPLE")
.build();
host2 =
new Host.Builder()
.setHostName("ns2.example.tld")
.setPersistedCurrentSponsorRegistrarId("example")
.setInetAddresses(
ImmutableSet.of(
InetAddresses.forString("192.0.2.123"),
InetAddresses.forString("2001:0DB8::1")))
.setRepoId("2-EXAMPLE")
.build();
host3 =
new Host.Builder()
.setHostName("ns1.zobo.tld")
.setSuperordinateDomain(domain.createVKey())
.setPersistedCurrentSponsorRegistrarId("example")
.setInetAddresses(
ImmutableSet.of(
InetAddresses.forString("192.0.2.123"),
InetAddresses.forString("2001:0DB8::1")))
.setRepoId("3-EXAMPLE")
.build();
}
@Test
void testGetTextOutput() {
NameserverWhoisResponse nameserverWhoisResponse =
new NameserverWhoisResponse(host1, clock.nowUtc());
assertThat(
nameserverWhoisResponse.getResponse(
false,
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
.isEqualTo(WhoisResponseResults.create(loadFile("whois_nameserver.txt"), 1));
}
@Test
void testGetMultipleNameserversResponse() {
NameserverWhoisResponse nameserverWhoisResponse =
new NameserverWhoisResponse(ImmutableList.of(host1, host2), clock.nowUtc());
assertThat(
nameserverWhoisResponse.getResponse(
false,
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
.isEqualTo(WhoisResponseResults.create(loadFile("whois_multiple_nameservers.txt"), 2));
}
@Test
void testSubordinateDomains() {
NameserverWhoisResponse nameserverWhoisResponse =
new NameserverWhoisResponse(host3, clock.nowUtc());
assertThat(
nameserverWhoisResponse.getResponse(
false,
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
.isEqualTo(WhoisResponseResults.create(loadFile("whois_subord_nameserver.txt"), 1));
}
}

View File

@@ -1,138 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistResources;
import static google.registry.whois.WhoisTestData.loadFile;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.FakeClock;
import google.registry.whois.WhoisResponse.WhoisResponseResults;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link RegistrarWhoisResponse}. */
class RegistrarWhoisResponseTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private final FakeClock clock = new FakeClock(DateTime.parse("2009-05-29T20:15:00Z"));
@Test
void getTextOutputTest() {
Registrar registrar =
new Registrar.Builder()
.setRegistrarId("exregistrar")
.setRegistrarName("Example Registrar, Inc.")
.setType(Registrar.Type.REAL)
.setIanaIdentifier(8L)
.setState(Registrar.State.ACTIVE)
.setLocalizedAddress(
new RegistrarAddress.Builder()
.setStreet(ImmutableList.of("1234 Admiralty Way"))
.setCity("Marina del Rey")
.setState("CA")
.setZip("90292")
.setCountryCode("US")
.build())
.setPhoneNumber("+1.3105551212")
.setFaxNumber("+1.3105551213")
.setEmailAddress("registrar@example.tld")
.setWhoisServer("whois.example-registrar.tld")
.setUrl("http://my.fake.url")
.build();
// Use the registrar key for contacts' parent.
ImmutableList<RegistrarPoc> contacts =
ImmutableList.of(
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("Joe Registrar")
.setEmailAddress("joeregistrar@example-registrar.tld")
.setPhoneNumber("+1.3105551213")
.setFaxNumber("+1.3105551213")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.build(),
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("John Doe")
.setEmailAddress("johndoe@example-registrar.tld")
.setPhoneNumber("+1.1111111111")
.setFaxNumber("+1.1111111111")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
.build(),
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("Jane Registrar")
.setEmailAddress("janeregistrar@example-registrar.tld")
.setPhoneNumber("+1.3105551214")
.setFaxNumber("+1.3105551213")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.ADMIN))
.setVisibleInWhoisAsAdmin(true)
.build(),
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("Jane Doe")
.setEmailAddress("janedoe@example-registrar.tld")
.setPhoneNumber("+1.1111111112")
.setFaxNumber("+1.1111111112")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
.build(),
new RegistrarPoc.Builder()
.setRegistrar(registrar)
.setName("Bonnie & Clyde")
.setEmailAddress("johngeek@example-registrar.tld")
.setPhoneNumber("+1.3105551215")
.setFaxNumber("+1.3105551216")
.setTypes(ImmutableSet.of(RegistrarPoc.Type.TECH))
.setVisibleInWhoisAsTech(true)
.build());
persistResource(registrar);
persistResources(contacts);
RegistrarWhoisResponse registrarWhoisResponse =
new RegistrarWhoisResponse(registrar, clock.nowUtc());
assertThat(
registrarWhoisResponse.getResponse(
false,
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested."))
.isEqualTo(WhoisResponseResults.create(loadFile("whois_registrar.txt"), 1));
}
@Test
void testSetOfFields() {
Registrar registrar =
persistNewRegistrar("exregistrar", "Ex-Registrar", Registrar.Type.REAL, 8L);
RegistrarWhoisResponse registrarWhoisResponse =
new RegistrarWhoisResponse(registrar, clock.nowUtc());
// Just make sure this doesn't NPE.
registrarWhoisResponse.getResponse(
false, "Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested.");
}
}

View File

@@ -1,679 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.bsa.persistence.BsaTestingUtils.persistBsaLabel;
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCacheIfEnabled;
import static google.registry.model.registrar.Registrar.State.ACTIVE;
import static google.registry.model.registrar.Registrar.Type.PDT;
import static google.registry.model.tld.Tlds.getTlds;
import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistResources;
import static google.registry.testing.FullFieldsTestEntityHelper.makeDomain;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarPocs;
import static google.registry.whois.WhoisTestData.loadFile;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.eppcommon.Trid;
import google.registry.model.host.Host;
import google.registry.model.registrar.Registrar;
import google.registry.model.tld.Tld;
import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.testing.FakeSleeper;
import google.registry.testing.FullFieldsTestEntityHelper;
import google.registry.testing.TestCacheExtension;
import google.registry.util.Retrier;
import google.registry.whois.WhoisMetrics.WhoisMetric;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.time.Duration;
import java.util.Optional;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link WhoisAction}. */
public class WhoisActionTest {
private final FakeClock clock = new FakeClock(DateTime.parse("2009-06-29T20:13:00Z"));
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
@RegisterExtension
public final TestCacheExtension testCacheExtension =
new TestCacheExtension.Builder()
.withEppResourceCache(Duration.ofDays(1))
.withForeignKeyCache(Duration.ofDays(1))
.build();
private final FakeResponse response = new FakeResponse();
private WhoisAction newWhoisAction(String input) {
WhoisAction whoisAction = new WhoisAction();
whoisAction.clock = clock;
whoisAction.input = new StringReader(input);
whoisAction.response = response;
whoisAction.whoisReader =
new WhoisReader(
WhoisCommandFactory.createCached(), "Please contact registrar", "Blocked by BSA: %s");
whoisAction.whoisMetrics = new WhoisMetrics();
whoisAction.metricBuilder = WhoisMetric.builderForRequest(clock);
whoisAction.disclaimer =
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested.";
whoisAction.retrier = new Retrier(new FakeSleeper(clock), 3);
return whoisAction;
}
@BeforeEach
void setUp() {
createTlds("lol", "xn--q9jyb4c", "1.test");
}
@Test
void testRun_badRequest_stillSends200() {
newWhoisAction("\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_no_command.txt"));
}
private static Domain makeDomainWithRegistrar(Registrar registrar) {
return makeDomain(
"cat.lol",
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "Goblin Market", "lol@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-IRL", "Santa Claus", "BOFH@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.lol")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
registrar);
}
@Test
void testRun_domainQuery_works() {
Registrar registrar =
persistResource(makeRegistrar("evilregistrar", "Yes Virginia", ACTIVE));
persistResource(makeDomainWithRegistrar(registrar));
persistResources(makeRegistrarPocs(registrar));
newWhoisAction("domain cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain.txt"));
}
@Test
void testRun_domainQuery_usesCache() {
Registrar registrar =
persistResource(makeRegistrar("evilregistrar", "Yes Virginia", ACTIVE));
persistResource(makeDomainWithRegistrar(registrar));
persistResources(makeRegistrarPocs(registrar));
// Populate the cache for both the domain and contact.
Domain domain = loadByForeignKeyByCacheIfEnabled(Domain.class, "cat.lol", clock.nowUtc()).get();
Contact contact =
loadByForeignKeyByCacheIfEnabled(Contact.class, "5372808-ERL", clock.nowUtc()).get();
// Make a change to the domain and contact that won't be seen because the cache will be hit.
persistResource(domain.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build());
persistResource(
contact
.asBuilder()
.setInternationalizedPostalInfo(
contact
.getInternationalizedPostalInfo()
.asBuilder()
.setOrg("Two by Two, Hands Blue Inc.")
.build())
.build());
newWhoisAction("domain cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain.txt"));
}
@Test
void testRun_domainQuery_registeredDomainUnaffectedByBsa() {
persistResource(
Tld.get("lol")
.asBuilder()
.setBsaEnrollStartTime(Optional.of(clock.nowUtc().minusDays(1)))
.build());
persistBsaLabel("cat");
Registrar registrar = persistResource(makeRegistrar("evilregistrar", "Yes Virginia", ACTIVE));
persistResource(makeDomainWithRegistrar(registrar));
persistResources(makeRegistrarPocs(registrar));
newWhoisAction("domain cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain.txt"));
}
@Test
void testRun_domainQuery_unregisteredDomainShowBsaMessage() {
persistResource(
Tld.get("lol")
.asBuilder()
.setBsaEnrollStartTime(Optional.of(clock.nowUtc().minusDays(1)))
.build());
persistBsaLabel("cat");
newWhoisAction("domain cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_blocked_by_bsa.txt"));
}
@Test
void testRun_domainAfterTransfer_hasUpdatedEppTimeAndClientId() {
Registrar registrar = persistResource(makeRegistrar("TheRegistrar", "Yes Virginia", ACTIVE));
persistResource(
makeDomainWithRegistrar(registrar)
.asBuilder()
.setTransferData(
new DomainTransferData.Builder()
.setGainingRegistrarId("TheRegistrar")
.setLosingRegistrarId("NewRegistrar")
.setTransferRequestTime(DateTime.parse("2009-05-29T20:13:00Z"))
.setPendingTransferExpirationTime(DateTime.parse("2010-03-01T00:00:00Z"))
.setTransferStatus(TransferStatus.PENDING)
.setTransferRequestTrid(Trid.create("client-trid", "server-trid"))
.build())
.build());
persistResources(makeRegistrarPocs(registrar));
clock.setTo(DateTime.parse("2011-01-01T00:00:00Z"));
newWhoisAction("domain cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_transferred_domain.txt"));
}
@Test
void testRun_idnDomain_works() {
Registrar registrar = persistResource(makeRegistrar(
"evilregistrar", "Yes Virginia", ACTIVE));
persistResource(
makeDomain(
"cat.みんな",
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "(◕‿◕)", "lol@cat.みんな")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-IRL", "Santa Claus", "BOFH@cat.みんな")),
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.みんな")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.みんな", "bad:f00d:cafe::15:beef")),
registrar));
persistResources(makeRegistrarPocs(registrar));
newWhoisAction("domain cat.みんな\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_idn_punycode.txt"));
}
@Test
void testRun_punycodeDomain_works() {
Registrar registrar = persistResource(makeRegistrar(
"evilregistrar", "Yes Virginia", ACTIVE));
persistResource(
makeDomain(
"cat.みんな",
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "(◕‿◕)", "lol@cat.みんな")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-IRL", "Santa Claus", "BOFH@cat.みんな")),
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.みんな")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.みんな", "bad:f00d:cafe::15:beef")),
registrar));
persistResources(makeRegistrarPocs(registrar));
newWhoisAction("domain cat.xn--q9jyb4c\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_idn_punycode.txt"));
}
@Test
void testRun_domainNotFound_returns200OkAndPlainTextResponse() {
newWhoisAction("domain cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_not_found.txt"));
}
@Test
void testRun_domainNotFound_usesCache() {
// Populate the cache with the nonexistence of this domain.
assertThat(loadByForeignKeyByCacheIfEnabled(Domain.class, "cat.lol", clock.nowUtc())).isEmpty();
// Add a new valid cat.lol domain that won't be found because the cache will be hit instead.
persistActiveDomain("cat.lol");
newWhoisAction("domain cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_not_found.txt"));
}
// todo (b/27378695): reenable or delete this test
@Disabled
@Test
void testRun_domainInTestTld_isConsideredNotFound() {
persistResource(Tld.get("lol").asBuilder().setTldType(Tld.TldType.TEST).build());
Registrar registrar = persistResource(makeRegistrar(
"evilregistrar", "Yes Virginia", ACTIVE));
persistResource(
makeDomain(
"cat.lol",
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-ERL", "Goblin Market", "lol@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-IRL", "Santa Claus", "BOFH@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.lol")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
registrar));
persistResources(makeRegistrarPocs(registrar));
newWhoisAction("domain cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_not_found.txt"));
}
@Test
void testRun_domainFlaggedAsDeletedInDatabase_isConsideredNotFound() {
Registrar registrar;
persistResource(
makeDomain(
"cat.lol",
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-ERL", "Peter Murphy", "lol@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-IRL", "Santa Claus", "BOFH@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-TRL", "The Raven", "bog@cat.lol")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
persistResource(registrar = makeRegistrar("example", "Example Registrar", ACTIVE)))
.asBuilder()
.setDeletionTime(clock.nowUtc().minusDays(1))
.build());
persistResources(makeRegistrarPocs(registrar));
newWhoisAction("domain cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_not_found.txt"));
}
/**
* Create a deleted domain and an active domain with the same label, and make sure only the active
* one is returned.
*/
@Test
void testRun_domainDeletedThenRecreated_isFound() {
Registrar registrar;
Domain domain1 =
persistResource(
makeDomain(
"cat.lol",
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-ERL", "Peter Murphy", "lol@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-IRL", "Santa Claus", "BOFH@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-TRL", "The Raven", "bog@cat.lol")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost(
"ns2.cat.lol", "bad:f00d:cafe::15:beef")),
persistResource(makeRegistrar("example", "Example Registrar", ACTIVE)))
.asBuilder()
.setCreationTimeForTest(clock.nowUtc().minusDays(2))
.setDeletionTime(clock.nowUtc().minusDays(1))
.build());
Domain domain2 =
persistResource(
makeDomain(
"cat.lol",
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372809-ERL", "Mrs. Alice Crypto", "alice@example.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372809-IRL", "Mr. Bob Crypto", "bob@example.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372809-TRL", "Dr. Pablo", "pmy@example.lol")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns1.google.lol", "9.9.9.9")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.google.lol", "4311::f143")),
persistResource(
registrar = makeRegistrar("example", "Example Registrar", ACTIVE)))
.asBuilder()
.setCreationTimeForTest(clock.nowUtc())
.build());
persistResources(makeRegistrarPocs(registrar));
assertThat(domain1.getRepoId()).isNotEqualTo(domain2.getRepoId());
newWhoisAction("domain cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).contains("ns1.google.lol");
}
@Test
void testRun_nameserverQuery_works() {
persistResource(loadRegistrar("TheRegistrar").asBuilder().setUrl("http://my.fake.url").build());
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
newWhoisAction("nameserver ns1.cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver.txt"));
}
@Test
void testRun_ipv6_displaysInCollapsedReadableFormat() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "bad:f00d:cafe::15:beef"));
newWhoisAction("nameserver ns1.cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).contains("ns1.cat.lol");
// The most important thing this tests is that the outputted address is compressed!
assertThat(response.getPayload()).contains("bad:f00d:cafe::15:beef");
assertThat(response.getPayload()).doesNotContain("bad:f00d:cafe:0:0:0:15:beef");
}
@Test
void testRun_idnNameserver_works() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4"));
newWhoisAction("nameserver ns1.cat.みんな\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).contains("ns1.cat.xn--q9jyb4c");
assertThat(response.getPayload()).contains("1.2.3.4");
}
@Test
void testRun_nameserver_usesCache() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.xn--q9jyb4c", "1.2.3.4"));
// Populate the cache.
Host host =
loadByForeignKeyByCacheIfEnabled(Host.class, "ns1.cat.xn--q9jyb4c", clock.nowUtc()).get();
// Make a change to the persisted host that won't be seen because the cache will be hit.
persistResource(
host.asBuilder()
.setInetAddresses(ImmutableSet.of(InetAddresses.forString("8.8.8.8")))
.build());
newWhoisAction("nameserver ns1.cat.みんな\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).contains("ns1.cat.xn--q9jyb4c");
assertThat(response.getPayload()).contains("1.2.3.4");
}
@Test
void testRun_punycodeNameserver_works() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4"));
newWhoisAction("nameserver ns1.cat.xn--q9jyb4c\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).contains("ns1.cat.xn--q9jyb4c");
assertThat(response.getPayload()).contains("1.2.3.4");
}
@Test
void testRun_nameserverNotFound_returns200AndText() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
newWhoisAction("nameserver ns1.cat.lulz\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver_not_found.txt"));
}
@Test
void testRun_nameserverFlaggedAsDeletedInDatabase_doesntGetLeaked() {
persistResource(
FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")
.asBuilder()
.setDeletionTime(clock.nowUtc().minusDays(1))
.build());
newWhoisAction("nameserver ns1.cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver_not_found.txt"));
}
@Test
void testRun_ipNameserverLookup_works() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
newWhoisAction("nameserver 1.2.3.4").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).contains("ns1.cat.lol");
}
@Test
void testRun_ipMapsToMultipleNameservers_theyAllGetReturned() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
persistResource(FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "1.2.3.4"));
newWhoisAction("nameserver 1.2.3.4").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).contains("ns1.cat.lol");
assertThat(response.getPayload()).contains("ns2.cat.lol");
}
@Test
void testRun_ipMapsToMultipleNameserverInDifferentTlds_showsThemAll() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.xn--q9jyb4c", "1.2.3.4"));
newWhoisAction("nameserver 1.2.3.4").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).contains("ns1.cat.lol");
assertThat(response.getPayload()).contains("ns1.cat.xn--q9jyb4c");
}
@Test
void testRun_ipNameserverEntityDoesNotExist_returns200NotFound() {
newWhoisAction("nameserver feed:a:bee::acab\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_ip_not_found.txt"));
}
@Test
void testRun_ipMapsToNameserverUnderNonAuthoritativeTld_notFound() {
assertThat(getTlds()).doesNotContain("com");
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.google.com", "1.2.3.4"));
newWhoisAction("nameserver 1.2.3.4").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_ip_not_found.txt"));
}
@Test
void testRun_nameserverUnderNonAuthoritativeTld_notFound() {
assertThat(getTlds()).doesNotContain("com");
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.google.com", "1.2.3.4"));
newWhoisAction("nameserver ns1.google.com").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver_not_found.txt"));
}
// todo (b/27378695): reenable or delete this test
@Disabled
@Test
void testRun_nameserverInTestTld_notFound() {
persistResource(Tld.get("lol").asBuilder().setTldType(Tld.TldType.TEST).build());
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
newWhoisAction("nameserver ns1.cat.lol").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver_not_found.txt"));
}
@Test
void testRun_registrarLookup_works() {
Registrar registrar = persistResource(
makeRegistrar("example", "Example Registrar, Inc.", ACTIVE));
persistResources(makeRegistrarPocs(registrar));
// Notice the partial search without "inc".
newWhoisAction("registrar example registrar").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar.txt"));
}
@Test
void testRun_pdtRegistrarLookup_works() {
Registrar registrar =
persistResource(
makeRegistrar("example", "Example Registrar, Inc.", ACTIVE)
.asBuilder()
.setIanaIdentifier(9995L)
.setType(PDT)
.build());
persistResources(makeRegistrarPocs(registrar));
// Notice the partial search without "inc".
newWhoisAction("registrar example registrar").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar.txt"));
}
@Test
void testRun_registrarLookupInPendingState_returnsNotFound() {
Registrar registrar = persistResource(
makeRegistrar("example", "Example Registrar, Inc.", Registrar.State.PENDING));
persistResources(makeRegistrarPocs(registrar));
newWhoisAction("registrar Example Registrar, Inc.").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar_not_found.txt"));
}
@Test
void testRun_registrarLookupWithTestType_returnsNotFound() {
Registrar registrar = persistResource(
makeRegistrar("example", "Example Registrar, Inc.", ACTIVE)
.asBuilder()
.setIanaIdentifier(null)
.setType(Registrar.Type.TEST)
.build());
persistResources(makeRegistrarPocs(registrar));
newWhoisAction("registrar Example Registrar, Inc.").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar_not_found.txt"));
}
@Test
void testRun_multilevelDomain_isNotConsideredAHostname() {
Registrar registrar =
persistResource(makeRegistrar("example", "Example Registrar", ACTIVE));
persistResource(
makeDomain(
"cat.1.test",
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "(◕‿◕)", "lol@cat.1.test")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-IRL", "Santa Claus", "BOFH@cat.1.test")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-TRL", "The Raven", "bog@cat.1.test")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.1.test", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.1.test", "bad:f00d:cafe::15:beef")),
registrar));
persistResources(makeRegistrarPocs(registrar));
newWhoisAction("domain cat.1.test\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).contains("Domain Name: cat.1.test\r\n");
}
@Test
void testRun_hostnameWithMultilevelTld_isStillConsideredHostname() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.1.test", "1.2.3.4"));
newWhoisAction("nameserver ns1.cat.1.test\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).contains("ns1.cat.1.test");
assertThat(response.getPayload()).contains("1.2.3.4");
}
@Test
void testRun_metricsLoggedForSuccessfulCommand() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
persistResource(FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "1.2.3.4"));
WhoisAction action = newWhoisAction("nameserver 1.2.3.4");
action.whoisMetrics = mock(WhoisMetrics.class);
action.run();
WhoisMetric expected =
WhoisMetric.builderForRequest(clock)
.setCommandName("NameserverLookupByIp")
.setNumResults(2)
.setStatus(SC_OK)
.build();
verify(action.whoisMetrics).recordWhoisMetric(eq(expected));
}
@Test
void testRun_metricsLoggedForUnsuccessfulCommand() {
WhoisAction action = newWhoisAction("domain cat.lol\r\n");
action.whoisMetrics = mock(WhoisMetrics.class);
action.run();
WhoisMetric expected =
WhoisMetric.builderForRequest(clock)
.setCommandName("DomainLookup")
.setNumResults(0)
.setStatus(SC_NOT_FOUND)
.build();
verify(action.whoisMetrics).recordWhoisMetric(eq(expected));
}
@Test
void testRun_metricsLoggedForInternalServerError() throws Exception {
persistResource(FullFieldsTestEntityHelper.makeHost("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), eq(false), any(DateTime.class)))
.thenThrow(new IOException("missing cat interface"));
action.whoisMetrics = mock(WhoisMetrics.class);
action.run();
WhoisMetric expected =
WhoisMetric.builderForRequest(clock)
.setNumResults(0)
.setStatus(SC_INTERNAL_SERVER_ERROR)
.build();
verify(action.whoisMetrics).recordWhoisMetric(eq(expected));
assertThat(response.getPayload()).isEqualTo("Internal Server Error");
}
}

View File

@@ -1,244 +0,0 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.newHost;
import static google.registry.testing.DatabaseHelper.newTld;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InternetDomainName;
import google.registry.config.RegistryConfig;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.model.registrar.Registrar;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.TestCacheExtension;
import java.net.InetAddress;
import java.time.Duration;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
class WhoisCommandFactoryTest {
FakeClock clock = new FakeClock();
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
@RegisterExtension
final TestCacheExtension testCacheExtension =
new TestCacheExtension.Builder().withEppResourceCache(Duration.ofSeconds(1000000)).build();
WhoisCommandFactory noncachedFactory = WhoisCommandFactory.createNonCached();
WhoisCommandFactory cachedFactory = WhoisCommandFactory.createCached();
Domain domain;
Host host;
Registrar otherRegistrar;
int origSingletonCacheRefreshSeconds;
@BeforeEach
void setUp() throws Exception {
persistResource(newTld("tld", "TLD"));
host =
newHost("ns.example.tld")
.asBuilder()
.setInetAddresses(ImmutableSet.of(InetAddress.getByName("1.2.3.4")))
.build();
persistResource(host);
domain = DatabaseHelper.newDomain("example.tld", host);
persistResource(domain);
otherRegistrar = persistNewRegistrar("OtherRegistrar");
otherRegistrar =
persistResource(
otherRegistrar
.asBuilder()
.setState(Registrar.State.ACTIVE)
.setPhoneNumber("+1.2223334444")
.build());
// In addition to the TestCacheExtension, we have to set a long singleton cache timeout.
RegistryConfig.CONFIG_SETTINGS.get().caching.singletonCacheRefreshSeconds = 1000000;
}
@AfterEach
void tearDown() {
// Restore the singleton cache timeout. For some reason, this doesn't work if we store the
// original value in an instance variable (I suspect there may be some overlap in test
// execution) so just restore to zero.
RegistryConfig.CONFIG_SETTINGS.get().caching.singletonCacheRefreshSeconds = 0;
}
@Test
void testNonCached_NameserverLookupByHostCommand() throws Exception {
WhoisResponse response =
noncachedFactory
.nameserverLookupByHost(InternetDomainName.from("ns.example.tld"))
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
// Note that we can't use persistResource() for these as that clears the cache.
tm().transact(
() ->
tm().put(
host.asBuilder()
.setPersistedCurrentSponsorRegistrarId("OtherRegistrar")
.build()));
response =
noncachedFactory
.nameserverLookupByHost(InternetDomainName.from("ns.example.tld"))
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: OtherRegistrar name");
}
@Test
void testCached_NameserverLookupByHostCommand() throws Exception {
WhoisResponse response =
cachedFactory
.nameserverLookupByHost(InternetDomainName.from("ns.example.tld"))
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
tm().transact(
() ->
tm().put(
host.asBuilder()
.setPersistedCurrentSponsorRegistrarId("OtherRegistrar")
.build()));
response =
cachedFactory
.nameserverLookupByHost(InternetDomainName.from("ns.example.tld"))
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
}
@Test
void testNonCached_DomainLookupCommand() throws Exception {
WhoisResponse response =
noncachedFactory
.domainLookup(
InternetDomainName.from("example.tld"), true, "REDACTED", "Blocked by BSA")
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
tm().transact(
() ->
tm().put(
domain
.asBuilder()
.setPersistedCurrentSponsorRegistrarId("OtherRegistrar")
.build()));
response =
noncachedFactory
.domainLookup(
InternetDomainName.from("example.tld"), true, "REDACTED", "Blocked by BSA")
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: OtherRegistrar name");
}
@Test
void testCached_DomainLookupCommand() throws Exception {
WhoisResponse response =
cachedFactory
.domainLookup(InternetDomainName.from("example.tld"), true, "REDACTED", "Not tested")
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
tm().transact(
() ->
tm().put(
domain
.asBuilder()
.setPersistedCurrentSponsorRegistrarId("OtherRegistrar")
.build()));
response =
cachedFactory
.domainLookup(
InternetDomainName.from("example.tld"), true, "REDACTED", "Blocked by BSA")
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
}
@Test
void testNonCached_RegistrarLookupCommand() throws Exception {
WhoisResponse response =
noncachedFactory.registrarLookup("OtherRegistrar").executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Phone Number: +1.2223334444");
tm().transact(
() -> tm().put(otherRegistrar.asBuilder().setPhoneNumber("+1.2345677890").build()));
response = noncachedFactory.registrarLookup("OtherRegistrar").executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Phone Number: +1.2345677890");
}
@Test
void testCached_RegistrarLookupCommand() throws Exception {
WhoisResponse response =
cachedFactory.registrarLookup("OtherRegistrar").executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Phone Number: +1.2223334444");
tm().transact(
() -> tm().put(otherRegistrar.asBuilder().setPhoneNumber("+1.2345677890").build()));
response = cachedFactory.registrarLookup("OtherRegistrar").executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Phone Number: +1.2223334444");
}
@Test
void testNonCached_NameserverLookupByIpCommand() throws Exception {
// Note that this lookup currently doesn't cache the hosts, so there's no point in testing the
// "cached" case. This test is here so that it will fail if anyone adds caching.
WhoisResponse response =
noncachedFactory
.nameserverLookupByIp(InetAddress.getByName("1.2.3.4"))
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: The Registrar");
tm().transact(
() ->
tm().put(
host.asBuilder()
.setPersistedCurrentSponsorRegistrarId("OtherRegistrar")
.build()));
response =
noncachedFactory
.nameserverLookupByIp(InetAddress.getByName("1.2.3.4"))
.executeQuery(clock.nowUtc());
assertThat(response.getResponse(false, "").plainTextOutput())
.contains("Registrar: OtherRegistrar");
}
}

View File

@@ -1,466 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.bsa.persistence.BsaTestingUtils.persistBsaLabel;
import static google.registry.model.registrar.Registrar.State.ACTIVE;
import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistResources;
import static google.registry.testing.FullFieldsTestEntityHelper.makeDomain;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar;
import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarPocs;
import static google.registry.whois.WhoisTestData.loadFile;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import google.registry.model.contact.Contact;
import google.registry.model.registrar.Registrar;
import google.registry.model.tld.Tld;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.testing.FullFieldsTestEntityHelper;
import google.registry.whois.WhoisMetrics.WhoisMetric;
import java.io.IOException;
import java.io.Reader;
import java.util.Optional;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/**
* Unit tests for {@link WhoisHttpAction}.
*
* <p>This class should be limited to testing the HTTP interface, as the bulk of the WHOIS testing
* can be found in {@link WhoisActionTest}.
*/
class WhoisHttpActionTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private final FakeResponse response = new FakeResponse();
private final FakeClock clock = new FakeClock(DateTime.parse("2009-06-29T20:13:00Z"));
private WhoisHttpAction newWhoisHttpAction(String pathInfo) {
WhoisHttpAction whoisAction = new WhoisHttpAction();
whoisAction.clock = clock;
whoisAction.expires = Duration.standardHours(1);
whoisAction.requestPath = WhoisHttpAction.PATH + pathInfo;
whoisAction.response = response;
whoisAction.whoisReader =
new WhoisReader(
WhoisCommandFactory.createCached(), "Please contact registrar", "Blocked by BSA: %s");
whoisAction.whoisMetrics = new WhoisMetrics();
whoisAction.metricBuilder = WhoisMetric.builderForRequest(clock);
whoisAction.disclaimer =
"Doodle Disclaimer\nI exist so that carriage return\nin disclaimer can be tested.";
return whoisAction;
}
@BeforeEach
void beforeEach() {
createTlds("lol", "xn--q9jyb4c", "1.test");
}
@Test
void testRun_emptyQuery_returns400BadRequestWithPlainTextOutput() {
newWhoisHttpAction("").run();
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.getContentType()).isEqualTo(PLAIN_TEXT_UTF_8);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_no_command.txt"));
}
@Test
void testRun_badUrlEncoding_returns400BadRequestWithPlainTextOutput() {
newWhoisHttpAction("nic.%u307F%u3093%u306A").run();
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.getContentType()).isEqualTo(PLAIN_TEXT_UTF_8);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_malformed_path.txt"));
}
@Test
void testRun_domainNotFound_returns404StatusAndPlainTextResponse() {
newWhoisHttpAction("/domain/cat.lol").run();
assertThat(response.getStatus()).isEqualTo(404);
assertThat(response.getContentType()).isEqualTo(PLAIN_TEXT_UTF_8);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_not_found.txt"));
}
// todo (b/27378695): reenable or delete this test
@Disabled
@Test
void testRun_domainInTestTld_isConsideredNotFound() {
persistResource(Tld.get("lol").asBuilder().setTldType(Tld.TldType.TEST).build());
Registrar registrar = persistResource(makeRegistrar(
"evilregistrar", "Yes Virginia", Registrar.State.ACTIVE));
persistResource(
makeDomain(
"cat.lol",
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-ERL", "Goblin Market", "lol@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-IRL", "Santa Claus", "BOFH@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.lol")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
registrar));
persistResources(makeRegistrarPocs(registrar));
newWhoisHttpAction("/domain/cat.lol").run();
assertThat(response.getStatus()).isEqualTo(404);
assertThat(response.getContentType()).isEqualTo(PLAIN_TEXT_UTF_8);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_not_found.txt"));
}
@Test
void testRun_domainQueryIdn_works() {
Registrar registrar = persistResource(makeRegistrar(
"evilregistrar", "Yes Virginia", Registrar.State.ACTIVE));
persistResource(
makeDomain(
"cat.みんな",
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "(◕‿◕)", "lol@cat.みんな")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-IRL", "Santa Claus", "BOFH@cat.みんな")),
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.みんな")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.みんな", "bad:f00d:cafe::15:beef")),
registrar));
persistResources(makeRegistrarPocs(registrar));
newWhoisHttpAction("/domain/cat.みんな").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_idn_utf8.txt"));
}
@Test
void testRun_wickedLineFeedForgeryInDatabase_crlfSubstitutedWithSpace() {
Contact trl =
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "Eric Schmidt", "bog@cat.みんな");
trl =
persistResource(
trl.asBuilder()
.setInternationalizedPostalInfo(
trl.getInternationalizedPostalInfo()
.asBuilder()
.setOrg("Galactic\r\nEmpire")
.build())
.build());
persistResource(
makeDomain(
"cat.みんな",
trl,
trl,
trl,
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.みんな", "bad:f00d:cafe::15:beef")),
persistResource(
makeRegistrar("example", "Example Registrar", Registrar.State.ACTIVE))));
newWhoisHttpAction("/domain/cat.みんな").run();
assertThat(response.getPayload()).contains("Galactic Empire");
}
@Test
void testRun_domainOnly_works() {
persistResource(
makeDomain(
"cat.みんな",
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "(◕‿◕)", "lol@cat.みんな")),
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-IRL", "Operator", "BOFH@cat.みんな")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-TRL", "Eric Schmidt", "bog@cat.みんな")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.みんな", "bad:f00d:cafe::15:beef")),
persistResource(
makeRegistrar("example", "Example Registrar", Registrar.State.ACTIVE))));
newWhoisHttpAction("cat.みんな").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).contains("Domain Name: cat.みんな\r\n");
}
@Test
void testRun_domainQuery_registeredDomainUnaffectedByBsa() {
persistResource(
Tld.get("lol")
.asBuilder()
.setBsaEnrollStartTime(Optional.of(clock.nowUtc().minusDays(1)))
.build());
persistBsaLabel("cat");
Registrar registrar = persistResource(makeRegistrar("evilregistrar", "Yes Virginia", ACTIVE));
persistResource(
makeDomain(
"cat.lol",
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-ERL", "Goblin Market", "lol@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-IRL", "Santa Claus", "BOFH@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.lol")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
registrar));
persistResources(makeRegistrarPocs(registrar));
newWhoisHttpAction("domain cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain.txt"));
}
@Test
void testRun_domainQuery_unregisteredDomainShowBsaMessage() {
persistResource(
Tld.get("lol")
.asBuilder()
.setBsaEnrollStartTime(Optional.of(clock.nowUtc().minusDays(1)))
.build());
persistBsaLabel("cat");
newWhoisHttpAction("domain cat.lol\r\n").run();
assertThat(response.getStatus()).isEqualTo(404);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_domain_blocked_by_bsa.txt"));
}
@Test
void testRun_hostnameOnly_works() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4"));
newWhoisHttpAction("ns1.cat.みんな").run();
assertThat(response.getPayload()).contains("Server Name: ns1.cat.みんな\r\n");
}
@Test
void testRun_domainQueryPunycode_works() {
Registrar registrar = persistResource(makeRegistrar(
"evilregistrar", "Yes Virginia", Registrar.State.ACTIVE));
persistResource(
makeDomain(
"cat.みんな",
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-ERL", "(◕‿◕)", "lol@cat.みんな")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-IRL", "Santa Claus", "BOFH@cat.みんな")),
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-TRL", "The Raven", "bog@cat.みんな")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.みんな", "bad:f00d:cafe::15:beef")),
registrar));
persistResources(makeRegistrarPocs(registrar));
newWhoisHttpAction("/domain/cat.xn--q9jyb4c").run();
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_idn_utf8.txt"));
}
@Test
void testRun_nameserverQuery_works() {
persistResource(loadRegistrar("TheRegistrar").asBuilder().setUrl("http://my.fake.url").build());
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
newWhoisHttpAction("/nameserver/ns1.cat.lol").run();
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver.txt"));
}
// todo (b/27378695): reenable or delete this test
@Disabled
@Test
void testRun_nameserverQueryInTestTld_notFound() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
newWhoisHttpAction("/nameserver/ns1.cat.lol").run();
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_nameserver.txt"));
}
@Test
void testRun_lastUpdateTimestamp_isPresentInResponse() {
clock.setTo(DateTime.parse("2020-07-12T23:52:43Z"));
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
newWhoisHttpAction("/nameserver/ns1.cat.lol").run();
assertThat(response.getPayload())
.contains(">>> Last update of WHOIS database: 2020-07-12T23:52:43Z <<<");
}
@Test
void testRun_nameserverQueryIdn_works() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4"));
newWhoisHttpAction("/nameserver/ns1.cat.みんな").run();
assertThat(response.getPayload()).contains("ns1.cat.みんな");
assertThat(response.getPayload()).contains("1.2.3.4");
}
@Test
void testRun_nameserverQueryPunycode_works() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4"));
newWhoisHttpAction("/nameserver/ns1.cat.xn--q9jyb4c").run();
assertThat(response.getPayload()).contains("ns1.cat.みんな");
assertThat(response.getPayload()).contains("1.2.3.4");
}
@Test
void testRun_trailingSlashInPath_getsIgnored() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.みんな", "1.2.3.4"));
newWhoisHttpAction("/nameserver/ns1.cat.xn--q9jyb4c/").run();
assertThat(response.getPayload()).contains("ns1.cat.みんな");
assertThat(response.getPayload()).contains("1.2.3.4");
}
@Test
void testRun_uppercaseDomain_ignoresCasing() {
persistResource(
makeDomain(
"cat.lol",
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-ERL", "Peter Murphy", "lol@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-IRL", "Operator", "BOFH@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-TRL", "Eric Schmidt", "bog@cat.lol")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
persistResource(
makeRegistrar("example", "Example Registrar", Registrar.State.ACTIVE))));
newWhoisHttpAction("/domain/cat.LOL").run();
assertThat(response.getPayload()).contains("Domain Name: cat.lol\r\n");
}
@Test
void testRun_hairyPath_getsDecoded() {
persistResource(
makeDomain(
"cat.lol",
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-ERL", "Peter Murphy", "lol@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact("5372808-IRL", "Operator", "BOFH@cat.lol")),
persistResource(
FullFieldsTestEntityHelper.makeContact(
"5372808-TRL", "Eric Schmidt", "bog@cat.lol")),
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4")),
persistResource(
FullFieldsTestEntityHelper.makeHost("ns2.cat.lol", "bad:f00d:cafe::15:beef")),
persistResource(
makeRegistrar("example", "Example Registrar", Registrar.State.ACTIVE))));
// python -c "print ''.join('%' + hex(ord(c))[2:] for c in 'cat.lol')"
newWhoisHttpAction("/domain/%63%61%74%2e%6c%6f%6c").run();
assertThat(response.getPayload()).contains("Domain Name: cat.lol\r\n");
}
@Test
void testRun_registrarLookup_works() {
Registrar registrar = persistResource(
makeRegistrar("example", "Example Registrar, Inc.", Registrar.State.ACTIVE));
persistResources(makeRegistrarPocs(registrar));
// Notice the partial search without "inc".
newWhoisHttpAction("/registrar/Example%20Registrar").run();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar.txt"));
}
@Test
void testRun_registrarLookupInPendingState_returnsNotFound() {
Registrar registrar = persistResource(
makeRegistrar("example", "Example Registrar, Inc.", Registrar.State.PENDING));
persistResources(makeRegistrarPocs(registrar));
newWhoisHttpAction("/registrar/Example%20Registrar,%20Inc.").run();
assertThat(response.getStatus()).isEqualTo(404);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar_not_found.txt"));
}
@Test
void testRun_registrarLookupWithTestType_returnsNotFound() {
Registrar registrar = persistResource(
makeRegistrar("example", "Example Registrar, Inc.", Registrar.State.ACTIVE)
.asBuilder().setType(Registrar.Type.TEST).setIanaIdentifier(null).build());
persistResources(makeRegistrarPocs(registrar));
newWhoisHttpAction("/registrar/Example%20Registrar,%20Inc.").run();
assertThat(response.getStatus()).isEqualTo(404);
assertThat(response.getPayload()).isEqualTo(loadFile("whois_action_registrar_not_found.txt"));
}
@Test
void testRun_metricsLoggedForSuccessfulCommand() {
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
WhoisHttpAction action = newWhoisHttpAction("/nameserver/ns1.cat.lol");
action.whoisMetrics = mock(WhoisMetrics.class);
action.run();
WhoisMetric expected =
WhoisMetric.builderForRequest(clock)
.setCommandName("NameserverLookupByHost")
.setNumResults(1)
.setStatus(SC_OK)
.build();
verify(action.whoisMetrics).recordWhoisMetric(eq(expected));
}
@Test
void testRun_metricsLoggedForUnsuccessfulCommand() {
WhoisHttpAction action = newWhoisHttpAction("nic.%u307F%u3093%u306A");
action.whoisMetrics = mock(WhoisMetrics.class);
action.run();
WhoisMetric expected =
WhoisMetric.builderForRequest(clock).setNumResults(0).setStatus(SC_BAD_REQUEST).build();
verify(action.whoisMetrics).recordWhoisMetric(eq(expected));
}
@Test
void testRun_metricsLoggedForInternalServerError() throws Exception {
persistResource(FullFieldsTestEntityHelper.makeHost("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), eq(false), any(DateTime.class)))
.thenThrow(new IOException("missing cat interface"));
action.whoisMetrics = mock(WhoisMetrics.class);
RuntimeException thrown = assertThrows(RuntimeException.class, action::run);
assertThat(thrown).hasCauseThat().hasMessageThat().isEqualTo("missing cat interface");
WhoisMetric expected =
WhoisMetric.builderForRequest(clock)
.setNumResults(0)
.setStatus(SC_INTERNAL_SERVER_ERROR)
.build();
verify(action.whoisMetrics).recordWhoisMetric(eq(expected));
}
}

View File

@@ -1,81 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.request.RequestModule;
import google.registry.testing.FullFieldsTestEntityHelper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for Dagger injection of the whois package. */
final class WhoisInjectionTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private final HttpServletRequest req = mock(HttpServletRequest.class);
private final HttpServletResponse rsp = mock(HttpServletResponse.class);
private final StringWriter httpOutput = new StringWriter();
@BeforeEach
void beforeEach() throws Exception {
when(rsp.getWriter()).thenReturn(new PrintWriter(httpOutput));
}
@Test
void testWhoisAction_injectsAndWorks() throws Exception {
createTld("lol");
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
when(req.getReader()).thenReturn(new BufferedReader(new StringReader("ns1.cat.lol\r\n")));
DaggerWhoisTestComponent.builder()
.requestModule(new RequestModule(req, rsp))
.build()
.whoisAction()
.run();
verify(rsp).setStatus(200);
assertThat(httpOutput.toString()).contains("ns1.cat.lol");
}
@Test
void testWhoisHttpAction_injectsAndWorks() {
createTld("lol");
persistResource(FullFieldsTestEntityHelper.makeHost("ns1.cat.lol", "1.2.3.4"));
when(req.getRequestURI()).thenReturn("/whois/ns1.cat.lol");
DaggerWhoisTestComponent.builder()
.requestModule(new RequestModule(req, rsp))
.build()
.whoisHttpAction()
.run();
verify(rsp).setStatus(200);
assertThat(httpOutput.toString()).contains("ns1.cat.lol");
}
}

View File

@@ -1,423 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.LogsSubject.assertAboutLogs;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.testing.TestLogHandler;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.testing.FakeClock;
import google.registry.util.JdkLoggerConfig;
import java.io.StringReader;
import java.util.logging.Level;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link WhoisReader}. */
class WhoisReaderTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
private final FakeClock clock = new FakeClock();
private final TestLogHandler testLogHandler = new TestLogHandler();
@BeforeEach
void beforeEach() {
createTlds("tld", "xn--kgbechtv", "1.test");
JdkLoggerConfig.getConfig(WhoisReader.class).addHandler(testLogHandler);
}
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
<T> T readCommand(String commandStr) throws Exception {
return (T)
new WhoisReader(
WhoisCommandFactory.createCached(), "Please contact registrar", "Blocked by BSA")
.readCommand(new StringReader(commandStr), false, clock.nowUtc());
}
void assertLoadsExampleTld(String commandString) throws Exception {
DomainLookupCommand command = readCommand(commandString);
assertThat(command.domainName.toString()).isEqualTo("example.tld");
}
void assertLoadsIDN(String commandString) throws Exception {
DomainLookupCommand command = readCommand(commandString);
assertThat(command.domainName.toString()).isEqualTo("xn--mgbh0fb.xn--kgbechtv");
}
void assertLoadsExampleNs(String commandString) throws Exception {
NameserverLookupByHostCommand command = readCommand(commandString);
assertThat(command.hostName.toString()).isEqualTo("ns.example.tld");
}
void assertLoadsIDNNs(String commandString) throws Exception {
NameserverLookupByHostCommand command = readCommand(commandString);
assertThat(command.hostName.toString()).isEqualTo("ns.xn--mgbh0fb.xn--kgbechtv");
}
void assertNsLookup(String commandString, String expectedIpAddress) throws Exception {
assertThat(
this.<NameserverLookupByIpCommand>readCommand(commandString).ipAddress.getHostAddress())
.isEqualTo(expectedIpAddress);
}
void assertLoadsRegistrar(String commandString) throws Exception {
assertThat(this.<RegistrarLookupCommand>readCommand(commandString).registrarName)
.isEqualTo("Example Registrar, Inc.");
}
@Test
void testRegistrarLookupWithOneToken() throws Exception {
assertThat(this.<RegistrarLookupCommand>readCommand("Example").registrarName)
.isEqualTo("Example");
}
@Test
void testDomainLookupWithoutCRLF() throws Exception {
assertLoadsExampleTld("example.tld");
}
@Test
void testWhitespaceOnDomainLookupWithCommand() throws Exception {
assertLoadsExampleTld(" \t domain \t \t example.tld \r\n");
}
@Test
void testDomainLookup() throws Exception {
assertLoadsExampleTld("example.tld\r\n");
}
@Test
void testDomainLookupWithCommand() throws Exception {
assertLoadsExampleTld("domain example.tld\r\n");
}
@Test
void testCaseInsensitiveDomainLookup() throws Exception {
assertLoadsExampleTld("EXAMPLE.TLD\r\n");
}
@Test
void testCaseInsensitiveDomainLookupWithCommand() throws Exception {
assertLoadsExampleTld("DOMAIN EXAMPLE.TLD\r\n");
}
@Test
void testIDNULabelDomainLookup() throws Exception {
assertLoadsIDN("مثال.إختبار\r\n");
}
@Test
void testIDNULabelDomainLookupWithCommand() throws Exception {
assertLoadsIDN("domain مثال.إختبار\r\n");
}
@Test
void testIDNALabelDomainLookupWithCommand() throws Exception {
assertLoadsIDN("domain xn--mgbh0fb.xn--kgbechtv\r\n");
}
@Test
void testIDNALabelDomainLookup() throws Exception {
assertLoadsIDN("xn--mgbh0fb.xn--kgbechtv\r\n");
}
@Test
void testTooManyArgsDomainLookup() {
assertThrows(WhoisException.class, () -> readCommand("domain example.tld foo.bar"));
}
@Test
void testTooFewArgsDomainLookup() {
assertThrows(WhoisException.class, () -> readCommand("domain"));
}
@Test
void testIllegalArgDomainLookup() {
assertThrows(WhoisException.class, () -> readCommand("domain 1.1"));
}
@Test
void testNameserverLookupWithoutCRLF() throws Exception {
assertLoadsExampleNs("ns.example.tld");
}
@Test
void testWhitespaceOnNameserverLookupWithCommand() throws Exception {
assertLoadsExampleNs(" \t nameserver \t \t ns.example.tld \r\n");
}
@Test
void testNameserverLookup() throws Exception {
assertLoadsExampleNs("ns.example.tld\r\n");
}
@Test
void testDeepNameserverLookup() throws Exception {
NameserverLookupByHostCommand command = readCommand("ns.foo.bar.baz.example.tld\r\n");
assertThat(command.hostName.toString()).isEqualTo("ns.foo.bar.baz.example.tld");
assertThat(command.hostName.toString()).isEqualTo("ns.foo.bar.baz.example.tld");
}
@Test
void testNameserverLookupWithCommand() throws Exception {
assertLoadsExampleNs("nameserver ns.example.tld\r\n");
}
@Test
void testCaseInsensitiveNameserverLookup() throws Exception {
assertLoadsExampleNs("NS.EXAMPLE.TLD\r\n");
}
@Test
void testCaseInsensitiveNameserverLookupWithCommand() throws Exception {
assertLoadsExampleNs("NAMESERVER NS.EXAMPLE.TLD\r\n");
}
@Test
void testIDNULabelNameserverLookup() throws Exception {
assertLoadsIDNNs("ns.مثال.إختبار\r\n");
}
@Test
void testIDNULabelNameserverLookupWithCommand() throws Exception {
assertLoadsIDNNs("nameserver ns.مثال.إختبار\r\n");
}
@Test
void testIDNALabelNameserverLookupWithCommand() throws Exception {
assertLoadsIDNNs("nameserver ns.xn--mgbh0fb.xn--kgbechtv\r\n");
}
@Test
void testIDNALabelNameserverLookup() throws Exception {
assertLoadsIDNNs("ns.xn--mgbh0fb.xn--kgbechtv\r\n");
}
@Test
void testTooManyArgsNameserverLookup() {
assertThrows(WhoisException.class, () -> readCommand("nameserver ns.example.tld foo.bar"));
}
@Test
void testTooFewArgsNameserverLookup() {
assertThrows(WhoisException.class, () -> readCommand("nameserver"));
}
@Test
void testIllegalArgNameserverLookup() {
assertThrows(WhoisException.class, () -> readCommand("nameserver 1.1"));
}
@Test
void testRegistrarLookup() throws Exception {
assertLoadsRegistrar("registrar Example Registrar, Inc.");
}
@Test
void testRegistrarLookupCaseInsensitive() throws Exception {
assertLoadsRegistrar("REGISTRAR Example Registrar, Inc.");
}
@Test
void testRegistrarLookupWhitespace() throws Exception {
assertLoadsRegistrar(" \t registrar \t \tExample Registrar, Inc. ");
}
@Test
void testRegistrarLookupByDefault() throws Exception {
assertLoadsRegistrar("Example Registrar, Inc.");
}
@Test
void testRegistrarLookupOnTLD() throws Exception {
assertThat(this.<RegistrarLookupCommand>readCommand("com").registrarName).isEqualTo("com");
}
@Test
void testRegistrarLookupNoArgs() {
assertThrows(WhoisException.class, () -> readCommand("registrar"));
}
@Test
void testNameserverLookupByIp() throws Exception {
assertNsLookup("43.34.12.213", "43.34.12.213");
}
@Test
void testNameserverLookupByIpv6() throws Exception {
assertNsLookup("1080:0:0:0:8:800:200c:417a", "1080:0:0:0:8:800:200c:417a");
}
@Test
void testNameserverLookupByCompressedIpv6() throws Exception {
assertNsLookup("1080::8:800:200c:417a", "1080:0:0:0:8:800:200c:417a");
}
@Test
void testNameserverLookupByNoncanonicalIpv6() throws Exception {
assertNsLookup("1080:0:0:0:8:800:200C:417A", "1080:0:0:0:8:800:200c:417a");
}
@Test
void testNameserverLookupByBackwardsCompatibleIpv6() throws Exception {
assertNsLookup("::FFFF:129.144.52.38", "129.144.52.38");
}
@Test
void testNameserverLookupByIpWithCommand() throws Exception {
assertNsLookup("nameserver 43.34.12.213", "43.34.12.213");
}
@Test
void testNameserverLookupByIpv6WithCommand() throws Exception {
assertNsLookup("nameserver 1080:0:0:0:8:800:200C:417a", "1080:0:0:0:8:800:200c:417a");
}
@Test
void testNameserverLookupByIpCaseInsenstive() throws Exception {
assertNsLookup("NAMESERVER 43.34.12.213", "43.34.12.213");
}
@Test
void testNameserverLookupByIpWhitespace() throws Exception {
assertNsLookup(" \t\t NAMESERVER \t 43.34.12.213 \r\n", "43.34.12.213");
}
@Test
void testNameserverLookupByIpTooManyArgs() {
assertThrows(WhoisException.class, () -> readCommand("nameserver 43.34.12.213 43.34.12.213"));
}
@Test
void testMultilevelDomainLookup() throws Exception {
this.<DomainLookupCommand>readCommand("example.1.test");
}
@Test
void testMultilevelNameserverLookup() throws Exception {
this.<NameserverLookupByHostCommand>readCommand("ns.example.1.test");
}
@Test
void testDeepMultilevelNameserverLookup() throws Exception {
this.<NameserverLookupByHostCommand>readCommand("ns.corp.example.1.test");
}
@Test
void testUnconfiguredTld() throws Exception {
this.<RegistrarLookupCommand>readCommand("example.test");
this.<RegistrarLookupCommand>readCommand("1.example.test");
this.<RegistrarLookupCommand>readCommand("ns.example.2.test");
this.<RegistrarLookupCommand>readCommand("test");
this.<RegistrarLookupCommand>readCommand("tld");
}
@Test
void testNoArgs() {
assertThrows(WhoisException.class, () -> readCommand(""));
}
@Test
void testLogsDomainLookupCommand() throws Exception {
readCommand("domain example.tld");
assertAboutLogs()
.that(testLogHandler)
.hasLogAtLevelWithMessage(
Level.INFO, "Attempting domain lookup command using domain name 'example.tld'.");
}
@Test
void testLogsNameserverLookupCommandWithIpAddress() throws Exception {
readCommand("nameserver 43.34.12.213");
assertAboutLogs()
.that(testLogHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Attempting nameserver lookup command using 43.34.12.213 as an IP address.");
}
@Test
void testLogsNameserverLookupCommandWithHostname() throws Exception {
readCommand("nameserver ns.example.tld");
assertAboutLogs()
.that(testLogHandler)
.hasLogAtLevelWithMessage(
Level.INFO, "Attempting nameserver lookup command using ns.example.tld as a hostname.");
}
@Test
void testLogsRegistrarLookupCommand() throws Exception {
readCommand("registrar Example Registrar, Inc.");
assertAboutLogs()
.that(testLogHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Attempting registrar lookup command using registrar Example Registrar, Inc.");
}
@Test
void testLogsSingleArgumentNameserverLookupUsingIpAddress() throws Exception {
readCommand("43.34.12.213");
assertAboutLogs()
.that(testLogHandler)
.hasLogAtLevelWithMessage(
Level.INFO, "Attempting nameserver lookup using 43.34.12.213 as an IP address.");
}
@Test
void testLogsSingleArgumentRegistrarLookup() throws Exception {
readCommand("test");
assertAboutLogs()
.that(testLogHandler)
.hasLogAtLevelWithMessage(
Level.INFO, "Attempting registrar lookup using test as a registrar.");
}
@Test
void testLogsSingleArgumentDomainLookup() throws Exception {
readCommand("example.tld");
assertAboutLogs()
.that(testLogHandler)
.hasLogAtLevelWithMessage(
Level.INFO, "Attempting domain lookup using example.tld as a domain name.");
}
@Test
void testLogsSingleArgumentNameserverLookupUsingHostname() throws Exception {
readCommand("ns.example.tld");
assertAboutLogs()
.that(testLogHandler)
.hasLogAtLevelWithMessage(
Level.INFO, "Attempting nameserver lookup using ns.example.tld as a hostname.");
}
@Test
void testLogsMultipleArgumentsButNoParticularCommand() throws Exception {
readCommand("Example Registrar, Inc.");
assertAboutLogs()
.that(testLogHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Attempting registrar lookup employing Example Registrar, Inc. as a registrar.");
}
}

View File

@@ -1,35 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import dagger.Component;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.request.RequestModule;
import google.registry.util.UtilsModule;
import jakarta.inject.Singleton;
@Singleton
@Component(
modules = {
ConfigModule.class,
RequestModule.class,
UtilsModule.class,
WhoisModule.class,
})
interface WhoisTestComponent {
WhoisHttpAction whoisHttpAction();
WhoisAction whoisAction();
}

View File

@@ -1,29 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.whois;
import google.registry.testing.TestDataHelper;
/** Test helper methods for the whois package. */
final class WhoisTestData {
/**
* Loads test data from file in {@code testdata/} directory, "fixing" newlines to have the ending
* that WHOIS requires.
*/
static String loadFile(String filename) {
return TestDataHelper.loadFile(WhoisTestData.class, filename).replaceAll("\r?\n", "\r\n");
}
}

View File

@@ -1,5 +1,4 @@
SERVICE PATH CLASS METHODS OK MIN USER_POLICY
PUBAPI /_dr/whois WhoisAction POST n APP ADMIN
PUBAPI /check CheckApiAction GET n NONE PUBLIC
PUBAPI /rdap/ RdapEmptyAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
@@ -12,4 +11,3 @@ PUBAPI /rdap/ip/(*) RdapIpAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
PUBAPI /ready/pubapi ReadinessProbeActionPubApi GET n NONE PUBLIC
PUBAPI /whois/(*) WhoisHttpAction GET n NONE PUBLIC

View File

@@ -56,7 +56,6 @@ BACKEND /_dr/task/tmchSmdrl TmchSmdrlAction
BACKEND /_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y APP ADMIN
BACKEND /_dr/task/uploadBsaUnavailableNames UploadBsaUnavailableDomainsAction GET,POST n APP ADMIN
BACKEND /_dr/task/wipeOutContactHistoryPii WipeOutContactHistoryPiiAction GET n APP ADMIN
PUBAPI /_dr/whois WhoisAction POST n APP ADMIN
PUBAPI /check CheckApiAction GET n NONE PUBLIC
PUBAPI /rdap/ RdapEmptyAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/autnum/(*) RdapAutnumAction GET,HEAD n NONE PUBLIC
@@ -69,7 +68,6 @@ PUBAPI /rdap/ip/(*) RdapIpAction
PUBAPI /rdap/nameserver/(*) RdapNameserverAction GET,HEAD n NONE PUBLIC
PUBAPI /rdap/nameservers RdapNameserverSearchAction GET,HEAD n NONE PUBLIC
PUBAPI /ready/pubapi ReadinessProbeActionPubApi GET n NONE PUBLIC
PUBAPI /whois/(*) WhoisHttpAction GET n NONE PUBLIC
CONSOLE /console-api/bulk-domain ConsoleBulkDomainAction POST n USER PUBLIC
CONSOLE /console-api/domain ConsoleDomainGetAction GET n USER PUBLIC
CONSOLE /console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC

View File

@@ -1,59 +0,0 @@
Domain Name: cat.lol
Registry Domain ID: 9-LOL
Registrar WHOIS Server: whois.example.com
Registrar URL: http://my.fake.url
Updated Date: 2009-05-29T20:13:00Z
Creation Date: 2000-10-08T00:45:00Z
Registry Expiry Date: 2110-10-08T00:44:59Z
Registrar: Yes Virginia
Registrar IANA ID: 1
Registrar Abuse Contact Email: jakedoe@example.com
Registrar Abuse Contact Phone: +1.2125551216
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
Registry Registrant ID: REDACTED FOR PRIVACY
Registrant Name: REDACTED FOR PRIVACY
Registrant Organization: GOOGLE INCORPORATED <script>
Registrant Street: REDACTED FOR PRIVACY
Registrant City: REDACTED FOR PRIVACY
Registrant State/Province: BM
Registrant Postal Code: REDACTED FOR PRIVACY
Registrant Country: US
Registrant Phone: REDACTED FOR PRIVACY
Registrant Fax: REDACTED FOR PRIVACY
Registrant Email: Please contact registrar
Registry Admin ID: REDACTED FOR PRIVACY
Admin Name: REDACTED FOR PRIVACY
Admin Organization: REDACTED FOR PRIVACY
Admin Street: REDACTED FOR PRIVACY
Admin City: REDACTED FOR PRIVACY
Admin State/Province: REDACTED FOR PRIVACY
Admin Postal Code: REDACTED FOR PRIVACY
Admin Country: REDACTED FOR PRIVACY
Admin Phone: REDACTED FOR PRIVACY
Admin Fax: REDACTED FOR PRIVACY
Admin Email: Please contact registrar
Registry Tech ID: REDACTED FOR PRIVACY
Tech Name: REDACTED FOR PRIVACY
Tech Organization: REDACTED FOR PRIVACY
Tech Street: REDACTED FOR PRIVACY
Tech City: REDACTED FOR PRIVACY
Tech State/Province: REDACTED FOR PRIVACY
Tech Postal Code: REDACTED FOR PRIVACY
Tech Country: REDACTED FOR PRIVACY
Tech Phone: REDACTED FOR PRIVACY
Tech Fax: REDACTED FOR PRIVACY
Tech Email: Please contact registrar
Name Server: ns1.cat.lol
Name Server: ns2.cat.lol
DNSSEC: signedDelegation
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
>>> Last update of WHOIS database: 2009-06-29T20:13: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.

View File

@@ -1,6 +0,0 @@
Blocked by BSA: cat.lol
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.

View File

@@ -1,6 +0,0 @@
Domain not found.
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.

View File

@@ -1,59 +0,0 @@
Domain Name: cat.xn--q9jyb4c
Registry Domain ID: 9-Q9JYB4C
Registrar WHOIS Server: whois.example.com
Registrar URL: http://my.fake.url
Updated Date: 2009-05-29T20:13:00Z
Creation Date: 2000-10-08T00:45:00Z
Registry Expiry Date: 2110-10-08T00:44:59Z
Registrar: Yes Virginia
Registrar IANA ID: 1
Registrar Abuse Contact Email: jakedoe@example.com
Registrar Abuse Contact Phone: +1.2125551216
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
Registry Registrant ID: REDACTED FOR PRIVACY
Registrant Name: REDACTED FOR PRIVACY
Registrant Organization: GOOGLE INCORPORATED <script>
Registrant Street: REDACTED FOR PRIVACY
Registrant City: REDACTED FOR PRIVACY
Registrant State/Province: BM
Registrant Postal Code: REDACTED FOR PRIVACY
Registrant Country: US
Registrant Phone: REDACTED FOR PRIVACY
Registrant Fax: REDACTED FOR PRIVACY
Registrant Email: Please contact registrar
Registry Admin ID: REDACTED FOR PRIVACY
Admin Name: REDACTED FOR PRIVACY
Admin Organization: REDACTED FOR PRIVACY
Admin Street: REDACTED FOR PRIVACY
Admin City: REDACTED FOR PRIVACY
Admin State/Province: REDACTED FOR PRIVACY
Admin Postal Code: REDACTED FOR PRIVACY
Admin Country: REDACTED FOR PRIVACY
Admin Phone: REDACTED FOR PRIVACY
Admin Fax: REDACTED FOR PRIVACY
Admin Email: Please contact registrar
Registry Tech ID: REDACTED FOR PRIVACY
Tech Name: REDACTED FOR PRIVACY
Tech Organization: REDACTED FOR PRIVACY
Tech Street: REDACTED FOR PRIVACY
Tech City: REDACTED FOR PRIVACY
Tech State/Province: REDACTED FOR PRIVACY
Tech Postal Code: REDACTED FOR PRIVACY
Tech Country: REDACTED FOR PRIVACY
Tech Phone: REDACTED FOR PRIVACY
Tech Fax: REDACTED FOR PRIVACY
Tech Email: Please contact registrar
Name Server: ns1.cat.xn--q9jyb4c
Name Server: ns2.cat.xn--q9jyb4c
DNSSEC: signedDelegation
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
>>> Last update of WHOIS database: 2009-06-29T20:13: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.

View File

@@ -1,59 +0,0 @@
Domain Name: cat.みんな
Registry Domain ID: 9-Q9JYB4C
Registrar WHOIS Server: whois.example.com
Registrar URL: http://my.fake.url
Updated Date: 2009-05-29T20:13:00Z
Creation Date: 2000-10-08T00:45:00Z
Registry Expiry Date: 2110-10-08T00:44:59Z
Registrar: Yes Virginia
Registrar IANA ID: 1
Registrar Abuse Contact Email: jakedoe@example.com
Registrar Abuse Contact Phone: +1.2125551216
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
Registry Registrant ID: REDACTED FOR PRIVACY
Registrant Name: REDACTED FOR PRIVACY
Registrant Organization: GOOGLE INCORPORATED <script>
Registrant Street: REDACTED FOR PRIVACY
Registrant City: REDACTED FOR PRIVACY
Registrant State/Province: BM
Registrant Postal Code: REDACTED FOR PRIVACY
Registrant Country: US
Registrant Phone: REDACTED FOR PRIVACY
Registrant Fax: REDACTED FOR PRIVACY
Registrant Email: Please contact registrar
Registry Admin ID: REDACTED FOR PRIVACY
Admin Name: REDACTED FOR PRIVACY
Admin Organization: REDACTED FOR PRIVACY
Admin Street: REDACTED FOR PRIVACY
Admin City: REDACTED FOR PRIVACY
Admin State/Province: REDACTED FOR PRIVACY
Admin Postal Code: REDACTED FOR PRIVACY
Admin Country: REDACTED FOR PRIVACY
Admin Phone: REDACTED FOR PRIVACY
Admin Fax: REDACTED FOR PRIVACY
Admin Email: Please contact registrar
Registry Tech ID: REDACTED FOR PRIVACY
Tech Name: REDACTED FOR PRIVACY
Tech Organization: REDACTED FOR PRIVACY
Tech Street: REDACTED FOR PRIVACY
Tech City: REDACTED FOR PRIVACY
Tech State/Province: REDACTED FOR PRIVACY
Tech Postal Code: REDACTED FOR PRIVACY
Tech Country: REDACTED FOR PRIVACY
Tech Phone: REDACTED FOR PRIVACY
Tech Fax: REDACTED FOR PRIVACY
Tech Email: Please contact registrar
Name Server: ns1.cat.みんな
Name Server: ns2.cat.みんな
DNSSEC: signedDelegation
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
>>> Last update of WHOIS database: 2009-06-29T20:13: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.

View File

@@ -1,6 +0,0 @@
No nameservers found.
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.

View File

@@ -1,6 +0,0 @@
Malformed path query.
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.

View File

@@ -1,10 +0,0 @@
Server Name: ns1.cat.lol
IP Address: 1.2.3.4
Registrar: The Registrar
Registrar WHOIS Server: whois.nic.fakewhois.example
Registrar URL: http://my.fake.url
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.

View File

@@ -1,6 +0,0 @@
Nameserver not found.
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.

View File

@@ -1,6 +0,0 @@
No WHOIS command specified.
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.

View File

@@ -1,24 +0,0 @@
Registrar: Example Registrar, Inc.
Street: 123 Example Boulevard <script>
City: Williamsburg <script>
State/Province: NY
Postal Code: 11211
Country: US
Phone Number: +1.2125551212
Fax Number: +1.2125551213
Email: contact-us@example.com
Registrar WHOIS Server: whois.example.com
Registrar URL: http://my.fake.url
Admin Contact: Jane Doe
Phone Number: +1.2125551215
Fax Number: +1.2125551216
Email: janedoe@example.com
Technical Contact: John Doe
Phone Number: +1.2125551213
Fax Number: +1.2125551213
Email: johndoe@example.com
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.

View File

@@ -1,6 +0,0 @@
No registrar found.
>>> Last update of WHOIS database: 2009-06-29T20:13:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.

View File

@@ -1,59 +0,0 @@
Domain Name: cat.lol
Registry Domain ID: 9-LOL
Registrar WHOIS Server: whois.example.com
Registrar URL: http://my.fake.url
Updated Date: 2010-03-06T00:00:00Z
Creation Date: 2000-10-08T00:45:00Z
Registry Expiry Date: 2020-03-01T00:00:00Z
Registrar: Yes Virginia
Registrar IANA ID: 1
Registrar Abuse Contact Email: jakedoe@example.com
Registrar Abuse Contact Phone: +1.2125551216
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
Registry Registrant ID: REDACTED FOR PRIVACY
Registrant Name: REDACTED FOR PRIVACY
Registrant Organization: GOOGLE INCORPORATED <script>
Registrant Street: REDACTED FOR PRIVACY
Registrant City: REDACTED FOR PRIVACY
Registrant State/Province: BM
Registrant Postal Code: REDACTED FOR PRIVACY
Registrant Country: US
Registrant Phone: REDACTED FOR PRIVACY
Registrant Fax: REDACTED FOR PRIVACY
Registrant Email: Please contact registrar
Registry Admin ID: REDACTED FOR PRIVACY
Admin Name: REDACTED FOR PRIVACY
Admin Organization: REDACTED FOR PRIVACY
Admin Street: REDACTED FOR PRIVACY
Admin City: REDACTED FOR PRIVACY
Admin State/Province: REDACTED FOR PRIVACY
Admin Postal Code: REDACTED FOR PRIVACY
Admin Country: REDACTED FOR PRIVACY
Admin Phone: REDACTED FOR PRIVACY
Admin Fax: REDACTED FOR PRIVACY
Admin Email: Please contact registrar
Registry Tech ID: REDACTED FOR PRIVACY
Tech Name: REDACTED FOR PRIVACY
Tech Organization: REDACTED FOR PRIVACY
Tech Street: REDACTED FOR PRIVACY
Tech City: REDACTED FOR PRIVACY
Tech State/Province: REDACTED FOR PRIVACY
Tech Postal Code: REDACTED FOR PRIVACY
Tech Country: REDACTED FOR PRIVACY
Tech Phone: REDACTED FOR PRIVACY
Tech Fax: REDACTED FOR PRIVACY
Tech Email: Please contact registrar
Name Server: ns1.cat.lol
Name Server: ns2.cat.lol
DNSSEC: signedDelegation
URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
>>> Last update of WHOIS database: 2011-01-01T00:00: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.

View File

@@ -1,66 +0,0 @@
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: REDACTED FOR PRIVACY
Registrant Name: REDACTED FOR PRIVACY
Registrant Organization: Tom & Jerry Corp.
Registrant Street: REDACTED FOR PRIVACY
Registrant City: REDACTED FOR PRIVACY
Registrant State/Province: AP
Registrant Postal Code: REDACTED FOR PRIVACY
Registrant Country: EX
Registrant Phone: REDACTED FOR PRIVACY
Registrant Phone Ext: REDACTED FOR PRIVACY
Registrant Fax: REDACTED FOR PRIVACY
Registrant Fax Ext: REDACTED FOR PRIVACY
Registrant Email: Please contact registrar
Registry Admin ID: REDACTED FOR PRIVACY
Admin Name: REDACTED FOR PRIVACY
Admin Organization: REDACTED FOR PRIVACY
Admin Street: REDACTED FOR PRIVACY
Admin City: REDACTED FOR PRIVACY
Admin State/Province: REDACTED FOR PRIVACY
Admin Postal Code: REDACTED FOR PRIVACY
Admin Country: REDACTED FOR PRIVACY
Admin Phone: REDACTED FOR PRIVACY
Admin Phone Ext: REDACTED FOR PRIVACY
Admin Fax: REDACTED FOR PRIVACY
Admin Email: Please contact registrar
Registry Tech ID: REDACTED FOR PRIVACY
Tech Name: REDACTED FOR PRIVACY
Tech Organization: REDACTED FOR PRIVACY
Tech Street: REDACTED FOR PRIVACY
Tech City: REDACTED FOR PRIVACY
Tech State/Province: REDACTED FOR PRIVACY
Tech Postal Code: REDACTED FOR PRIVACY
Tech Country: REDACTED FOR PRIVACY
Tech Phone: REDACTED FOR PRIVACY
Tech Phone Ext: REDACTED FOR PRIVACY
Tech Fax: REDACTED FOR PRIVACY
Tech Fax Ext: REDACTED FOR PRIVACY
Tech Email: Please contact registrar
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.

View File

@@ -1,66 +0,0 @@
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-ROID
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-ROID
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-ROID
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.

View File

@@ -1,28 +0,0 @@
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
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.

View File

@@ -1,53 +0,0 @@
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 Admin ID: REDACTED FOR PRIVACY
Admin Name: REDACTED FOR PRIVACY
Admin Organization: REDACTED FOR PRIVACY
Admin Street: REDACTED FOR PRIVACY
Admin City: REDACTED FOR PRIVACY
Admin State/Province: REDACTED FOR PRIVACY
Admin Postal Code: REDACTED FOR PRIVACY
Admin Country: REDACTED FOR PRIVACY
Admin Phone: REDACTED FOR PRIVACY
Admin Phone Ext: REDACTED FOR PRIVACY
Admin Fax: REDACTED FOR PRIVACY
Admin Email: Please contact registrar
Registry Tech ID: REDACTED FOR PRIVACY
Tech Name: REDACTED FOR PRIVACY
Tech Organization: REDACTED FOR PRIVACY
Tech Street: REDACTED FOR PRIVACY
Tech City: REDACTED FOR PRIVACY
Tech State/Province: REDACTED FOR PRIVACY
Tech Postal Code: REDACTED FOR PRIVACY
Tech Country: REDACTED FOR PRIVACY
Tech Phone: REDACTED FOR PRIVACY
Tech Phone Ext: REDACTED FOR PRIVACY
Tech Fax: REDACTED FOR PRIVACY
Tech Fax Ext: REDACTED FOR PRIVACY
Tech Email: Please contact registrar
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.

View File

@@ -1,64 +0,0 @@
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:
Registrar Abuse Contact Phone:
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: REDACTED FOR PRIVACY
Registrant Name: REDACTED FOR PRIVACY
Registrant Organization: Tom & Jerry Corp.
Registrant Street: REDACTED FOR PRIVACY
Registrant City: REDACTED FOR PRIVACY
Registrant State/Province: AP
Registrant Postal Code: REDACTED FOR PRIVACY
Registrant Country: EX
Registrant Phone: REDACTED FOR PRIVACY
Registrant Phone Ext: REDACTED FOR PRIVACY
Registrant Fax: REDACTED FOR PRIVACY
Registrant Fax Ext: REDACTED FOR PRIVACY
Registrant Email: Please contact registrar
Registry Admin ID: REDACTED FOR PRIVACY
Admin Name: REDACTED FOR PRIVACY
Admin Organization: REDACTED FOR PRIVACY
Admin Street: REDACTED FOR PRIVACY
Admin City: REDACTED FOR PRIVACY
Admin State/Province: REDACTED FOR PRIVACY
Admin Postal Code: REDACTED FOR PRIVACY
Admin Country: REDACTED FOR PRIVACY
Admin Phone: REDACTED FOR PRIVACY
Admin Phone Ext: REDACTED FOR PRIVACY
Admin Fax: REDACTED FOR PRIVACY
Admin Email: Please contact registrar
Registry Tech ID: REDACTED FOR PRIVACY
Tech Name: REDACTED FOR PRIVACY
Tech Organization: REDACTED FOR PRIVACY
Tech Street: REDACTED FOR PRIVACY
Tech City: REDACTED FOR PRIVACY
Tech State/Province: REDACTED FOR PRIVACY
Tech Postal Code: REDACTED FOR PRIVACY
Tech Country: REDACTED FOR PRIVACY
Tech Phone: REDACTED FOR PRIVACY
Tech Phone Ext: REDACTED FOR PRIVACY
Tech Fax: REDACTED FOR PRIVACY
Tech Fax Ext: REDACTED FOR PRIVACY
Tech Email: Please contact registrar
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
Footer

View File

@@ -1,18 +0,0 @@
Server Name: ns1.example.tld
IP Address: 192.0.2.123
IP Address: 2001:db8::1
Registrar: Hänsel & Gretel Registrar, Inc.
Registrar WHOIS Server: whois.nic.fakewhois.example
Registrar URL: http://my.fake.url
Server Name: ns2.example.tld
IP Address: 192.0.2.123
IP Address: 2001:db8::1
Registrar: Hänsel & Gretel Registrar, Inc.
Registrar WHOIS Server: whois.nic.fakewhois.example
Registrar URL: http://my.fake.url
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.

View File

@@ -1,11 +0,0 @@
Server Name: ns1.example.tld
IP Address: 192.0.2.123
IP Address: 2001:db8::1
Registrar: Hänsel & Gretel Registrar, Inc.
Registrar WHOIS Server: whois.nic.fakewhois.example
Registrar URL: http://my.fake.url
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.

View File

@@ -1,28 +0,0 @@
Registrar: Example Registrar, Inc.
Street: 1234 Admiralty Way
City: Marina del Rey
State/Province: CA
Postal Code: 90292
Country: US
Phone Number: +1.3105551212
Fax Number: +1.3105551213
Email: registrar@example.tld
Registrar WHOIS Server: whois.example-registrar.tld
Registrar URL: http://my.fake.url
Admin Contact: Jane Registrar
Phone Number: +1.3105551214
Fax Number: +1.3105551213
Email: janeregistrar@example-registrar.tld
Admin Contact: Joe Registrar
Phone Number: +1.3105551213
Fax Number: +1.3105551213
Email: joeregistrar@example-registrar.tld
Technical Contact: Bonnie & Clyde
Phone Number: +1.3105551215
Fax Number: +1.3105551216
Email: johngeek@example-registrar.tld
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.

View File

@@ -1,11 +0,0 @@
Server Name: ns1.zobo.tld
IP Address: 192.0.2.123
IP Address: 2001:db8::1
Registrar: The Registrar
Registrar WHOIS Server: whois.nic.fakewhois.example
Registrar URL: http://my.fake.url
>>> Last update of WHOIS database: 2009-05-29T20:15:00Z <<<
Doodle Disclaimer
I exist so that carriage return
in disclaimer can be tested.