8221518: Normalize normalization

Reviewed-by: chegar, igerasim, ahgross, rhalade
This commit is contained in:
Daniel Fuchs 2019-04-10 15:49:24 +01:00
parent 657f017400
commit c9da623b6f
6 changed files with 233 additions and 7 deletions

View file

@ -45,6 +45,7 @@ import java.util.ServiceLoader;
import jdk.internal.access.JavaNetURLAccess; import jdk.internal.access.JavaNetURLAccess;
import jdk.internal.access.SharedSecrets; import jdk.internal.access.SharedSecrets;
import sun.net.util.IPAddressUtil;
import sun.security.util.SecurityConstants; import sun.security.util.SecurityConstants;
import sun.security.action.GetPropertyAction; import sun.security.action.GetPropertyAction;
@ -466,13 +467,19 @@ public final class URL implements java.io.Serializable {
this.file = path; 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 // right now, but worth considering for future reference. -br
if (handler == null && if (handler == null &&
(handler = getURLStreamHandler(protocol)) == null) { (handler = getURLStreamHandler(protocol)) == null) {
throw new MalformedURLException("unknown protocol: " + protocol); throw new MalformedURLException("unknown protocol: " + protocol);
} }
this.handler = handler; this.handler = handler;
if (host != null && isBuiltinStreamHandler(handler)) {
String s = IPAddressUtil.checkExternalForm(this);
if (s != null) {
throw new MalformedURLException(s);
}
}
} }
/** /**
@ -1038,7 +1045,12 @@ public final class URL implements java.io.Serializable {
* @since 1.5 * @since 1.5
*/ */
public URI toURI() throws URISyntaxException { 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 @@ public final class URL implements java.io.Serializable {
return replacementURL; return replacementURL;
} }
boolean isBuiltinStreamHandler(URLStreamHandler handler) {
return isBuiltinStreamHandler(handler.getClass().getName());
}
private boolean isBuiltinStreamHandler(String handlerClassName) { private boolean isBuiltinStreamHandler(String handlerClassName) {
return (handlerClassName.startsWith(BUILTIN_HANDLERS_PREFIX)); return (handlerClassName.startsWith(BUILTIN_HANDLERS_PREFIX));
} }

View file

@ -516,12 +516,15 @@ public abstract class URLStreamHandler {
* different from this one * different from this one
* @since 1.3 * @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 authority, String userInfo, String path,
String query, String ref) { String query, String ref) {
if (this != u.handler) { if (this != u.handler) {
throw new SecurityException("handler for url different from " + throw new SecurityException("handler for url different from " +
"this handler"); "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. // ensure that no one can reset the protocol on a given URL.
u.set(u.getProtocol(), host, port, authority, userInfo, path, query, ref); u.set(u.getProtocol(), host, port, authority, userInfo, path, query, ref);

View file

@ -32,9 +32,11 @@ import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.net.URL;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException; import java.security.PrivilegedActionException;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -372,4 +374,181 @@ public class IPAddressUtil {
return null; 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;
}
} }

View file

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -36,6 +36,7 @@ import java.io.BufferedInputStream;
import java.io.FilterInputStream; import java.io.FilterInputStream;
import java.io.FilterOutputStream; import java.io.FilterOutputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.SocketPermission; import java.net.SocketPermission;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@ -48,6 +49,7 @@ import java.util.Iterator;
import java.security.Permission; import java.security.Permission;
import java.util.Properties; import java.util.Properties;
import sun.net.NetworkClient; import sun.net.NetworkClient;
import sun.net.util.IPAddressUtil;
import sun.net.www.MessageHeader; import sun.net.www.MessageHeader;
import sun.net.www.MeteredStream; import sun.net.www.MeteredStream;
import sun.net.www.URLConnection; import sun.net.www.URLConnection;
@ -157,6 +159,21 @@ public class FtpURLConnection extends URLConnection {
} }
} }
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. * Creates an FtpURLConnection from a URL.
* *
@ -170,7 +187,7 @@ public class FtpURLConnection extends URLConnection {
* Same as FtpURLconnection(URL) with a per connection proxy specified * Same as FtpURLconnection(URL) with a per connection proxy specified
*/ */
FtpURLConnection(URL url, Proxy p) { FtpURLConnection(URL url, Proxy p) {
super(url); super(checkURL(url));
instProxy = p; instProxy = p;
host = url.getHost(); host = url.getHost();
port = url.getPort(); port = url.getPort();

View file

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -68,6 +68,7 @@ import java.util.StringJoiner;
import jdk.internal.access.JavaNetHttpCookieAccess; import jdk.internal.access.JavaNetHttpCookieAccess;
import jdk.internal.access.SharedSecrets; import jdk.internal.access.SharedSecrets;
import sun.net.*; import sun.net.*;
import sun.net.util.IPAddressUtil;
import sun.net.www.*; import sun.net.www.*;
import sun.net.www.http.HttpClient; import sun.net.www.http.HttpClient;
import sun.net.www.http.PosterOutputStream; import sun.net.www.http.PosterOutputStream;
@ -868,8 +869,13 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
throw new MalformedURLException("Illegal character in URL"); throw new MalformedURLException("Illegal character in URL");
} }
} }
String s = IPAddressUtil.checkAuthority(u);
if (s != null) {
throw new MalformedURLException(s);
}
return u; return u;
} }
protected HttpURLConnection(URL u, Proxy p, Handler handler) protected HttpURLConnection(URL u, Proxy p, Handler handler)
throws IOException { throws IOException {
super(checkURL(u)); super(checkURL(u));

View file

@ -37,6 +37,7 @@ import java.security.Principal;
import java.util.Map; import java.util.Map;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import sun.net.util.IPAddressUtil;
import sun.net.www.http.HttpClient; import sun.net.www.http.HttpClient;
/** /**
@ -69,6 +70,10 @@ public class HttpsURLConnectionImpl
throw new MalformedURLException("Illegal character in URL"); throw new MalformedURLException("Illegal character in URL");
} }
} }
String s = IPAddressUtil.checkAuthority(u);
if (s != null) {
throw new MalformedURLException(s);
}
return u; return u;
} }
@ -289,7 +294,7 @@ public class HttpsURLConnectionImpl
* @param key the keyword by which the request is known * @param key the keyword by which the request is known
* (e.g., "<code>accept</code>"). * (e.g., "<code>accept</code>").
* @param value the value associated with it. * @param value the value associated with it.
* @see #getRequestProperties(java.lang.String) * @see #getRequestProperty(java.lang.String)
* @since 1.4 * @since 1.4
*/ */
public void addRequestProperty(String key, String value) { public void addRequestProperty(String key, String value) {