8293590: Some syntax checks performed by URL.openConnection() could be performed earlier, at URL construction
Reviewed-by: jpai, michaelm
This commit is contained in:
parent
78fed9d307
commit
bd41428a56
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1995, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1995, 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
|
||||
@ -504,10 +504,23 @@ public abstract class URLStreamHandler {
|
||||
String query, String ref) {
|
||||
if (this != u.handler) {
|
||||
throw new SecurityException("handler for url different from " +
|
||||
"this handler");
|
||||
} else if (host != null && u.isBuiltinStreamHandler(this)) {
|
||||
String s = IPAddressUtil.checkHostString(host);
|
||||
if (s != null) throw new IllegalArgumentException(s);
|
||||
"this handler");
|
||||
}
|
||||
// if early parsing, perform additional checks here rather than waiting
|
||||
// for openConnection()
|
||||
boolean earlyURLParsing = IPAddressUtil.earlyURLParsing();
|
||||
boolean isBuiltInHandler = u.isBuiltinStreamHandler(this);
|
||||
if (host != null && isBuiltInHandler) {
|
||||
String errMsg = IPAddressUtil.checkHostString(host);
|
||||
if (errMsg != null) throw new IllegalArgumentException(errMsg);
|
||||
}
|
||||
if (userInfo != null && isBuiltInHandler && earlyURLParsing) {
|
||||
String errMsg = IPAddressUtil.checkUserInfo(userInfo);
|
||||
if (errMsg != null) throw new IllegalArgumentException(errMsg);
|
||||
}
|
||||
if (authority != null && isBuiltInHandler && earlyURLParsing) {
|
||||
String errMsg = IPAddressUtil.checkAuth(authority);
|
||||
if (errMsg != null) throw new IllegalArgumentException(errMsg);
|
||||
}
|
||||
// ensure that no one can reset the protocol on a given URL.
|
||||
u.set(u.getProtocol(), host, port, authority, userInfo, path, query, ref);
|
||||
|
@ -427,6 +427,16 @@ public class IPAddressUtil {
|
||||
// All of the above
|
||||
private static final long L_EXCLUDE = 0x84008008ffffffffL;
|
||||
private static final long H_EXCLUDE = 0x8000000038000001L;
|
||||
// excluded delims: "<>\" " - we don't include % and # here
|
||||
private static final long L_EXCLUDED_DELIMS = 0x5000000500000000L;
|
||||
private static final long H_EXCLUDED_DELIMS = 0x0L;
|
||||
// unwise "{}|\\^[]`";
|
||||
private static final long L_UNWISE = 0x0L;
|
||||
private static final long H_UNWISE = 0x3800000178000000L;
|
||||
private static final long L_FRAGMENT = 0x0000000800000000L;
|
||||
private static final long H_FRAGMENT = 0x0L;
|
||||
private static final long L_QUERY = 0x8000000000000000L;
|
||||
private static final long H_QUERY = 0x0L;
|
||||
|
||||
private static final char[] OTHERS = {
|
||||
8263,8264,8265,8448,8449,8453,8454,10868,
|
||||
@ -479,10 +489,14 @@ public class IPAddressUtil {
|
||||
return "'" + c + "'";
|
||||
}
|
||||
|
||||
private static String checkUserInfo(String str) {
|
||||
// Check user-info component.
|
||||
// This method returns an error message if a problem
|
||||
// is found. The caller is expected to use that message to
|
||||
// throw an exception.
|
||||
public static String checkUserInfo(String str) {
|
||||
// colon is permitted in user info
|
||||
int index = scan(str, L_EXCLUDE & ~L_COLON,
|
||||
H_EXCLUDE & ~H_COLON);
|
||||
int index = scan(str, MASKS.L_USERINFO_MASK,
|
||||
MASKS.H_USERINFO_MASK);
|
||||
if (index >= 0) {
|
||||
return "Illegal character found in user-info: "
|
||||
+ describeChar(str.charAt(index));
|
||||
@ -498,8 +512,7 @@ public class IPAddressUtil {
|
||||
index = str.indexOf('%');
|
||||
if (index >= 0) {
|
||||
index = scan(str = str.substring(index),
|
||||
L_NON_PRINTABLE | L_IPV6_DELIMS,
|
||||
H_NON_PRINTABLE | H_IPV6_DELIMS);
|
||||
MASKS.L_SCOPE_MASK, MASKS.H_SCOPE_MASK);
|
||||
if (index >= 0) {
|
||||
return "Illegal character found in IPv6 scoped address: "
|
||||
+ describeChar(str.charAt(index));
|
||||
@ -509,7 +522,8 @@ public class IPAddressUtil {
|
||||
}
|
||||
return "Unrecognized IPv6 address format";
|
||||
} else {
|
||||
index = scan(str, L_EXCLUDE, H_EXCLUDE);
|
||||
index = scan(str, L_EXCLUDE | MASKS.L_HOSTNAME_MASK,
|
||||
H_EXCLUDE | MASKS.H_HOSTNAME_MASK, OTHERS);
|
||||
if (index >= 0) {
|
||||
return "Illegal character found in host: "
|
||||
+ describeChar(str.charAt(index));
|
||||
@ -518,7 +532,14 @@ public class IPAddressUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String checkAuth(String str) {
|
||||
// Simple checks for the authority component.
|
||||
// Deeper checks on the various parts of a server-based
|
||||
// authority component may be performed by calling
|
||||
// #checkAuthority(URL url)
|
||||
// This method returns an error message if a problem
|
||||
// is found. The caller is expected to use that message to
|
||||
// throw an exception.
|
||||
public static String checkAuth(String str) {
|
||||
int index = scan(str,
|
||||
L_EXCLUDE & ~L_AUTH_DELIMS,
|
||||
H_EXCLUDE & ~H_AUTH_DELIMS);
|
||||
@ -529,8 +550,11 @@ public class IPAddressUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
// check authority of hierarchical URL. Appropriate for
|
||||
// HTTP-like protocol handlers
|
||||
// check authority of hierarchical (server based) URL.
|
||||
// Appropriate for HTTP-like protocol handlers
|
||||
// This method returns an error message if a problem
|
||||
// is found. The caller is expected to use that message to
|
||||
// throw an exception.
|
||||
public static String checkAuthority(URL url) {
|
||||
String s, u, h;
|
||||
if (url == null) return null;
|
||||
@ -546,33 +570,53 @@ public class IPAddressUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
// minimal syntax checks - deeper check may be performed
|
||||
// by the appropriate protocol handler
|
||||
// minimal syntax checks if delayed parsing is
|
||||
// enabled - deeper check will be performed
|
||||
// later by the appropriate protocol handler
|
||||
// This method returns an error message if a problem
|
||||
// is found. The caller is expected to use that message to
|
||||
// throw an exception.
|
||||
public static String checkExternalForm(URL url) {
|
||||
String s;
|
||||
if (url == null) return null;
|
||||
int index = scan(s = url.getUserInfo(),
|
||||
L_NON_PRINTABLE | L_SLASH,
|
||||
H_NON_PRINTABLE | H_SLASH);
|
||||
if (index >= 0) {
|
||||
return "Illegal character found in authority: "
|
||||
+ describeChar(s.charAt(index));
|
||||
boolean earlyURLParsing = earlyURLParsing();
|
||||
String userInfo = url.getUserInfo();
|
||||
if (earlyURLParsing) {
|
||||
if ((s = checkUserInfo(userInfo)) != null) return s;
|
||||
} else {
|
||||
int index = scan(s = userInfo,
|
||||
L_NON_PRINTABLE | L_SLASH,
|
||||
H_NON_PRINTABLE | H_SLASH);
|
||||
if (index >= 0) {
|
||||
return "Illegal character found in authority: "
|
||||
+ describeChar(s.charAt(index));
|
||||
}
|
||||
}
|
||||
if ((s = checkHostString(url.getHost())) != null) {
|
||||
String host = url.getHost();
|
||||
if ((s = checkHostString(host)) != null) {
|
||||
return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check host component.
|
||||
// This method returns an error message if a problem
|
||||
// is found. The caller is expected to use that message to
|
||||
// throw an exception.
|
||||
public static String checkHostString(String host) {
|
||||
if (host == null) return null;
|
||||
int index = scan(host,
|
||||
L_NON_PRINTABLE | L_SLASH,
|
||||
H_NON_PRINTABLE | H_SLASH,
|
||||
OTHERS);
|
||||
if (index >= 0) {
|
||||
return "Illegal character found in host: "
|
||||
+ describeChar(host.charAt(index));
|
||||
if (earlyURLParsing()) {
|
||||
// also validate IPv6 literal format if present
|
||||
return checkHost(host);
|
||||
} else {
|
||||
int index = scan(host,
|
||||
MASKS.L_HOSTNAME_MASK,
|
||||
MASKS.H_HOSTNAME_MASK,
|
||||
OTHERS);
|
||||
if (index >= 0) {
|
||||
return "Illegal character found in host: "
|
||||
+ describeChar(host.charAt(index));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -803,6 +847,14 @@ public class IPAddressUtil {
|
||||
return parseAsciiDigit(c, DECIMAL);
|
||||
}
|
||||
|
||||
public static boolean earlyURLParsing() {
|
||||
return !MASKS.DELAY_URL_PARSING_SP_VALUE;
|
||||
}
|
||||
|
||||
public static boolean delayURLParsing() {
|
||||
return MASKS.DELAY_URL_PARSING_SP_VALUE;
|
||||
}
|
||||
|
||||
// Supported radixes
|
||||
private static final int HEXADECIMAL = 16;
|
||||
private static final int DECIMAL = 10;
|
||||
@ -817,4 +869,33 @@ public class IPAddressUtil {
|
||||
private static final String ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP = "jdk.net.allowAmbiguousIPAddressLiterals";
|
||||
private static final boolean ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP_VALUE = Boolean.valueOf(
|
||||
GetPropertyAction.privilegedGetProperty(ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP, "false"));
|
||||
private static class MASKS {
|
||||
private static final String DELAY_URL_PARSING_SP = "jdk.net.url.delayParsing";
|
||||
private static final boolean DELAY_URL_PARSING_SP_VALUE;
|
||||
static final long L_USERINFO_MASK = L_EXCLUDE & ~L_COLON;
|
||||
static final long H_USERINFO_MASK = H_EXCLUDE & ~H_COLON;
|
||||
static final long L_HOSTNAME_MASK;
|
||||
static final long H_HOSTNAME_MASK;
|
||||
static final long L_SCOPE_MASK;
|
||||
static final long H_SCOPE_MASK;
|
||||
static {
|
||||
var value = GetPropertyAction.privilegedGetProperty(
|
||||
DELAY_URL_PARSING_SP, "false");
|
||||
DELAY_URL_PARSING_SP_VALUE = value.isEmpty()
|
||||
|| Boolean.parseBoolean(value);
|
||||
if (DELAY_URL_PARSING_SP_VALUE) {
|
||||
L_HOSTNAME_MASK = L_NON_PRINTABLE | L_SLASH;
|
||||
H_HOSTNAME_MASK = H_NON_PRINTABLE | H_SLASH;
|
||||
L_SCOPE_MASK = L_NON_PRINTABLE | L_IPV6_DELIMS;
|
||||
H_SCOPE_MASK = H_NON_PRINTABLE | H_IPV6_DELIMS;
|
||||
} else {
|
||||
// the hostname mask can also forbid [ ] brackets, because IPv6 should be
|
||||
// checked early before the mask is used when earlier parsing checks are performed
|
||||
L_HOSTNAME_MASK = L_NON_PRINTABLE | L_SLASH | L_UNWISE | L_EXCLUDED_DELIMS;
|
||||
H_HOSTNAME_MASK = H_NON_PRINTABLE | H_SLASH | H_UNWISE | H_EXCLUDED_DELIMS;
|
||||
L_SCOPE_MASK = L_NON_PRINTABLE | L_IPV6_DELIMS | L_SLASH | L_BACKSLASH | L_FRAGMENT | L_QUERY;
|
||||
H_SCOPE_MASK = H_NON_PRINTABLE | H_IPV6_DELIMS | H_SLASH | H_BACKSLASH | H_FRAGMENT | H_QUERY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -159,17 +159,15 @@ public class FtpURLConnection extends URLConnection {
|
||||
}
|
||||
}
|
||||
|
||||
static URL checkURL(URL u) throws IllegalArgumentException {
|
||||
private static URL checkURL(URL u) throws MalformedURLException {
|
||||
if (u != null) {
|
||||
if (u.toExternalForm().indexOf('\n') > -1) {
|
||||
Exception mfue = new MalformedURLException("Illegal character in URL");
|
||||
throw new IllegalArgumentException(mfue.getMessage(), mfue);
|
||||
throw new MalformedURLException("Illegal character in URL");
|
||||
}
|
||||
}
|
||||
String s = IPAddressUtil.checkAuthority(u);
|
||||
if (s != null) {
|
||||
Exception mfue = new MalformedURLException(s);
|
||||
throw new IllegalArgumentException(mfue.getMessage(), mfue);
|
||||
String errMsg = IPAddressUtil.checkAuthority(u);
|
||||
if (errMsg != null) {
|
||||
throw new MalformedURLException(errMsg);
|
||||
}
|
||||
return u;
|
||||
}
|
||||
@ -179,14 +177,14 @@ public class FtpURLConnection extends URLConnection {
|
||||
*
|
||||
* @param url The {@code URL} to retrieve or store.
|
||||
*/
|
||||
public FtpURLConnection(URL url) {
|
||||
public FtpURLConnection(URL url) throws MalformedURLException {
|
||||
this(url, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as FtpURLconnection(URL) with a per connection proxy specified
|
||||
*/
|
||||
FtpURLConnection(URL url, Proxy p) {
|
||||
FtpURLConnection(URL url, Proxy p) throws MalformedURLException {
|
||||
super(checkURL(url));
|
||||
instProxy = p;
|
||||
host = url.getHost();
|
||||
|
507
test/jdk/java/net/URL/EarlyOrDelayedParsing.java
Normal file
507
test/jdk/java/net/URL/EarlyOrDelayedParsing.java
Normal file
@ -0,0 +1,507 @@
|
||||
/*
|
||||
* Copyright (c) 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
|
||||
* 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 8293590
|
||||
* @summary URL built-in protocol handlers should parse the URL early
|
||||
* to avoid constructing URLs for which openConnection
|
||||
* would later throw an exception, when possible.
|
||||
* A jdk.net.url.delayParsing property allows to switch that
|
||||
* behavior off to mitigate risks of regression
|
||||
* @run junit EarlyOrDelayedParsing
|
||||
* @run junit/othervm -Djdk.net.url.delayParsing EarlyOrDelayedParsing
|
||||
* @run junit/othervm -Djdk.net.url.delayParsing=true EarlyOrDelayedParsing
|
||||
* @run junit/othervm -Djdk.net.url.delayParsing=false EarlyOrDelayedParsing
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static java.lang.System.err;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class EarlyOrDelayedParsing {
|
||||
|
||||
public final boolean EARLY_PARSING;
|
||||
{
|
||||
String value = System.getProperty("jdk.net.url.delayParsing", "false");
|
||||
EARLY_PARSING = !value.isEmpty() && !Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
// Some characters that when included at the wrong place
|
||||
// in the authority component, without being escaped, would
|
||||
// cause an exception.
|
||||
private static final String EXCLUDED_DELIMS = "<>\" ";
|
||||
private static final String UNWISE = "{}|\\^`";
|
||||
private static final String DELIMS = "[]/?#@";
|
||||
|
||||
// Test data used to test exceptions thrown by URL
|
||||
// at some point, when constructed with some illegal input.
|
||||
sealed interface URLArgTest
|
||||
permits OneArgTest, TwoArgsTest, ThreeArgsTest, FourArgsTest {
|
||||
|
||||
// Some character that is expected to cause an exception
|
||||
// at some point, and which this test case is built for
|
||||
int character();
|
||||
|
||||
// An URL string containing the illegal character
|
||||
String url();
|
||||
|
||||
// Some characters are already checked at construction
|
||||
// time. They will cause an exception to be thrown,
|
||||
// whether delayed parsing is activated or not.
|
||||
// This method returns true if an exception is
|
||||
// expected at construction time for this test case,
|
||||
// even when delayed parsing is activated.
|
||||
boolean early(int c);
|
||||
|
||||
// The URL scheme this test case is built for.
|
||||
// Typically, one of "http", "https", "ftp"...
|
||||
default String scheme() {
|
||||
return scheme(url());
|
||||
}
|
||||
|
||||
// Return the URL string of this test case, after
|
||||
// substituting its scheme with the given scheme.
|
||||
default String urlWithScheme(String scheme) {
|
||||
String url = url();
|
||||
int colon = url.indexOf(':');
|
||||
String urlWithScheme = scheme + url.substring(colon);
|
||||
return urlWithScheme;
|
||||
}
|
||||
|
||||
// Which exception to expect when parsing is delayed
|
||||
default boolean acceptDelayedException(Throwable exception) {
|
||||
return exception instanceof MalformedURLException
|
||||
|| exception instanceof UnknownHostException;
|
||||
}
|
||||
|
||||
default String describe() {
|
||||
return this.getClass().getSimpleName() + "(url=" + url() + ")";
|
||||
}
|
||||
|
||||
static int port(String protocol) {
|
||||
return switch (protocol) {
|
||||
case "http" -> 80;
|
||||
case "https" -> 443;
|
||||
case "ftp" -> 21;
|
||||
default -> -1;
|
||||
};
|
||||
}
|
||||
|
||||
static String scheme(String url) {
|
||||
return url.substring(0, url.indexOf(':'));
|
||||
}
|
||||
}
|
||||
|
||||
// Test data for the one arg constructor
|
||||
// public URL(String spec) throws MalformedURLException
|
||||
sealed interface OneArgTest extends URLArgTest {
|
||||
|
||||
// Create a new test case identical to this one but
|
||||
// with a different URL scheme
|
||||
default OneArgTest withScheme(String scheme) {
|
||||
String urlWithScheme = urlWithScheme(scheme);
|
||||
if (this instanceof OfHost) {
|
||||
return new OfHost(character(), urlWithScheme);
|
||||
}
|
||||
if (this instanceof OfUserInfo) {
|
||||
return new OfUserInfo(character(), urlWithScheme);
|
||||
}
|
||||
throw new AssertionError("unexpected subclass: " + this.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean early(int c) {
|
||||
return this instanceof OfHost &&
|
||||
(c < 31 || c == 127);
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean acceptDelayedException(Throwable exception) {
|
||||
return URLArgTest.super.acceptDelayedException(exception)
|
||||
|| "file".equalsIgnoreCase(scheme())
|
||||
&& character() == '\\'
|
||||
&& exception instanceof IOException;
|
||||
}
|
||||
|
||||
record OfHost(int character, String url) implements OneArgTest { }
|
||||
record OfUserInfo(int character, String url) implements OneArgTest { }
|
||||
|
||||
static OneArgTest ofHost(int c) {
|
||||
return new OfHost(c, "http://local%shost/".formatted(Character.toString(c)));
|
||||
}
|
||||
static OneArgTest ofUserInfo(int c) {
|
||||
return new OfUserInfo(c, "http://user%sinfo@localhost:9999/".formatted(Character.toString(c)));
|
||||
}
|
||||
}
|
||||
|
||||
// Test data for the two arg constructor
|
||||
// public URL(URL context, String spec) throws MalformedURLException
|
||||
sealed interface TwoArgsTest extends URLArgTest {
|
||||
|
||||
// Create a new test case identical to this one but
|
||||
// with a different URL scheme
|
||||
default TwoArgsTest withScheme(String scheme) {
|
||||
String urlWithScheme = urlWithScheme(scheme);
|
||||
if (this instanceof OfTwoArgsHost) {
|
||||
return new OfTwoArgsHost(character(), urlWithScheme);
|
||||
}
|
||||
if (this instanceof OfTwoArgsUserInfo) {
|
||||
return new OfTwoArgsUserInfo(character(), urlWithScheme);
|
||||
}
|
||||
throw new AssertionError("unexpected subclass: " + this.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean early(int c) {
|
||||
return this instanceof OfTwoArgsHost &&
|
||||
(c < 31 || c == 127);
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean acceptDelayedException(Throwable exception) {
|
||||
return URLArgTest.super.acceptDelayedException(exception)
|
||||
|| "file".equalsIgnoreCase(scheme())
|
||||
&& character() == '\\'
|
||||
&& exception instanceof IOException;
|
||||
}
|
||||
|
||||
record OfTwoArgsHost(int character, String url) implements TwoArgsTest { }
|
||||
record OfTwoArgsUserInfo(int character, String url) implements TwoArgsTest { }
|
||||
|
||||
static TwoArgsTest ofHost(int c) {
|
||||
return new OfTwoArgsHost(c, "http://local%shost/".formatted(Character.toString(c)));
|
||||
}
|
||||
static TwoArgsTest ofUserInfo(int c) {
|
||||
return new OfTwoArgsUserInfo(c, "http://user%sinfo@localhost:9999/".formatted(Character.toString(c)));
|
||||
}
|
||||
static TwoArgsTest ofOneArgTest(OneArgTest test) {
|
||||
if (test instanceof OneArgTest.OfHost) {
|
||||
return ofHost(test.character());
|
||||
} else if (test instanceof OneArgTest.OfUserInfo) {
|
||||
return ofUserInfo(test.character());
|
||||
}
|
||||
throw new AssertionError("can't convert to TwoArgsTest: "
|
||||
+ test.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Test data for the three args constructor
|
||||
// public URL(String scheme, String host, String file)
|
||||
// throws MalformedURLException
|
||||
sealed interface ThreeArgsTest extends URLArgTest {
|
||||
|
||||
// the host component
|
||||
String host();
|
||||
|
||||
// the path + query components
|
||||
String file();
|
||||
|
||||
// Create a new test case identical to this one but
|
||||
// with a different URL scheme and port
|
||||
default ThreeArgsTest withScheme(String scheme) {
|
||||
String urlWithScheme = urlWithScheme(scheme);
|
||||
if (this instanceof OfHostFile) {
|
||||
return new OfHostFile(character(), host(), file(), urlWithScheme);
|
||||
}
|
||||
throw new AssertionError("unexpected subclass: " + this.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean early(int c) {
|
||||
return (c < 31 || c == 127 || c == '/');
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean acceptDelayedException(Throwable exception) {
|
||||
return URLArgTest.super.acceptDelayedException(exception)
|
||||
|| "file".equalsIgnoreCase(scheme())
|
||||
&& exception instanceof IOException;
|
||||
}
|
||||
|
||||
record OfHostFile(int character, String host, String file, String url)
|
||||
implements ThreeArgsTest {
|
||||
}
|
||||
|
||||
static ThreeArgsTest ofHostFile(int c) {
|
||||
String host = "local%shost".formatted(Character.toString(c));
|
||||
String url = "http://" + host + "/";
|
||||
return new OfHostFile(c, host, "/", url);
|
||||
}
|
||||
}
|
||||
|
||||
// Test data for the four args constructor
|
||||
// public URL(String scheme, String host, int port, String file)
|
||||
// throws MalformedURLException
|
||||
sealed interface FourArgsTest extends URLArgTest {
|
||||
|
||||
// the host component
|
||||
String host();
|
||||
|
||||
// the port component
|
||||
int port();
|
||||
|
||||
// the path + query components
|
||||
String file();
|
||||
|
||||
// Create a new test case identical to this one but
|
||||
// with a different URL scheme and port
|
||||
default FourArgsTest withScheme(String scheme) {
|
||||
String urlWithScheme = urlWithScheme(scheme);
|
||||
if (this instanceof OfHostFilePort) {
|
||||
int port = URLArgTest.port(scheme);
|
||||
return new OfHostFilePort(character(), host(), port, file(), urlWithScheme);
|
||||
}
|
||||
throw new AssertionError("unexpected subclass: " + this.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean early(int c) {
|
||||
return (c < 31 || c == 127 || c == '/');
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean acceptDelayedException(Throwable exception) {
|
||||
return URLArgTest.super.acceptDelayedException(exception)
|
||||
|| "file".equalsIgnoreCase(scheme())
|
||||
&& exception instanceof IOException;
|
||||
}
|
||||
|
||||
record OfHostFilePort(int character, String host, int port, String file, String url)
|
||||
implements FourArgsTest {
|
||||
}
|
||||
|
||||
static FourArgsTest ofHostPortFile(int c) {
|
||||
String host = "local%shost".formatted(Character.toString(c));
|
||||
String url = "http://" + host + "/";
|
||||
int port = URLArgTest.port(URLArgTest.scheme(url));
|
||||
return new OfHostFilePort(c, host, port, "/", url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Generate test data for the URL one arg constructor, with variations
|
||||
// of the host component.
|
||||
static Stream<OneArgTest> oneArgHostTests() {
|
||||
List<OneArgTest> tests = new ArrayList<>();
|
||||
List<OneArgTest> urls = new ArrayList<>();
|
||||
urls.addAll((UNWISE + EXCLUDED_DELIMS).chars()
|
||||
.mapToObj(OneArgTest::ofHost).toList());
|
||||
urls.addAll(IntStream.concat(IntStream.range(0, 31), IntStream.of(127))
|
||||
.mapToObj(OneArgTest::ofHost).toList());
|
||||
for (String scheme : List.of("http", "https", "ftp")) {
|
||||
for (var test : urls) {
|
||||
tests.add(test.withScheme(scheme));
|
||||
}
|
||||
}
|
||||
return tests.stream();
|
||||
}
|
||||
|
||||
// Generate test data for the URL one arg constructor, with variations
|
||||
// of the user info component.
|
||||
static Stream<OneArgTest> oneArgUserInfoTests() {
|
||||
List<OneArgTest> tests = new ArrayList<>();
|
||||
List<OneArgTest> urls = new ArrayList<>();
|
||||
urls.addAll(IntStream.concat(IntStream.range(0, 31), IntStream.of(127))
|
||||
.mapToObj(OneArgTest::ofUserInfo).toList());
|
||||
urls.add(OneArgTest.ofUserInfo('\\'));
|
||||
for (String scheme : List.of("http", "https", "ftp")) {
|
||||
for (var test : urls) {
|
||||
tests.add(test.withScheme(scheme));
|
||||
}
|
||||
}
|
||||
return tests.stream();
|
||||
}
|
||||
|
||||
// Test data with all variations for the URL one arg
|
||||
// constructor (spec)
|
||||
static Stream<OneArgTest> oneArgTests() {
|
||||
return Stream.concat(oneArgHostTests(), oneArgUserInfoTests());
|
||||
}
|
||||
|
||||
// Test data with all variations for the URL two arg
|
||||
// constructor (URL, spec)
|
||||
static Stream<TwoArgsTest> twoArgTests() {
|
||||
return oneArgTests().map(TwoArgsTest::ofOneArgTest);
|
||||
}
|
||||
|
||||
// Generate test data for the URL three arguments constructor
|
||||
// (scheme, host, file)
|
||||
static Stream<ThreeArgsTest> threeArgsTests() {
|
||||
List<ThreeArgsTest> urls = new ArrayList<>();
|
||||
urls.addAll((UNWISE + EXCLUDED_DELIMS + DELIMS).chars()
|
||||
.mapToObj(ThreeArgsTest::ofHostFile).toList());
|
||||
urls.addAll(IntStream.concat(IntStream.range(0, 31), IntStream.of(127))
|
||||
.mapToObj(ThreeArgsTest::ofHostFile).toList());
|
||||
List<ThreeArgsTest> tests = new ArrayList<>();
|
||||
for (String scheme : List.of("http", "https", "ftp", "file")) {
|
||||
for (var test : urls) {
|
||||
tests.add(test.withScheme(scheme));
|
||||
}
|
||||
}
|
||||
return tests.stream();
|
||||
}
|
||||
|
||||
// Generate test data for the URL four arguments constructor
|
||||
// (scheme, host, port, file)
|
||||
static Stream<FourArgsTest> fourArgsTests() {
|
||||
List<FourArgsTest> urls = new ArrayList<>();
|
||||
urls.addAll((UNWISE + EXCLUDED_DELIMS + DELIMS).chars()
|
||||
.mapToObj(FourArgsTest::ofHostPortFile).toList());
|
||||
urls.addAll(IntStream.concat(IntStream.range(0, 31), IntStream.of(127))
|
||||
.mapToObj(FourArgsTest::ofHostPortFile).toList());
|
||||
List<FourArgsTest> tests = new ArrayList<>();
|
||||
for (String scheme : List.of("http", "https", "ftp", "file")) {
|
||||
for (var test : urls) {
|
||||
tests.add(test.withScheme(scheme));
|
||||
}
|
||||
}
|
||||
return tests.stream();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("oneArgTests")
|
||||
public void testOneArgConstructor(OneArgTest test) throws Exception {
|
||||
|
||||
int c = test.character();
|
||||
String url = test.url();
|
||||
if (EARLY_PARSING || test.early(c)) {
|
||||
err.println("Early parsing: " + test.describe());
|
||||
var exception = assertThrows(MalformedURLException.class, () -> {
|
||||
new URL(url);
|
||||
});
|
||||
err.println("Got expected exception: " + exception);
|
||||
} else {
|
||||
err.println("Delayed parsing: " + test.describe());
|
||||
URL u = new URL(url);
|
||||
var exception = assertThrows(IOException.class, () -> {
|
||||
u.openConnection().connect();
|
||||
});
|
||||
if (!test.acceptDelayedException(exception)) {
|
||||
err.println("unexpected exception type: " + exception);
|
||||
throw exception;
|
||||
}
|
||||
err.println("Got expected exception: " + exception);
|
||||
assertFalse(exception instanceof ConnectException);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("twoArgTests")
|
||||
public void testTwoArgConstructor(TwoArgsTest test) throws Exception {
|
||||
|
||||
int c = test.character();
|
||||
String url = test.url();
|
||||
String scheme = URLArgTest.scheme(url);
|
||||
URL u = new URL(scheme, null,"");
|
||||
if (EARLY_PARSING || test.early(c)) {
|
||||
err.println("Early parsing: " + test.describe());
|
||||
var exception = assertThrows(MalformedURLException.class, () -> {
|
||||
new URL(u, url);
|
||||
});
|
||||
err.println("Got expected exception: " + exception);
|
||||
} else {
|
||||
err.println("Delayed parsing: " + test.describe());
|
||||
URL u2 = new URL(u, url);
|
||||
var exception = assertThrows(IOException.class, () -> {
|
||||
u2.openConnection().connect();
|
||||
});
|
||||
if (!test.acceptDelayedException(exception)) {
|
||||
err.println("unexpected exception type: " + exception);
|
||||
throw exception;
|
||||
}
|
||||
err.println("Got expected exception: " + exception);
|
||||
assertFalse(exception instanceof ConnectException);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("threeArgsTests")
|
||||
public void testThreeArgsConstructor(ThreeArgsTest test) throws Exception {
|
||||
|
||||
int c = test.character();
|
||||
String url = test.url();
|
||||
if (EARLY_PARSING || test.early(c)) {
|
||||
err.println("Early parsing: " + url);
|
||||
var exception = assertThrows(MalformedURLException.class, () -> {
|
||||
new URL(test.scheme(), test.host(), test.file());
|
||||
});
|
||||
err.println("Got expected exception: " + exception);
|
||||
} else {
|
||||
err.println("Delayed parsing: " + url);
|
||||
URL u = new URL(test.scheme(), test.host(), test.file());
|
||||
var exception = assertThrows(IOException.class, () -> {
|
||||
u.openConnection().connect();
|
||||
});
|
||||
if (!test.acceptDelayedException(exception)) {
|
||||
err.println("unexpected exception type: " + exception);
|
||||
throw exception;
|
||||
}
|
||||
err.println("Got expected exception: " + exception);
|
||||
assertFalse(exception instanceof ConnectException);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("fourArgsTests")
|
||||
public void testFourArgsConstructor(FourArgsTest test) throws Exception {
|
||||
|
||||
int c = test.character();
|
||||
String url = test.url();
|
||||
if (EARLY_PARSING || test.early(c)) {
|
||||
err.println("Early parsing: " + url);
|
||||
var exception = assertThrows(MalformedURLException.class, () -> {
|
||||
new URL(test.scheme(), test.host(), test.port(), test.file());
|
||||
});
|
||||
err.println("Got expected exception: " + exception);
|
||||
} else {
|
||||
err.println("Delayed parsing: " + url);
|
||||
URL u = new URL(test.scheme(), test.host(), test.port(), test.file());
|
||||
var exception = assertThrows(IOException.class, () -> {
|
||||
u.openConnection().connect();
|
||||
});
|
||||
if (!test.acceptDelayedException(exception)) {
|
||||
err.println("unexpected exception type: " + exception);
|
||||
throw exception;
|
||||
}
|
||||
err.println("Got expected exception: " + exception);
|
||||
assertFalse(exception instanceof ConnectException);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user