Project

Profile

Help

Bug #5271

closed

classpath: URI resolution appears to lose the path

Added by Norm Tovey-Walsh almost 3 years ago. Updated over 2 years ago.

Status:
Closed
Priority:
Normal
Category:
-
Sprint/Milestone:
-
Start date:
2022-02-03
Due date:
% Done:

0%

Estimated time:
Legacy ID:
Applies to branch:
11, trunk
Fix Committed on Branch:
11, trunk
Fixed in Maintenance Release:
Platforms:

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.

Actions #1

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());
Actions #4

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);
                }
            }
Actions #5

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));
                }

Actions #6

Updated by Norm Tovey-Walsh almost 3 years ago

  • Status changed from New to In Progress
Actions #7

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;
            }
        }
Actions #8

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.

Actions #9

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.

Actions #10

Updated by Norm Tovey-Walsh almost 3 years ago

  • Status changed from In Progress to Resolved

Nice catch. Thank you!

Actions #11

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

Also available in: Atom PDF