jetty.project icon indicating copy to clipboard operation
jetty.project copied to clipboard

500 response when trying to display symlinked directory

Open lachlan-roberts opened this issue 1 year ago • 2 comments

Jetty version(s) 12

Enhancement Description

Scenario: The contexts base resource is /foo, there is a symlink at /foo/bar which points to /other which is a directory. Inside /other you have another directory called files.

If you try to access the path /foo/bar/other, and the server will generate a 500 response.

jakarta.servlet.ServletException: java.io.IOException: Is a directory
    at org.eclipse.jetty.ee10.servlet.DefaultServlet.doGet(DefaultServlet.java:539)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:527)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
    at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:736)
    at org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1614)
    at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1547)
    at org.eclipse.jetty.ee10.servlet.ServletChannel.dispatch(ServletChannel.java:799)
    at org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:428)
    at org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:464)
    at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:761)
    at org.eclipse.jetty.server.handler.HotSwapHandler.handle(HotSwapHandler.java:92)
    at org.eclipse.jetty.server.Server.handle(Server.java:179)
    at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:594)
    at org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:424)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)
    at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:971)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1201)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1156)
    at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.io.IOException: Is a directory
    at java.base/sun.nio.ch.UnixFileDispatcherImpl.read0(Native Method)
    at java.base/sun.nio.ch.UnixFileDispatcherImpl.read(UnixFileDispatcherImpl.java:51)
    at java.base/sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:340)
    at java.base/sun.nio.ch.IOUtil.read(IOUtil.java:294)
    at java.base/sun.nio.ch.IOUtil.read(IOUtil.java:283)
    at java.base/sun.nio.ch.FileChannelImpl.read(FileChannelImpl.java:234)
    at org.eclipse.jetty.server.ResourceService$ContentWriterIteratingCallback.process(ResourceService.java:947)
    at org.eclipse.jetty.util.IteratingCallback.processing(IteratingCallback.java:250)
    at org.eclipse.jetty.util.IteratingCallback.iterate(IteratingCallback.java:231)
    at org.eclipse.jetty.server.ResourceService.writeHttpContent(ResourceService.java:722)
    at org.eclipse.jetty.server.ResourceService.sendData(ResourceService.java:666)
    at org.eclipse.jetty.server.ResourceService.doGet(ResourceService.java:205)
    at org.eclipse.jetty.ee10.servlet.DefaultServlet.doGet(DefaultServlet.java:534)
    ... 20 more

This is because ResourceService does content.getResource().isDirectory() to determine whether to do the directory listing. This is implemented by PathResource as Files.isDirectory(getPath(), LinkOption.NOFOLLOW_LINKS) and /foo/bar is a symlink file not a directory when you don't follow links.

Interestingly this does not affect the path /foo/bar/files, for some reason Files.isDirectory("/foo/bar/files", LinkOption.NOFOLLOW_LINKS) would return true, even though /foo/bar is a symlink.

lachlan-roberts avatar Jan 16 '24 06:01 lachlan-roberts

@joakime thoughts?

lachlan-roberts avatar Jan 16 '24 06:01 lachlan-roberts

I wonder if the Resource.isDirectory() protection on LinkOption.NOFOLLOW_LINKS is sane here. I don't think it is. I cannot think of a sane reason we use NOFOLLOW_LINKS for this Resource.isDirectory() test. We have other mechanisms for determining if the Resource is an alias.

joakime avatar Jan 16 '24 16:01 joakime