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

throws IllegalArgumentException when downloading file when filename includes '%' for undertow server #17853

Closed
aruanruan opened this issue Aug 13, 2019 · 10 comments
Assignees
Labels
type: bug A general bug
Milestone

Comments

@aruanruan
Copy link

we map url: '/s/com-huawei-dcp-deploy-deploy-service/v1/api/file/download/main/os/mys/larg%25e_1.0.0.zip' to downloading the special file named 'larg%e_1.0.0.zip' ,
but it throws

java.lang.IllegalArgumentException: null
	at sun.net.www.ParseUtil.decode(Unknown Source)
	at java.net.JarURLConnection.parseSpecs(Unknown Source)
	at java.net.JarURLConnection.<init>(Unknown Source)
	at sun.net.www.protocol.jar.JarURLConnection.<init>(Unknown Source)
	at sun.net.www.protocol.jar.Handler.openConnection(Unknown Source)
	at java.net.URL.openConnection(Unknown Source)
	at io.undertow.server.handlers.resource.URLResource.openConnection(URLResource.java:86)
	at io.undertow.server.handlers.resource.URLResource.getContentLength(URLResource.java:273)

becuase '%25' is decode to '%', and URLResource use the path '/s/com-huawei-dcp-deploy-deploy-service/v1/api/file/download/main/os/mys/larg%e_1.0.0.zip', the class ParseUtils throws the IllegaglArgumentException when decoding '%e_'

bug is in the class org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory$MetaInfResourcesResourceManager

private URLResource getMetaInfResource(URL resourceJar, String path) {
			try {
				URL resourceUrl = new URL(resourceJar + "META-INF/resources" + path);
				URLResource resource = new URLResource(resourceUrl, path);
				if (resource.getContentLength() < 0) {
					return null;
				}
				return resource;
			}
			catch (MalformedURLException ex) {
				return null;
			}
		}
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Aug 13, 2019
@wilkinsona
Copy link
Member

Thanks for the report. Can you please provide a minimal sample that reproduces the problem?

@wilkinsona wilkinsona added the status: waiting-for-feedback We need additional information before we can continue label Aug 13, 2019
@aruanruan
Copy link
Author

any empty springboot (2.1.7.RELEASE) project using embed undertow server.
listen on 127.0.0.1: 8080
in chrome browser , use url 'http://127.0.0.1:8008/abc/def/%25_e.zip'

java.lang.IllegalArgumentException: null
        at sun.net.www.ParseUtil.decode(Unknown Source)
        at java.net.JarURLConnection.parseSpecs(Unknown Source)
        at java.net.JarURLConnection.<init>(Unknown Source)
        at sun.net.www.protocol.jar.JarURLConnection.<init>(Unknown Source)
        at sun.net.www.protocol.jar.Handler.openConnection(Unknown Source)
        at java.net.URL.openConnection(Unknown Source)
        at io.undertow.server.handlers.resource.URLResource.openConnection(URLResource.java:86)
        at io.undertow.server.handlers.resource.URLResource.getContentLength(URLResource.java:273)
        at org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory$MetaInfResourcesResourceManager.getMetaInfResource(UndertowServletWebServerFactory.java:572)
        at org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory$MetaInfResourcesResourceManager.getResource(UndertowServletWebServerFactory.java:546)
        at org.springframework.boot.web.embedded.undertow.CompositeResourceManager.getResource(CompositeResourceManager.java:51)
        at io.undertow.servlet.handlers.ServletPathMatches.getServletHandlerByPath(ServletPathMatches.java:96)
        at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:146)
        at io.undertow.server.handlers.HttpContinueReadHandler.handleRequest(HttpContinueReadHandler.java:65)
        at io.undertow.server.Connectors.executeRootHandler(Connectors.java:376)
        at io.undertow.server.protocol.http.HttpReadListener.handleEventWithNoRunningRequest(HttpReadListener.java:255)
        at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:136)
        at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:162)
        at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:100)
        at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:57)
        at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92)
        at org.xnio.ChannelListeners$10.handleEvent(ChannelListeners.java:291)
        at org.xnio.ChannelListeners$10.handleEvent(ChannelListeners.java:286)
        at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92)
        at org.xnio.nio.QueuedNioTcpServer$1.run(QueuedNioTcpServer.java:129)
        at org.xnio.nio.WorkerThread.safeRun(WorkerThread.java:582)
        at org.xnio.nio.WorkerThread.run(WorkerThread.java:466)
 

if i modify URLResource getMetaInfResource(URL resourceJar, String path) into:

private URLResource getMetaInfResource(URL resourceJar, String path) {
			try {
                               // replace % to url safe
				if(path.indexOf('%') >= 0) {
					path = path.replace("%", "%25");
				}
                                // end 
				URL resourceUrl = new URL(resourceJar + "META-INF/resources" + path);
				URLResource resource = new URLResource(resourceUrl, path);
				if (resource.getContentLength() < 0) {
					return null;
				}
				return resource;
			}
			catch (MalformedURLException ex) {
				return null;
			}
		}

compile & run,
chrome browser shows:

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.

Wed Aug 14 15:35:07 CST 2019
There was an unexpected error (type=Not Found, status=404).
Not Found

it is ok.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Aug 14, 2019
@wilkinsona
Copy link
Member

Thanks for the additional details. I cannot reproduce the problem with an empty app that uses Undertow. As far as I can tell, for the problem to occur, there must be at least one jar on the classpath that contains a META-INF/resources/ directory. Can you please confirm?

I'm not sure that we can fix this by solely replacing % with %25. There are other characters that may appear in the path that should really be escaped when being used in a URL.

@aruanruan
Copy link
Author

aruanruan commented Aug 15, 2019

when i add springfox's swagger2 to project, it happens:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>gs-spring-boot</artifactId>
    <version>0.1.0</version>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<!-- Import dependency management from Spring Boot -->
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-dependencies</artifactId>
				<version>2.1.7.RELEASE</version>
				<type>pom</type>
				<scope>import</scope>
				
			</dependency>
			
		</dependencies>
	</dependencyManagement>
	 

    <dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<exclusions>
				<exclusion>
                   <groupId>org.springframework.boot</groupId>
                   <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
		</dependency>
		<dependency>
		   <groupId>org.springframework.boot</groupId>
		   <artifactId>spring-boot-starter-undertow</artifactId>
		</dependency>
		<dependency>
		   <groupId>io.undertow</groupId>
		   <artifactId>undertow-core</artifactId>
		   </dependency>
		<dependency>
		   <groupId>io.undertow</groupId>
		   <artifactId>undertow-servlet</artifactId>
		</dependency>
		<dependency>
			<groupId>io.swagger</groupId>
			<artifactId>swagger-models</artifactId>
			<version>1.5.20</version>
		</dependency>
		<dependency>
			<groupId>io.swagger</groupId>
			<artifactId>swagger-annotations</artifactId>
			<version>1.5.20</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-core</artifactId>
			<version>2.9.2</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-common</artifactId>
			<version>2.9.2</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.9.2</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.9.2</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-spring-web</artifactId>
			<version>2.9.2</version>
		</dependency>
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>


    <build>
		<sourceDirectory>src/main/java</sourceDirectory>
		<testSourceDirectory>src/test/java</testSourceDirectory>
		<outputDirectory>target/classes</outputDirectory>
		<testOutputDirectory>target/test-classes</testOutputDirectory>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
			</resource>
		</resources>
		<testResources>
			<testResource>
				<directory>src/test/resources</directory>
			</testResource>
		</testResources>
		<directory>./target</directory>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<executions>
					<execution>
						<id>default-compile</id>
						<phase>compile</phase>
						<goals>
							<goal>compile</goal>
						</goals>
						<configuration>
							<source>1.8</source>
							<target>1.8</target>
							<encoding>UTF-8</encoding>
							<compilerArgs>
								<arg>-parameters</arg>
							</compilerArgs>
						</configuration>
					</execution>
					<execution>
						<id>default-testCompile</id>
						<phase>test-compile</phase>
						<goals>
							<goal>testCompile</goal>
						</goals>
						<configuration>
							<source>1.8</source>
							<target>1.8</target>
							<encoding>UTF-8</encoding>
						</configuration>
					</execution>
				</executions>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
					<encoding>UTF-8</encoding>
				</configuration>
			</plugin>
        </plugins>
    </build>

</project>

only two source file: /src/main/java:

Application.java:

package test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

	public static void main(String[] args) throws MalformedURLException {
		SpringApplication.run(Application.class, args);
	}
}

HelloController.java:

package test;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class HelloController {

    @RequestMapping("/")
    public String index() {
        return "Greetings from Spring Boot!";
    }
}

@philwebb
Copy link
Member

@aruanruan You you please share a complete project either as a GitHub project that we can clone or an attached zip file. It's then easier for us to make sure we are talking about the same thing.

@philwebb philwebb added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Aug 16, 2019
@marcusportmann
Copy link

marcusportmann commented Aug 19, 2019

Thanks for the additional details. I cannot reproduce the problem with an empty app that uses Undertow. As far as I can tell, for the problem to occur, there must be at least one jar on the classpath that contains a META-INF/resources/ directory. Can you please confirm?

I'm not sure that we can fix this by solely replacing % with %25. There are other characters that may appear in the path that should really be escaped when being used in a URL.

I am experiencing the same issue when I have a '%' character that is correctly encoded in a URL for a Rest controller e.g. http://localhost:8080/api/configuration/configurations/A%25T.

I also have the springfox-swagger-ui dependency on my classpath.

By the time the path is passed to the getMetaInfResource() method on the UndertowServletWebServerFactory class the '%25' has already been decoded as '%', which results in an invalid resource URL being constructed that cannot be parse correctly by the JarURLConnection class.

private URLResource getMetaInfResource(URL resourceJar, String path) {
			try {
				URL resourceUrl = new URL(resourceJar + "META-INF/resources" + path);
				URLResource resource = new URLResource(resourceUrl, path);
				if (resource.getContentLength() < 0) {
					return null;
				}
				return resource;
			}
			catch (MalformedURLException ex) {
				return null;
			}
		}

	}

The stack trace I get is as follows:

          at sun.net.www.ParseUtil.unescape(ParseUtil.java:162)
	  at sun.net.www.ParseUtil.decode(ParseUtil.java:198)
	  at java.net.JarURLConnection.parseSpecs(JarURLConnection.java:189)
	  at java.net.JarURLConnection.<init>(JarURLConnection.java:158)
	  at sun.net.www.protocol.jar.JarURLConnection.<init>(JarURLConnection.java:81)
	  at sun.net.www.protocol.jar.Handler.openConnection(Handler.java:41)
	  at java.net.URL.openConnection(URL.java:1051)
	  at io.undertow.server.handlers.resource.URLResource.openConnection(URLResource.java:86)
	  at io.undertow.server.handlers.resource.URLResource.getContentLength(URLResource.java:273)
	  at org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory$MetaInfResourcesResourceManager.getMetaInfResource(UndertowServletWebServerFactory.java:565)
	  at org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory$MetaInfResourcesResourceManager.getResource(UndertowServletWebServerFactory.java:539)
	  at org.springframework.boot.web.embedded.undertow.CompositeResourceManager.getResource(CompositeResourceManager.java:51)
	  at io.undertow.servlet.handlers.ServletPathMatches.getServletHandlerByPath(ServletPathMatches.java:96)
	  at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:151)
	  at io.undertow.server.handlers.HttpContinueReadHandler.handleRequest(HttpContinueReadHandler.java:65)
	  at io.undertow.server.Connectors.executeRootHandler(Connectors.java:364)
	  at io.undertow.server.protocol.http.HttpReadListener.handleEventWithNoRunningRequest(HttpReadListener.java:255)
	  at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:136)
	  at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:59)
	  at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92)
	  at org.xnio.conduits.ReadReadyHandler$ChannelListenerHandler.readReady(ReadReadyHandler.java:66)
	  at org.xnio.nio.NioSocketConduit.handleReady(NioSocketConduit.java:88)
	  at org.xnio.nio.WorkerThread.run(WorkerThread.java:561)

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Aug 19, 2019
@philwebb
Copy link
Member

@marcusportmann Do you have a sample application that you can share? We're still ideally looking for something that we can clone and run.

@philwebb philwebb added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Aug 20, 2019
@marcusportmann
Copy link

@philwebb I have created a sample application that you can obtain from here: https://github.com/marcusportmann/spring-boot-sample.

If you run the SampleApplication class and open the URL http://localhost:8080/swagger-ui.html#/Sample%20API/testUsingGET in your browser you will be able to invoke the rest controller.

To produce the error just add a '%' character to the testValue parameter.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Aug 21, 2019
@wilkinsona wilkinsona added type: bug A general bug and removed status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged labels Aug 23, 2019
@wilkinsona wilkinsona added this to the 2.1.x milestone Aug 23, 2019
@wilkinsona
Copy link
Member

wilkinsona commented Aug 29, 2019

Curiously, Undertow is inconsistent with regards to which characters are decoded before calling ResourceManager.getResource(String). %2F is left as-is rather than being decoded to /, but, as noted above, %25 is decoded to %. As suspected, this means that replacing % with %25 alone will not work. To be able to safely use URLResource, I believe we need to replace %2F with / and then use URLEncoder to make the entire path URL-friendly.

@wilkinsona wilkinsona self-assigned this Aug 29, 2019
@wilkinsona wilkinsona modified the milestones: 2.1.x, 2.1.8 Aug 29, 2019
@wilkinsona
Copy link
Member

Undertow's own FileResourceManager doesn't take into account %2F not being decoded so it looks for a file with %2F in its name rather than /. If we take it into account in our MetaInfResourcesResourceManager, we end up with the situation where a resource in a jar's META-INF/resources is found correctly, but if that jar's exploded it is not. The most likely scenario for the "jar" being exploded is when developing an application in an IDE and the jar is actually a another project that ultimately jarred and packaged in the app's jar or war.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

5 participants