diff --git a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java index 63edeb3f3d6..c945cdb4f14 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java @@ -218,7 +218,7 @@ public static InternetDomainName validateDomainName(String name) throws EppExcep return domainName; } - private static void validateFirstLabel(String firstLabel) throws EppException { + public static void validateFirstLabel(String firstLabel) throws EppException { if (firstLabel.length() > MAX_LABEL_SIZE) { throw new DomainLabelTooLongException(); } diff --git a/core/src/main/java/google/registry/flows/host/HostCheckFlow.java b/core/src/main/java/google/registry/flows/host/HostCheckFlow.java index dc00a363b74..c11e47d5c33 100644 --- a/core/src/main/java/google/registry/flows/host/HostCheckFlow.java +++ b/core/src/main/java/google/registry/flows/host/HostCheckFlow.java @@ -65,6 +65,7 @@ public EppResponse run() throws EppException { ForeignKeyUtils.loadKeys(Host.class, hostnames, clock.nowUtc()).keySet(); ImmutableList.Builder checks = new ImmutableList.Builder<>(); for (String hostname : hostnames) { + HostFlowUtils.validateHostName(hostname); boolean unused = !existingIds.contains(hostname); checks.add(HostCheck.create(unused, hostname, unused ? null : "In use")); } diff --git a/core/src/main/java/google/registry/flows/host/HostFlowUtils.java b/core/src/main/java/google/registry/flows/host/HostFlowUtils.java index 5652a9a60b1..1c1d32f8c39 100644 --- a/core/src/main/java/google/registry/flows/host/HostFlowUtils.java +++ b/core/src/main/java/google/registry/flows/host/HostFlowUtils.java @@ -14,12 +14,14 @@ package google.registry.flows.host; +import static google.registry.flows.domain.DomainFlowUtils.validateFirstLabel; import static google.registry.model.EppResourceUtils.isActive; import static google.registry.model.tld.Tlds.findTldForName; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import static java.util.stream.Collectors.joining; import com.google.common.base.Ascii; +import com.google.common.base.CharMatcher; import com.google.common.net.InternetDomainName; import google.registry.flows.EppException; import google.registry.flows.EppException.AuthorizationErrorException; @@ -38,6 +40,10 @@ /** Static utility functions for host flows. */ public class HostFlowUtils { + /** Validator for ASCII lowercase letters, digits, and "-_", allowing "." as a separator */ + private static final CharMatcher HOST_NAME_ALLOWED_CHARS = + CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('0', '9').or(CharMatcher.anyOf("-._"))); + /** Checks that a host name is valid. */ public static InternetDomainName validateHostName(String name) throws EppException { checkArgumentNotNull(name, "Must specify host name to validate"); @@ -53,6 +59,9 @@ public static InternetDomainName validateHostName(String name) throws EppExcepti if (!name.equals(hostNamePunyCoded)) { throw new HostNameNotPunyCodedException(hostNamePunyCoded); } + if (!HOST_NAME_ALLOWED_CHARS.matchesAllOf(name)) { + throw new BadHostNameCharacterException(); + } InternetDomainName hostName = InternetDomainName.from(name); if (!name.equals(hostName.toString())) { throw new HostNameNotNormalizedException(hostName.toString()); @@ -71,6 +80,7 @@ public static InternetDomainName validateHostName(String name) throws EppExcepti if (hostName.parts().size() < effectiveTld.parts().size() + 2) { throw new HostNameTooShallowException(); } + validateFirstLabel(hostName.parts().getFirst()); return hostName; } catch (IllegalArgumentException e) { throw new InvalidHostNameException(); @@ -180,4 +190,11 @@ public HostNameNotNormalizedException(String expectedHostName) { String.format("Host names must be in normalized format; expected %s", expectedHostName)); } } + + /** Host names can only contain a-z, 0-9, '.', '_', and '-'. */ + static class BadHostNameCharacterException extends ParameterValueSyntaxErrorException { + public BadHostNameCharacterException() { + super("Host names can only contain a-z, 0-9, '.', '_', and '-'"); + } + } } diff --git a/core/src/test/java/google/registry/flows/host/HostCheckFlowTest.java b/core/src/test/java/google/registry/flows/host/HostCheckFlowTest.java index ca1d69426fe..4d8bc24d07f 100644 --- a/core/src/test/java/google/registry/flows/host/HostCheckFlowTest.java +++ b/core/src/test/java/google/registry/flows/host/HostCheckFlowTest.java @@ -20,7 +20,9 @@ import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.google.common.collect.ImmutableMap; import google.registry.flows.EppException; +import google.registry.flows.EppException.ParameterValueSyntaxErrorException; import google.registry.flows.FlowUtils.NotLoggedInException; import google.registry.flows.ResourceCheckFlowTestCase; import google.registry.flows.exceptions.TooManyResourceChecksException; @@ -95,4 +97,36 @@ void testIcannActivityReportField_getsLogged() throws Exception { runFlow(); assertIcannReportingActivityFieldLogged("srs-host-check"); } + + @Test + void testFailure_dotHost() throws Exception { + setEppInput("host_check_generic.xml", ImmutableMap.of("HOSTNAME", ".host")); + assertAboutEppExceptions() + .that(assertThrows(ParameterValueSyntaxErrorException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_dashHost() { + setEppInput("host_check_generic.xml", ImmutableMap.of("HOSTNAME", "-host")); + assertAboutEppExceptions() + .that(assertThrows(ParameterValueSyntaxErrorException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_underscoreHost() { + setEppInput("host_check_generic.xml", ImmutableMap.of("HOSTNAME", "_host")); + assertAboutEppExceptions() + .that(assertThrows(ParameterValueSyntaxErrorException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_hostDash() { + setEppInput("host_check_generic.xml", ImmutableMap.of("HOSTNAME", "host-")); + assertAboutEppExceptions() + .that(assertThrows(ParameterValueSyntaxErrorException.class, this::runFlow)) + .marshalsToXml(); + } } diff --git a/core/src/test/java/google/registry/flows/host/HostCreateFlowTest.java b/core/src/test/java/google/registry/flows/host/HostCreateFlowTest.java index 8297737e232..f466db2ec07 100644 --- a/core/src/test/java/google/registry/flows/host/HostCreateFlowTest.java +++ b/core/src/test/java/google/registry/flows/host/HostCreateFlowTest.java @@ -39,12 +39,12 @@ import google.registry.flows.exceptions.ResourceCreateContentionException; import google.registry.flows.host.HostCreateFlow.SubordinateHostMustHaveIpException; import google.registry.flows.host.HostCreateFlow.UnexpectedExternalHostIpException; +import google.registry.flows.host.HostFlowUtils.BadHostNameCharacterException; import google.registry.flows.host.HostFlowUtils.HostNameNotLowerCaseException; import google.registry.flows.host.HostFlowUtils.HostNameNotNormalizedException; import google.registry.flows.host.HostFlowUtils.HostNameNotPunyCodedException; import google.registry.flows.host.HostFlowUtils.HostNameTooLongException; import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException; -import google.registry.flows.host.HostFlowUtils.InvalidHostNameException; import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException; import google.registry.flows.host.HostFlowUtils.SuperordinateDomainInPendingDeleteException; import google.registry.model.ForeignKeyUtils; @@ -286,7 +286,7 @@ private void doFailingHostNameTest(String hostName, Class - www1.tld - www2.tld - www3.tld - www4.tld - www5.tld - www6.tld - www7.tld - www8.tld - www9.tld - www10.tld - www11.tld - www12.tld - www13.tld - www14.tld - www15.tld - www16.tld - www17.tld - www18.tld - www19.tld - www20.tld - www21.tld - www22.tld - www23.tld - www24.tld - www25.tld - www26.tld - www27.tld - www28.tld - www29.tld - www30.tld - www31.tld - www32.tld - www33.tld - www34.tld - www35.tld - www36.tld - www37.tld - www38.tld - www39.tld - www40.tld - www41.tld - www42.tld - www43.tld - www44.tld - www45.tld - www46.tld - www47.tld - www48.tld - www49.tld - www50.tld + ns1.www1.tld + ns1.www2.tld + ns1.www3.tld + ns1.www4.tld + ns1.www5.tld + ns1.www6.tld + ns1.www7.tld + ns1.www8.tld + ns1.www9.tld + ns1.www10.tld + ns1.www11.tld + ns1.www12.tld + ns1.www13.tld + ns1.www14.tld + ns1.www15.tld + ns1.www16.tld + ns1.www17.tld + ns1.www18.tld + ns1.www19.tld + ns1.www20.tld + ns1.www21.tld + ns1.www22.tld + ns1.www23.tld + ns1.www24.tld + ns1.www25.tld + ns1.www26.tld + ns1.www27.tld + ns1.www28.tld + ns1.www29.tld + ns1.www30.tld + ns1.www31.tld + ns1.www32.tld + ns1.www33.tld + ns1.www34.tld + ns1.www35.tld + ns1.www36.tld + ns1.www37.tld + ns1.www38.tld + ns1.www39.tld + ns1.www40.tld + ns1.www41.tld + ns1.www42.tld + ns1.www43.tld + ns1.www44.tld + ns1.www45.tld + ns1.www46.tld + ns1.www47.tld + ns1.www48.tld + ns1.www49.tld + ns1.www50.tld ABC-12345 diff --git a/core/src/test/resources/google/registry/flows/host/host_check_generic.xml b/core/src/test/resources/google/registry/flows/host/host_check_generic.xml new file mode 100644 index 00000000000..4418fd34315 --- /dev/null +++ b/core/src/test/resources/google/registry/flows/host/host_check_generic.xml @@ -0,0 +1,11 @@ + + + + + %HOSTNAME% + + + ABC-12345 + +