From 22d0dbfb611c8516285b25e6ba59a3523f7cd09f Mon Sep 17 00:00:00 2001 From: Yuri Nesterenko Date: Tue, 15 Mar 2022 11:15:24 +0300 Subject: [PATCH] 8278972: Improve URL supports Reviewed-by: bae Backport-of: 94f1fbfd6d23f18ade3cc1b36f6dd368963c9d02 --- .../classes/com/sun/jndi/dns/DnsUrl.java | 71 +++- .../classes/com/sun/jndi/ldap/LdapURL.java | 67 +++- .../jndi/toolkit/url/GenericURLContext.java | 19 +- .../classes/com/sun/jndi/toolkit/url/Uri.java | 283 +++++++++++++++- .../com/sun/jndi/url/rmi/rmiURLContext.java | 309 ++++++++++++++---- 5 files changed, 668 insertions(+), 81 deletions(-) diff --git a/jdk/src/share/classes/com/sun/jndi/dns/DnsUrl.java b/jdk/src/share/classes/com/sun/jndi/dns/DnsUrl.java index ef17259695..fbea3b4b17 100644 --- a/jdk/src/share/classes/com/sun/jndi/dns/DnsUrl.java +++ b/jdk/src/share/classes/com/sun/jndi/dns/DnsUrl.java @@ -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 @@ package com.sun.jndi.dns; 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 @@ import com.sun.jndi.toolkit.url.UrlUtil; 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 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 @@ public class DnsUrl extends Uri { 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("/") diff --git a/jdk/src/share/classes/com/sun/jndi/ldap/LdapURL.java b/jdk/src/share/classes/com/sun/jndi/ldap/LdapURL.java index ada02f6f3b..f0cdfe3f86 100644 --- a/jdk/src/share/classes/com/sun/jndi/ldap/LdapURL.java +++ b/jdk/src/share/classes/com/sun/jndi/ldap/LdapURL.java @@ -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 @@ -31,6 +31,11 @@ import javax.naming.spi.*; import java.net.URL; 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; @@ -67,6 +72,24 @@ import com.sun.jndi.toolkit.url.UrlUtil; 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 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; @@ -86,7 +109,7 @@ final public class LdapURL extends Uri { 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 @@ -102,6 +125,21 @@ final public class LdapURL extends Uri { } } + @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. */ @@ -154,13 +192,33 @@ final public class LdapURL extends Uri { 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. */ @@ -184,7 +242,8 @@ final public class LdapURL extends Uri { 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"); diff --git a/jdk/src/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java b/jdk/src/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java index 3d8757b760..6cfc50688d 100644 --- a/jdk/src/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java +++ b/jdk/src/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java @@ -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 @@ -32,6 +32,8 @@ import javax.naming.spi.NamingManager; 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 @@ import java.net.MalformedURLException; * @author Rosanna Lee */ abstract public class GenericURLContext implements Context { + protected Hashtable myEnv = null; @SuppressWarnings("unchecked") // Expect Hashtable @@ -158,8 +161,18 @@ abstract public class GenericURLContext implements Context { 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 { diff --git a/jdk/src/share/classes/com/sun/jndi/toolkit/url/Uri.java b/jdk/src/share/classes/com/sun/jndi/toolkit/url/Uri.java index da5771ae31..64da901529 100644 --- a/jdk/src/share/classes/com/sun/jndi/toolkit/url/Uri.java +++ b/jdk/src/share/classes/com/sun/jndi/toolkit/url/Uri.java @@ -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 @@ package com.sun.jndi.toolkit.url; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; /** @@ -36,15 +38,17 @@ import java.net.MalformedURLException; * *

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. * - *

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. + *

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. * *

The format of an absolute URI (see the RFCs mentioned above) is: *

@@ -105,6 +109,28 @@ import java.net.MalformedURLException;
 
 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 @@ public class Uri {
     protected boolean hasAuthority;
     protected String path;
     protected String query = null;
+    protected String fragment;
 
 
     /**
@@ -128,6 +155,15 @@ public class Uri {
     protected Uri() {
     }
 
+    /**
+     * 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
@@ -135,7 +171,7 @@ public class Uri {
      */
     protected void init(String uri) throws MalformedURLException {
         this.uri = uri;
-        parse(uri);
+        parse(uri, parseMode());
     }
 
     /**
@@ -188,10 +224,235 @@ public class Uri {
         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 parse(String uri) throws MalformedURLException {
+    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 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 ":"} 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
diff --git a/jdk/src/share/classes/com/sun/jndi/url/rmi/rmiURLContext.java b/jdk/src/share/classes/com/sun/jndi/url/rmi/rmiURLContext.java
index 4c6c78110f..9b17555391 100644
--- a/jdk/src/share/classes/com/sun/jndi/url/rmi/rmiURLContext.java
+++ b/jdk/src/share/classes/com/sun/jndi/url/rmi/rmiURLContext.java
@@ -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 @@ import com.sun.jndi.rmi.registry.RegistryContext;
  */
 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 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 @@ public class rmiURLContext extends GenericURLContext {
     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();