From aaeb5eb0d211ebd1946bc61ee062ec7102f49d91 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 14 Nov 2022 23:23:12 +0100 Subject: [PATCH] Avoid direct URL construction and URL equality checks Closes gh-29486 --- .../factory/xml/ResourceEntityResolver.java | 8 +-- .../beans/propertyeditors/PathEditor.java | 4 +- .../core/io/DefaultResourceLoader.java | 2 +- .../springframework/core/io/UrlResource.java | 55 +++++----------- .../springframework/core/io/VfsResource.java | 3 +- .../springframework/util/ResourceUtils.java | 63 ++++++++++++++++--- .../PersistenceUnitReader.java | 6 +- .../oxm/jaxb/Jaxb2Marshaller.java | 5 +- 8 files changed, 87 insertions(+), 59 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java index eea87d5524b4..dec51c9b79ec 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import java.io.File; import java.io.IOException; -import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -30,6 +29,7 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.lang.Nullable; +import org.springframework.util.ResourceUtils; /** * {@code EntityResolver} implementation that tries to resolve entity references @@ -82,7 +82,7 @@ public InputSource resolveEntity(@Nullable String publicId, @Nullable String sys String resourcePath = null; try { String decodedSystemId = URLDecoder.decode(systemId, StandardCharsets.UTF_8); - String givenUrl = new URL(decodedSystemId).toString(); + String givenUrl = ResourceUtils.toURL(decodedSystemId).toString(); String systemRootUrl = new File("").toURI().toURL().toString(); // Try relative to resource base if currently in system root. if (givenUrl.startsWith(systemRootUrl)) { @@ -116,7 +116,7 @@ else if (systemId.endsWith(DTD_SUFFIX) || systemId.endsWith(XSD_SUFFIX)) { url = "https:" + url.substring(5); } try { - source = new InputSource(new URL(url).openStream()); + source = new InputSource(ResourceUtils.toURL(url).openStream()); source.setPublicId(publicId); source.setSystemId(systemId); } diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java index 0ee0f5e80f60..13226e6ca0db 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,7 +77,7 @@ public void setAsText(String text) throws IllegalArgumentException { boolean nioPathCandidate = !text.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX); if (nioPathCandidate && !text.startsWith("/")) { try { - URI uri = new URI(text); + URI uri = ResourceUtils.toURI(text); if (uri.getScheme() != null) { nioPathCandidate = false; // Let's try NIO file system providers via Paths.get(URI) diff --git a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java index ef24b3803e68..27c5bf830603 100644 --- a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java +++ b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java @@ -162,7 +162,7 @@ else if (location.startsWith(CLASSPATH_URL_PREFIX)) { else { try { // Try to parse the location as a URL... - URL url = new URL(location); + URL url = ResourceUtils.toURL(location); return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); } catch (MalformedURLException ex) { diff --git a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java index 72e43ae4efd9..7038c3026929 100644 --- a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java @@ -31,6 +31,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; /** @@ -57,10 +58,10 @@ public class UrlResource extends AbstractFileResolvingResource { private final URL url; /** - * Cleaned URL (with normalized path), used for comparisons. + * Cleaned URL String (with normalized path), used for comparisons. */ @Nullable - private volatile URL cleanedUrl; + private volatile String cleanedUrl; /** @@ -97,8 +98,8 @@ public UrlResource(URI uri) throws MalformedURLException { public UrlResource(String path) throws MalformedURLException { Assert.notNull(path, "Path must not be null"); this.uri = null; - this.url = new URL(path); - this.cleanedUrl = getCleanedUrl(this.url, path); + this.url = ResourceUtils.toURL(path); + this.cleanedUrl = StringUtils.cleanPath(path); } /** @@ -144,7 +145,7 @@ public UrlResource(String protocol, String location, @Nullable String fragment) * Create a new {@code UrlResource} from the given {@link URI}. *

This factory method is a convenience for {@link #UrlResource(URI)} that * catches any {@link MalformedURLException} and rethrows it wrapped in an - * {@link UncheckedIOException}; suitable for use in {@link java.util.Stream} + * {@link UncheckedIOException}; suitable for use in {@link java.util.stream.Stream} * and {@link java.util.Optional} APIs or other scenarios when a checked * {@link IOException} is undesirable. * @param uri a URI @@ -165,7 +166,7 @@ public static UrlResource from(URI uri) throws UncheckedIOException { * Create a new {@code UrlResource} from the given URL path. *

This factory method is a convenience for {@link #UrlResource(String)} * that catches any {@link MalformedURLException} and rethrows it wrapped in an - * {@link UncheckedIOException}; suitable for use in {@link java.util.Stream} + * {@link UncheckedIOException}; suitable for use in {@link java.util.stream.Stream} * and {@link java.util.Optional} APIs or other scenarios when a checked * {@link IOException} is undesirable. * @param path a URL path @@ -183,36 +184,16 @@ public static UrlResource from(String path) throws UncheckedIOException { } - /** - * Determine a cleaned URL for the given original URL. - * @param originalUrl the original URL - * @param originalPath the original URL path - * @return the cleaned URL (possibly the original URL as-is) - * @see org.springframework.util.StringUtils#cleanPath - */ - private static URL getCleanedUrl(URL originalUrl, String originalPath) { - String cleanedPath = StringUtils.cleanPath(originalPath); - if (!cleanedPath.equals(originalPath)) { - try { - return new URL(cleanedPath); - } - catch (MalformedURLException ex) { - // Cleaned URL path cannot be converted to URL -> take original URL. - } - } - return originalUrl; - } - /** * Lazily determine a cleaned URL for the given original URL. - * @see #getCleanedUrl(URL, String) */ - private URL getCleanedUrl() { - URL cleanedUrl = this.cleanedUrl; + private String getCleanedUrl() { + String cleanedUrl = this.cleanedUrl; if (cleanedUrl != null) { return cleanedUrl; } - cleanedUrl = getCleanedUrl(this.url, (this.uri != null ? this.uri : this.url).toString()); + String originalPath = (this.uri != null ? this.uri : this.url).toString(); + cleanedUrl = StringUtils.cleanPath(originalPath); this.cleanedUrl = cleanedUrl; return cleanedUrl; } @@ -305,16 +286,13 @@ public Resource createRelative(String relativePath) throws MalformedURLException * A leading slash will get dropped; a "#" symbol will get encoded. * @since 5.2 * @see #createRelative(String) - * @see java.net.URL#URL(java.net.URL, String) + * @see ResourceUtils#toRelativeURL(URL, String) */ protected URL createRelativeURL(String relativePath) throws MalformedURLException { if (relativePath.startsWith("/")) { relativePath = relativePath.substring(1); } - // # can appear in filenames, java.net.URL should not treat it as a fragment - relativePath = StringUtils.replace(relativePath, "#", "%23"); - // Use the URL constructor for applying the relative path as a URL spec - return new URL(this.url, relativePath); + return ResourceUtils.toRelativeURL(this.url, relativePath); } /** @@ -324,9 +302,10 @@ protected URL createRelativeURL(String relativePath) throws MalformedURLExceptio * @see java.net.URLDecoder#decode(String, java.nio.charset.Charset) */ @Override + @Nullable public String getFilename() { - String filename = StringUtils.getFilename(getCleanedUrl().getPath()); - return URLDecoder.decode(filename, StandardCharsets.UTF_8); + String filename = StringUtils.getFilename(this.uri != null ? this.uri.getPath() : this.url.getPath()); + return (filename != null ? URLDecoder.decode(filename, StandardCharsets.UTF_8) : null); } /** @@ -334,7 +313,7 @@ public String getFilename() { */ @Override public String getDescription() { - return "URL [" + this.url + "]"; + return "URL [" + (this.uri != null ? this.uri : this.url) + "]"; } diff --git a/spring-core/src/main/java/org/springframework/core/io/VfsResource.java b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java index ffb9374033e1..3574a6e67c33 100644 --- a/spring-core/src/main/java/org/springframework/core/io/VfsResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java @@ -24,6 +24,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; /** * JBoss VFS based {@link Resource} implementation. @@ -115,7 +116,7 @@ public Resource createRelative(String relativePath) throws IOException { } } - return new VfsResource(VfsUtils.getRelative(new URL(getURL(), relativePath))); + return new VfsResource(VfsUtils.getRelative(ResourceUtils.toRelativeURL(getURL(), relativePath))); } @Override diff --git a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java index 2caaf33597f7..2d7a77879af9 100644 --- a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -109,7 +109,7 @@ public static boolean isUrl(@Nullable String resourceLocation) { return true; } try { - new URL(resourceLocation); + toURL(resourceLocation); return true; } catch (MalformedURLException ex) { @@ -141,7 +141,7 @@ public static URL getURL(String resourceLocation) throws FileNotFoundException { } try { // try URL - return new URL(resourceLocation); + return toURL(resourceLocation); } catch (MalformedURLException ex) { // no URL -> treat as file path @@ -181,7 +181,7 @@ public static File getFile(String resourceLocation) throws FileNotFoundException } try { // try URL - return getFile(new URL(resourceLocation)); + return getFile(toURL(resourceLocation)); } catch (MalformedURLException ex) { // no URL -> treat as file path @@ -311,7 +311,7 @@ public static URL extractJarFileURL(URL jarUrl) throws MalformedURLException { if (separatorIndex != -1) { String jarFile = urlFile.substring(0, separatorIndex); try { - return new URL(jarFile); + return toURL(jarFile); } catch (MalformedURLException ex) { // Probably no protocol in original jar URL, like "jar:C:/mypath/myjar.jar". @@ -319,7 +319,7 @@ public static URL extractJarFileURL(URL jarUrl) throws MalformedURLException { if (!jarFile.startsWith("/")) { jarFile = "/" + jarFile; } - return new URL(FILE_URL_PREFIX + jarFile); + return toURL(FILE_URL_PREFIX + jarFile); } } else { @@ -346,11 +346,11 @@ public static URL extractArchiveURL(URL jarUrl) throws MalformedURLException { // Tomcat's "war:file:...mywar.war*/WEB-INF/lib/myjar.jar!/myentry.txt" String warFile = urlFile.substring(0, endIndex); if (URL_PROTOCOL_WAR.equals(jarUrl.getProtocol())) { - return new URL(warFile); + return toURL(warFile); } int startIndex = warFile.indexOf(WAR_URL_PREFIX); if (startIndex != -1) { - return new URL(warFile.substring(startIndex + WAR_URL_PREFIX.length())); + return toURL(warFile.substring(startIndex + WAR_URL_PREFIX.length())); } } @@ -381,6 +381,53 @@ public static URI toURI(String location) throws URISyntaxException { return new URI(StringUtils.replace(location, " ", "%20")); } + /** + * Create a URL instance for the given location String, + * going through URI construction and then URL conversion. + * @param location the location String to convert into a URL instance + * @return the URL instance + * @throws MalformedURLException if the location wasn't a valid URL + * @since 6.0 + */ + public static URL toURL(String location) throws MalformedURLException { + // Not fully equivalent - but to be moved in the given direction + // since JDK 20 deprecates all direct java.net.URL constructors. + /* + try { + return toURI(location).toURL(); + } + catch (URISyntaxException ex) { + MalformedURLException exToThrow = new MalformedURLException(ex.getMessage()); + exToThrow.initCause(ex); + throw exToThrow; + } + */ + + return new URL(location); + } + + /** + * Create a URL instance for the given root URL and relative path, + * going through URI construction and then URL conversion. + * @param root the root URL to start from + * @param relativePath the relative path to apply + * @return the relative URL instance + * @throws MalformedURLException if the end result is not a valid URL + * @since 6.0 + */ + public static URL toRelativeURL(URL root, String relativePath) throws MalformedURLException { + // # can appear in filenames, java.net.URL should not treat it as a fragment + relativePath = StringUtils.replace(relativePath, "#", "%23"); + + // Not fully equivalent - but to be moved in the given direction + // since JDK 20 deprecates all direct java.net.URL constructors. + /* + return toURL(StringUtils.applyRelativePath(root.toString(), relativePath)); + */ + + return new URL(root, relativePath); + } + /** * Set the {@link URLConnection#setUseCaches "useCaches"} flag on the * given connection, preferring {@code false} but leaving the diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java index 1b2cd4b53e22..00a167f8d5ca 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -312,7 +312,7 @@ protected void parseJarFiles(Element persistenceUnit, SpringPersistenceUnitInfo // relative to the persistence unit root, according to the JPA spec URL rootUrl = unitInfo.getPersistenceUnitRootUrl(); if (rootUrl != null) { - unitInfo.addJarFileUrl(new URL(rootUrl, value)); + unitInfo.addJarFileUrl(ResourceUtils.toRelativeURL(rootUrl, value)); } else { logger.warn("Cannot resolve jar-file entry [" + value + "] in persistence unit '" + @@ -363,7 +363,7 @@ static URL determinePersistenceUnitRootUrl(Resource resource) throws IOException if (persistenceUnitRoot.endsWith("/")) { persistenceUnitRoot = persistenceUnitRoot.substring(0, persistenceUnitRoot.length() - 1); } - return new URL(persistenceUnitRoot); + return ResourceUtils.toURL(persistenceUnitRoot); } } diff --git a/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java index 32f544067d8a..2f69913413ea 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -101,6 +101,7 @@ import org.springframework.util.ClassUtils; import org.springframework.util.FileCopyUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; import org.springframework.util.xml.StaxUtils; @@ -985,7 +986,7 @@ public String addMtomAttachment(DataHandler dataHandler, String elementNamespace private String getHost(String elementNamespace, DataHandler dataHandler) { try { - URI uri = new URI(elementNamespace); + URI uri = ResourceUtils.toURI(elementNamespace); return uri.getHost(); } catch (URISyntaxException ex) {