8221518: Normalize normalization
authordfuchs
Wed, 10 Apr 2019 15:49:24 +0100
changeset 55711 0c143aaa2c99
parent 55710 18130ed28231
child 55712 107c8ea4f7c8
8221518: Normalize normalization Reviewed-by: chegar, igerasim, ahgross, rhalade
src/java.base/share/classes/java/net/URL.java
src/java.base/share/classes/java/net/URLStreamHandler.java
src/java.base/share/classes/sun/net/util/IPAddressUtil.java
src/java.base/share/classes/sun/net/www/protocol/ftp/FtpURLConnection.java
src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java
src/java.base/share/classes/sun/net/www/protocol/https/HttpsURLConnectionImpl.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));
     }
--- 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);
--- 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;
+    }
 }
--- 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();
--- 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));
--- 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., "<code>accept</code>").
      * @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) {