/* * 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 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 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 withNoHandler() { return Stream.concat(uris(), nonOverridableUris()) .map(TestInput::withNoHandler); } static Stream 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 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); } } }