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

Comments

@aruanruan
Copy link

commented Aug 13, 2019

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;
			}
		}
@wilkinsona

This comment has been minimized.

Copy link
Member

commented Aug 13, 2019

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

@aruanruan

This comment has been minimized.

Copy link
Author

commented Aug 14, 2019

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.

@wilkinsona

This comment has been minimized.

Copy link
Member

commented Aug 14, 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.

@aruanruan

This comment has been minimized.

Copy link
Author

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

This comment has been minimized.

Copy link
Member

commented Aug 16, 2019

@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.

@marcusportmann

This comment has been minimized.

Copy link

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)
@philwebb

This comment has been minimized.

Copy link
Member

commented Aug 20, 2019

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

@marcusportmann

This comment has been minimized.

Copy link

commented Aug 21, 2019

@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.

@wilkinsona

This comment has been minimized.

Copy link
Member

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

This comment has been minimized.

Copy link
Member

commented Aug 29, 2019

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
Projects
None yet
5 participants
You can’t perform that action at this time.