e803855708
Reviewed-by: dfuchs, cstein
378 lines
15 KiB
Java
378 lines
15 KiB
Java
/*
|
|
* Copyright (c) 2022, 2023, 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
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/* @test
|
|
* @bug 8294241
|
|
* @library /test/lib
|
|
* @modules java.base/java.net:+open
|
|
* @summary Test URL::fromURI(URI, URLStreamHandler)
|
|
* @run junit/othervm URLFromURITest
|
|
*/
|
|
|
|
import java.io.IOException;
|
|
import java.lang.invoke.MethodHandles;
|
|
import java.lang.invoke.MethodHandles.Lookup;
|
|
import java.lang.invoke.VarHandle;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URI;
|
|
import java.net.URL;
|
|
import java.net.URLConnection;
|
|
import java.net.URLStreamHandler;
|
|
import java.util.Locale;
|
|
import java.util.Random;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.stream.Stream;
|
|
import jdk.test.lib.RandomFactory;
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.params.ParameterizedTest;
|
|
import org.junit.jupiter.params.provider.MethodSource;
|
|
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
public class URLFromURITest {
|
|
|
|
static final Random RAND = RandomFactory.getRandom();
|
|
|
|
record TestInput(String uri, URLStreamHandler handler) {
|
|
static TestInput withNoHandler(String uri) {
|
|
return new TestInput(uri, null);
|
|
}
|
|
TestInput withCustomHandler() {
|
|
return new TestInput(uri(), new CustomStreamHandler());
|
|
}
|
|
TestInput withUrlPrefix() {return inputWithUrlPrefix(this);}
|
|
}
|
|
|
|
static URI uriWithUrlPrefix(URI uri) {
|
|
return URI.create(stringWithUrlPrefix(uri.toString()));
|
|
}
|
|
|
|
static String stringWithUrlPrefix(String uriStr) {
|
|
if (uriStr.regionMatches(true, 0, "url:", 0, 4)) return uriStr;
|
|
return RAND.nextBoolean() ? "url:" + uriStr : "Url:" + uriStr;
|
|
}
|
|
|
|
static TestInput inputWithUrlPrefix(TestInput input) {
|
|
String uriStr = input.uri();
|
|
var handler = input.handler();
|
|
|
|
var urlUriStr = stringWithUrlPrefix(uriStr);
|
|
if (uriStr.equals(urlUriStr)) return null;
|
|
|
|
var urlURI = URI.create(urlUriStr);
|
|
try {
|
|
new URL(null, urlURI.toString(), handler);
|
|
} catch (Throwable t) {
|
|
System.err.println("skipping new URL(null, \"" + urlURI + "\", handler): " + t);
|
|
return null;
|
|
}
|
|
return new TestInput(urlUriStr, handler);
|
|
}
|
|
|
|
static Stream<String> uris() {
|
|
var uris = Stream.of(
|
|
"http://jag:cafebabe@java.sun.com:94/b/c/d?q#g",
|
|
"http://[1080:0:0:0:8:800:200C:417A]/index.html",
|
|
"http://a/b/c/d;p?q",
|
|
"mailto:mduerst@ifi.unizh.ch",
|
|
"http:comp.infosystems.www.servers.unix",
|
|
"http://j%41g:cafeb%41be@java.sun.com:94/%41/b/c/d?q#g",
|
|
"jar:file:///x.jar!/",
|
|
"jmod:/java.base",
|
|
"jmod:///java.base");
|
|
|
|
if (hasFtp()) {
|
|
uris = Stream.concat(uris,
|
|
Stream.of("ftp://ftp.is.co.za/rfc/rfc1808.txt"));
|
|
}
|
|
|
|
return uris;
|
|
}
|
|
|
|
static Stream<String> nonOverridableUris() {
|
|
return Stream.of("file:///nohost/%41/",
|
|
"file://with.host/%41/",
|
|
"file:/x/y/z",
|
|
"jrt:/java.base/java/lang/Integer.class",
|
|
"jrt:///java.base/java/lang/Integer.class");
|
|
}
|
|
static Stream<TestInput> withNoHandler() {
|
|
return Stream.concat(uris(), nonOverridableUris())
|
|
.map(TestInput::withNoHandler);
|
|
}
|
|
|
|
static Stream<TestInput> withCustomHandler() {
|
|
var withHandlers = uris()
|
|
.map(TestInput::withNoHandler)
|
|
.map(TestInput::withCustomHandler);
|
|
return Stream.concat(withHandlers, Stream.of(
|
|
new TestInput("foo:bar:baz", new CustomStreamHandler()),
|
|
new TestInput("jar:file:///x.jar!/", new CustomStreamHandler()),
|
|
new TestInput("jar:jar:file///x.jar!/bing", new CustomStreamHandler()),
|
|
new TestInput("blah://localhost:80/x/y/z", new CustomStreamHandler())
|
|
));
|
|
}
|
|
|
|
static Stream<TestInput> overridingNonOverridable() {
|
|
return nonOverridableUris().map(TestInput::withNoHandler)
|
|
.map(TestInput::withCustomHandler);
|
|
}
|
|
|
|
@Test
|
|
public void checkExceptions() {
|
|
var noscheme = URI.create("http");
|
|
var unknown = URI.create("unknown:///foo/bar");
|
|
var opaque = URI.create("opaque:opaque-path");
|
|
var jrt = URI.create("jrt:/java.base/java.lang.Integer.class");
|
|
var file = URI.create("file:/");
|
|
var unoscheme = uriWithUrlPrefix(noscheme);
|
|
var uunknown = uriWithUrlPrefix(unknown);
|
|
var uopaque = uriWithUrlPrefix(opaque);
|
|
var ujrt = uriWithUrlPrefix(jrt);
|
|
var ufile = uriWithUrlPrefix(file);
|
|
var handler = new CustomStreamHandler();
|
|
assertThrows(NullPointerException.class, () -> URL.of(null, null));
|
|
assertThrows(NullPointerException.class, () -> URL.of(null, handler));
|
|
assertThrows(IllegalArgumentException.class, () -> URL.of(noscheme, null));
|
|
assertThrows(IllegalArgumentException.class, () -> URL.of(noscheme, handler));
|
|
assertThrows(IllegalArgumentException.class, () -> URL.of(jrt, handler));
|
|
assertThrows(IllegalArgumentException.class, () -> URL.of(file, handler));
|
|
assertThrows(IllegalArgumentException.class, () -> URL.of(ujrt, handler));
|
|
assertThrows(IllegalArgumentException.class, () -> URL.of(ufile, handler));
|
|
assertThrows(MalformedURLException.class, () -> URL.of(unknown, null));
|
|
assertThrows(MalformedURLException.class, () -> URL.of(opaque, null));
|
|
assertThrows(MalformedURLException.class, () -> URL.of(uunknown, null));
|
|
assertThrows(MalformedURLException.class, () -> URL.of(uopaque, null));
|
|
assertThrows(MalformedURLException.class, () -> URL.of(unoscheme, null));
|
|
assertThrows(MalformedURLException.class, () -> URL.of(unoscheme, handler));
|
|
}
|
|
|
|
@ParameterizedTest
|
|
@MethodSource(value = "withNoHandler")
|
|
public void testWithNoHandler(TestInput input) throws Exception {
|
|
String uriStr = input.uri();
|
|
URLStreamHandler handler = input.handler();
|
|
System.err.println("testWithNoHandler: " + uriStr);
|
|
assertNull(handler, input + ": input handler");
|
|
URI uri = new URI(uriStr);
|
|
URL url = URL.of(uri, handler);
|
|
checkNoHandler(input, uri, url);
|
|
var urlInput = input.withUrlPrefix();
|
|
if (urlInput != null) {
|
|
try {
|
|
var urlURI = URI.create(input.uri());
|
|
checkNoHandler(urlInput, uri, URL.of(urlURI, null));
|
|
} catch (Throwable x) {
|
|
throw new AssertionError("Failed: " + urlInput.uri() + " with: " + x, x);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void checkNoHandler(TestInput input, URI uri, URL url) throws Exception {
|
|
System.err.println("Testing: " + uri);
|
|
checkURL(input, uri, url);
|
|
URLStreamHandler urlHandler = URLAccess.getHandler(url);
|
|
assertNotNull(urlHandler, input + ": URL.handler");
|
|
assertNull(urlHandler.getClass().getClassLoader(),
|
|
input + ": URL.handler class loader");
|
|
}
|
|
|
|
@ParameterizedTest
|
|
@MethodSource(value = "withCustomHandler")
|
|
public void checkCustomHandler(TestInput input) throws Exception {
|
|
String uriStr = input.uri();
|
|
URLStreamHandler handler = input.handler();
|
|
System.err.println("testWithCustomHandler: " + input);
|
|
assertNotNull(handler, input + ": input handler");
|
|
URI uri = new URI(uriStr);
|
|
URL url = URL.of(uri, handler);
|
|
checkCustomHandler(input, uri, url, handler);
|
|
var urlInput = input.withUrlPrefix();
|
|
if (urlInput != null) {
|
|
urlInput = urlInput.withCustomHandler();
|
|
handler = urlInput.handler();
|
|
try {
|
|
var urlURI = URI.create(urlInput.uri());
|
|
checkCustomHandler(urlInput, uri, URL.of(urlURI, handler), handler);
|
|
} catch (Throwable x) {
|
|
throw new AssertionError("Failed with handler: " + urlInput.uri() + " with: " + x, x);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void checkCustomHandler(TestInput input, URI uri, URL url,
|
|
URLStreamHandler handler) throws Exception {
|
|
System.err.println("Testing: " + uri);
|
|
checkURL(input, uri, url);
|
|
URLStreamHandler urlHandler = URLAccess.getHandler(url);
|
|
assertSame(handler, urlHandler, input + ": URL.handler");
|
|
URLConnection c = url.openConnection();
|
|
assertNotNull(c, input + ": opened connection");
|
|
assertEquals(CustomURLConnection.class, c.getClass(),
|
|
input + ": connection class");
|
|
assertEquals(CustomStreamHandler.class, urlHandler.getClass(),
|
|
input + ": handler class");
|
|
assertEquals(((CustomURLConnection)c).handler, handler, input + ": handler");
|
|
assertEquals(c.getURL(), url, input + ": connection url");
|
|
var customHandler = (CustomStreamHandler)urlHandler;
|
|
assertEquals(customHandler.parseURLCalled(), 1, "parseURL calls");
|
|
}
|
|
|
|
@ParameterizedTest
|
|
@MethodSource(value = "overridingNonOverridable")
|
|
public void testOverridingNonOverridable(TestInput input) throws Exception {
|
|
String uriStr = input.uri();
|
|
URLStreamHandler handler = input.handler();
|
|
System.err.println("testOverridingNonOverridable: " + input);
|
|
assertNotNull(handler, input + ": input handler");
|
|
URI uri = new URI(uriStr);
|
|
try {
|
|
URL url = URL.of(uri, handler);
|
|
throw new AssertionError("Should not be able to specify handler for: " + uriStr);
|
|
} catch (IllegalArgumentException x) {
|
|
System.err.println("Got expected exception: " + x);
|
|
}
|
|
}
|
|
|
|
private static boolean isFileBased(URI uri) {
|
|
String scheme = uri.getScheme();
|
|
boolean isJrt = "jrt".equals(scheme.toLowerCase(Locale.ROOT));
|
|
boolean isJmod = "jmod".equals(scheme.toLowerCase(Locale.ROOT));
|
|
boolean isFile = "file".equals(scheme.toLowerCase(Locale.ROOT));
|
|
return isJmod || isJrt || isFile;
|
|
}
|
|
|
|
private static void checkURL(TestInput input, URI uri, URL url) throws MalformedURLException {
|
|
String scheme = uri.getScheme();
|
|
assertEquals(scheme, url.getProtocol(), input + ": scheme");
|
|
|
|
if (uri.isOpaque()) {
|
|
String ssp = uri.getSchemeSpecificPart();
|
|
assertEquals(ssp, url.getPath(), input + ": ssp");
|
|
} else {
|
|
String authority = uri.getRawAuthority();
|
|
boolean isHierarchical = uri.toString().startsWith(scheme + "://");
|
|
boolean isFileBased = isFileBased(uri);
|
|
|
|
// Network based URLs usually follow URI, but file based
|
|
// protocol handlers have a few discrepancies in how they
|
|
// treat an absent authority:
|
|
// - URI authority is null if there is no authority, always
|
|
// - URL authority is null or empty depending on the protocol
|
|
// and on whether the URL is hierarchical or not.
|
|
if (isFileBased && authority == null) {
|
|
// jrt: takes a fastpath - so that jrt:/ is equivalent to jrt:///
|
|
if (scheme.equals("jrt")) {
|
|
authority = "";
|
|
}
|
|
if (isHierarchical) {
|
|
authority = "";
|
|
}
|
|
}
|
|
assertEquals(authority, url.getAuthority(), input + ": authority");
|
|
|
|
// Network based URLs usually follow URI, but file based
|
|
// protocol handlers have a few discrepancies in how they
|
|
// treat an absent host:
|
|
String host = uri.getHost();
|
|
if (isFileBased && host == null) {
|
|
host = "";
|
|
}
|
|
|
|
assertEquals(host, url.getHost(), input + ": host");
|
|
if (host != null) {
|
|
String userInfo = uri.getRawUserInfo();
|
|
assertEquals(userInfo, url.getUserInfo(), input + ": userInfo");
|
|
assertEquals(uri.getPort(), url.getPort(), input + ": port");
|
|
}
|
|
|
|
String path = uri.getRawPath();
|
|
assertEquals(path, url.getPath(), input + ": path");
|
|
|
|
String query = uri.getQuery();
|
|
assertEquals(query, url.getQuery(), input + ": query");
|
|
}
|
|
String frag = uri.getRawFragment();
|
|
assertEquals(frag, url.getRef(), input + ": fragment");
|
|
}
|
|
|
|
@SuppressWarnings("deprecation")
|
|
private static boolean hasFtp() {
|
|
try {
|
|
return new java.net.URL("ftp://localhost/") != null;
|
|
} catch (java.net.MalformedURLException x) {
|
|
System.err.println("FTP not supported by this runtime.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static class CustomURLConnection extends URLConnection {
|
|
|
|
public final CustomStreamHandler handler;
|
|
CustomURLConnection(CustomStreamHandler handler, URL url) {
|
|
super(url);
|
|
this.handler = handler;
|
|
}
|
|
|
|
@Override
|
|
public void connect() throws IOException {
|
|
|
|
}
|
|
}
|
|
static class CustomStreamHandler extends URLStreamHandler {
|
|
|
|
final AtomicInteger parseURLCalled = new AtomicInteger();
|
|
|
|
@Override
|
|
protected void parseURL(URL u, String spec, int start, int limit) {
|
|
parseURLCalled.incrementAndGet();
|
|
super.parseURL(u, spec, start, limit);
|
|
}
|
|
|
|
@Override
|
|
protected URLConnection openConnection(URL u) throws IOException {
|
|
return new CustomURLConnection(this, u);
|
|
}
|
|
|
|
public int parseURLCalled() {
|
|
return parseURLCalled.get();
|
|
}
|
|
}
|
|
|
|
static final class URLAccess {
|
|
static final VarHandle HANDLER;
|
|
static {
|
|
try {
|
|
Lookup lookup = MethodHandles.privateLookupIn(URL.class, MethodHandles.lookup());
|
|
HANDLER = lookup.findVarHandle(URL.class, "handler", URLStreamHandler.class);
|
|
} catch (Exception x) {
|
|
throw new ExceptionInInitializerError(x);
|
|
}
|
|
}
|
|
static URLStreamHandler getHandler(URL url) {
|
|
return (URLStreamHandler)HANDLER.get(url);
|
|
}
|
|
}
|
|
}
|