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]

Reply via email to