changeset 48883:a29e720c80d4

8278972: Improve URL supports Reviewed-by: andrew
author yan
date Fri, 11 Mar 2022 10:38:18 +0300
parents d6b71ab410ee
children 37e3c2de46aa
files jdk/src/share/classes/com/sun/jndi/dns/DnsUrl.java jdk/src/share/classes/com/sun/jndi/ldap/LdapURL.java jdk/src/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java jdk/src/share/classes/com/sun/jndi/toolkit/url/Uri.java jdk/src/share/classes/com/sun/jndi/url/rmi/rmiURLContext.java
diffstat 5 files changed, 668 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/jdk/src/share/classes/com/sun/jndi/dns/DnsUrl.java	Fri Apr 15 03:37:34 2022 +0100
+++ b/jdk/src/share/classes/com/sun/jndi/dns/DnsUrl.java	Fri Mar 11 10:38:18 2022 +0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2002, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2022, 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
@@ -27,7 +27,12 @@
 
 
 import java.net.MalformedURLException;
-import java.util.Hashtable;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import sun.security.action.GetPropertyAction;
+import java.util.Locale;
 import java.util.StringTokenizer;
 
 import com.sun.jndi.toolkit.url.Uri;
@@ -56,6 +61,23 @@
 
 public class DnsUrl extends Uri {
 
+    private static final String PARSE_MODE_PROP = "com.sun.jndi.dnsURLParsing";
+    private static final ParseMode DEFAULT_PARSE_MODE = ParseMode.COMPAT;
+
+    public static final ParseMode PARSE_MODE;
+    static {
+        PrivilegedAction<String> action =
+                new GetPropertyAction(PARSE_MODE_PROP, DEFAULT_PARSE_MODE.toString());
+        ParseMode parseMode = DEFAULT_PARSE_MODE;
+        try {
+            String mode = AccessController.doPrivileged(action);
+            parseMode = ParseMode.valueOf(mode.toUpperCase(Locale.ROOT));
+        } catch (Throwable t) {
+            parseMode = DEFAULT_PARSE_MODE;
+        } finally {
+            PARSE_MODE = parseMode;
+        }
+    }
     private String domain;      // domain name of the context
 
 
@@ -71,19 +93,58 @@
         StringTokenizer st = new StringTokenizer(urlList, " ");
 
         while (st.hasMoreTokens()) {
-            urls[i++] = new DnsUrl(st.nextToken());
+            try {
+                urls[i++] = new DnsUrl(validateURI(st.nextToken()));
+            } catch (URISyntaxException e) {
+                MalformedURLException mue = new MalformedURLException(e.getMessage());
+                mue.initCause(e);
+                throw mue;
+            }
         }
         DnsUrl[] trimmed = new DnsUrl[i];
         System.arraycopy(urls, 0, trimmed, 0, i);
         return trimmed;
     }
 
+    @Override
+    protected ParseMode parseMode() {
+        return PARSE_MODE;
+    }
+
+    @Override
+    protected final boolean isSchemeOnly(String uri) {
+        return isDnsSchemeOnly(uri);
+    }
+
+    @Override
+    protected boolean checkSchemeOnly(String uri, String scheme) {
+        return uri.equals(scheme + ":") || uri.equals(scheme + "://");
+    }
+
+    @Override
+    protected final MalformedURLException newInvalidURISchemeException(String uri) {
+        return new MalformedURLException(
+                uri + " is not a valid DNS pseudo-URL");
+    }
+
+    private static boolean isDnsSchemeOnly(String uri) {
+        return "dns:".equals(uri) || "dns://".equals(uri);
+    }
+
+    private static String validateURI(String uri) throws URISyntaxException {
+        // no validation in legacy parsing mode
+        if (PARSE_MODE == ParseMode.LEGACY) return uri;
+        // special case of scheme-only URIs
+        if (isDnsSchemeOnly(uri)) return uri;
+        // use java.net.URI to validate the uri syntax
+        return new URI(uri).toString();
+    }
+
     public DnsUrl(String url) throws MalformedURLException {
         super(url);
 
         if (!scheme.equals("dns")) {
-            throw new MalformedURLException(
-                    url + " is not a valid DNS pseudo-URL");
+            throw newInvalidURISchemeException(url);
         }
 
         domain = path.startsWith("/")
--- a/jdk/src/share/classes/com/sun/jndi/ldap/LdapURL.java	Fri Apr 15 03:37:34 2022 +0100
+++ b/jdk/src/share/classes/com/sun/jndi/ldap/LdapURL.java	Fri Mar 11 10:38:18 2022 +0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2002, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2022, 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
@@ -28,6 +28,11 @@
 import javax.naming.*;
 import java.net.MalformedURLException;
 import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import sun.security.action.GetPropertyAction;
+import java.util.Locale;
 import java.util.StringTokenizer;
 import com.sun.jndi.toolkit.url.Uri;
 import com.sun.jndi.toolkit.url.UrlUtil;
@@ -64,6 +69,24 @@
 
 final public class LdapURL extends Uri {
 
+    private static final String PARSE_MODE_PROP = "com.sun.jndi.ldapURLParsing";
+    private static final ParseMode DEFAULT_PARSE_MODE = ParseMode.COMPAT;
+
+    public static final ParseMode PARSE_MODE;
+    static {
+        PrivilegedAction<String> action =
+                new GetPropertyAction(PARSE_MODE_PROP, DEFAULT_PARSE_MODE.toString());
+        ParseMode parseMode = DEFAULT_PARSE_MODE;
+        try {
+            String mode = AccessController.doPrivileged(action);
+            parseMode = ParseMode.valueOf(mode.toUpperCase(Locale.ROOT));
+        } catch (Throwable t) {
+            parseMode = DEFAULT_PARSE_MODE;
+        } finally {
+            PARSE_MODE = parseMode;
+        }
+    }
+
     private boolean useSsl = false;
     private String DN = null;
     private String attributes = null;
@@ -83,7 +106,7 @@
             useSsl = scheme.equalsIgnoreCase("ldaps");
 
             if (! (scheme.equalsIgnoreCase("ldap") || useSsl)) {
-                throw new MalformedURLException("Not an LDAP URL: " + url);
+                throw newInvalidURISchemeException(url);
             }
 
             parsePathAndQuery(); // DN, attributes, scope, filter, extensions
@@ -99,6 +122,21 @@
         }
     }
 
+    @Override
+    protected MalformedURLException newInvalidURISchemeException(String uri) {
+        return new MalformedURLException("Not an LDAP URL: " + uri);
+    }
+
+    @Override
+    protected boolean isSchemeOnly(String uri) {
+        return isLdapSchemeOnly(uri);
+    }
+
+    @Override
+    protected ParseMode parseMode() {
+        return PARSE_MODE;
+    }
+
     /**
      * Returns true if the URL is an LDAPS URL.
      */
@@ -151,13 +189,33 @@
         StringTokenizer st = new StringTokenizer(urlList, " ");
 
         while (st.hasMoreTokens()) {
-            urls[i++] = st.nextToken();
+            // we don't accept scheme-only URLs here
+            urls[i++] = validateURI(st.nextToken());
         }
         String[] trimmed = new String[i];
         System.arraycopy(urls, 0, trimmed, 0, i);
         return trimmed;
     }
 
+    public static boolean isLdapSchemeOnly(String uri) {
+        return "ldap:".equals(uri) || "ldaps:".equals(uri);
+    }
+
+    public static String validateURI(String uri) {
+        // no validation in legacy mode parsing
+        if (PARSE_MODE == ParseMode.LEGACY) {
+            return uri;
+        }
+
+        // special case of scheme-only URIs
+        if (isLdapSchemeOnly(uri)) {
+            return uri;
+        }
+
+        // use java.net.URI to validate the uri syntax
+        return URI.create(uri).toString();
+    }
+
     /**
      * Derermines whether an LDAP URL has query components.
      */
@@ -181,7 +239,8 @@
             String p = (port != -1) ? (":" + port) : "";
             String d = (dn != null) ? ("/" + UrlUtil.encode(dn, "UTF8")) : "";
 
-            return useSsl ? "ldaps://" + h + p + d : "ldap://" + h + p + d;
+            String uri = useSsl ? "ldaps://" + h + p + d : "ldap://" + h + p + d;
+            return validateURI(uri);
         } catch (UnsupportedEncodingException e) {
             // UTF8 should always be supported
             throw new IllegalStateException("UTF-8 encoding unavailable");
--- a/jdk/src/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java	Fri Apr 15 03:37:34 2022 +0100
+++ b/jdk/src/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java	Fri Mar 11 10:38:18 2022 +0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2022, 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
@@ -32,6 +32,8 @@
 import java.util.Hashtable;
 import java.net.MalformedURLException;
 
+import com.sun.jndi.toolkit.url.Uri.ParseMode;
+
 /**
  * This abstract class is a generic URL context that accepts as the
  * name argument either a string URL or a Name whose first component
@@ -48,6 +50,7 @@
  * @author Rosanna Lee
  */
 abstract public class GenericURLContext implements Context {
+
     protected Hashtable<String, Object> myEnv = null;
 
     @SuppressWarnings("unchecked") // Expect Hashtable<String, Object>
@@ -159,8 +162,18 @@
         if (url.startsWith("//", start)) {
             start += 2;  // skip double slash
 
-            // find last slash
-            int posn = url.indexOf("/", start);
+            // find where the authority component ends
+            // and the rest of the URL starts
+            int slash = url.indexOf('/', start);
+            int qmark = url.indexOf('?', start);
+            int fmark = url.indexOf('#', start);
+            if (fmark > -1 && qmark > fmark) qmark = -1;
+            if (fmark > -1 && slash > fmark) slash = -1;
+            if (qmark > -1 && slash > qmark) slash = -1;
+            int posn = slash > -1 ? slash
+                    : (qmark > -1 ? qmark
+                    : (fmark > -1 ? fmark
+                    : url.length()));
             if (posn >= 0) {
                 start = posn;
             } else {
--- a/jdk/src/share/classes/com/sun/jndi/toolkit/url/Uri.java	Fri Apr 15 03:37:34 2022 +0100
+++ b/jdk/src/share/classes/com/sun/jndi/toolkit/url/Uri.java	Fri Mar 11 10:38:18 2022 +0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2001, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2022, 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
@@ -27,6 +27,8 @@
 
 
 import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
 
 
 /**
@@ -36,15 +38,17 @@
  *
  * <p> The java.net.URL class cannot be used to parse URIs since it
  * requires the installation of URL stream handlers that may not be
- * available.  The hack of getting around this by temporarily
- * replacing the scheme part of a URI is not appropriate here: JNDI
- * service providers must work on older Java platforms, and we want
- * new features and bug fixes that are not available in old versions
- * of the URL class.
+ * available.
  *
- * <p> It may be appropriate to drop this code in favor of the
- * java.net.URI class.  The changes would need to be written so as to
- * still run on pre-1.4 platforms not containing that class.
+ * <p> The {@linkplain ParseMode#STRICT strict} parsing mode uses
+ * the java.net.URI class to syntactically validate URI strings.
+ * The {@linkplain ParseMode#COMPAT compat} mode validate the
+ * URI authority and rejects URI fragments, but doesn't perform any
+ * additional validation on path and query, other than that
+ * which may be implemented in the concrete the Uri subclasses.
+ * The {@linkplain ParseMode#LEGACY legacy} mode should not be
+ * used unless the application is capable of validating all URI
+ * strings before any constructors of this class is invoked.
  *
  * <p> The format of an absolute URI (see the RFCs mentioned above) is:
  * <p><blockquote><pre>
@@ -105,6 +109,28 @@
 
 public class Uri {
 
+    // three parsing modes
+    public enum ParseMode {
+        /**
+         * Strict validation mode.
+         * Validate the URI syntactically using {@link java.net.URI}.
+         * Rejects URI fragments unless explicitly supported by the
+         * subclass.
+         */
+        STRICT,
+        /**
+         * Compatibility mode. The URI authority is syntactically validated.
+         * Rejects URI fragments unless explicitly supported by the
+         * subclass.
+         * This is the default.
+         */
+        COMPAT,
+        /**
+         * Legacy mode. In this mode, no validation is performed.
+         */
+        LEGACY
+     }
+
     protected String uri;
     protected String scheme;
     protected String host = null;
@@ -112,6 +138,7 @@
     protected boolean hasAuthority;
     protected String path;
     protected String query = null;
+    protected String fragment;
 
 
     /**
@@ -129,13 +156,22 @@
     }
 
     /**
+     * The parse mode for parsing this URI.
+     * The default is {@link ParseMode#COMPAT}.
+     * @return the parse mode for parsing this URI.
+     */
+    protected ParseMode parseMode() {
+        return ParseMode.COMPAT;
+    }
+
+    /**
      * Initializes a Uri object given a URI string.
      * This method must be called exactly once, and before any other Uri
      * methods.
      */
     protected void init(String uri) throws MalformedURLException {
         this.uri = uri;
-        parse(uri);
+        parse(uri, parseMode());
     }
 
     /**
@@ -188,10 +224,235 @@
         return uri;
     }
 
+    private void parse(String uri, ParseMode mode) throws MalformedURLException {
+        switch (mode) {
+            case STRICT:
+                parseStrict(uri);
+                break;
+            case COMPAT:
+                parseCompat(uri);
+                break;
+            case LEGACY:
+                parseLegacy(uri);
+                break;
+        }
+    }
+
+    /*
+     * Parses a URI string and sets this object's fields accordingly.
+     * Use java.net.URI to validate the uri string syntax
+     */
+    private void parseStrict(String uri) throws MalformedURLException {
+        try {
+            if (!isSchemeOnly(uri)) {
+                URI u = new URI(uri);
+                scheme = u.getScheme();
+                if (scheme == null) throw new MalformedURLException("Invalid URI: " + uri);
+                String auth = u.getRawAuthority();
+                hasAuthority = auth != null;
+                if (hasAuthority) {
+                    String host = u.getHost();
+                    int port = u.getPort();
+                    if (host != null) this.host = host;
+                    if (port != -1) this.port = port;
+                    String hostport = (host == null ? "" : host)
+                            + (port == -1 ? "" : (":" + port));
+                    if (!hostport.equals(auth)) {
+                        // throw if we have user info or regname
+                        throw new MalformedURLException("unsupported authority: " + auth);
+                    }
+                }
+                path = u.getRawPath();
+                if (u.getRawQuery() != null) {
+                    query = "?" + u.getRawQuery();
+                }
+                if (u.getRawFragment() != null) {
+                    if (!acceptsFragment()) {
+                        throw new MalformedURLException("URI fragments not supported: " + uri);
+                    }
+                    fragment = "#" + u.getRawFragment();
+                }
+            } else {
+                // scheme-only URIs are not supported by java.net.URI
+                // validate the URI by appending "/" to the uri string.
+                String s = uri.substring(0, uri.indexOf(':'));
+                URI u = new URI(uri + "/");
+                if (!s.equals(u.getScheme())
+                        || !checkSchemeOnly(uri, u.getScheme())) {
+                    throw newInvalidURISchemeException(uri);
+                }
+                scheme = s;
+                path = "";
+            }
+        } catch (URISyntaxException e) {
+            MalformedURLException mue =  new MalformedURLException(e.getMessage());
+            mue.initCause(e);
+            throw mue;
+        }
+    }
+
+
     /*
      * Parses a URI string and sets this object's fields accordingly.
+     * Compatibility mode. Use java.net.URI to validate the syntax of
+     * the uri string authority.
      */
-    private void parse(String uri) throws MalformedURLException {
+    private void parseCompat(String uri) throws MalformedURLException {
+        int i;  // index into URI
+
+        i = uri.indexOf(':');                           // parse scheme
+        int slash = uri.indexOf('/');
+        int qmark = uri.indexOf('?');
+        int fmark = uri.indexOf('#');
+        if (i < 0 || slash > 0 && i > slash || qmark > 0 && i > qmark || fmark > 0 && i > fmark) {
+            throw new MalformedURLException("Invalid URI: " + uri);
+        }
+        if (fmark > -1) {
+            if (!acceptsFragment()) {
+                throw new MalformedURLException("URI fragments not supported: " + uri);
+            }
+        }
+        if (i == uri.length() - 1) {
+            if (!isSchemeOnly(uri)) {
+                throw newInvalidURISchemeException(uri);
+            }
+        }
+        scheme = uri.substring(0, i);
+        i++;                                            // skip past ":"
+
+        hasAuthority = uri.startsWith("//", i);
+        if (fmark > -1 && qmark > fmark) qmark = -1;
+        int endp = qmark > -1 ? qmark : fmark > -1 ? fmark : uri.length();
+        if (hasAuthority) {                             // parse "//host:port"
+            i += 2;                                     // skip past "//"
+            int starta = i;
+            // authority ends at the first appearance of /, ?, or #
+            int enda = uri.indexOf('/', i);
+            if (enda == -1 || qmark > -1 && qmark < enda) enda = qmark;
+            if (enda == -1 || fmark > -1 && fmark < enda) enda = fmark;
+            if (enda < 0) {
+                enda = uri.length();
+            }
+            if (uri.startsWith(":", i)) {
+                // LdapURL supports empty host.
+                i++;
+                host = "";
+                if (enda > i) {
+                    port = Integer.parseInt(uri.substring(i, enda));
+                }
+            } else {
+                // Use URI to parse authority
+                try {
+                    // URI requires at least one char after authority:
+                    // we use "/" and expect that the resulting URI path
+                    // will be exactly "/".
+                    URI u = new URI(uri.substring(0, enda) + "/");
+                    String auth = uri.substring(starta, enda);
+                    host = u.getHost();
+                    port = u.getPort();
+                    String p = u.getRawPath();
+                    String q = u.getRawQuery();
+                    String f = u.getRawFragment();
+                    String ui = u.getRawUserInfo();
+                    if (ui != null) {
+                        throw new MalformedURLException("user info not supported in authority: " + ui);
+                    }
+                    if (!"/".equals(p)) {
+                        throw new MalformedURLException("invalid authority: " + auth);
+                    }
+                    if (q != null) {
+                        throw new MalformedURLException("invalid trailing characters in authority: ?" + q);
+                    }
+                    if (f != null) {
+                        throw new MalformedURLException("invalid trailing characters in authority: #" + f);
+                    }
+                    String hostport = (host == null ? "" : host)
+                            + (port == -1?"":(":" + port));
+                    if (!auth.equals(hostport)) {
+                        // throw if we have user info or regname
+                        throw new MalformedURLException("unsupported authority: " + auth);
+                    }
+                } catch (URISyntaxException e) {
+                    MalformedURLException mue = new MalformedURLException(e.getMessage());
+                    mue.initCause(e);
+                    throw mue;
+                }
+            }
+            i = enda;
+        }
+        path = uri.substring(i, endp);
+        // look for query
+        if (qmark > -1) {
+            if (fmark > -1) {
+                query = uri.substring(qmark, fmark);
+            } else {
+                query = uri.substring(qmark);
+            }
+        }
+        if (fmark > -1) {
+            fragment = uri.substring(fmark);
+        }
+    }
+
+    /**
+     * A subclass of {@code Uri} that supports scheme only
+     * URIs can override this method and return true in the
+     * case where the URI string is a scheme-only URI that
+     * the subclass supports.
+     * @implSpec
+     * The default implementation of this method returns false,
+     * always.
+     * @param uri An URI string
+     * @return if this is a scheme-only URI supported by the subclass
+     */
+    protected boolean isSchemeOnly(String uri) {
+        return false;
+    }
+
+    /**
+     * Checks whether the given uri string should be considered
+     * as a scheme-only URI. For some protocols - e.g. DNS, we
+     * might accept "dns://" as a valid URL denoting default DNS.
+     * For others - we might only accept "scheme:".
+     * @implSpec
+     * The default implementation of this method returns true if
+     * the URI is of the form {@code "<scheme>:"} with nothing
+     * after the scheme delimiter.
+     * @param uri the URI
+     * @param scheme the scheme
+     * @return true if the URI should be considered as a scheme-only
+     *         URI supported by this URI scheme.
+     */
+    protected boolean checkSchemeOnly(String uri, String scheme) {
+        return uri.equals(scheme + ":");
+    }
+
+    /**
+     * Creates a {@code MalformedURLException} to be thrown when the
+     * URI scheme is not supported.
+     *
+     * @param uri the URI string
+     * @return a {@link MalformedURLException}
+     */
+    protected MalformedURLException newInvalidURISchemeException(String uri) {
+        return new MalformedURLException("Invalid URI scheme: " + uri);
+    }
+
+    /**
+     * Whether fragments are supported.
+     * @implSpec
+     * The default implementation of this method retturns false, always.
+     * @return true if fragments are supported.
+     */
+    protected boolean acceptsFragment() {
+        return parseMode() == ParseMode.LEGACY;
+    }
+
+    /*
+     * Parses a URI string and sets this object's fields accordingly.
+     * Legacy parsing mode.
+     */
+    private void parseLegacy(String uri) throws MalformedURLException {
         int i;  // index into URI
 
         i = uri.indexOf(':');                           // parse scheme
--- a/jdk/src/share/classes/com/sun/jndi/url/rmi/rmiURLContext.java	Fri Apr 15 03:37:34 2022 +0100
+++ b/jdk/src/share/classes/com/sun/jndi/url/rmi/rmiURLContext.java	Fri Mar 11 10:38:18 2022 +0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2022, 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
@@ -25,12 +25,18 @@
 
 package com.sun.jndi.url.rmi;
 
+import java.net.URI;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import sun.security.action.GetPropertyAction;
 import java.util.Hashtable;
+import java.util.Locale;
 
 import javax.naming.*;
 import javax.naming.spi.ResolveResult;
 import com.sun.jndi.toolkit.url.GenericURLContext;
 import com.sun.jndi.rmi.registry.RegistryContext;
+import com.sun.jndi.toolkit.url.Uri.ParseMode;
 
 
 /**
@@ -47,10 +53,249 @@
  */
 public class rmiURLContext extends GenericURLContext {
 
+    private static final String PARSE_MODE_PROP = "com.sun.jndi.rmiURLParsing";
+    private static final ParseMode DEFAULT_PARSE_MODE = ParseMode.COMPAT;
+
+    public static final ParseMode PARSE_MODE;
+    static {
+        PrivilegedAction<String> action =
+                new GetPropertyAction(PARSE_MODE_PROP, DEFAULT_PARSE_MODE.toString());
+        ParseMode parseMode = DEFAULT_PARSE_MODE;
+        try {
+            String mode = AccessController.doPrivileged(action);
+            parseMode = ParseMode.valueOf(mode.toUpperCase(Locale.ROOT));
+        } catch (Throwable t) {
+            parseMode = DEFAULT_PARSE_MODE;
+        } finally {
+            PARSE_MODE = parseMode;
+        }
+    }
+
     public rmiURLContext(Hashtable<?,?> env) {
         super(env);
     }
 
+    public static class Parser {
+        final String url;
+        final ParseMode mode;
+        String host = null;
+        int port = -1;
+        String objName = null;
+        public Parser(String url) {
+            this(url, PARSE_MODE);
+        }
+        public Parser(String url, ParseMode mode) {
+            this.url = url;
+            this.mode = mode;
+        }
+
+        public String url() {return url;}
+        public String host() {return host;}
+        public int port() {return port;}
+        public String objName() {return objName;}
+        public ParseMode mode() {return mode;}
+
+        public void parse() throws NamingException {
+            if (!url.startsWith("rmi:")) {
+                throw (new IllegalArgumentException(
+                        "rmiURLContext: name is not an RMI URL: " + url));
+            }
+
+            switch (mode) {
+                case STRICT:
+                    parseStrict();
+                    break;
+                case COMPAT:
+                    parseCompat();
+                    break;
+                case LEGACY:
+                    parseLegacy();
+                    break;
+            }
+
+        }
+
+        private void parseStrict() throws NamingException {
+            assert url.startsWith("rmi:");
+
+            if (url.equals("rmi:") || url.equals("rmi://")) return;
+
+            // index into url, following the "rmi:"
+            int i = 4;
+
+            if (url.startsWith("//", i)) {
+                i += 2;
+                try {
+                    URI uri = URI.create(url);
+                    host = uri.getHost();
+                    port = uri.getPort();
+                    String auth = uri.getRawAuthority();
+                    String hostport = (host == null ? "" : host)
+                            + (port == -1 ? "" : ":" + port);
+                    if (!hostport.equals(auth)) {
+                        boolean failed = true;
+                        if (hostport.equals("") && auth.startsWith(":")) {
+                            // supports missing host
+                            try {
+                                port = Integer.parseInt(auth.substring(1));
+                                failed = false;
+                            } catch (NumberFormatException x) {
+                                failed = true;
+                            }
+                        }
+                        if (failed) {
+                            throw newNamingException(new IllegalArgumentException("invalid authority: "
+                                    + auth));
+                        }
+                    }
+                    i += auth.length();
+                } catch (IllegalArgumentException iae) {
+                    throw newNamingException(iae);
+                }
+            }
+
+            if ("".equals(host)) {
+                host = null;
+            }
+            if (url.startsWith("/", i)) {           // skip "/" before object name
+                i++;
+            }
+            if (i < url.length()) {
+                objName = url.substring(i);
+            }
+        }
+
+        private void parseCompat() throws NamingException {
+            assert url.startsWith("rmi:");
+
+            int i = 4;              // index into url, following the "rmi:"
+            boolean hasAuthority = url.startsWith("//", i);
+            if (hasAuthority) i += 2;  // skip past "//"
+            int slash = url.indexOf('/', i);
+            int qmark = url.indexOf('?', i);
+            int fmark = url.indexOf('#', i);
+            if (fmark > -1 && qmark > fmark) qmark = -1;
+            if (fmark > -1 && slash > fmark) slash = -1;
+            if (qmark > -1 && slash > qmark) slash = -1;
+
+            // The end of the authority component is either the
+            // slash (slash will be -1 if it doesn't come before
+            // query or fragment), or the question mark (qmark will
+            // be -1 if it doesn't come before the fragment), or
+            // the fragment separator, or the end of the URI
+            // string if there is no path, no query, and no fragment.
+            int enda = slash > -1 ? slash
+                    : (qmark > -1 ? qmark
+                    : (fmark > -1 ? fmark
+                    : url.length()));
+            if (fmark > -1) {
+                if (!acceptsFragment()) {
+                    throw newNamingException(new IllegalArgumentException("URI fragments not supported: " + url));
+                }
+            }
+
+            if (hasAuthority && enda > i) {          // parse "//host:port"
+                if (url.startsWith(":", i)) {
+                    // LdapURL supports empty host.
+                    i++;
+                    host = "";
+                    if (enda > i) {
+                        port = Integer.parseInt(url.substring(i, enda));
+                    }
+                } else {
+                    try {
+                        URI uri = URI.create(url.substring(0, enda));
+                        host = uri.getHost();
+                        port = uri.getPort();
+                        String hostport = (host == null ? "" : host)
+                                + (port == -1 ? "" : ":" + port);
+                        if (!hostport.equals(uri.getRawAuthority())) {
+                            throw newNamingException(new IllegalArgumentException("invalid authority: "
+                                    + uri.getRawAuthority()));
+                        }
+                    } catch (IllegalArgumentException iae) {
+                        throw newNamingException(iae);
+                    }
+                }
+                i = enda;
+            }
+            if ("".equals(host)) {
+                host = null;
+            }
+            if (url.startsWith("/", i)) {           // skip "/" before object name
+                i++;
+            }
+            if (i < url.length()) {
+                objName = url.substring(i);
+            }
+
+        }
+
+        // The legacy parsing used to only throw IllegalArgumentException
+        // and continues to do so
+        private void parseLegacy() {
+            assert url.startsWith("rmi:");
+
+            // Parse the URL.
+            int i = 4;              // index into url, following the "rmi:"
+
+            if (url.startsWith("//", i)) {          // parse "//host:port"
+                i += 2;                             // skip past "//"
+                int slash = url.indexOf('/', i);
+                if (slash < 0) {
+                    slash = url.length();
+                }
+                if (url.startsWith("[", i)) {               // at IPv6 literal
+                    int brac = url.indexOf(']', i + 1);
+                    if (brac < 0 || brac > slash) {
+                        throw new IllegalArgumentException(
+                                "rmiURLContext: name is an Invalid URL: " + url);
+                    }
+                    host = url.substring(i, brac + 1);      // include brackets
+                    i = brac + 1;                           // skip past "[...]"
+                } else {                                    // at host name or IPv4
+                    int colon = url.indexOf(':', i);
+                    int hostEnd = (colon < 0 || colon > slash)
+                            ? slash
+                            : colon;
+                    if (i < hostEnd) {
+                        host = url.substring(i, hostEnd);
+                    }
+                    i = hostEnd;                            // skip past host
+                }
+                if ((i + 1 < slash)) {
+                    if ( url.startsWith(":", i)) {       // parse port
+                        i++;                             // skip past ":"
+                        port = Integer.parseInt(url.substring(i, slash));
+                    } else {
+                        throw new IllegalArgumentException(
+                                "rmiURLContext: name is an Invalid URL: " + url);
+                    }
+                }
+                i = slash;
+            }
+            if ("".equals(host)) {
+                host = null;
+            }
+            if (url.startsWith("/", i)) {           // skip "/" before object name
+                i++;
+            }
+            if (i < url.length()) {
+                objName = url.substring(i);
+            }
+        }
+
+        NamingException newNamingException(Throwable cause) {
+            NamingException ne = new NamingException(cause.getMessage());
+            ne.initCause(cause);
+            return ne;
+        }
+
+        boolean acceptsFragment() {
+            return true;
+        }
+    }
+
     /**
      * Resolves the registry portion of "url" to the corresponding
      * RMI registry, and returns the atomic object name as the
@@ -59,63 +304,11 @@
     protected ResolveResult getRootURLContext(String url, Hashtable<?,?> env)
             throws NamingException
     {
-        if (!url.startsWith("rmi:")) {
-            throw (new IllegalArgumentException(
-                    "rmiURLContext: name is not an RMI URL: " + url));
-        }
-
-        // Parse the URL.
-
-        String host = null;
-        int port = -1;
-        String objName = null;
-
-        int i = 4;              // index into url, following the "rmi:"
-
-        if (url.startsWith("//", i)) {          // parse "//host:port"
-            i += 2;                             // skip past "//"
-            int slash = url.indexOf('/', i);
-            if (slash < 0) {
-                slash = url.length();
-            }
-            if (url.startsWith("[", i)) {               // at IPv6 literal
-                int brac = url.indexOf(']', i + 1);
-                if (brac < 0 || brac > slash) {
-                    throw new IllegalArgumentException(
-                        "rmiURLContext: name is an Invalid URL: " + url);
-                }
-                host = url.substring(i, brac + 1);      // include brackets
-                i = brac + 1;                           // skip past "[...]"
-            } else {                                    // at host name or IPv4
-                int colon = url.indexOf(':', i);
-                int hostEnd = (colon < 0 || colon > slash)
-                    ? slash
-                    : colon;
-                if (i < hostEnd) {
-                    host = url.substring(i, hostEnd);
-                }
-                i = hostEnd;                            // skip past host
-            }
-            if ((i + 1 < slash)) {
-                if ( url.startsWith(":", i)) {       // parse port
-                    i++;                             // skip past ":"
-                    port = Integer.parseInt(url.substring(i, slash));
-                } else {
-                    throw new IllegalArgumentException(
-                        "rmiURLContext: name is an Invalid URL: " + url);
-                }
-            }
-            i = slash;
-        }
-        if ("".equals(host)) {
-            host = null;
-        }
-        if (url.startsWith("/", i)) {           // skip "/" before object name
-            i++;
-        }
-        if (i < url.length()) {
-            objName = url.substring(i);
-        }
+        Parser parser = new Parser(url);
+        parser.parse();
+        String host = parser.host;
+        int port = parser.port;
+        String objName = parser.objName;
 
         // Represent object name as empty or single-component composite name.
         CompositeName remaining = new CompositeName();