Bug #5271
closedclasspath: URI resolution appears to lose the path
0%
Description
In email (msg8459), Vladimir Nesterovsky writes:
Saxon resolver produced "classpath:my-style.xslt" for "my-style.xslt" against "classpath:my/class/path/". Custom resolver
produced "classpath:my/class/path/my-style.xslt" for "my-style.xslt" against "classpath:my/class/path/".
That looks like a bug to me.
Updated by Norm Tovey-Walsh almost 3 years ago
The heart of the issue here is that the java.net.URI
method does the wrong thing:
String base = "classpath:my/class/path/";
String relativeURI = "my-style.xslt";
URI absoluteURI, resolvedURI;
absoluteURI = new URI(base);
resolvedURI = absoluteURI.resolve(relativeURI);
Assert.assertEquals("my-style.xslt", resolvedURI.toString());
There's a resolve method in a utility class in the XML Resolver that does the right thing. Its javadoc begins:
* <p>What's special here is that we take special care to attempt to resolve <code>jar:</code>
* and <code>classpath:</code> URIs. The {@link URI} class doesn't handle those, but if
* we're going to support them in catalogs, we need to do better.</p>
:-(
And it does work:
resolvedURI = URIUtils.resolve(absoluteURI, relativeURI);
Assert.assertEquals("classpath:my/class/path/my-style.xslt", resolvedURI.toString());
Updated by Norm Tovey-Walsh almost 3 years ago
Among other issues, we find this in ResolveURI
:
} else if (base.startsWith("classpath:")) {
absoluteURI = new URI(relativeURI);
if (!absoluteURI.isAbsolute()) {
absoluteURI = new URI("classpath:" + relativeURI);
}
} else {
This seems insufficient because it completely ignores the base URI. Maybe because resolving relative classpath:
scheme URIs turns out to be so troublesome. This, inspired by what the method in the XML Resolver does, fixes at least part of the issue:
} else if (base.startsWith("classpath:")) {
absoluteURI = new URI(relativeURI);
if (!absoluteURI.isAbsolute()) {
// URIs in the classpath: scheme are a bit of a mess. Given "classpath:/path/to/thing",
// if you attempt to use ClassLoader.getSystemResourceAsStream("/path/to/thing"), it
// will fail because the leading slash is a problem. Conversely, if you have
// "classpath:path/to/thing" and you try to resolve "otherthing" against it,
// you'll get "classpath:otherthing" which is almost certainly wrong. The only
// way around it seems to be to fake the scheme long enough to get correct
// resolution.
String path = base.substring(10);
URI fakeURI;
if (path.startsWith("/")) {
fakeURI = URI.create("file://" + path).resolve(relativeURI);
} else {
fakeURI = URI.create("file:///" + path).resolve(relativeURI);
}
String cpath = fakeURI.getPath().substring(1);
if (cpath.startsWith("../")) {
throw new IllegalArgumentException("Attempt to navigate above root: classpath:" + cpath);
}
absoluteURI = URI.create("classpath:" + cpath);
}
}
Updated by Norm Tovey-Walsh almost 3 years ago
The DirectResourceResolver
also needs a patch, I think. Currently, it relies on ResourceLoader
to load the resource:
stream = ResourceLoader.urlStream(new URL(request.uri));
But that all to new URL
will fail if the scheme is classpath:
. Proposed workaround:
if (request.uri.startsWith("classpath:")) {
String s = request.uri.substring(10);
if (s.startsWith("/")) {
s = s.substring(1);
}
stream = ClassLoader.getSystemResourceAsStream(s);
} else {
stream = ResourceLoader.urlStream(new URL(request.uri));
}
Updated by Norm Tovey-Walsh almost 3 years ago
- Status changed from New to In Progress
Updated by Vladimir Nesterovsky almost 3 years ago
Per StandardURIResolver, which is not used anymore the code should be:
if (uriString.startsWith("classpath:") && uriString.length() > 10) {
InputStream is = getConfiguration().getDynamicLoader().getResourceAsStream(uriString.substring(10));
if (is != null) {
source.setInputSource(new InputSource(is));
source.setSystemId(uriString);
return;
}
}
Updated by Norm Tovey-Walsh almost 3 years ago
Thanks for the suggestion. The code in the DirectResourceResolver
isn't structured quite the way it was in StandardURIResolver
, so I think it wants to carry on with the stream, not immediately return it. I'm also going to leave in the "handle a leading /" case because I managed to construct one in a test. The convention of "scheme-colon-slash" is so pervasive, I don't think I'll be the only one who accidentally does that.
Updated by Vladimir Nesterovsky almost 3 years ago
Important part here was: getConfiguration().getDynamicLoader().getResourceAsStream()
instead of ClassLoader.getSystemResourceAsStream()
.
Later one won't probably work in non trivial environments like web containers or OSGI.
Updated by Norm Tovey-Walsh almost 3 years ago
- Status changed from In Progress to Resolved
Nice catch. Thank you!
Updated by Norm Tovey-Walsh over 2 years ago
- Status changed from Resolved to Closed
- Applies to branch 11, trunk added
- Fix Committed on Branch 11, trunk added
- Fixed in Maintenance Release 11.2 added
Please register to edit this issue