aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuri Nesterenko <yan@openjdk.org>2022-03-14 13:39:13 +0300
committerChristoph Langer <clanger@openjdk.org>2022-04-08 08:53:12 +0200
commit666f21fe139873e877a3b2e05e46437b96e552e7 (patch)
treea662e6b58eba3817eda1c7e4c2b83675e0bfbf97
parent0ae04bd00f53e0ccf4e6459d5933664ba99b32fc (diff)
downloadjdk11-666f21fe139873e877a3b2e05e46437b96e552e7.tar.gz
8278972: Improve URL supports
Reviewed-by: andrew Backport-of: 94f1fbfd6d23f18ade3cc1b36f6dd368963c9d02
-rw-r--r--src/java.naming/share/classes/com/sun/jndi/ldap/LdapURL.java67
-rw-r--r--src/java.naming/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java19
-rw-r--r--src/java.naming/share/classes/com/sun/jndi/toolkit/url/Uri.java283
-rw-r--r--src/jdk.naming.dns/share/classes/com/sun/jndi/dns/DnsUrl.java71
-rw-r--r--src/jdk.naming.rmi/share/classes/com/sun/jndi/url/rmi/rmiURLContext.java293
5 files changed, 660 insertions, 73 deletions
diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapURL.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapURL.java
index 5abbc9de0e..769bc3f039 100644
--- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapURL.java
+++ b/src/java.naming/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
@@ -28,6 +28,10 @@ package com.sun.jndi.ldap;
import javax.naming.*;
import java.net.MalformedURLException;
import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Locale;
import java.util.StringTokenizer;
import com.sun.jndi.toolkit.url.Uri;
import com.sun.jndi.toolkit.url.UrlUtil;
@@ -64,6 +68,25 @@ 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<String> action = () ->
+ System.getProperty(PARSE_MODE_PROP, DEFAULT_PARSE_MODE.toString());
+ ParseMode parseMode = DEFAULT_PARSE_MODE;
+ try {
+ @SuppressWarnings("removal")
+ 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 @@ 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
@@ -99,6 +122,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.
*/
@@ -151,13 +189,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();
+ }
+
/**
* Determines whether an LDAP URL has query components.
*/
@@ -181,7 +239,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/src/java.naming/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java b/src/java.naming/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java
index 3fbd5eb978..315d216b29 100644
--- a/src/java.naming/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java
+++ b/src/java.naming/share/classes/com/sun/jndi/toolkit/url/GenericURLContext.java
@@ -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 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<String, Object> myEnv = null;
@SuppressWarnings("unchecked") // Expect Hashtable<String, Object>
@@ -161,8 +164,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/src/java.naming/share/classes/com/sun/jndi/toolkit/url/Uri.java b/src/java.naming/share/classes/com/sun/jndi/toolkit/url/Uri.java
index aaeb0eab6c..46b3fa14d1 100644
--- a/src/java.naming/share/classes/com/sun/jndi/toolkit/url/Uri.java
+++ b/src/java.naming/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;
*
* <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:
* <blockquote><pre>{@code
@@ -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;
/**
@@ -129,13 +156,22 @@ public class 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
* methods.
*/
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 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);
+ var auth = u.getRawAuthority();
+ hasAuthority = auth != null;
+ if (hasAuthority) {
+ var host = u.getHost();
+ var 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.
+ var 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) {
+ var 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) {
+ var 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 parse(String uri) throws MalformedURLException {
+ private void parseLegacy(String uri) throws MalformedURLException {
int i; // index into URI
i = uri.indexOf(':'); // parse scheme
diff --git a/src/jdk.naming.dns/share/classes/com/sun/jndi/dns/DnsUrl.java b/src/jdk.naming.dns/share/classes/com/sun/jndi/dns/DnsUrl.java
index ef17259695..80ef7b61f7 100644
--- a/src/jdk.naming.dns/share/classes/com/sun/jndi/dns/DnsUrl.java
+++ b/src/jdk.naming.dns/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,11 @@ 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 java.util.Locale;
import java.util.StringTokenizer;
import com.sun.jndi.toolkit.url.Uri;
@@ -56,6 +60,24 @@ 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<String> action = () ->
+ System.getProperty(PARSE_MODE_PROP, DEFAULT_PARSE_MODE.toString());
+ ParseMode parseMode = DEFAULT_PARSE_MODE;
+ try {
+ @SuppressWarnings("removal")
+ 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/src/jdk.naming.rmi/share/classes/com/sun/jndi/url/rmi/rmiURLContext.java b/src/jdk.naming.rmi/share/classes/com/sun/jndi/url/rmi/rmiURLContext.java
index 4c6c78110f..4bd7d6587d 100644
--- a/src/jdk.naming.rmi/share/classes/com/sun/jndi/url/rmi/rmiURLContext.java
+++ b/src/jdk.naming.rmi/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,17 @@
package com.sun.jndi.url.rmi;
+import java.net.URI;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
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,75 +52,263 @@ import com.sun.jndi.rmi.registry.RegistryContext;
*/
public class rmiURLContext extends GenericURLContext {
- public rmiURLContext(Hashtable<?,?> env) {
- super(env);
- }
+ private static final String PARSE_MODE_PROP = "com.sun.jndi.rmiURLParsing";
+ private static final ParseMode DEFAULT_PARSE_MODE = ParseMode.COMPAT;
- /**
- * Resolves the registry portion of "url" to the corresponding
- * RMI registry, and returns the atomic object name as the
- * remaining name.
- */
- protected ResolveResult getRootURLContext(String url, Hashtable<?,?> env)
- throws NamingException
- {
- if (!url.startsWith("rmi:")) {
- throw (new IllegalArgumentException(
- "rmiURLContext: name is not an RMI URL: " + url));
+ public static final ParseMode PARSE_MODE;
+ static {
+ PrivilegedAction<String> action = () ->
+ System.getProperty(PARSE_MODE_PROP, DEFAULT_PARSE_MODE.toString());
+ ParseMode parseMode = DEFAULT_PARSE_MODE;
+ try {
+ @SuppressWarnings("removal")
+ String mode = AccessController.doPrivileged(action);
+ parseMode = ParseMode.valueOf(mode.toUpperCase(Locale.ROOT));
+ } catch (Throwable t) {
+ parseMode = DEFAULT_PARSE_MODE;
+ } finally {
+ PARSE_MODE = parseMode;
}
+ }
- // Parse the URL.
+ 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;
+ }
- int i = 4; // index into url, following the "rmi:"
+ 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;}
- if (url.startsWith("//", i)) { // parse "//host:port"
- i += 2; // skip past "//"
- int slash = url.indexOf('/', i);
- if (slash < 0) {
- slash = url.length();
+ 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;
}
- 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);
+
+ }
+
+ 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);
}
- 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);
+ }
+
+ 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));
}
- 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));
+
+ 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 {
- throw new IllegalArgumentException(
- "rmiURLContext: name is an Invalid URL: " + url);
+ 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);
}
- i = slash;
+
}
- if ("".equals(host)) {
- host = null;
+
+ // 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);
+ }
}
- if (url.startsWith("/", i)) { // skip "/" before object name
- i++;
+
+ NamingException newNamingException(Throwable cause) {
+ NamingException ne = new NamingException(cause.getMessage());
+ ne.initCause(cause);
+ return ne;
}
- if (i < url.length()) {
- objName = url.substring(i);
+
+ boolean acceptsFragment() {
+ return true;
}
+ }
+
+ /**
+ * Resolves the registry portion of "url" to the corresponding
+ * RMI registry, and returns the atomic object name as the
+ * remaining name.
+ */
+ protected ResolveResult getRootURLContext(String url, Hashtable<?,?> env)
+ throws NamingException
+ {
+ 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();