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:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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 <FQDN><dd>
|
||||
* Looks up the domain record for the fully qualified domain name.
|
||||
* <dt>nameserver <FQDN><dd>
|
||||
* Looks up the nameserver record for the fully qualified domain name.
|
||||
* <dt>nameserver <IP><dd>
|
||||
* Looks up the nameserver record at the given IP address.
|
||||
* <dt>registrar <IANA ID><dd>
|
||||
* Looks up the registrar record with the given IANA ID.
|
||||
* <dt>registrar <NAME><dd>
|
||||
* Looks up the registrar record with the given name.
|
||||
* <dt><IP><dd>
|
||||
* Looks up the nameserver record with the given IP address.
|
||||
* <dt><FQDN><dd>
|
||||
* Looks up the nameserver or domain record for the fully qualified domain name.
|
||||
* <dt><IANA ID><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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> {}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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),
|
||||
|
||||
@@ -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")));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user