Author: markt Date: Wed Jan 31 09:01:40 2018 New Revision: 1822775 URL: http://svn.apache.org/viewvc?rev=1822775&view=rev Log: Update the host validation to permit host names and components of domain names (excluding top-level domains) to start with a number and to ensure that top-level domains are fully alphabetic.
Modified: tomcat/trunk/java/org/apache/tomcat/util/http/parser/HttpParser.java tomcat/trunk/test/org/apache/tomcat/util/http/parser/TestHttpParserHost.java tomcat/trunk/webapps/docs/changelog.xml Modified: tomcat/trunk/java/org/apache/tomcat/util/http/parser/HttpParser.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/util/http/parser/HttpParser.java?rev=1822775&r1=1822774&r2=1822775&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/util/http/parser/HttpParser.java (original) +++ tomcat/trunk/java/org/apache/tomcat/util/http/parser/HttpParser.java Wed Jan 31 09:01:40 2018 @@ -494,6 +494,9 @@ public class HttpParser { int c; int pos = 0; + // readAheadLimit doesn't matter as all the readers passed to this + // method buffer the entire content. + reader.mark(1); do { c = reader.read(); if (c == '.') { @@ -501,9 +504,14 @@ public class HttpParser { // Valid octetCount++; octet = -1; - } else { + } else if (inIPv6 || octet == -1) { throw new IllegalArgumentException( sm.getString("http.invalidOctet", Integer.toString(octet))); + } else { + // Might not be an IPv4 address. Could be a host / FQDN with + // a fully numeric component. + reader.reset(); + return readHostDomainName(reader); } } else if (isNumeric(c)) { if (octet == -1) { @@ -527,6 +535,10 @@ public class HttpParser { } else { throw new IllegalArgumentException(sm.getString("http.closingBracket")); } + } else if (!inIPv6 && (isAlpha(c) || c == '-')) { + // Go back to the start and parse as a host / FQDN + reader.reset(); + return readHostDomainName(reader); } else { throw new IllegalArgumentException(sm.getString( "http.illegalCharacterIpv4", Character.toString((char) c))); @@ -535,8 +547,11 @@ public class HttpParser { } while (true); if (octetCount != 4) { - throw new IllegalArgumentException( - sm.getString("http.wrongOctetCount", Integer.toString(octetCount))); + // Might not be an IPv4 address. Could be a host name or a FQDN with + // fully numeric components. Go back to the start and parse as a + // host / FQDN. + reader.reset(); + return readHostDomainName(reader); } if (octet < 0 || octet > 255) { throw new IllegalArgumentException( @@ -652,9 +667,13 @@ public class HttpParser { static int readHostDomainName(Reader reader) throws IOException { DomainParseState state = DomainParseState.NEW; int pos = 0; + int segmentIndex = 0; while (state.mayContinue()) { - state = state.next(reader.read()); + state = state.next(reader.read(), segmentIndex); + if (DomainParseState.PERIOD == state) { + segmentIndex++; + } pos++; } @@ -682,28 +701,32 @@ public class HttpParser { } } + private enum AllowsEnd { + NEVER, + FIRST, + ALWAYS + } private enum DomainParseState { - NEW( true, false, false, false, false, false, " at the start of"), - ALPHA( true, true, true, true, true, true, " after a letter in"), - NUMERIC( true, true, true, true, true, true, " after a number in"), - PERIOD( true, false, false, false, true, true, " after a period in"), - HYPHEN( true, true, true, false, false, false, " after a hypen in"), - COLON( false, false, false, false, false, false, " after a colon in"), - END( false, false, false, false, false, false, " at the end of"); + NEW( true, false, false, AllowsEnd.NEVER, AllowsEnd.NEVER, " at the start of"), + ALL_ALPHA( true, true, true, AllowsEnd.ALWAYS, AllowsEnd.ALWAYS, " after a letter in"), + ALPHA( true, true, true, AllowsEnd.FIRST, AllowsEnd.FIRST, " after a letter in"), + NUMERIC( true, true, true, AllowsEnd.FIRST, AllowsEnd.FIRST, " after a number in"), + PERIOD( true, false, false, AllowsEnd.NEVER, AllowsEnd.NEVER, " after a period in"), + HYPHEN( true, true, false, AllowsEnd.NEVER, AllowsEnd.NEVER, " after a hypen in"), + COLON( false, false, false, AllowsEnd.NEVER, AllowsEnd.NEVER, " after a colon in"), + END( false, false, false, AllowsEnd.NEVER, AllowsEnd.NEVER, " at the end of"); private final boolean mayContinue; - private final boolean allowsNumeric; private final boolean allowsHyphen; private final boolean allowsPeriod; - private final boolean allowsColon; - private final boolean allowsEnd; + private final AllowsEnd allowsColon; + private final AllowsEnd allowsEnd; private final String errorLocation; - private DomainParseState(boolean mayContinue, boolean allowsNumeric, boolean allowsHyphen, - boolean allowsPeriod, boolean allowsColon, boolean allowsEnd, String errorLocation) { + private DomainParseState(boolean mayContinue, boolean allowsHyphen, boolean allowsPeriod, + AllowsEnd allowsColon, AllowsEnd allowsEnd, String errorLocation) { this.mayContinue = mayContinue; - this.allowsNumeric = allowsNumeric; this.allowsHyphen = allowsHyphen; this.allowsPeriod = allowsPeriod; this.allowsColon = allowsColon; @@ -715,16 +738,15 @@ public class HttpParser { return mayContinue; } - public DomainParseState next(int c) { + public DomainParseState next(int c, int segmentIndex) { if (HttpParser.isAlpha(c)) { - return ALPHA; - } else if (HttpParser.isNumeric(c)) { - if (allowsNumeric) { - return NUMERIC; + if (ALL_ALPHA == this || NEW == this || PERIOD == this) { + return ALL_ALPHA; } else { - throw new IllegalArgumentException(sm.getString("http.invalidCharacterDomain", - Character.toString((char) c), errorLocation)); + return ALPHA; } + } else if (HttpParser.isNumeric(c)) { + return NUMERIC; } else if (c == '.') { if (allowsPeriod) { return PERIOD; @@ -733,14 +755,16 @@ public class HttpParser { Character.toString((char) c), errorLocation)); } } else if (c == ':') { - if (allowsColon) { + if (allowsColon == AllowsEnd.ALWAYS || + allowsColon == AllowsEnd.FIRST && segmentIndex == 0) { return COLON; } else { throw new IllegalArgumentException(sm.getString("http.invalidCharacterDomain", Character.toString((char) c), errorLocation)); } } else if (c == -1) { - if (allowsEnd) { + if (allowsEnd == AllowsEnd.ALWAYS || + allowsEnd == AllowsEnd.FIRST && segmentIndex == 0) { return END; } else { throw new IllegalArgumentException(sm.getString("http.invalidCharacterDomain", Modified: tomcat/trunk/test/org/apache/tomcat/util/http/parser/TestHttpParserHost.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/util/http/parser/TestHttpParserHost.java?rev=1822775&r1=1822774&r2=1822775&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/tomcat/util/http/parser/TestHttpParserHost.java (original) +++ tomcat/trunk/test/org/apache/tomcat/util/http/parser/TestHttpParserHost.java Wed Jan 31 09:01:40 2018 @@ -46,7 +46,7 @@ public class TestHttpParserHost { public Class<? extends Exception> expectedException; - @Parameters + @Parameters(name="{index}: host {1}") public static Collection<Object[]> inputs() { List<Object[]> result = new ArrayList<>(); // IPv4 - valid @@ -54,8 +54,8 @@ public class TestHttpParserHost { result.add(new Object[] { TestType.IPv4, "127.0.0.1:8080", Integer.valueOf(9), null} ); result.add(new Object[] { TestType.IPv4, "0.0.0.0", Integer.valueOf(-1), null} ); result.add(new Object[] { TestType.IPv4, "0.0.0.0:8080", Integer.valueOf(7), null} ); + result.add(new Object[] { TestType.IPv4, "0", Integer.valueOf(-1), null} ); // IPv4 - invalid - result.add(new Object[] { TestType.IPv4, "0", Integer.valueOf(-1), IAE} ); result.add(new Object[] { TestType.IPv4, "0.0", Integer.valueOf(-1), IAE} ); result.add(new Object[] { TestType.IPv4, "0.0.0", Integer.valueOf(-1), IAE} ); result.add(new Object[] { TestType.IPv4, ".0.0.0", Integer.valueOf(-1), IAE} ); @@ -68,23 +68,48 @@ public class TestHttpParserHost { result.add(new Object[] { TestType.IPv4, "0..0.0", Integer.valueOf(-1), IAE} ); result.add(new Object[] { TestType.IPv4, "0]", Integer.valueOf(-1), IAE} ); // Domain Name - valid - result.add(new Object[] { TestType.DOMAIN_NAME, "localhost", Integer.valueOf(-1), null} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "localhost:8080", Integer.valueOf(9), null} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "tomcat.apache.org", Integer.valueOf(-1), null} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "tomcat.apache.org:8080", Integer.valueOf(17), null} ); + result.add(new Object[] { TestType.IPv4, "localhost", Integer.valueOf(-1), null} ); + result.add(new Object[] { TestType.IPv4, "localhost:8080", Integer.valueOf(9), null} ); + result.add(new Object[] { TestType.IPv4, "tomcat.apache.org", Integer.valueOf(-1), null} ); + result.add(new Object[] { TestType.IPv4, "tomcat.apache.org:8080", Integer.valueOf(17), null} ); + result.add(new Object[] { TestType.IPv4, "0.0.0.com", Integer.valueOf(-1), null} ); + result.add(new Object[] { TestType.IPv4, "0.0.0.com:8080", Integer.valueOf(9), null} ); + result.add(new Object[] { TestType.IPv4, "0.0.0.0.com", Integer.valueOf(-1), null} ); + result.add(new Object[] { TestType.IPv4, "0.0.0.0.com:8080", Integer.valueOf(11), null} ); + result.add(new Object[] { TestType.IPv4, "foo.0.0.com", Integer.valueOf(-1), null} ); + result.add(new Object[] { TestType.IPv4, "foo.0.0.com:8080", Integer.valueOf(11), null} ); + result.add(new Object[] { TestType.IPv4, "1foo.0.0.com", Integer.valueOf(-1), null} ); + result.add(new Object[] { TestType.IPv4, "1foo.0.0.com:8080", Integer.valueOf(12), null} ); + result.add(new Object[] { TestType.IPv4, "1-foo.0.0.com", Integer.valueOf(-1), null} ); + result.add(new Object[] { TestType.IPv4, "1-foo.0.0.com:8080", Integer.valueOf(13), null} ); + result.add(new Object[] { TestType.IPv4, "1--foo.0.0.com", Integer.valueOf(-1), null} ); + result.add(new Object[] { TestType.IPv4, "1--foo.0.0.com:8080", Integer.valueOf(14), null} ); + result.add(new Object[] { TestType.IPv4, "com", Integer.valueOf(-1), null} ); + result.add(new Object[] { TestType.IPv4, "com:8080", Integer.valueOf(3), null} ); + result.add(new Object[] { TestType.IPv4, "0com", Integer.valueOf(-1), null} ); + result.add(new Object[] { TestType.IPv4, "0com:8080", Integer.valueOf(4), null} ); + result.add(new Object[] { TestType.IPv4, "123", Integer.valueOf(-1), null} ); + result.add(new Object[] { TestType.IPv4, "123:8080", Integer.valueOf(3), null} ); // Domain Name - invalid - result.add(new Object[] { TestType.DOMAIN_NAME, ".foo.bar", Integer.valueOf(-1), IAE} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "2foo.bar", Integer.valueOf(-1), IAE} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "-foo.bar", Integer.valueOf(-1), IAE} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "^foo.bar", Integer.valueOf(-1), IAE} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "foo-.bar", Integer.valueOf(-1), IAE} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "f*oo.bar", Integer.valueOf(-1), IAE} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "foo..bar", Integer.valueOf(-1), IAE} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "foo.2bar", Integer.valueOf(-1), IAE} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "foo.-bar", Integer.valueOf(-1), IAE} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "foo.^bar", Integer.valueOf(-1), IAE} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "foo.bar-", Integer.valueOf(-1), IAE} ); - result.add(new Object[] { TestType.DOMAIN_NAME, "foo.b*ar", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, ".", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, ".:8080", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, ".foo.bar", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "-foo.bar", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "foo.bar.", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "foo.bar-", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "foo.bar.:8080", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "foo.bar-:8080", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "^foo.bar", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "foo-.bar", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "f*oo.bar", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "foo..bar", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "foo.-bar", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "foo.^bar", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "foo.b*ar", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "0.0.0com", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "0.0.0.0com", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "foo.bar.0com", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv4, "foo.bar.0com:8080", Integer.valueOf(-1), IAE} ); // IPv6 - valid result.add(new Object[] { TestType.IPv6, "[::1]", Integer.valueOf(-1), null} ); result.add(new Object[] { TestType.IPv6, "[::1]:8080", Integer.valueOf(5), null} ); @@ -138,6 +163,12 @@ public class TestHttpParserHost { result.add(new Object[] { TestType.IPv6, "[0::0::127.0.0.1]", Integer.valueOf(-1), IAE} ); result.add(new Object[] { TestType.IPv6, "[0:0:G:0:0:0:127.0.0.1]", Integer.valueOf(-1), IAE} ); result.add(new Object[] { TestType.IPv6, "[00000:0:0:0:0:0:127.0.0.1]", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv6, "[1::127..0.1]", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv6, "[1::127..0.1]:8080", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv6, "[1::127.a.0.1]", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv6, "[1::127.a.0.1]:8080", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv6, "[1::127.-.0.1]", Integer.valueOf(-1), IAE} ); + result.add(new Object[] { TestType.IPv6, "[1::127.-.0.1]:8080", Integer.valueOf(-1), IAE} ); result.add(new Object[] { TestType.IPv6, "[::1]'", Integer.valueOf(-1), IAE} ); result.add(new Object[] { TestType.IPv6, "[:2222:3333:4444:5555:6666:7777:8888]", Integer.valueOf(-1), IAE} ); @@ -167,9 +198,6 @@ public class TestHttpParserHost { case IPv6: result = HttpParser.readHostIPv6(sr); break; - case DOMAIN_NAME: - result = HttpParser.readHostDomainName(sr); - break; } } catch (Exception e) { @@ -187,7 +215,6 @@ public class TestHttpParserHost { private static enum TestType { IPv4, - IPv6, - DOMAIN_NAME + IPv6 } } Modified: tomcat/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1822775&r1=1822774&r2=1822775&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/changelog.xml (original) +++ tomcat/trunk/webapps/docs/changelog.xml Wed Jan 31 09:01:40 2018 @@ -130,6 +130,11 @@ connectors. Requests with invalid host names and/or ports will be rejected with a 400 response. (markt) </add> + <fix> + Update the host validation to permit host names and components of domain + names (excluding top-level domains) to start with a number and to ensure + that top-level domains are fully alphabetic. (markt) + </fix> </changelog> </subsection> <subsection name="Jasper"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org