diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java index 706ebe15b..b0b42412c 100644 --- a/java/google/registry/config/RegistryConfig.java +++ b/java/google/registry/config/RegistryConfig.java @@ -990,6 +990,13 @@ public final class RegistryConfig { return "google.registry.flows.custom.CustomLogicFactory"; } + @Provides + @Config("whoisCommandFactoryClass") + public static String provideWhoisCommandFactoryClass() { + // TODO(b/32875427): This will be converted to YAML configuration in a future refactor. + return "google.registry.whois.WhoisCommandFactory"; + } + private static final String RESERVED_TERMS_EXPORT_DISCLAIMER = "" + "# This list contains reserve terms for the TLD. Other terms may be reserved\n" + "# but not included in this list, including terms EXAMPLE REGISTRY chooses not\n" diff --git a/java/google/registry/tools/RegistryToolComponent.java b/java/google/registry/tools/RegistryToolComponent.java index 5a2d52d32..5c9fb0aec 100644 --- a/java/google/registry/tools/RegistryToolComponent.java +++ b/java/google/registry/tools/RegistryToolComponent.java @@ -26,6 +26,7 @@ import google.registry.request.Modules.Jackson2Module; import google.registry.request.Modules.URLFetchServiceModule; import google.registry.util.SystemClock.SystemClockModule; import google.registry.util.SystemSleeper.SystemSleeperModule; +import google.registry.whois.WhoisModule; import javax.inject.Singleton; /** @@ -50,6 +51,7 @@ import javax.inject.Singleton; SystemSleeperModule.class, URLFetchServiceModule.class, VoidDnsWriterModule.class, + WhoisModule.class, }, dependencies = { HttpRequestFactoryComponent.class, diff --git a/java/google/registry/whois/DomainLookupCommand.java b/java/google/registry/whois/DomainLookupCommand.java index 35a909102..0c0deee1d 100644 --- a/java/google/registry/whois/DomainLookupCommand.java +++ b/java/google/registry/whois/DomainLookupCommand.java @@ -14,13 +14,16 @@ package google.registry.whois; +import static google.registry.model.EppResourceUtils.loadByForeignKey; + +import com.google.common.base.Optional; import com.google.common.net.InternetDomainName; import google.registry.model.domain.DomainResource; import javax.annotation.Nullable; import org.joda.time.DateTime; /** Represents a WHOIS lookup on a domain name (i.e. SLD). */ -class DomainLookupCommand extends DomainOrHostLookupCommand { +public class DomainLookupCommand extends DomainOrHostLookupCommand { DomainLookupCommand(InternetDomainName domainName) { this(domainName, null); @@ -31,7 +34,10 @@ class DomainLookupCommand extends DomainOrHostLookupCommand { } @Override - WhoisResponse getSuccessResponse(DomainResource domain, DateTime now) { - return new DomainWhoisResponse(domain, now); + protected Optional getResponse(InternetDomainName domainName, DateTime now) { + final DomainResource domainResource = + loadByForeignKey(DomainResource.class, domainName.toString(), now); + return Optional.fromNullable( + domainResource == null ? null : new DomainWhoisResponse(domainResource, now)); } } diff --git a/java/google/registry/whois/DomainOrHostLookupCommand.java b/java/google/registry/whois/DomainOrHostLookupCommand.java index 40c4b4db4..9071ac95f 100644 --- a/java/google/registry/whois/DomainOrHostLookupCommand.java +++ b/java/google/registry/whois/DomainOrHostLookupCommand.java @@ -15,7 +15,6 @@ package google.registry.whois; import static com.google.common.base.Preconditions.checkNotNull; -import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.registry.Registries.findTldForName; import static google.registry.model.registry.Registries.getTlds; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; @@ -23,16 +22,13 @@ import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.net.InternetDomainName; -import google.registry.model.EppResource; -import google.registry.util.TypeUtils.TypeInstantiator; import javax.annotation.Nullable; import org.joda.time.DateTime; /** Represents a WHOIS lookup on a domain name (i.e. SLD) or a nameserver. */ -abstract class DomainOrHostLookupCommand implements WhoisCommand { +public abstract class DomainOrHostLookupCommand implements WhoisCommand { - @VisibleForTesting - final InternetDomainName domainOrHostName; + @VisibleForTesting final InternetDomainName domainOrHostName; private final String errorPrefix; @@ -52,17 +48,15 @@ abstract class DomainOrHostLookupCommand implements Whois } // Google Policy: Do not return records under TLDs for which we're not authoritative. if (tld.isPresent() && getTlds().contains(tld.get().toString())) { - T domainOrHost = loadByForeignKey( - new TypeInstantiator(getClass()){}.getExactType(), - domainOrHostName.toString(), - now); - if (domainOrHost != null) { - return getSuccessResponse(domainOrHost, now); + final Optional response = getResponse(domainOrHostName, now); + if (response.isPresent()) { + return response.get(); } } throw new WhoisException(now, SC_NOT_FOUND, errorPrefix + " not found."); } /** Renders a response record, provided its successfully retrieved datastore entity. */ - abstract WhoisResponse getSuccessResponse(T domainOrHost, DateTime now); + protected abstract Optional getResponse( + InternetDomainName domainName, DateTime now); } diff --git a/java/google/registry/whois/NameserverLookupByHostCommand.java b/java/google/registry/whois/NameserverLookupByHostCommand.java index e235acf76..298f95b3b 100644 --- a/java/google/registry/whois/NameserverLookupByHostCommand.java +++ b/java/google/registry/whois/NameserverLookupByHostCommand.java @@ -14,13 +14,16 @@ package google.registry.whois; +import static google.registry.model.EppResourceUtils.loadByForeignKey; + +import com.google.common.base.Optional; import com.google.common.net.InternetDomainName; import google.registry.model.host.HostResource; import javax.annotation.Nullable; import org.joda.time.DateTime; /** Represents a WHOIS lookup on a nameserver based on its hostname. */ -final class NameserverLookupByHostCommand extends DomainOrHostLookupCommand { +public class NameserverLookupByHostCommand extends DomainOrHostLookupCommand { NameserverLookupByHostCommand(InternetDomainName hostName) { this(hostName, null); @@ -31,7 +34,10 @@ final class NameserverLookupByHostCommand extends DomainOrHostLookupCommand getResponse(InternetDomainName hostName, DateTime now) { + final HostResource hostResource = + loadByForeignKey(HostResource.class, hostName.toString(), now); + return Optional.fromNullable( + hostResource == null ? null : new NameserverWhoisResponse(hostResource, now)); } } diff --git a/java/google/registry/whois/Whois.java b/java/google/registry/whois/Whois.java index a1cf733e0..9c296b5fc 100644 --- a/java/google/registry/whois/Whois.java +++ b/java/google/registry/whois/Whois.java @@ -26,18 +26,23 @@ public final class Whois { private final Clock clock; private final String disclaimer; + private final WhoisCommandFactory commandFactory; @Inject - public Whois(Clock clock, @Config("whoisDisclaimer") String disclaimer) { + public Whois( + Clock clock, + @Config("whoisDisclaimer") String disclaimer, + @Config("whoisCommandFactory") WhoisCommandFactory commandFactory) { this.clock = clock; this.disclaimer = disclaimer; + this.commandFactory = commandFactory; } /** Performs a WHOIS lookup on a plaintext query string. */ public String lookup(String query, boolean preferUnicode) { DateTime now = clock.nowUtc(); try { - return new WhoisReader(new StringReader(query), now) + return new WhoisReader(new StringReader(query), commandFactory, now) .readCommand() .executeQuery(now) .getPlainTextOutput(preferUnicode, disclaimer); diff --git a/java/google/registry/whois/WhoisCommand.java b/java/google/registry/whois/WhoisCommand.java index 7b7b85d14..ee4d4ea0f 100644 --- a/java/google/registry/whois/WhoisCommand.java +++ b/java/google/registry/whois/WhoisCommand.java @@ -17,7 +17,7 @@ package google.registry.whois; import org.joda.time.DateTime; /** Represents a WHOIS command request from a client. */ -interface WhoisCommand { +public interface WhoisCommand { /** * Executes a WHOIS query and returns the resultant data. diff --git a/java/google/registry/whois/WhoisCommandFactory.java b/java/google/registry/whois/WhoisCommandFactory.java new file mode 100644 index 000000000..0d3d9beb9 --- /dev/null +++ b/java/google/registry/whois/WhoisCommandFactory.java @@ -0,0 +1,74 @@ +// Copyright 2016 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; +import javax.annotation.Nullable; + +/** + * A class used to configure WHOIS commands. + * + *

To add custom commands, extend this class, then configure it in + * {@link ConfigModule#provideWhoisCommandFactoryClass()}. + */ +public class WhoisCommandFactory { + + /** Returns a new {@link WhoisCommand} to perform a domain lookup on the specified domain name. */ + public final WhoisCommand domainLookup(InternetDomainName domainName) { + return domainLookup(domainName, null); + } + + /** + * Returns a new {@link WhoisCommand} to perform a domain lookup on the specified domain name in + * the specified TLD. + */ + public WhoisCommand domainLookup( + InternetDomainName domainName, @Nullable InternetDomainName tld) { + return new DomainLookupCommand(domainName, tld); + } + + /** + * 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 final WhoisCommand nameserverLookupByHost(InternetDomainName hostName) { + return nameserverLookupByHost(hostName, null); + } + + /** + * Returns a new {@link WhoisCommand} to perform a nameserver lookup on the specified host name in + * the specified TLD. + */ + public WhoisCommand nameserverLookupByHost( + InternetDomainName hostName, @Nullable InternetDomainName tld) { + return new NameserverLookupByHostCommand(hostName, tld); + } + + /** + * Returns a new {@link WhoisCommand} to perform a registrar lookup on the specified registrar + * name. + */ + public WhoisCommand registrarLookup(String registrar) { + return new RegistrarLookupCommand(registrar); + } +} diff --git a/java/google/registry/whois/WhoisHttpServer.java b/java/google/registry/whois/WhoisHttpServer.java index 6fd56d668..7f73d64b5 100644 --- a/java/google/registry/whois/WhoisHttpServer.java +++ b/java/google/registry/whois/WhoisHttpServer.java @@ -132,6 +132,7 @@ public final class WhoisHttpServer implements Runnable { @Inject Response response; @Inject @Config("whoisDisclaimer") String disclaimer; @Inject @Config("whoisHttpExpires") Duration expires; + @Inject @Config("whoisCommandFactory") WhoisCommandFactory commandFactory; @Inject @RequestPath String requestPath; @Inject WhoisHttpServer() {} @@ -144,7 +145,8 @@ public final class WhoisHttpServer implements Runnable { String command = decode(JOINER.join(SLASHER.split(path.substring(PATH.length())))) + "\r\n"; Reader reader = new StringReader(command); DateTime now = clock.nowUtc(); - sendResponse(SC_OK, new WhoisReader(reader, now).readCommand().executeQuery(now)); + sendResponse( + SC_OK, new WhoisReader(reader, commandFactory, now).readCommand().executeQuery(now)); } catch (WhoisException e) { sendResponse(e.getStatus(), e); } catch (IOException e) { diff --git a/java/google/registry/whois/WhoisModule.java b/java/google/registry/whois/WhoisModule.java index 9cf6ff5bd..e1eaaace7 100644 --- a/java/google/registry/whois/WhoisModule.java +++ b/java/google/registry/whois/WhoisModule.java @@ -14,8 +14,12 @@ 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; import java.io.IOException; import java.io.Reader; import javax.servlet.http.HttpServletRequest; @@ -26,7 +30,7 @@ import javax.servlet.http.HttpServletRequest; *

Dependencies

* *
    - *
  • {@link google.registry.request.RequestModule RequestModule} + *
  • {@link google.registry.request.RequestModule RequestModule} *
* * @see "google.registry.module.frontend.FrontendComponent" @@ -42,4 +46,11 @@ public final class WhoisModule { throw new RuntimeException(e); } } + + @Provides + @Config("whoisCommandFactory") + static WhoisCommandFactory provideWhoisCommandFactory( + @Config("whoisCommandFactoryClass") String factoryClass) { + return instantiate(getClassFromString(factoryClass, WhoisCommandFactory.class)); + } } diff --git a/java/google/registry/whois/WhoisReader.java b/java/google/registry/whois/WhoisReader.java index cf70a9f59..3f5497161 100644 --- a/java/google/registry/whois/WhoisReader.java +++ b/java/google/registry/whois/WhoisReader.java @@ -70,10 +70,12 @@ class WhoisReader { private final Reader reader; private final DateTime now; + private final WhoisCommandFactory commandFactory; /** Creates a new WhoisReader that extracts its command from the specified Reader. */ - WhoisReader(Reader reader, DateTime now) { + WhoisReader(Reader reader, WhoisCommandFactory commandFactory, DateTime now) { this.reader = checkNotNull(reader, "reader"); + this.commandFactory = checkNotNull(commandFactory, "commandFactory"); this.now = checkNotNull(now, "now"); } @@ -111,7 +113,7 @@ class WhoisReader { // Try to parse the argument as a domain name. try { - return new DomainLookupCommand(InternetDomainName.from( + return commandFactory.domainLookup(InternetDomainName.from( canonicalizeDomainName(tokens.get(1)))); } catch (IllegalArgumentException iae) { // If we can't interpret the argument as a host name, then return an error. @@ -129,14 +131,14 @@ class WhoisReader { // Try to parse the argument as an IP address. try { - return new NameserverLookupByIpCommand(InetAddresses.forString(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 { - return new NameserverLookupByHostCommand(InternetDomainName.from( + return commandFactory.nameserverLookupByHost(InternetDomainName.from( canonicalizeDomainName(tokens.get(1)))); } catch (IllegalArgumentException iae) { // Silently ignore this exception. @@ -153,14 +155,14 @@ class WhoisReader { throw new WhoisException(now, SC_BAD_REQUEST, String.format( "Too few arguments to '%s' command.", REGISTRAR_LOOKUP_COMMAND)); } - return new RegistrarLookupCommand(Joiner.on(' ').join(tokens.subList(1, tokens.size()))); + return commandFactory.registrarLookup(Joiner.on(' ').join(tokens.subList(1, tokens.size()))); } // 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 { - return new NameserverLookupByIpCommand(InetAddresses.forString(arg1)); + return commandFactory.nameserverLookupByIp(InetAddresses.forString(arg1)); } catch (IllegalArgumentException iae) { // Silently ignore this exception. } @@ -180,11 +182,11 @@ class WhoisReader { // If the target is exactly one level above the TLD, then this is an second level domain // (SLD) and we should do a domain lookup on it. if (targetName.parent().equals(tld.get())) { - return new DomainLookupCommand(targetName, tld.get()); + return commandFactory.domainLookup(targetName, tld.get()); } // The target is more than one level above the TLD, so we'll assume it's a nameserver. - return new NameserverLookupByHostCommand(targetName, tld.get()); + return commandFactory.nameserverLookupByHost(targetName, tld.get()); } catch (IllegalArgumentException e) { // Silently ignore this exception. } @@ -194,7 +196,7 @@ class WhoisReader { // 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. - return new RegistrarLookupCommand(Joiner.on(' ').join(tokens)); + return commandFactory.registrarLookup(Joiner.on(' ').join(tokens)); } /** Returns an ArrayList containing the contents of the String array minus any empty strings. */ diff --git a/java/google/registry/whois/WhoisServer.java b/java/google/registry/whois/WhoisServer.java index 8f6731f08..745d34025 100644 --- a/java/google/registry/whois/WhoisServer.java +++ b/java/google/registry/whois/WhoisServer.java @@ -58,9 +58,8 @@ public class WhoisServer implements Runnable { @Inject Clock clock; @Inject Reader input; @Inject Response response; - @Inject - @Config("whoisDisclaimer") - String disclaimer; + @Inject @Config("whoisCommandFactory") WhoisCommandFactory commandFactory; + @Inject @Config("whoisDisclaimer") String disclaimer; @Inject WhoisServer() {} @Override @@ -69,7 +68,7 @@ public class WhoisServer implements Runnable { DateTime now = clock.nowUtc(); try { responseText = - new WhoisReader(input, now) + new WhoisReader(input, commandFactory, now) .readCommand() .executeQuery(now) .getPlainTextOutput(PREFER_UNICODE, disclaimer); diff --git a/javatests/google/registry/whois/WhoisHttpServerTest.java b/javatests/google/registry/whois/WhoisHttpServerTest.java index 5ef31eb14..c1888952b 100644 --- a/javatests/google/registry/whois/WhoisHttpServerTest.java +++ b/javatests/google/registry/whois/WhoisHttpServerTest.java @@ -71,6 +71,7 @@ public class WhoisHttpServerTest { result.requestPath = WhoisHttpServer.PATH + pathInfo; result.response = response; result.disclaimer = "Doodle Disclaimer"; + result.commandFactory = new WhoisCommandFactory(); return result; } diff --git a/javatests/google/registry/whois/WhoisReaderTest.java b/javatests/google/registry/whois/WhoisReaderTest.java index 520647451..0a8e77d1d 100644 --- a/javatests/google/registry/whois/WhoisReaderTest.java +++ b/javatests/google/registry/whois/WhoisReaderTest.java @@ -31,13 +31,9 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class WhoisReaderTest { - @Rule - public final AppEngineRule appEngine = AppEngineRule.builder() - .withDatastore() - .build(); + @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); - @Rule - public final ExceptionRule thrown = new ExceptionRule(); + @Rule public final ExceptionRule thrown = new ExceptionRule(); private final FakeClock clock = new FakeClock(); @@ -46,9 +42,11 @@ public class WhoisReaderTest { createTlds("tld", "xn--kgbechtv", "1.test"); } - @SuppressWarnings("unchecked") // XXX: Generic abuse ftw. + @SuppressWarnings("unchecked") // XXX: Generic abuse ftw. T readCommand(String commandStr) throws Exception { - return (T) new WhoisReader(new StringReader(commandStr), clock.nowUtc()).readCommand(); + return (T) + new WhoisReader(new StringReader(commandStr), new WhoisCommandFactory(), clock.nowUtc()) + .readCommand(); } void assertLoadsExampleTld(String commandString) throws Exception { @@ -73,8 +71,8 @@ public class WhoisReaderTest { void assertNsLookup(String commandString, String expectedIpAddress) throws Exception { assertThat( - this.readCommand(commandString).ipAddress.getHostAddress()) - .isEqualTo(expectedIpAddress); + this.readCommand(commandString).ipAddress.getHostAddress()) + .isEqualTo(expectedIpAddress); } void assertLoadsRegistrar(String commandString) throws Exception { diff --git a/javatests/google/registry/whois/WhoisServerTest.java b/javatests/google/registry/whois/WhoisServerTest.java index 471d346c2..7e11e8914 100644 --- a/javatests/google/registry/whois/WhoisServerTest.java +++ b/javatests/google/registry/whois/WhoisServerTest.java @@ -66,6 +66,7 @@ public class WhoisServerTest { result.input = new StringReader(input); result.response = response; result.disclaimer = "Doodle Disclaimer"; + result.commandFactory = new WhoisCommandFactory(); return result; }