This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/10.1.x by this push:
     new 5b48790abd Allow WebSocket request URIs to contain '{' and/or '}'
5b48790abd is described below

commit 5b48790abd13d94c2bd351027a39a671916b5ddf
Author: Mark Thomas <[email protected]>
AuthorDate: Wed Aug 19 11:32:22 2026 +0100

    Allow WebSocket request URIs to contain '{' and/or '}'
---
 .../websocket/server/LocalStrings.properties       |  1 +
 .../tomcat/websocket/server/UriTemplate.java       | 35 +++++++----
 .../tomcat/websocket/server/WsServerContainer.java |  2 +-
 .../tomcat/websocket/server/TestUriTemplate.java   | 68 ++++++++++++++++++++++
 webapps/docs/changelog.xml                         |  5 ++
 5 files changed, 99 insertions(+), 12 deletions(-)

diff --git a/java/org/apache/tomcat/websocket/server/LocalStrings.properties 
b/java/org/apache/tomcat/websocket/server/LocalStrings.properties
index 0587733b41..6a40e51bb2 100644
--- a/java/org/apache/tomcat/websocket/server/LocalStrings.properties
+++ b/java/org/apache/tomcat/websocket/server/LocalStrings.properties
@@ -26,6 +26,7 @@ serverContainer.servletContextMissing=No ServletContext was 
specified
 upgradeUtil.incompatibleRsv=Extensions were specified that have incompatible 
RSV bit usage
 upgradeUtil.pojoMapFail=Unable to complete method mapping for POJO class [{0}]
 
+uriTemplate.candidateParameters=Candidate URIs are not permitted to contain 
URI template parameters
 uriTemplate.duplicateParameter=The parameter [{0}] appears more than once in 
the path which is not permitted
 uriTemplate.invalidPath=The path [{0}] is not valid.
 uriTemplate.invalidSegment=The segment [{0}] is not valid in the provided path 
[{1}]
diff --git a/java/org/apache/tomcat/websocket/server/UriTemplate.java 
b/java/org/apache/tomcat/websocket/server/UriTemplate.java
index 39571cde2a..e94f53bcc1 100644
--- a/java/org/apache/tomcat/websocket/server/UriTemplate.java
+++ b/java/org/apache/tomcat/websocket/server/UriTemplate.java
@@ -48,6 +48,10 @@ public class UriTemplate {
      * @throws DeploymentException if the path is invalid
      */
     public UriTemplate(String path) throws DeploymentException {
+        this(path, true);
+    }
+
+    public UriTemplate(String path, boolean parseParameters) throws 
DeploymentException {
 
         if (path == null || !path.startsWith("/") || path.contains("/../") || 
path.contains("/./") ||
                 path.contains("//")) {
@@ -74,19 +78,23 @@ public class UriTemplate {
             }
             normalized.append('/');
             int index = -1;
-            if (segment.startsWith("{") && segment.endsWith("}")) {
-                index = segmentCount;
-                segment = segment.substring(1, segment.length() - 1);
-                normalized.append('{');
-                normalized.append(paramCount++);
-                normalized.append('}');
-                if (!paramNames.add(segment)) {
-                    throw new 
DeploymentException(sm.getString("uriTemplate.duplicateParameter", segment));
+            if (parseParameters) {
+                if (segment.startsWith("{") && segment.endsWith("}")) {
+                    index = segmentCount;
+                    segment = segment.substring(1, segment.length() - 1);
+                    normalized.append('{');
+                    normalized.append(paramCount++);
+                    normalized.append('}');
+                    if (!paramNames.add(segment)) {
+                        throw new 
DeploymentException(sm.getString("uriTemplate.duplicateParameter", segment));
+                    }
+                } else {
+                    if (segment.contains("{") || segment.contains("}")) {
+                        throw new 
DeploymentException(sm.getString("uriTemplate.invalidSegment", segment, path));
+                    }
+                    normalized.append(segment);
                 }
             } else {
-                if (segment.contains("{") || segment.contains("}")) {
-                    throw new 
DeploymentException(sm.getString("uriTemplate.invalidSegment", segment, path));
-                }
                 normalized.append(segment);
             }
             this.segments.add(new Segment(index, segment));
@@ -106,6 +114,11 @@ public class UriTemplate {
      */
     public Map<String,String> match(UriTemplate candidate) {
 
+        // Candidate URIs should not contain parameters.
+        if (candidate.hasParameters) {
+            throw new 
IllegalStateException(sm.getString("uriTemplate.candidateParameters"));
+        }
+
         Map<String,String> result = new HashMap<>();
 
         // Should not happen but for safety
diff --git a/java/org/apache/tomcat/websocket/server/WsServerContainer.java 
b/java/org/apache/tomcat/websocket/server/WsServerContainer.java
index a0ac176314..4b9edbd910 100644
--- a/java/org/apache/tomcat/websocket/server/WsServerContainer.java
+++ b/java/org/apache/tomcat/websocket/server/WsServerContainer.java
@@ -294,7 +294,7 @@ public class WsServerContainer extends WsWebSocketContainer 
implements ServerCon
         // No exact match. Need to look for template matches.
         UriTemplate pathUriTemplate;
         try {
-            pathUriTemplate = new UriTemplate(path);
+            pathUriTemplate = new UriTemplate(path, false);
         } catch (DeploymentException e) {
             // Path is not valid so can't be matched to a WebSocketEndpoint
             return null;
diff --git a/test/org/apache/tomcat/websocket/server/TestUriTemplate.java 
b/test/org/apache/tomcat/websocket/server/TestUriTemplate.java
index 6cc795be93..5aa5fa6404 100644
--- a/test/org/apache/tomcat/websocket/server/TestUriTemplate.java
+++ b/test/org/apache/tomcat/websocket/server/TestUriTemplate.java
@@ -227,4 +227,72 @@ public class TestUriTemplate {
         @SuppressWarnings("unused")
         UriTemplate t = new UriTemplate("//b");
     }
+
+
+    @Test
+    public void testCandidateBraces01() throws Exception {
+        UriTemplate t = new UriTemplate("/a/b/{c}");
+        Map<String, String> result = t.match(new UriTemplate("/a/{b}/other", 
false));
+        Assert.assertNull(result);
+    }
+
+
+    @Test
+    public void testCandidateBraces02() throws Exception {
+        UriTemplate t = new UriTemplate("/a/b/{c}");
+        Map<String, String> result = t.match(new UriTemplate("/a/b/{other}", 
false));
+        Assert.assertNotNull(result);
+        Assert.assertEquals(1, result.size());
+        Assert.assertEquals("{other}", result.get("c"));
+    }
+
+
+    @Test
+    public void testCandidateBraces03() throws Exception {
+        UriTemplate t = new UriTemplate("/a/b/{c}");
+        Map<String, String> result = t.match(new UriTemplate("/a/b/ot{h}er", 
false));
+        Assert.assertNotNull(result);
+        Assert.assertEquals(1, result.size());
+        Assert.assertEquals("ot{h}er", result.get("c"));
+    }
+
+
+    @Test
+    public void testCandidateBraces04() throws Exception {
+        UriTemplate t = new UriTemplate("/a/b/{c}");
+        Map<String, String> result = t.match(new UriTemplate("/a/b/{other", 
false));
+        Assert.assertNotNull(result);
+        Assert.assertEquals(1, result.size());
+        Assert.assertEquals("{other", result.get("c"));
+    }
+
+
+    @Test
+    public void testCandidateBraces05() throws Exception {
+        UriTemplate t = new UriTemplate("/a/b/{c}");
+        Map<String, String> result = t.match(new UriTemplate("/a/b/ot{her", 
false));
+        Assert.assertNotNull(result);
+        Assert.assertEquals(1, result.size());
+        Assert.assertEquals("ot{her", result.get("c"));
+    }
+
+
+    @Test
+    public void testCandidateBraces06() throws Exception {
+        UriTemplate t = new UriTemplate("/a/b/{c}");
+        Map<String, String> result = t.match(new UriTemplate("/a/b/other}", 
false));
+        Assert.assertNotNull(result);
+        Assert.assertEquals(1, result.size());
+        Assert.assertEquals("other}", result.get("c"));
+    }
+
+
+    @Test
+    public void testCandidateBraces07() throws Exception {
+        UriTemplate t = new UriTemplate("/a/b/{c}");
+        Map<String, String> result = t.match(new UriTemplate("/a/b/oth}er", 
false));
+        Assert.assertNotNull(result);
+        Assert.assertEquals(1, result.size());
+        Assert.assertEquals("oth}er", result.get("c"));
+    }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 4eec0edef8..ba7cc3130c 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -267,6 +267,11 @@
       <fix>
         Reduce CPU usage while sending WebSocket close message. (markt)
       </fix>
+      <fix>
+        Fix overly broad check that prevented request URIs containing literal
+        <code>{</code> and <code>}</code> characters from being mapped to
+        WebSocket end points. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Web applications">


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to