Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle paths with a question mark in them #23696

Merged
merged 1 commit into from
Feb 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.io.InterruptedIOException;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.channels.ClosedChannelException;
import java.nio.file.Files;
Expand Down Expand Up @@ -208,7 +209,25 @@ public URL getUrl() {
lock.readLock().lock();
try {
if (pathTree.isOpen()) {
return url = path.toUri().toURL();
URI uri = path.toUri();
URL url;
// the URLClassLoader doesn't add trailing slashes to directories, so we make sure we return
// the same URL as it would to avoid having QuarkusClassLoader return different URLs
// (one with a trailing slash and one without) for same resource
if (uri.getPath() != null && uri.getPath().endsWith("/")) {
String uriStr = uri.toString();
url = new URL(uriStr.substring(0, uriStr.length() - 1));
} else {
url = uri.toURL();
}
if (url.getQuery() != null) {
//huge hack to work around a JDK bug. ZipPath does not escape properly, so if there is a ?
//in the path then it ends up in the query string and everything is screwy.
//if there are multiple question marks the extra ones end up in the path and not the query string as you would expect
url = new URL(url.getProtocol(), url.getHost(), url.getPort(),
url.getPath().replaceAll("\\?", "%3F") + "%3F" + url.getQuery());
}
return this.url = url;
}
return url = apply(tree -> tree.apply(name, visit -> visit == null ? null : visit.getUrl()));
} catch (MalformedURLException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package io.quarkus.bootstrap.classloader;

import io.quarkus.bootstrap.classloading.ClassPathElement;
import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.bootstrap.util.IoUtils;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.exporter.ExplodedExporter;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

public class ClassLoadingPathTreeResourceUrlTestCase {

/**
* URLClassLoader will return URL's that end with a / if the call to getResource ends with a /
*/
@ParameterizedTest
@MethodSource("paths")
public void testUrlReturnedFromClassLoaderDirectory(String testPath) throws Exception {
JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
.add(new StringAsset("a"), "a.txt")
.add(new StringAsset("b"), "b/b.txt");
Path path = Files.createTempDirectory(testPath);
try {
jar.as(ExplodedExporter.class).exportExploded(path.toFile(), "tmp");

ClassLoader cl = QuarkusClassLoader.builder("test", getClass().getClassLoader(), false)
.addElement(ClassPathElement.fromPath(path.resolve("tmp"), true))
.build();
URL res = cl.getResource("a.txt");
Assertions.assertNotNull(res);
Assertions.assertNull(res.getQuery());
res = cl.getResource("a.txt/");
Assertions.assertNull(res);

res = cl.getResource("b");
Assertions.assertNotNull(res);
Assertions.assertFalse(res.toExternalForm().endsWith("/"));

res = cl.getResource("b/");
Assertions.assertNotNull(res);
Assertions.assertTrue(res.toExternalForm().endsWith("/"));

} finally {
IoUtils.recursiveDelete(path);
}
}

/**
* Test that {@link QuarkusClassLoader#getResourceAsStream(String)} returns a stream for directory
* resources
*
* @throws Exception
* @see <a href="https://github.com/quarkusio/quarkus/issues/11707"/>
*/
@ParameterizedTest
@MethodSource("paths")
public void testResourceAsStreamForDirectory(String testPath) throws Exception {
final JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
.add(new StringAsset("a"), "a.txt")
.add(new StringAsset("b"), "b/b.txt");
final Path tmpDir = Files.createTempDirectory(testPath);
try {
jar.as(ExplodedExporter.class).exportExploded(tmpDir.toFile(), "tmpcltest");
final ClassLoader cl = QuarkusClassLoader.builder("test", getClass().getClassLoader(), false)
.addElement(ClassPathElement.fromPath(tmpDir.resolve("tmpcltest"), true))
.build();

try (final InputStream is = cl.getResourceAsStream("b/")) {
Assertions.assertNotNull(is, "InputStream is null for a directory resource");
}
try (final InputStream is = cl.getResourceAsStream("b")) {
Assertions.assertNotNull(is, "InputStream is null for a directory resource");
}
} finally {
IoUtils.recursiveDelete(tmpDir);
}
}

/**
* URLClassLoader will return URL's that end with a / if the call to getResource ends with a /
*/
@ParameterizedTest
@MethodSource("paths")
public void testUrlReturnedFromClassLoaderJarFile(String testPath) throws Exception {
JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
.add(new StringAsset("a"), "a.txt")
.add(new StringAsset("b"), "b/b.txt");
Path path = Files.createTempFile(testPath, "quarkus-test.jar");
try {
jar.as(ZipExporter.class).exportTo(path.toFile(), true);

ClassLoader cl = QuarkusClassLoader.builder("test", getClass().getClassLoader(), false)
.addElement(ClassPathElement.fromPath(path, true))
.build();
URL res = cl.getResource("a.txt");
Assertions.assertNotNull(res);
Assertions.assertNull(res.getQuery());
res = cl.getResource("a.txt/");
Assertions.assertNull(res);

res = cl.getResource("b");
Assertions.assertNotNull(res);
Assertions.assertFalse(res.toExternalForm().endsWith("/"));

res = cl.getResource("b/");
Assertions.assertNotNull(res);
Assertions.assertTrue(res.toExternalForm().endsWith("/"));

} finally {
IoUtils.recursiveDelete(path);
}
}

public static Iterable<Object[]> paths() {
if (OS.WINDOWS.isCurrentOs()) {
return List.<Object[]> of(new Object[] { "test" });
}
return List.<Object[]> of(new Object[] { "test" }, new Object[] { "dir?with?question?mark" });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,31 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.exporter.ExplodedExporter;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

//see https://github.com/quarkusio/quarkus/issues/10943
public class ClassLoadingResourceUrlTestCase {

/**
* URLClassLoader will return URL's that end with a / if the call to getResource ends with a /
*/
@Test
public void testUrlReturnedFromClassLoaderDirectory() throws Exception {
@ParameterizedTest
@MethodSource("paths")
public void testUrlReturnedFromClassLoaderDirectory(String testPath) throws Exception {
JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
.add(new StringAsset("a"), "a.txt")
.add(new StringAsset("b"), "b/b.txt");
Path path = Files.createTempDirectory("test");
Path path = Files.createTempDirectory(testPath);
try {
jar.as(ExplodedExporter.class).exportExploded(path.toFile(), "tmp");

Expand All @@ -40,6 +45,7 @@ public void testUrlReturnedFromClassLoaderDirectory() throws Exception {
.build();
URL res = cl.getResource("a.txt");
Assertions.assertNotNull(res);
Assertions.assertNull(res.getQuery());
res = cl.getResource("a.txt/");
Assertions.assertNull(res);

Expand All @@ -63,12 +69,13 @@ public void testUrlReturnedFromClassLoaderDirectory() throws Exception {
* @throws Exception
* @see <a href="https://github.com/quarkusio/quarkus/issues/11707"/>
*/
@Test
public void testResourceAsStreamForDirectory() throws Exception {
@ParameterizedTest
@MethodSource("paths")
public void testResourceAsStreamForDirectory(String testPath) throws Exception {
final JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
.add(new StringAsset("a"), "a.txt")
.add(new StringAsset("b"), "b/b.txt");
final Path tmpDir = Files.createTempDirectory("test");
final Path tmpDir = Files.createTempDirectory(testPath);
try {
jar.as(ExplodedExporter.class).exportExploded(tmpDir.toFile(), "tmpcltest");
final ClassLoader cl = QuarkusClassLoader.builder("test", getClass().getClassLoader(), false)
Expand All @@ -89,12 +96,13 @@ public void testResourceAsStreamForDirectory() throws Exception {
/**
* URLClassLoader will return URL's that end with a / if the call to getResource ends with a /
*/
@Test
public void testUrlReturnedFromClassLoaderJarFile() throws Exception {
@ParameterizedTest
@MethodSource("paths")
public void testUrlReturnedFromClassLoaderJarFile(String testPath) throws Exception {
JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
.add(new StringAsset("a"), "a.txt")
.add(new StringAsset("b"), "b/b.txt");
Path path = Files.createTempFile("test", "quarkus-test.jar");
Path path = Files.createTempFile(testPath, "quarkus-test.jar");
try {
jar.as(ZipExporter.class).exportTo(path.toFile(), true);

Expand All @@ -103,6 +111,7 @@ public void testUrlReturnedFromClassLoaderJarFile() throws Exception {
.build();
URL res = cl.getResource("a.txt");
Assertions.assertNotNull(res);
Assertions.assertNull(res.getQuery());
res = cl.getResource("a.txt/");
Assertions.assertNull(res);

Expand Down Expand Up @@ -137,4 +146,11 @@ public void testMemoryUrlConnections() throws Exception {
Assertions.assertEquals("hello", new String(urlConnection.getInputStream().readAllBytes(), StandardCharsets.UTF_8));

}

public static Iterable<Object[]> paths() {
if (OS.WINDOWS.isCurrentOs()) {
return List.<Object[]> of(new Object[] { "test" });
}
return List.<Object[]> of(new Object[] { "test" }, new Object[] { "dir?with?question?mark" });
}
}
2 changes: 1 addition & 1 deletion independent-projects/bootstrap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
<plexus-classworlds.version>2.6.0</plexus-classworlds.version> <!-- not actually used but ClassRealm class is referenced from the API used in BootstrapWagonConfigurator -->
<smallrye-common.version>1.9.0</smallrye-common.version>
<gradle-tooling.version>7.4</gradle-tooling.version>
<quarkus-fs-util.version>0.0.8</quarkus-fs-util.version>
<quarkus-fs-util.version>0.0.9</quarkus-fs-util.version>
</properties>
<modules>
<module>bom</module>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ public URL getResourceURL(String resource) {
// for the "path" which includes the "realName"
final URL resUrl = new URI(jarUri.getScheme(), jarUri.getPath() + "!/" + realName, null).toURL();
// wrap it up into a "jar" protocol URL
return new URL("jar", null, resUrl.getProtocol() + ':' + resUrl.getPath());
//horrible hack to deal with '?' characters in the URL
//seems to be the only way, the URI constructor just does not let you handle them in a sane way
return new URL("jar", null, resUrl.getProtocol() + ':' + resUrl.getPath()
+ (resUrl.getQuery() == null ? "" : ("%3F" + resUrl.getQuery())));
} catch (MalformedURLException | URISyntaxException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;

/**
* Tests {@link JarResource}
Expand All @@ -25,12 +27,31 @@ public class JarResourceTest {
*/
@Test
public void testResourceURLEncoding() throws Exception {
testInternal("test");
}

/**
* As above but with a question mark in the path
* <p>
* Disabled on windows as such paths are not allowed
*/
@Test
@DisabledOnOs(OS.WINDOWS)
public void testResourceURLEncodingWithQuestionMark() throws Exception {
testInternal("test?dir");
}

/**
* Tests that the URL(s) returned from {@link JarResource#getResourceURL(String)} are properly encoded and can be used
* to open connection to the URL to read data
*/
private void testInternal(String path) throws Exception {
final JavaArchive jar = ShrinkWrap.create(JavaArchive.class);
final String[] files = new String[] { "a.txt", "a b.txt", ",;~!@#$%^&().txt" };
for (final String file : files) {
jar.add(new StringAsset("hello"), file);
}
final Path testDir = Files.createTempDirectory("test");
final Path testDir = Files.createTempDirectory(path);
// create a child dir with special characters
final Path specialCharDir = Files.createDirectory(Paths.get(testDir.toString(), ",;~!@#$%^&()"));
final Path jarFilePath = Files.createTempFile(specialCharDir, "test", "quarkus-test.jar");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
public class LegacyJarQuarkusIntegrationTestIT extends QuarkusITBase {

@Test
public void testFastJar() throws MavenInvocationException, IOException {
public void testLegacyJar() throws MavenInvocationException, IOException {
doTest("qit-legacy-jar", "legacyjar");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.maven.it;

import java.io.IOException;

import org.apache.maven.shared.invoker.MavenInvocationException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;

@DisableForNative
@DisabledOnOs(OS.WINDOWS)
public class QuestionMarkInPathIntegrationTestIT extends QuarkusITBase {

@Test
public void testFastJar() throws MavenInvocationException, IOException {
doTest("qit?fast?jar", "fastjar");
}

@Test
public void testLegacyJar() throws MavenInvocationException, IOException {
doTest("qit?legacy?jar", "legacyjar");
}

@Test
public void testUberJar() throws MavenInvocationException, IOException {
doTest("qit?uber?jar", "uberjar");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
public class UberJarQuarkusIntegrationTestIT extends QuarkusITBase {

@Test
public void testFastJar() throws MavenInvocationException, IOException {
public void testUberJar() throws MavenInvocationException, IOException {
doTest("qit-uber-jar", "uberjar");
}
}