Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion java/org/apache/jasper/compiler/Compiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.Jar;
import org.apache.tomcat.util.descriptor.tld.TldResourcePath;
import org.apache.tomcat.util.scan.JarFactory;

/**
Expand Down Expand Up @@ -474,7 +475,40 @@ public boolean isOutDated(boolean checkClass) {
String key = include.getKey();
URL includeUrl;
long includeLastModified;
if (key.startsWith("jar:jar:")) {
if (key.startsWith("uri:")) {
// Key is a stable taglib URI used for TLDs in JARs outside
// the web application (avoids baking absolute paths into the
// generated code). Two forms exist:
// "uri:<taglib-uri>" – the JAR file itself
// "uri:<taglib-uri>!/<entry>" – a TLD entry within the JAR
int bangSlash = key.indexOf("!/");
String tagUri = bangSlash < 0
? key.substring(4)
: key.substring(4, bangSlash);
TldCache tldCache = ctxt.getOptions().getTldCache();
TldResourcePath tldPath = tldCache.getTldResourcePath(tagUri);
if (tldPath == null) {
return true;
}
if (bangSlash < 0) {
// JAR-level key: check the JAR file's last-modified
URLConnection urlConn = tldPath.getUrl().openConnection();
try {
includeLastModified = urlConn.getLastModified();
} finally {
urlConn.getInputStream().close();
}
} else {
// TLD-entry key: check the entry's last-modified within the JAR
String entryName = key.substring(bangSlash + 2);
try (Jar jar = tldPath.openJar()) {
if (jar == null) {
return true;
}
includeLastModified = jar.getLastModified(entryName);
}
}
} else if (key.startsWith("jar:jar:")) {
// Assume we constructed this correctly
int entryStart = key.lastIndexOf("!/");
String entry = key.substring(entryStart + 2);
Expand Down
18 changes: 14 additions & 4 deletions java/org/apache/jasper/compiler/TagLibraryInfoImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ public String toString() {
}
if (jar != null) {
if (path == null) {
// JAR not in the web application so add it directly
// JAR not in the web application so add it directly. Use the
// stable taglib URI as the dependency key instead of the
// absolute JAR URL to keep the generated servlet code
// deterministic across build environments.
URL jarUrl = jar.getJarFileURL();
long lastMod;
URLConnection urlConn = null;
Expand All @@ -151,12 +154,19 @@ public String toString() {
}
}
}
pageInfo.addDependant(jarUrl.toExternalForm(), Long.valueOf(lastMod));
pageInfo.addDependant("uri:" + uriIn, Long.valueOf(lastMod));
}
// Add TLD within the JAR to the dependency list
// Add TLD within the JAR to the dependency list. For external
// JARs (path == null) use a stable "uri:...!/entryName" key
// instead of the absolute jar.getURL(entryName) to keep the
// generated servlet code deterministic across build environments.
String entryName = tldResourcePath.getEntryName();
try {
pageInfo.addDependant(jar.getURL(entryName), Long.valueOf(jar.getLastModified(entryName)));
String tldKey = path != null
? jar.getURL(entryName)
: "uri:" + uriIn + "!/" + entryName;
pageInfo.addDependant(tldKey,
Long.valueOf(jar.getLastModified(entryName)));
} catch (IOException ioe) {
throw new JasperException(ioe);
}
Expand Down
120 changes: 119 additions & 1 deletion test/org/apache/jasper/compiler/TestTagLibraryInfoImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,29 @@
*/
package org.apache.jasper.compiler;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;

import jakarta.servlet.http.HttpServletResponse;

import org.junit.Assert;
import org.junit.Test;

import org.apache.catalina.Context;
import org.apache.catalina.Wrapper;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.jasper.servlet.JspServlet;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.scan.StandardJarScanFilter;
import org.apache.tomcat.util.scan.StandardJarScanner;

/**
* Test case for {@link TagLibraryInfoImpl}.
Expand All @@ -39,7 +55,6 @@ public void testRelativeTldLocation() throws Exception {
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
}


/*
* https://bz.apache.org/bugzilla/show_bug.cgi?id=64373
*/
Expand All @@ -53,4 +68,107 @@ public void testTldFromExplodedWar() throws Exception {
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
}

/*
* https://bz.apache.org/bugzilla/show_bug.cgi?id=70001
*
* Verify that taglib directives referencing a TLD in a JAR that is outside
* the web application (i.e. on the classpath but not in WEB-INF/lib) produce
* a stable, environment-independent key in the generated servlet's
* {@code _jspx_dependants} map.
*
* Before the fix, the key was an absolute {@code jar:file:/...} URL that
* encoded the build-environment-specific JAR location, making JSP compilation
* non-deterministic. After the fix the key must use the {@code "uri:"} prefix
* followed by the taglib URI from the JSP directive.
*/
@Test
public void testExternalTaglibDependantUsesUri() throws Exception {
Tomcat tomcat = getTomcatInstance();
File appDir = new File("test/webapp");
Context ctx = tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());

StandardJarScanner scanner = (StandardJarScanner) ctx.getJarScanner();
StandardJarScanFilter filter = (StandardJarScanFilter) scanner.getJarScanFilter();
filter.setTldSkip(filter.getTldSkip() + ",testclasses");
filter.setPluggabilitySkip(filter.getPluggabilitySkip() + ",testclasses");

// Add a JAR containing the test TLD to the *parent* classloader rather
// than to WEB-INF/lib. The TLD scanner then sees it as an external JAR
// (TldResourcePath.getWebappPath() == null), which is the code path that
// the fix for non-deterministic _jspx_dependants addresses.
File jar = createExternalTaglibJar();
ClassLoader parent = Thread.currentThread().getContextClassLoader();
ctx.setParentClassLoader(new URLClassLoader(new URL[] { jar.toURI().toURL() }, parent));

tomcat.start();

ByteChunk body = new ByteChunk();
int rc = getUrl("http://localhost:" + getPort() +
"/test/jsp/generator/external-taglib.jsp", body, null);
Assert.assertEquals(body.toString(), HttpServletResponse.SC_OK, rc);

// Retrieve the _jspx_dependants map from the compiled servlet via the
// JspServletWrapper.
Context webCtx = (Context) tomcat.getHost().findChild("/test");
Wrapper jspWrapper = (Wrapper) webCtx.findChild("jsp");
JspServlet jspServlet = (JspServlet) jspWrapper.getServlet();
Field rctxtField = JspServlet.class.getDeclaredField("rctxt");
rctxtField.setAccessible(true);
JspRuntimeContext rctxt = (JspRuntimeContext) rctxtField.get(jspServlet);
Map<String,Long> dependants = rctxt.getWrapper(
"/jsp/generator/external-taglib.jsp").getDependants();

Assert.assertNotNull("Expected non-null _jspx_dependants map", dependants);

// No key in _jspx_dependants should be an absolute file/jar URL.
// Such URLs embed environment-specific paths and make JSP compilation
// non-deterministic.
for (String key : dependants.keySet()) {
Assert.assertFalse(
"_jspx_dependants must not contain absolute paths for external taglib JARs, got: " + key,
key.startsWith("jar:file:") || key.startsWith("file:"));
}

// The external taglib JAR and its TLD entry must each be recorded with
// a stable "uri:" key rather than an absolute path.
Assert.assertTrue(
"Expected 'uri:http://tomcat.apache.org/test/external-taglib' key in _jspx_dependants",
dependants.containsKey("uri:http://tomcat.apache.org/test/external-taglib"));
Assert.assertTrue(
"Expected 'uri:http://tomcat.apache.org/test/external-taglib!/META-INF/external-taglib-test.tld'" +
" key in _jspx_dependants",
dependants.containsKey(
"uri:http://tomcat.apache.org/test/external-taglib!/META-INF/external-taglib-test.tld"));
}

/**
* Creates a temporary JAR containing a minimal TLD with URI
* {@code http://tomcat.apache.org/test/external-taglib}. The TLD has no
* validator and no tag-handler classes so the JAR itself is the only
* dependency required to compile a JSP that references it.
*/
private static File createExternalTaglibJar() throws Exception {
String tld =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<taglib xmlns=\"http://java.sun.com/xml/ns/javaee\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee " +
"http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd\"\n" +
" version=\"2.1\">\n" +
" <tlib-version>1.0</tlib-version>\n" +
" <short-name>ext</short-name>\n" +
" <uri>http://tomcat.apache.org/test/external-taglib</uri>\n" +
"</taglib>\n";

File jar = File.createTempFile("external-taglib-test", ".jar");
jar.deleteOnExit();

try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(jar))) {
jos.putNextEntry(new JarEntry("META-INF/external-taglib-test.tld"));
jos.write(tld.getBytes(StandardCharsets.UTF_8));
jos.closeEntry();
}

return jar;
}
}
19 changes: 19 additions & 0 deletions test/webapp/jsp/generator/external-taglib.jsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<%--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--%>
<%@ page contentType="text/plain" %>
<%@ taglib prefix="ext" uri="http://tomcat.apache.org/test/external-taglib" %>
OK