Tomcat 8.5.35 introduced a behavior, which is a bug for us. Still consists in 8.5.50. In our development environments we use symlinks for all of our webapp folders. So under tomcat (resp. catalina base) there is the webapps folder, that contains only symlinks, which point to the actual webapps (not wars). The applications' web.xml files use XML imports like this: ###################### <!DOCTYPE web-xml [ <!ENTITY myentity SYSTEM "../../../foo/bar/myentity.xml"> ]> ###################### This relative import worked just fine in 8.5.34 and prior, but will fail in 8.5.35+. The error message in the log unfortunately does not tell you more than "file not found", but does not say, where it was looking for it. The class org.apache.catalina.startup.ContextConfig, method fixDocBase() introduced a change in 8.5.35 (line 655 in 8.5.50's source), that uses getCanonicalPath() to resolve the absolute path for a resource (in this case web.xml). This path is used as base (systemId) for the WebXmlParser. Since the path has resolved symlinks, but the relative import assumes to originate from a standard catalina_base structure, it won't find the imported file. Instead of getCanonicalPath() you could use something like toPath().toAbsolutePath().normalize(), which does NOT follow symlinks. The behavior differs on Windows, where symlinks (Junktions) are not followed. This bug is critical for us. And there's no way to work around it.
I'm afraid I am going to take a different view. The behaviour you were relying on was a bug and, now that bug has been fixed, the behaviour has changed. For reference the change was as a result of this thread: https://tomcat.markmail.org/thread/gonkmfw5acognpy3 Further, the entity reference in the web.xml shown in the example accessing a location outside of the root of the web application. That goes against the general principal that web applications are meant to be self-contained (and access additional resources via JNDI). I'm wondering if it is possible to map these external xml files into a web application via http://tomcat.apache.org/tomcat-9.0-doc/config/resources.html There have been some changes recently that might impact on this so I'll do some testing locally and report back.
Hi Mark, thanks a lot for taking a look at this. What a pity, that this is considered correct behavior. Couldn't this be configurable like allowLinking? I mean, it may be correct for many, but not for all.
How about the difference between Linux and Windows? Shouldn't this fail on Windows, too, if it does on Linux? Or vice versa, work on both?
Dealing with this first. The behaviour on Linux and Windows is the same. There is no platform specific code in Tomcat's resource handling. Junctions != symbolic links and the JRE treats them differently. Unfortunately the relevant XML API - EntityResolver2 - passes URLs around as Strings. Tomcat has some special handling in the URL instances returned for resources. This is primarily so resource access via URLs is cache aware but the same behaviour would have helped here. As soon as the URLs are converted to Strings that special handling is lost. The Tomcat 7 approach of implementing a Tomcat specific URL scheme would work but the resources refactoring in 8.5.x onwards took a deliberate decision not to do that - for a combination of complexity, performance, maintenance and ease of embedding reasons. I have a few more ideas to try to see if I can find a work-around. Hopefully one that won't require a new configuration option but I'm not against that if that is what is required.
Would it help if Tomcat added ${...} property replacement support so you could do something like: ###################### <!DOCTYPE web-xml [ <!ENTITY myentity SYSTEM "${property.containing.correct.path}"> ]> ###################### And then you could add property.containing.correct.path=/path/relative/to/canonical/location to catalina.properties (or one of the other ways of setting properties). This isn't currently supported but I think I can see a way to add it in a backwards compatible way.
Yes, I was about to suggest that. I was thinking about using ${CATALINA_BASE} as part of the import path. So, yes, that would help a lot. Is it possible by the way to specify a custom entity resolver, which would enable us to solve the problem?
Glad we are thinking in the same direction. The EntityResolver isn't currently configurable. I'll consider that option as well but adding support for ${...} looks like the simplest solution at the moment.
${catalina.base}/../../../myentity.txt works now. Fixed in: - master for 10.0.0.0-M1 onwards - 9.0.x for 9.0.31 onwards - 8.5.x for 8.5.51 onwards - 7.0.x for 7.0.100 onwards
Wow, that was quick. Unfortunately I missed the notification. Sorry for that. This solution is perfect. Thanks a lot.
I'm trying to get this to work. But none of my attempts was successful. $CATALINA_BASE points to "/opt/tomcat/current" <!ENTITY myentity SYSTEM "${catalina.base}/foo/bar/myentity.xml"> The above leads to a MalformedURLException: java.net.MalformedURLException: no protocol: ${catalina.base}/foo/bar/myentity.xml <!ENTITY myentity SYSTEM "file://${catalina.base}/foo/bar/myentity.xml"> The above leads to: java.net.UnknownHostException: ${catalina.base} <!ENTITY myentity SYSTEM "${CATALINA_BASE}/foo/bar/myentity.xml"> And this one leads to: Parse error in application web.xml file at [file:/path/to/webapp/WEB-INF/web.xml] I'm on Tomcat 9.0.31. What am I doing wrong? I should mention, that still my webapp folders under catalina-base/webapps are all symlinks.
I also tried <!ENTITY myentity SYSTEM "file:${catalina.base}/foo/bar/myentity.xml"> which led to: java.io.FileNotFoundException: ${catalina.base}/foo/bar/myentity.xml (Datei oder Verzeichnis nicht gefunden)
I'll take a look. Meanwhile, can you provide the full stack trace please.
Sure. This one is for the variant with only "${catalina.base}/foo/bar/myentity.xml" java.net.MalformedURLException: no protocol: ${catalina.base}/foo/bar/myentity.xml at java.base/java.net.URL.<init>(URL.java:634) at java.base/java.net.URL.<init>(URL.java:530) at java.base/java.net.URL.<init>(URL.java:477) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:651) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1401) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1337) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEntityReference(XMLDocumentFragmentScannerImpl.java:1844) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2985) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:534) at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:888) at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:824) at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141) at java.xml/com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:246) at java.xml/com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:339) at java.xml/javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:206) at com.infolog.commons.xml.w3c.XMLHelper.loadDocument(XMLHelper.java:464) at com.infolog.commons.xml.w3c.XMLHelper.loadDocument(XMLHelper.java:485) at com.infolog.webdoc.server.TomcatEnvironment.readWebXML(TomcatEnvironment.java:232) at com.infolog.webdoc.server.TomcatEnvironment.getMaxUploadSize(TomcatEnvironment.java:259) at com.infolog.webdoc.database.DatabaseParameters.checkValidity(DatabaseParameters.java:127) at com.infolog.webdoc.server.WebDocServletValidation.sanityCheckDatabaseConnection(WebDocServletValidation.java:94) at com.infolog.webdoc.server.WebDocServletValidation.sanityCheckEnvironment(WebDocServletValidation.java:198) at com.infolog.webdoc.server.WebDocServlet.init(WebDocServlet.java:388) at javax.servlet.GenericServlet.init(GenericServlet.java:158) at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1134) at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1089) at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:983) at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4871) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5180) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:717) at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:690) at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:705) at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1133) at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1867) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:118) at org.apache.catalina.startup.HostConfig.deployDirectories(HostConfig.java:1045) at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:429) at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1576) at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:309) at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:123) at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:423) at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:366) at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:936) at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:841) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140) at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909) at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:262) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) at org.apache.catalina.core.StandardService.startInternal(StandardService.java:421) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:930) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) at org.apache.catalina.startup.Catalina.start(Catalina.java:633) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:343) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:474) I am on Java 11 (Coretto). I have stepped into the entity resolver using the debugger. It actually replaces the the property just fine. But somehow the original value reaches the XMLEntityManager.
Oh, please forget that. This code comes from my own XML parsing. I'm very sorry!
If you can, please delete today's comments here (except for the first one) to avoid confusion for others.