Bug 53257 - getLastModified() of compilation context returns negative number
Summary: getLastModified() of compilation context returns negative number
Status: RESOLVED FIXED
Alias: None
Product: Tomcat 7
Classification: Unclassified
Component: Jasper (show other bugs)
Version: 7.0.27
Hardware: PC Windows Server 2003
: P2 normal (vote)
Target Milestone: ---
Assignee: Tomcat Developers Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2012-05-18 06:24 UTC by A. Etzlstorfer
Modified: 2012-05-31 20:43 UTC (History)
0 users



Attachments
full stacktrace (3.50 KB, text/plain)
2012-05-18 06:24 UTC, A. Etzlstorfer
Details

Note You need to log in before you can comment on or make changes to this bug.
Description A. Etzlstorfer 2012-05-18 06:24:44 UTC
Created attachment 28802 [details]
full stacktrace

Jasper Compiler has an issue on compiling JSPs if they are contained in a folder like the following example: /a+b/test.jsp. The plus charachter seems to break 
 the org.apache.jasper.JspCompilationContext#getLastModified() method, because it returns -1 every time:

java.lang.IllegalArgumentException: Negative time
	java.io.File.setLastModified(File.java:1258)
	org.apache.jasper.compiler.Compiler.compile(Compiler.java:376)
	org.apache.jasper.compiler.Compiler.compile(Compiler.java:353)
	org.apache.jasper.compiler.Compiler.compile(Compiler.java:340)
	org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:644)
	org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:358)
	org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:389)
	org.apache.jasper.servlet.JspServlet.service(JspServlet.java:333)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
	*******************************************************
	*******************************************************
	net.sf.ehcache.constructs.web.filter.GzipFilter.doFilter(GzipFilter.java:81)
	net.sf.ehcache.constructs.web.filter.Filter.doFilter(Filter.java:92)
	org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
	org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)

Some details:
 - First I suspected URIEncoding="utf-8" in the Connector, but removing the configuration didn't fix the bug
 - The same bug occurs on my developing machine (Windows 7, NTFS, Apache Tomcat 7.0.22)
 - On a other customer machine the same example as above worked fine! (Any Linux Distribution, Apache Tomcat 7.0.19)
 - By the way I also tried the right URI-Encoding: http://.../a%2Bb/test.jsp

Please find attached the full stack trace

Best Regards
A. Etzlstorfer
Comment 1 Konstantin Kolinko 2012-05-18 06:37:32 UTC
Confirmed, with 7.0.x built 2012-05-13.

E.g. in examples webapp rename
webapps\examples\jsp\jsp2\el\basic-arithmetic.jsp 
into
webapps\examples\jsp\jsp2\el\basic+arithmetic.jsp 

Trying to access it fails
http://localhost:8080/examples/jsp/jsp2/el/basic+arithmetic.jsp
[[[
java.lang.IllegalArgumentException: Negative time
	java.io.File.setLastModified(File.java:1258)
	org.apache.jasper.compiler.Compiler.compile(Compiler.java:376)
]]]
Comment 2 Konstantin Kolinko 2012-05-18 07:50:43 UTC
To determine the file date Compiler class calls JspCompilationContext#getLastModified(String resource)
where in my example the resource value is
'/jsp/jsp2/el/basic+arithmetic.jsp'

It calls some method that converts it into URL,
'jndi:/localhost/examples/jsp/jsp2/el/basic+arithmetic.jsp'

It opens url connection from this URL (implemented by DirContextUrlConnection), then calls methods on it, which result in calling DirContextUrlConnection#connect().


The problem is in the following line in o.a.naming.resource.DirContextUrlConnection#connect():

160        path = URLDecoder.decode(path, "UTF-8");

This call was added in r1152593 to address bug 51584.

There are two problems with the line 160 here.
1) The JRE method called here decodes '+' as ' '. That would be correct in a query string or in POST body, but here (in a path) it is not correct.

2) The decode call itself here is wrong. To demonstrate, create one more copy of that sample jsp under the following name: "basic%abarithmetic.jsp"

Now let's try accessing
http://localhost:8080/examples/jsp/jsp2/el/basic%2Barithmetic.jsp
http://localhost:8080/examples/jsp/jsp2/el/basic%25abarithmetic.jsp

In JspContext#getLastModified(String resource) the value of resource argument will be
'/jsp/jsp2/el/basic+arithmetic.jsp'
'/jsp/jsp2/el/basic%abarithmetic.jsp'

The URLDecoder.decode() call will decode the above paths one more time, which is wrong.
Comment 3 Konstantin Kolinko 2012-05-18 08:56:51 UTC
Conversion of string -> URL is performed via a call to ServletContext.getResource(path).

I have some trouble understanding whether ServletContext API operates on urlencoded on non-urlencoded paths.

My point of view is that argument of ServletContext.getResource() and the paths returned by ServletContext.getResourcePaths() are just file system paths and they should not be urlencoded.


From the above it follows that method
o.a.catalina.core.ApplicationContext#getResource(String) that returns

                return new URL
                    ("jndi", "", 0, getJNDIUri(hostName, fullPath),
                     new DirContextURLStreamHandler(resources));

is responsible to perform urlencoding of the path. The docs for java.net.URL say that "The URL class does not itself encode or decode any URL components according to the escaping mechanism defined in RFC2396." So it should urlencode the path before constructing the URL. 
Maybe use new URI(.,.,.,.,.,.,).toURL(); here, as URI class handles encoding.


I think that JspCompilationContext methods such as #getResource(String), #getLastModified(String) should also operate on non-urlencoded strings, like ServletContext does. (That is what they already do today. I am just clarifying my point of view).


There is method DirContextURLConnection#list(). I do not see it called anywhere besides o.a.c.startup.ContextConfig#processAnnotationsJndi(), so it is like our internal API. It returns url-encoded paths (as modified by 1152593). It looks OK with the only place where it is used (and this API is not related to ServletContext.getResourcePaths()), but needs to be documented.


(- BTW, in JspContext#getResource(String) there is
306   result = context.getResource(canonicalURI(res));

The "canonicalURI(res)" call does not hurt, but it seems excessive because ApplicationContext#getResource(String) will call RequestUtil.normalize() on its argument, which does effectively the same.
)
Comment 4 Mark Thomas 2012-05-31 18:31:19 UTC
(In reply to comment #3)
> Conversion of string -> URL is performed via a call to
> ServletContext.getResource(path).
> 
> I have some trouble understanding whether ServletContext API operates on
> urlencoded on non-urlencoded paths.
> 
> My point of view is that argument of ServletContext.getResource() and the
> paths returned by ServletContext.getResourcePaths() are just file system
> paths and they should not be urlencoded.

Having looked at the Javadoc for ServletContext.getResource() I have the opposite view. Since the URL returned is meant to be independent of where the resource is located, then it can't be a file system path. It is a URL and as such needs to be encoded. It could easily be using the http scheme. I haven't yet checked the rest of the spec for language that supports or undermines that view. That job is next.

> From the above it follows that method
> o.a.catalina.core.ApplicationContext#getResource(String) that returns
> 
>                 return new URL
>                     ("jndi", "", 0, getJNDIUri(hostName, fullPath),
>                      new DirContextURLStreamHandler(resources));
> 
> is responsible to perform urlencoding of the path. The docs for java.net.URL
> say that "The URL class does not itself encode or decode any URL components
> according to the escaping mechanism defined in RFC2396." So it should
> urlencode the path before constructing the URL. 
> Maybe use new URI(.,.,.,.,.,.,).toURL(); here, as URI class handles encoding.

That I do agree with, although I worry about what it might break.

> I think that JspCompilationContext methods such as #getResource(String),
> #getLastModified(String) should also operate on non-urlencoded strings, like
> ServletContext does. (That is what they already do today. I am just
> clarifying my point of view).

Not sure on this one. I suspect that these may also need to be encoded to handle all the edge cases.

> There is method DirContextURLConnection#list(). I do not see it called
> anywhere besides o.a.c.startup.ContextConfig#processAnnotationsJndi(), so it
> is like our internal API. It returns url-encoded paths (as modified by
> 1152593). It looks OK with the only place where it is used (and this API is
> not related to ServletContext.getResourcePaths()), but needs to be
> documented.

Whatever is decided, more documentation is almost certain to be helpful to those looking at this code in a few years time.

> (- BTW, in JspContext#getResource(String) there is
> 306   result = context.getResource(canonicalURI(res));
> 
> The "canonicalURI(res)" call does not hurt, but it seems excessive because
> ApplicationContext#getResource(String) will call RequestUtil.normalize() on
> its argument, which does effectively the same.
> )

Then lets remove that bit.
Comment 5 Mark Thomas 2012-05-31 20:43:52 UTC
The original fix for bug 51584 has been reverted and a new fix applied that fixes that issue and some additional issues new unit tests created for this bug identified.

The fix has been applied to trunk and 7.0.x and will be included in 7.0.28 onwards.