This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 3ca8cae5fd Fix xml output of webxml
3ca8cae5fd is described below
commit 3ca8cae5fd3796b1bd9759e11b0e238161e7a39c
Author: Mark Thomas <[email protected]>
AuthorDate: Mon Jun 8 17:22:18 2026 +0100
Fix xml output of webxml
Missing entries, formatting, correct URI encoding
---
.../apache/tomcat/util/descriptor/web/WebXml.java | 117 ++++++++++----
.../tomcat/util/descriptor/web/TestWebXml.java | 172 +++++++++++++++++++++
webapps/docs/changelog.xml | 5 +
3 files changed, 263 insertions(+), 31 deletions(-)
diff --git a/java/org/apache/tomcat/util/descriptor/web/WebXml.java
b/java/org/apache/tomcat/util/descriptor/web/WebXml.java
index 62986b1d42..2d3c7cda5e 100644
--- a/java/org/apache/tomcat/util/descriptor/web/WebXml.java
+++ b/java/org/apache/tomcat/util/descriptor/web/WebXml.java
@@ -16,9 +16,9 @@
*/
package org.apache.tomcat.util.descriptor.web;
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
-import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
@@ -43,6 +43,8 @@ import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.B2CConverter;
import org.apache.tomcat.util.buf.UDecoder;
+import org.apache.tomcat.util.buf.UEncoder;
+import org.apache.tomcat.util.buf.UEncoder.SafeCharsSet;
import org.apache.tomcat.util.descriptor.XmlIdentifiers;
import org.apache.tomcat.util.digester.DocumentProperties;
import org.apache.tomcat.util.res.StringManager;
@@ -71,6 +73,8 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
private final Log log = LogFactory.getLog(WebXml.class); // must not be
static
+ private final UEncoder urlEncoder = new UEncoder(SafeCharsSet.WITH_SLASH);
+
/**
* Global defaults are overridable but Servlets and Servlet mappings need
to be unique. Duplicates normally trigger
* an error. This flag indicates if newly added Servlet elements are
marked as overridable.
@@ -1432,7 +1436,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "param-value", entry.getValue());
sb.append(" </context-param>\n");
}
- sb.append('\n');
+ if (!contextParams.isEmpty()) {
+ sb.append('\n');
+ }
// Filters were introduced in Servlet 2.3
if (getMajorVersion() > 2 || getMinorVersion() > 2) {
@@ -1455,7 +1461,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
}
sb.append(" </filter>\n");
}
- sb.append('\n');
+ if (!filters.isEmpty()) {
+ sb.append('\n');
+ }
for (FilterMap filterMap : filterMaps) {
sb.append(" <filter-mapping>\n");
@@ -1485,7 +1493,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
}
sb.append(" </filter-mapping>\n");
}
- sb.append('\n');
+ if (!filterMaps.isEmpty()) {
+ sb.append('\n');
+ }
}
// Listeners were introduced in Servlet 2.3
@@ -1495,7 +1505,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "listener-class", listener);
sb.append(" </listener>\n");
}
- sb.append('\n');
+ if (!listeners.isEmpty()) {
+ sb.append('\n');
+ }
}
for (Map.Entry<String,ServletDef> entry : servlets.entrySet()) {
@@ -1546,7 +1558,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
}
sb.append(" </servlet>\n");
}
- sb.append('\n');
+ if (!servlets.isEmpty()) {
+ sb.append('\n');
+ }
for (Map.Entry<String,String> entry : servletMappings.entrySet()) {
sb.append(" <servlet-mapping>\n");
@@ -1554,9 +1568,12 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "url-pattern",
encodeUrl(entry.getKey()));
sb.append(" </servlet-mapping>\n");
}
- sb.append('\n');
+ if (!servletMappings.isEmpty()) {
+ sb.append('\n');
+ }
- if (sessionConfig != null) {
+ if (sessionConfig.getSessionTimeout() != null ||
sessionConfig.getCookieName() != null ||
+ !sessionConfig.getSessionTrackingModes().isEmpty()) {
sb.append(" <session-config>\n");
appendElement(sb, INDENT4, "session-timeout",
sessionConfig.getSessionTimeout());
if (majorVersion >= 3) {
@@ -1582,7 +1599,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "mime-type", entry.getValue());
sb.append(" </mime-mapping>\n");
}
- sb.append('\n');
+ if (!mimeMappings.isEmpty()) {
+ sb.append('\n');
+ }
if (!welcomeFiles.isEmpty()) {
sb.append(" <welcome-file-list>\n");
@@ -1606,10 +1625,12 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
} else if (errorPage.getErrorCode() > 0) {
appendElement(sb, INDENT4, "error-code",
Integer.toString(errorCode));
}
- appendElement(sb, INDENT4, "location", errorPage.getLocation());
+ appendElement(sb, INDENT4, "location", errorPage.getLocation(),
true);
sb.append(" </error-page>\n");
}
- sb.append('\n');
+ if (!errorPages.isEmpty()) {
+ sb.append('\n');
+ }
// jsp-config was added in Servlet 2.4. Prior to that, tag-libs was
used
// directly and jsp-property-group did not exist
@@ -1667,7 +1688,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "lookup-name",
resourceEnvRef.getLookupName());
sb.append(" </resource-env-ref>\n");
}
- sb.append('\n');
+ if (!resourceEnvRefs.isEmpty()) {
+ sb.append('\n');
+ }
}
for (ContextResource resourceRef : resourceRefs.values()) {
@@ -1690,7 +1713,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "lookup-name",
resourceRef.getLookupName());
sb.append(" </resource-ref>\n");
}
- sb.append('\n');
+ if (!resourceRefs.isEmpty()) {
+ sb.append('\n');
+ }
for (SecurityConstraint constraint : securityConstraints) {
sb.append(" <security-constraint>\n");
@@ -1713,12 +1738,20 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
}
sb.append(" </web-resource-collection>\n");
}
- if (constraint.findAuthRoles().length > 0) {
+ if (constraint.findAuthRoles().length > 0 ||
constraint.getAllRoles() || constraint.getAuthenticatedUsers()) {
sb.append(" <auth-constraint>\n");
for (String role : constraint.findAuthRoles()) {
appendElement(sb, INDENT6, "role-name", role);
}
+ if (constraint.getAllRoles()) {
+ appendElement(sb, INDENT6, "role-name",
SecurityConstraint.ROLE_ALL_ROLES);
+ }
+ if (constraint.getAuthenticatedUsers()) {
+ appendElement(sb, INDENT6, "role-name",
SecurityConstraint.ROLE_ALL_AUTHENTICATED_USERS);
+ }
sb.append(" </auth-constraint>\n");
+ } else if (constraint.getAuthConstraint()) {
+ sb.append(" <auth-constraint/>\n");
}
if (constraint.getUserConstraint() != null) {
sb.append(" <user-data-constraint>\n");
@@ -1727,7 +1760,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
}
sb.append(" </security-constraint>\n");
}
- sb.append('\n');
+ if (!securityConstraints.isEmpty()) {
+ sb.append('\n');
+ }
if (loginConfig != null) {
sb.append(" <login-config>\n");
@@ -1735,8 +1770,8 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "realm-name",
loginConfig.getRealmName());
if (loginConfig.getErrorPage() != null ||
loginConfig.getLoginPage() != null) {
sb.append(" <form-login-config>\n");
- appendElement(sb, INDENT6, "form-login-page",
loginConfig.getLoginPage());
- appendElement(sb, INDENT6, "form-error-page",
loginConfig.getErrorPage());
+ appendElement(sb, INDENT6, "form-login-page",
loginConfig.getLoginPage(), true);
+ appendElement(sb, INDENT6, "form-error-page",
loginConfig.getErrorPage(), true);
sb.append(" </form-login-config>\n");
}
sb.append(" </login-config>\n\n");
@@ -1764,7 +1799,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "lookup-name",
envEntry.getLookupName());
sb.append(" </env-entry>\n");
}
- sb.append('\n');
+ if (!envEntries.isEmpty()) {
+ sb.append('\n');
+ }
for (ContextEjb ejbRef : ejbRefs.values()) {
sb.append(" <ejb-ref>\n");
@@ -1784,7 +1821,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "lookup-name", ejbRef.getLookupName());
sb.append(" </ejb-ref>\n");
}
- sb.append('\n');
+ if (!ejbRefs.isEmpty()) {
+ sb.append('\n');
+ }
// ejb-local-ref was introduced in Servlet 2.3
if (getMajorVersion() > 2 || getMinorVersion() > 2) {
@@ -1806,7 +1845,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "lookup-name",
ejbLocalRef.getLookupName());
sb.append(" </ejb-local-ref>\n");
}
- sb.append('\n');
+ if (!ejbLocalRefs.isEmpty()) {
+ sb.append('\n');
+ }
}
// service-ref was introduced in Servlet 2.4
@@ -1854,7 +1895,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "lookup-name",
serviceRef.getLookupName());
sb.append(" </service-ref>\n");
}
- sb.append('\n');
+ if (!serviceRefs.isEmpty()) {
+ sb.append('\n');
+ }
}
if (!postConstructMethods.isEmpty()) {
@@ -1897,7 +1940,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "lookup-name", mdr.getLookupName());
sb.append(" </message-destination-ref>\n");
}
- sb.append('\n');
+ if (!messageDestinationRefs.isEmpty()) {
+ sb.append('\n');
+ }
for (MessageDestination md : messageDestinations.values()) {
sb.append(" <message-destination>\n");
@@ -1908,7 +1953,9 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
appendElement(sb, INDENT4, "lookup-name", md.getLookupName());
sb.append(" </message-destination>\n");
}
- sb.append('\n');
+ if (!messageDestinations.isEmpty()) {
+ sb.append('\n');
+ }
}
// locale-encoding-mapping-list was introduced in Servlet 2.4
@@ -1944,17 +1991,21 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
}
- private String encodeUrl(String input) {
+ private synchronized String encodeUrl(String input) {
try {
- return URLEncoder.encode(input, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- // Impossible. UTF-8 is a required character set
- return null;
+ return urlEncoder.encodeURL(input, 0, input.length()).toString();
+ } catch (IOException e) {
+ throw new IllegalArgumentException(input, e);
}
}
- private static void appendElement(StringBuilder sb, String indent, String
elementName, String value) {
+ private void appendElement(StringBuilder sb, String indent, String
elementName, String value) {
+ appendElement(sb, indent, elementName, value, false);
+ }
+
+ private void appendElement(StringBuilder sb, String indent, String
elementName, String value, boolean encodeValue) {
+
if (value == null) {
return;
}
@@ -1968,14 +2019,18 @@ public class WebXml extends XmlEncodingBase implements
DocumentProperties.Charse
sb.append('<');
sb.append(elementName);
sb.append('>');
- sb.append(Escape.xml(value));
+ if (encodeValue) {
+ sb.append(Escape.xml(encodeUrl(value)));
+ } else {
+ sb.append(Escape.xml(value));
+ }
sb.append("</");
sb.append(elementName);
sb.append(">\n");
}
}
- private static void appendElement(StringBuilder sb, String indent, String
elementName, Object value) {
+ private void appendElement(StringBuilder sb, String indent, String
elementName, Object value) {
if (value == null) {
return;
}
diff --git a/test/org/apache/tomcat/util/descriptor/web/TestWebXml.java
b/test/org/apache/tomcat/util/descriptor/web/TestWebXml.java
index 3c19f70b4e..01ec9c7489 100644
--- a/test/org/apache/tomcat/util/descriptor/web/TestWebXml.java
+++ b/test/org/apache/tomcat/util/descriptor/web/TestWebXml.java
@@ -530,4 +530,176 @@ public class TestWebXml {
}
}
+
+
+ @Test
+ public void testToXml01() throws Exception {
+ // Empty web.xml
+ doTestToXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<web-app xmlns=\"http://xmlns.jcp.org/xml/ns/javaee\"\n" +
+ "
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ "
xsi:schemaLocation=\"http://xmlns.jcp.org/xml/ns/javaee" +
+ " http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd\"\n" +
+ " version=\"4.0\"\n" +
+ " metadata-complete=\"true\">\n" +
+ "\n" +
+ "</web-app>");
+ }
+
+
+ @Test
+ public void testToXml02() throws Exception {
+ // Deny all
+ doTestToXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<web-app xmlns=\"http://xmlns.jcp.org/xml/ns/javaee\"\n" +
+ "
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ "
xsi:schemaLocation=\"http://xmlns.jcp.org/xml/ns/javaee" +
+ " http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd\"\n" +
+ " version=\"4.0\"\n" +
+ " metadata-complete=\"true\">\n" +
+ "\n" +
+ " <security-constraint>\n" +
+ " <web-resource-collection>\n" +
+ " <web-resource-name>Test</web-resource-name>\n" +
+ " <url-pattern>/deny-all</url-pattern>\n" +
+ " </web-resource-collection>\n" +
+ " <auth-constraint/>\n" +
+ " <user-data-constraint>\n" +
+ " <transport-guarantee>NONE</transport-guarantee>\n" +
+ " </user-data-constraint>\n" +
+ " </security-constraint>\n" +
+ "\n" +
+ "</web-app>");
+ }
+
+
+ @Test
+ public void testToXml03() throws Exception {
+ // other role plus all roles
+ doTestToXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<web-app xmlns=\"http://xmlns.jcp.org/xml/ns/javaee\"\n" +
+ "
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ "
xsi:schemaLocation=\"http://xmlns.jcp.org/xml/ns/javaee" +
+ " http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd\"\n" +
+ " version=\"4.0\"\n" +
+ " metadata-complete=\"true\">\n" +
+ "\n" +
+ " <security-constraint>\n" +
+ " <web-resource-collection>\n" +
+ " <web-resource-name>Test</web-resource-name>\n" +
+ " <url-pattern>/deny-all</url-pattern>\n" +
+ " </web-resource-collection>\n" +
+ " <auth-constraint>\n" +
+ " <role-name>other</role-name>\n" +
+ " <role-name>*</role-name>\n" +
+ " </auth-constraint>\n" +
+ " <user-data-constraint>\n" +
+ " <transport-guarantee>NONE</transport-guarantee>\n" +
+ " </user-data-constraint>\n" +
+ " </security-constraint>\n" +
+ "\n" +
+ "</web-app>");
+ }
+
+
+ @Test
+ public void testToXml04() throws Exception {
+ // all roles
+ doTestToXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<web-app xmlns=\"http://xmlns.jcp.org/xml/ns/javaee\"\n" +
+ "
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ "
xsi:schemaLocation=\"http://xmlns.jcp.org/xml/ns/javaee" +
+ " http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd\"\n" +
+ " version=\"4.0\"\n" +
+ " metadata-complete=\"true\">\n" +
+ "\n" +
+ " <security-constraint>\n" +
+ " <web-resource-collection>\n" +
+ " <web-resource-name>Test</web-resource-name>\n" +
+ " <url-pattern>/deny-all</url-pattern>\n" +
+ " </web-resource-collection>\n" +
+ " <auth-constraint>\n" +
+ " <role-name>*</role-name>\n" +
+ " </auth-constraint>\n" +
+ " <user-data-constraint>\n" +
+ " <transport-guarantee>NONE</transport-guarantee>\n" +
+ " </user-data-constraint>\n" +
+ " </security-constraint>\n" +
+ "\n" +
+ "</web-app>");
+ }
+
+
+ @Test
+ public void testToXml05() throws Exception {
+ // other role plus authenticated users
+ doTestToXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<web-app xmlns=\"http://xmlns.jcp.org/xml/ns/javaee\"\n" +
+ "
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ "
xsi:schemaLocation=\"http://xmlns.jcp.org/xml/ns/javaee" +
+ " http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd\"\n" +
+ " version=\"4.0\"\n" +
+ " metadata-complete=\"true\">\n" +
+ "\n" +
+ " <security-constraint>\n" +
+ " <web-resource-collection>\n" +
+ " <web-resource-name>Test</web-resource-name>\n" +
+ " <url-pattern>/deny-all</url-pattern>\n" +
+ " </web-resource-collection>\n" +
+ " <auth-constraint>\n" +
+ " <role-name>other</role-name>\n" +
+ " <role-name>**</role-name>\n" +
+ " </auth-constraint>\n" +
+ " <user-data-constraint>\n" +
+ " <transport-guarantee>NONE</transport-guarantee>\n" +
+ " </user-data-constraint>\n" +
+ " </security-constraint>\n" +
+ "\n" +
+ "</web-app>");
+ }
+
+
+ @Test
+ public void testToXml06() throws Exception {
+ // authenticated users
+ doTestToXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<web-app xmlns=\"http://xmlns.jcp.org/xml/ns/javaee\"\n" +
+ "
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ "
xsi:schemaLocation=\"http://xmlns.jcp.org/xml/ns/javaee" +
+ " http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd\"\n" +
+ " version=\"4.0\"\n" +
+ " metadata-complete=\"true\">\n" +
+ "\n" +
+ " <security-constraint>\n" +
+ " <web-resource-collection>\n" +
+ " <web-resource-name>Test</web-resource-name>\n" +
+ " <url-pattern>/deny-all</url-pattern>\n" +
+ " </web-resource-collection>\n" +
+ " <auth-constraint>\n" +
+ " <role-name>**</role-name>\n" +
+ " </auth-constraint>\n" +
+ " <user-data-constraint>\n" +
+ " <transport-guarantee>NONE</transport-guarantee>\n" +
+ " </user-data-constraint>\n" +
+ " </security-constraint>\n" +
+ "\n" +
+ "</web-app>");
+ }
+
+
+ private void doTestToXml(String input) throws Exception {
+ Digester digester = DigesterFactory.newDigester(true, true, new
WebRuleSet(), true);
+
+ XmlErrorHandler handler = new XmlErrorHandler();
+ digester.setErrorHandler(handler);
+
+ InputSource is = new InputSource(new StringReader(input));
+ WebXml webxml = new WebXml();
+ digester.push(webxml);
+ digester.parse(is);
+
+ String output = webxml.toXml();
+
+ Assert.assertEquals(input, output);
+ }
}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index c824eb5e87..50fb6c24c0 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -281,6 +281,11 @@
(according to the examples in the RFC). Use the first acceptable value.
(remm)
</fix>
+ <fix>
+ Fix various issues when logging the effective web.xml for a web
+ application. Empty sections are no longer logged. Special roles and
+ empty authorisation constraints are included. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]