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

PathMatchingResourcePatternResolver cannot load resources with a '#' in their file name within JARs #23532

Closed
tofdragon opened this issue Aug 28, 2019 · 7 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@tofdragon
Copy link

tofdragon commented Aug 28, 2019

Affects: spring version 5.1.5

Description

Given file /validation/api#test.json within a.jar which is in the classpath...

a project dependent a.jar, in project code

ResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();
Resource[] resources22 = resourceLoader.getResources("classpath*:/validation/**/api#test.json");
InputStream inputStream = resources[0].getInputStream();

error:

Exception in thread "main" java.io.FileNotFoundException: JAR entry validation/api not found in a.jar
	at sun.net.www.protocol.jar.JarURLConnection.connect(JarURLConnection.java:142)
	at sun.net.www.protocol.jar.JarURLConnection.getInputStream(JarURLConnection.java:150)
	at java.net.URL.openStream(URL.java:1038)

Analysis

The URL created in org.springframework.core.io.UrlResource.createRelative(String) treats everything after # as the URL fragment, causing the file path to be incorrect when the file is last read.

URL object:

  • file: a.jar!/validation/api
  • path: a.jar!/validation/api
  • ref: test.json

Solutions

Solution 1

extends PathMatchingResourcePatternResolver

@Override
protected Set<Resource> doFindPathMatchingJarResources() {
  ....
    
      result.add(rootDirResource.createRelative(ParseUtil.encodePath(relativePath)))
  ...
}

Solution 2

 URL url = resources[0].getURL();
            if (ResourceUtils.isJarURL(url) || ResourceUtils.isJarFileURL(url)) {
                String parent = url.toExternalForm().substring(0, url.toExternalForm().indexOf("!/validation/") + 13);
                String encodeFileName = url.toExternalForm().substring(url.toExternalForm().indexOf("!/validation/") + 13);
                String encodeFile = ParseUtil.encodePath(encodeFileName);
                URL url21 = new URL(parent + encodeFile);
                inputStream = url21.openStream();
            } else {
                inputStream = resources[0].getInputStream();
            }

Question

Is there any other better solution, or is there a problem with my use?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Aug 28, 2019
@sbrannen
Copy link
Member

sbrannen commented Aug 28, 2019

Thanks for raising the issue.

As an interim solution, is it possible for you to rename the JSON files so that they do not contain hash tags (i.e., #) in their file names?

Also, where does ParseUtil.encodePath(String) come from? Is that a utility that you wrote, and if so, what type of encoding does it perform?

After a bit of research, I assume you're referring to sun.net.www.ParseUtil, but Spring cannot use that class. So, perhaps java.net.URLEncoder.encode(String, String) is an alternative.

@sbrannen
Copy link
Member

Is there any other better solution, or is there a problem with my use?

Are your JSON files always in JAR files?

If so, have you tried the following?

Resource[] resources = resourceLoader.getResources("classpath*:/validation/**/api%23test.json");

@sbrannen sbrannen changed the title PathMatchingResourcePatternResolver Load the file in the jar, the file name contains #, error PathMatchingResourcePatternResolver cannot load resources within JARs that contain a '#' in their file name Aug 28, 2019
@sbrannen sbrannen changed the title PathMatchingResourcePatternResolver cannot load resources within JARs that contain a '#' in their file name PathMatchingResourcePatternResolver cannot load resources with a '#' in their file name within JARs Aug 28, 2019
@sbrannen sbrannen added the in: core Issues in core modules (aop, beans, core, context, expression) label Aug 28, 2019
@sbrannen
Copy link
Member

I've edited this issue to improve the formatting. You might want to check out this Mastering Markdown guide for future reference.

sbrannen added a commit that referenced this issue Aug 28, 2019
…n name

This commit introduces a test that verifies that
PathMatchingResourcePatternResolver can find files in the filesystem
that contain hashtags (#) in their names.

See gh-23532
@sbrannen
Copy link
Member

FYI: b173a93 demonstrates that PathMatchingResourcePatternResolver does not have a problem loading resources with a # in their file name from the filesystem.

@tofdragon
Copy link
Author

tofdragon commented Sep 1, 2019

@sbrannen

  1. where does ParseUtil.encodePath(String) come from?

<1-1>ParseUtil is sun.net.www.ParseUtil. java.net.URLEncoder.encode() is OK!

<1-2>java.net.JarURLConnection

private void parseSpecs(URL url) throws MalformedURLException {
       ....
            // *****  use ParseUtil ******
            entryName = ParseUtil.decode (entryName);
       ...
    }

<1-3>I found that the spring boot also has a JarUrlConnection, and there is also a decoding function in the guess.

  1. Resource[] resources = resourceLoader.getResources("classpath*:/validation/**/api%23test.json");

That does not work

PathMatchingResourcePatternResolver method

protected Set<Resource> doFindPathMatchingJarResources()

···
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
				JarEntry entry = entries.nextElement();
				String entryPath = entry.getName();
				if (entryPath.startsWith(rootEntryPath)) {
					String relativePath = entryPath.substring(rootEntryPath.length()); 
                                        // ******  Unable to match escaped  *****
					if (getPathMatcher().match(subPattern, relativePath)) {
						result.add(rootDirResource.createRelative(relativePath));
					}
				}
			}
···
  1. PathMatchingResourcePatternResolver does not have a problem loading resources with a # in their file name from the filesystem

It is OK when in the file system

the processing logic of jar and file is not the same. When dealing with url, I did not carefully look at the source code to analyze the difference between jar and file

  1. It is best to say that the file name does not contain #. Because of the project, there is no way to make a large rewrite, and the decoding has been considered in JarUrlConnection. Does it mean that the contract of Url Jar is considered to support encoding?

@lgxbslgx
Copy link
Contributor

lgxbslgx commented Sep 1, 2019

@tofdragon Could you give us a demo code which can reproduce the bug?

@tofdragon
Copy link
Author

@lgxbslgx Please see the problem description

@jhoeller jhoeller self-assigned this Sep 2, 2019
@jhoeller jhoeller added type: bug A general bug and removed for: team-attention status: waiting-for-triage An issue we've not yet triaged or decided on labels Sep 2, 2019
@jhoeller jhoeller added this to the 5.1.10 milestone Sep 2, 2019
@jhoeller jhoeller modified the milestones: 5.1.10, 5.2 GA Sep 17, 2019
@jhoeller jhoeller added type: enhancement A general enhancement and removed type: bug A general bug labels Sep 17, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

5 participants