This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/11.0.x by this push:
new e182d86b7d Allow WebSocket request URIs to contain '{' and/or '}'
e182d86b7d is described below
commit e182d86b7d4cc19ec4c24c38f37acc004404fe8e
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 7f46e6c948..0a761b3745 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 6a30452b2c..7f46ed3544 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 f55d7d6709..e1a983968c 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -283,6 +283,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]