# HG changeset patch # User dfuchs # Date 1554907764 -3600 # Node ID 0c143aaa2c99739458b1bdc38955a3ec891db1c8 # Parent 18130ed28231cee6e12f27011e26598ff551aadf 8221518: Normalize normalization Reviewed-by: chegar, igerasim, ahgross, rhalade diff -r 18130ed28231 -r 0c143aaa2c99 src/java.base/share/classes/java/net/URL.java --- a/src/java.base/share/classes/java/net/URL.java Wed Apr 03 13:35:11 2019 -0700 +++ b/src/java.base/share/classes/java/net/URL.java Wed Apr 10 15:49:24 2019 +0100 @@ -45,6 +45,7 @@ import jdk.internal.access.JavaNetURLAccess; import jdk.internal.access.SharedSecrets; +import sun.net.util.IPAddressUtil; import sun.security.util.SecurityConstants; import sun.security.action.GetPropertyAction; @@ -466,13 +467,19 @@ this.file = path; } - // Note: we don't do validation of the URL here. Too risky to change + // Note: we don't do full validation of the URL here. Too risky to change // right now, but worth considering for future reference. -br if (handler == null && (handler = getURLStreamHandler(protocol)) == null) { throw new MalformedURLException("unknown protocol: " + protocol); } this.handler = handler; + if (host != null && isBuiltinStreamHandler(handler)) { + String s = IPAddressUtil.checkExternalForm(this); + if (s != null) { + throw new MalformedURLException(s); + } + } } /** @@ -1038,7 +1045,12 @@ * @since 1.5 */ public URI toURI() throws URISyntaxException { - return new URI (toString()); + URI uri = new URI(toString()); + if (authority != null && isBuiltinStreamHandler(handler)) { + String s = IPAddressUtil.checkAuthority(this); + if (s != null) throw new URISyntaxException(authority, s); + } + return uri; } /** @@ -1635,6 +1647,10 @@ return replacementURL; } + boolean isBuiltinStreamHandler(URLStreamHandler handler) { + return isBuiltinStreamHandler(handler.getClass().getName()); + } + private boolean isBuiltinStreamHandler(String handlerClassName) { return (handlerClassName.startsWith(BUILTIN_HANDLERS_PREFIX)); } diff -r 18130ed28231 -r 0c143aaa2c99 src/java.base/share/classes/java/net/URLStreamHandler.java --- a/src/java.base/share/classes/java/net/URLStreamHandler.java Wed Apr 03 13:35:11 2019 -0700 +++ b/src/java.base/share/classes/java/net/URLStreamHandler.java Wed Apr 10 15:49:24 2019 +0100 @@ -516,12 +516,15 @@ * different from this one * @since 1.3 */ - protected void setURL(URL u, String protocol, String host, int port, + protected void setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref) { if (this != u.handler) { throw new SecurityException("handler for url different from " + "this handler"); + } else if (host != null && u.isBuiltinStreamHandler(this)) { + String s = IPAddressUtil.checkHostString(host); + if (s != null) throw new IllegalArgumentException(s); } // ensure that no one can reset the protocol on a given URL. u.set(u.getProtocol(), host, port, authority, userInfo, path, query, ref); diff -r 18130ed28231 -r 0c143aaa2c99 src/java.base/share/classes/sun/net/util/IPAddressUtil.java --- a/src/java.base/share/classes/sun/net/util/IPAddressUtil.java Wed Apr 03 13:35:11 2019 -0700 +++ b/src/java.base/share/classes/sun/net/util/IPAddressUtil.java Wed Apr 10 15:49:24 2019 +0100 @@ -32,9 +32,11 @@ import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.net.SocketException; +import java.net.URL; import java.security.AccessController; import java.security.PrivilegedExceptionAction; import java.security.PrivilegedActionException; +import java.util.Arrays; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -372,4 +374,181 @@ return null; } } + + // See java.net.URI for more details on how to generate these + // masks. + // + // square brackets + private static final long L_IPV6_DELIMS = 0x0L; // "[]" + private static final long H_IPV6_DELIMS = 0x28000000L; // "[]" + // RFC 3986 gen-delims + private static final long L_GEN_DELIMS = 0x8400800800000000L; // ":/?#[]@" + private static final long H_GEN_DELIMS = 0x28000001L; // ":/?#[]@" + // These gen-delims can appear in authority + private static final long L_AUTH_DELIMS = 0x400000000000000L; // "@[]:" + private static final long H_AUTH_DELIMS = 0x28000001L; // "@[]:" + // colon is allowed in userinfo + private static final long L_COLON = 0x400000000000000L; // ":" + private static final long H_COLON = 0x0L; // ":" + // slash should be encoded in authority + private static final long L_SLASH = 0x800000000000L; // "/" + private static final long H_SLASH = 0x0L; // "/" + // backslash should always be encoded + private static final long L_BACKSLASH = 0x0L; // "\" + private static final long H_BACKSLASH = 0x10000000L; // "\" + // ASCII chars 0-31 + 127 - various controls + CRLF + TAB + private static final long L_NON_PRINTABLE = 0xffffffffL; + private static final long H_NON_PRINTABLE = 0x8000000000000000L; + // All of the above + private static final long L_EXCLUDE = 0x84008008ffffffffL; + private static final long H_EXCLUDE = 0x8000000038000001L; + + private static final char[] OTHERS = { + 8263,8264,8265,8448,8449,8453,8454,10868, + 65109,65110,65119,65131,65283,65295,65306,65311,65312 + }; + + // Tell whether the given character is found by the given mask pair + public static boolean match(char c, long lowMask, long highMask) { + if (c < 64) + return ((1L << c) & lowMask) != 0; + if (c < 128) + return ((1L << (c - 64)) & highMask) != 0; + return false; // other non ASCII characters are not filtered + } + + // returns -1 if the string doesn't contain any characters + // from the mask, the index of the first such character found + // otherwise. + public static int scan(String s, long lowMask, long highMask) { + int i = -1, len; + if (s == null || (len = s.length()) == 0) return -1; + boolean match = false; + while (++i < len && !(match = match(s.charAt(i), lowMask, highMask))); + if (match) return i; + return -1; + } + + public static int scan(String s, long lowMask, long highMask, char[] others) { + int i = -1, len; + if (s == null || (len = s.length()) == 0) return -1; + boolean match = false; + char c, c0 = others[0]; + while (++i < len && !(match = match((c=s.charAt(i)), lowMask, highMask))) { + if (c >= c0 && (Arrays.binarySearch(others, c) > -1)) { + match = true; break; + } + } + if (match) return i; + + return -1; + } + + private static String describeChar(char c) { + if (c < 32 || c == 127) { + if (c == '\n') return "LF"; + if (c == '\r') return "CR"; + return "control char (code=" + (int)c + ")"; + } + if (c == '\\') return "'\\'"; + return "'" + c + "'"; + } + + private static String checkUserInfo(String str) { + // colon is permitted in user info + int index = scan(str, L_EXCLUDE & ~L_COLON, + H_EXCLUDE & ~H_COLON); + if (index >= 0) { + return "Illegal character found in user-info: " + + describeChar(str.charAt(index)); + } + return null; + } + + private static String checkHost(String str) { + int index; + if (str.startsWith("[") && str.endsWith("]")) { + str = str.substring(1, str.length() - 1); + if (isIPv6LiteralAddress(str)) { + index = str.indexOf('%'); + if (index >= 0) { + index = scan(str = str.substring(index), + L_NON_PRINTABLE | L_IPV6_DELIMS, + H_NON_PRINTABLE | H_IPV6_DELIMS); + if (index >= 0) { + return "Illegal character found in IPv6 scoped address: " + + describeChar(str.charAt(index)); + } + } + return null; + } + return "Unrecognized IPv6 address format"; + } else { + index = scan(str, L_EXCLUDE, H_EXCLUDE); + if (index >= 0) { + return "Illegal character found in host: " + + describeChar(str.charAt(index)); + } + } + return null; + } + + private static String checkAuth(String str) { + int index = scan(str, + L_EXCLUDE & ~L_AUTH_DELIMS, + H_EXCLUDE & ~H_AUTH_DELIMS); + if (index >= 0) { + return "Illegal character found in authority: " + + describeChar(str.charAt(index)); + } + return null; + } + + // check authority of hierarchical URL. Appropriate for + // HTTP-like protocol handlers + public static String checkAuthority(URL url) { + String s, u, h; + if (url == null) return null; + if ((s = checkUserInfo(u = url.getUserInfo())) != null) { + return s; + } + if ((s = checkHost(h = url.getHost())) != null) { + return s; + } + if (h == null && u == null) { + return checkAuth(url.getAuthority()); + } + return null; + } + + // minimal syntax checks - deeper check may be performed + // by the appropriate protocol handler + public static String checkExternalForm(URL url) { + String s; + if (url == null) return null; + int index = scan(s = url.getUserInfo(), + L_NON_PRINTABLE | L_SLASH, + H_NON_PRINTABLE | H_SLASH); + if (index >= 0) { + return "Illegal character found in authority: " + + describeChar(s.charAt(index)); + } + if ((s = checkHostString(url.getHost())) != null) { + return s; + } + return null; + } + + public static String checkHostString(String host) { + if (host == null) return null; + int index = scan(host, + L_NON_PRINTABLE | L_SLASH, + H_NON_PRINTABLE | H_SLASH, + OTHERS); + if (index >= 0) { + return "Illegal character found in host: " + + describeChar(host.charAt(index)); + } + return null; + } } diff -r 18130ed28231 -r 0c143aaa2c99 src/java.base/share/classes/sun/net/www/protocol/ftp/FtpURLConnection.java --- a/src/java.base/share/classes/sun/net/www/protocol/ftp/FtpURLConnection.java Wed Apr 03 13:35:11 2019 -0700 +++ b/src/java.base/share/classes/sun/net/www/protocol/ftp/FtpURLConnection.java Wed Apr 10 15:49:24 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,6 +36,7 @@ import java.io.FilterInputStream; import java.io.FilterOutputStream; import java.io.FileNotFoundException; +import java.net.MalformedURLException; import java.net.URL; import java.net.SocketPermission; import java.net.UnknownHostException; @@ -48,6 +49,7 @@ import java.security.Permission; import java.util.Properties; import sun.net.NetworkClient; +import sun.net.util.IPAddressUtil; import sun.net.www.MessageHeader; import sun.net.www.MeteredStream; import sun.net.www.URLConnection; @@ -157,6 +159,21 @@ } } + static URL checkURL(URL u) throws IllegalArgumentException { + if (u != null) { + if (u.toExternalForm().indexOf('\n') > -1) { + Exception mfue = new MalformedURLException("Illegal character in URL"); + throw new IllegalArgumentException(mfue.getMessage(), mfue); + } + } + String s = IPAddressUtil.checkAuthority(u); + if (s != null) { + Exception mfue = new MalformedURLException(s); + throw new IllegalArgumentException(mfue.getMessage(), mfue); + } + return u; + } + /** * Creates an FtpURLConnection from a URL. * @@ -170,7 +187,7 @@ * Same as FtpURLconnection(URL) with a per connection proxy specified */ FtpURLConnection(URL url, Proxy p) { - super(url); + super(checkURL(url)); instProxy = p; host = url.getHost(); port = url.getPort(); diff -r 18130ed28231 -r 0c143aaa2c99 src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java --- a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java Wed Apr 03 13:35:11 2019 -0700 +++ b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java Wed Apr 10 15:49:24 2019 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -68,6 +68,7 @@ import jdk.internal.access.JavaNetHttpCookieAccess; import jdk.internal.access.SharedSecrets; import sun.net.*; +import sun.net.util.IPAddressUtil; import sun.net.www.*; import sun.net.www.http.HttpClient; import sun.net.www.http.PosterOutputStream; @@ -868,8 +869,13 @@ throw new MalformedURLException("Illegal character in URL"); } } + String s = IPAddressUtil.checkAuthority(u); + if (s != null) { + throw new MalformedURLException(s); + } return u; } + protected HttpURLConnection(URL u, Proxy p, Handler handler) throws IOException { super(checkURL(u)); diff -r 18130ed28231 -r 0c143aaa2c99 src/java.base/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.java --- a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.java Wed Apr 03 13:35:11 2019 -0700 +++ b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.java Wed Apr 10 15:49:24 2019 +0100 @@ -37,6 +37,7 @@ import java.util.Map; import java.util.List; import java.util.Optional; +import sun.net.util.IPAddressUtil; import sun.net.www.http.HttpClient; /** @@ -69,6 +70,10 @@ throw new MalformedURLException("Illegal character in URL"); } } + String s = IPAddressUtil.checkAuthority(u); + if (s != null) { + throw new MalformedURLException(s); + } return u; } @@ -289,7 +294,7 @@ * @param key the keyword by which the request is known * (e.g., "accept"). * @param value the value associated with it. - * @see #getRequestProperties(java.lang.String) + * @see #getRequestProperty(java.lang.String) * @since 1.4 */ public void addRequestProperty(String key, String value) {