You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2021/10/14 13:24:36 UTC

[tomcat] branch main updated (d33cce6 -> fee1f45)

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

markt pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git.


    from d33cce6  Ensure request URIs start with /
     new 7095d1b  Add option to reject "suspicious" URIs
     new fee1f45  Add tests cases for URI canonicalization from the Servlet spec

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 java/org/apache/catalina/connector/Connector.java  |  13 ++
 .../apache/catalina/connector/CoyoteAdapter.java   |  91 ++++++++
 .../TestCoyoteAdapterCanonicalization.java         | 235 +++++++++++++++++++++
 .../apache/catalina/startup/SimpleHttpClient.java  |  17 +-
 webapps/docs/changelog.xml                         |   7 +-
 webapps/docs/config/http.xml                       |   6 +
 6 files changed, 363 insertions(+), 6 deletions(-)
 create mode 100644 test/org/apache/catalina/connector/TestCoyoteAdapterCanonicalization.java

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


[tomcat] 02/02: Add tests cases for URI canonicalization from the Servlet spec

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit fee1f457f287a56d3d490a5ab5b3f643d280ecf5
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Wed Oct 13 18:29:30 2021 +0100

    Add tests cases for URI canonicalization from the Servlet spec
---
 .../TestCoyoteAdapterCanonicalization.java         | 235 +++++++++++++++++++++
 .../apache/catalina/startup/SimpleHttpClient.java  |  17 +-
 2 files changed, 247 insertions(+), 5 deletions(-)

diff --git a/test/org/apache/catalina/connector/TestCoyoteAdapterCanonicalization.java b/test/org/apache/catalina/connector/TestCoyoteAdapterCanonicalization.java
new file mode 100644
index 0000000..710e0c7
--- /dev/null
+++ b/test/org/apache/catalina/connector/TestCoyoteAdapterCanonicalization.java
@@ -0,0 +1,235 @@
+/*
+ *  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.
+ */
+package org.apache.catalina.connector;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+import static org.apache.catalina.startup.SimpleHttpClient.CRLF;
+import org.apache.catalina.Context;
+import org.apache.catalina.startup.SimpleHttpClient;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+
+/*
+ * Tests for the canonicalization clarifications in Servlet 6.0
+ */
+@RunWith(Parameterized.class)
+public class TestCoyoteAdapterCanonicalization extends TomcatBaseTest {
+
+    @Parameterized.Parameters(name = "{index}: requestURI[{0}]")
+    public static Collection<Object[]> parameters() {
+        List<Object[]> parameterSets = new ArrayList<>();
+
+        // This should be consistent with the table in the Servlet specification
+        parameterSets.add(new Object[] { "foo/bar", "/foo/bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/bar", "/foo/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/bar;jsessionid=1234", "/foo/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/bar/", "/foo/bar/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/bar/;jsessionid=1234", "/foo/bar/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo;/bar;", "/foo/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo;/bar;/;", "/foo/bar/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo%00/bar/", "/foo\000/bar/", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo%7Fbar", "/foo\177bar", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo%2Fbar", "/foo%2Fbar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo%2Fb%25r", "/foo%2Fb%25r", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/b%25r", "/foo/b%r", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo\\bar", "/foo\\bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo%5Cbar", "/foo\\bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo;%2F/bar", "/foo/bar", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/./bar", "/foo/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/././bar", "/foo/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/./foo/bar", "/foo/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/%2e/bar", "/foo/bar", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/.;/bar", "/foo/bar", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/%2e;/bar", "/foo/bar", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/.%2Fbar", "/foo/.%2Fbar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/.%5Cbar", "/foo/.\\bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/bar/.", "/foo/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/bar/./", "/foo/bar/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/bar/.;", "/foo/bar", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/bar/./;", "/foo/bar/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/.bar", "/foo/.bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/../bar", "/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/../../bar", "/../bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/../foo/bar", "/../foo/bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/%2e%2E/bar", "/bar", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/%2e%2e/%2E%2E/bar", "/../bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/./../bar", "/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/..;/bar", "/bar", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/%2e%2E;/bar", "/bar", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/..%2Fbar", "/foo/..%2Fbar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/..%5Cbar", "/foo/..\\bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/bar/..", "/foo", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/bar/../", "/foo/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/bar/..;", "/foo", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/bar/../;", "/foo/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/..bar", "/foo/..bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/.../bar", "/foo/.../bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo//bar", "/foo/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "//foo//bar//", "/foo/bar/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/;/foo;/;/bar/;/;", "/foo/bar/", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo//../bar", "/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/;/../bar", "/bar", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo%E2%82%ACbar", "/foo€bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo%20bar", "/foo bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo%E2%82", "/foo%E2%82", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo%E2%82bar", "/foo%E2%82bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo%-1/bar", "/foo%-1/bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo%XX/bar", "/foo%XX/bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo%/bar", "/foo%/bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/bar%0", "/foo/bar%0", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/good%20/bad%/%20mix%", "/good /bad%/%20mix%", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/bar?q", "/foo/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/bar#f", "/foo/bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/bar?q#f", "/foo/bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/bar/?q", "/foo/bar/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/bar/#f", "/foo/bar/", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/bar/?q#f", "/foo/bar/", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/bar;?q", "/foo/bar", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/foo/bar;#f", "/foo/bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/foo/bar;?q#f", "/foo/bar", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/", "/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "//", "/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/;/", "/", Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/.", "/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/..", "/..", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/./", "/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "/../", "/../", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "foo/bar/", "/foo/bar/", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "./foo/bar/", "/foo/bar/", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "%2e/foo/bar/", "/foo/bar/", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "../foo/bar/", "/../foo/bar/", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { ".%2e/foo/bar/", "/../foo/bar/", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { ";/foo/bar/", "/foo/bar/", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/#f", "/", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "#f", "/", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { "/?q", "/", Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { "?q", "/", Boolean.TRUE, Boolean.TRUE });
+
+        return parameterSets;
+    }
+
+    @Parameter(0)
+    public String requestURI;
+
+    @Parameter(1)
+    public String canonicalizedURI;
+
+    @Parameter(2)
+    public boolean badRequestServlet;
+
+    @Parameter(3)
+    public boolean badRequestTomcat;
+
+
+    @Test
+    public void testCanonicalizationSpecification() throws Exception {
+        doTestCanonicalization(true, badRequestServlet);
+    }
+
+    @Test
+    public void testCanonicalizationTomcat() throws Exception {
+        doTestCanonicalization(false, badRequestTomcat);
+    }
+
+
+    private void doTestCanonicalization(boolean specCompliant, boolean expectBadRequest) throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        // ROOT web application so context path is ""
+        Context root = tomcat.addContext("", TEMP_DIR);
+        Tomcat.addServlet(root, "EchoServletPath", new EchoServletPath());
+        // Map as default servlet so servlet path should be URI less context
+        // path. Since the content path is "" the servlet path should be the
+        // URI.
+        root.addServletMappingDecoded("/", "EchoServletPath");
+
+        if (specCompliant) {
+            // Enabled options for stricter checking
+            tomcat.getConnector().setRejectSuspiciousURIs(true);
+        }
+
+        tomcat.start();
+
+        Client client = new Client(tomcat.getConnector().getLocalPort(), canonicalizedURI);
+        client.setRequest(new String[] {
+                "GET " + requestURI + " HTTP/1.1" + CRLF +
+                "Host: localhost" + CRLF +
+                CRLF
+        });
+        client.setResponseBodyEncoding(StandardCharsets.UTF_8);
+
+        client.connect();
+        client.processRequest();
+
+        // Expected response
+        String line = client.getResponseLine();
+        String body = client.getResponseBody();
+        if (expectBadRequest) {
+            Assert.assertTrue(line + CRLF + body, line.startsWith("HTTP/1.1 " + HttpServletResponse.SC_BAD_REQUEST));
+        } else {
+            Assert.assertTrue(line + CRLF + body, line.startsWith("HTTP/1.1 " + HttpServletResponse.SC_OK));
+            Assert.assertTrue(line + CRLF + body, body.equals(canonicalizedURI));
+        }
+    }
+
+
+    public class EchoServletPath extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+            resp.setContentType("text/plain");
+            resp.setCharacterEncoding("UTF-8");
+            resp.getWriter().write(req.getServletPath());
+        }
+    }
+
+
+    private static final class Client extends SimpleHttpClient {
+
+        private final String expected;
+
+        public Client(int port, String expected) {
+            this.expected = expected;
+            setPort(port);
+            setRequestPause(0);
+            setUseContentLength(true);
+        }
+
+        @Override
+        public boolean isResponseBodyOK() {
+            return expected.equals(getResponseBody());
+        }
+    }
+}
diff --git a/test/org/apache/catalina/startup/SimpleHttpClient.java b/test/org/apache/catalina/startup/SimpleHttpClient.java
index b0ba161..2282823 100644
--- a/test/org/apache/catalina/startup/SimpleHttpClient.java
+++ b/test/org/apache/catalina/startup/SimpleHttpClient.java
@@ -29,6 +29,8 @@ import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.SocketException;
 import java.net.UnknownHostException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
@@ -96,6 +98,7 @@ public abstract class SimpleHttpClient {
     private boolean useCookies = true;
     private boolean useHttp09 = false;
     private int requestPause = 1000;
+    private Charset requestBodyEncoding = StandardCharsets.ISO_8859_1;
 
     private String responseLine;
     private List<String> responseHeaders = new ArrayList<>();
@@ -106,6 +109,7 @@ public abstract class SimpleHttpClient {
 
     private String responseBody;
     private List<String> bodyUriElements = null;
+    private Charset responseBodyEncoding = StandardCharsets.ISO_8859_1;
 
     public void setPort(int thePort) {
         port = thePort;
@@ -150,6 +154,10 @@ public abstract class SimpleHttpClient {
         return responseHeaders;
     }
 
+    public void setResponseBodyEncoding(Charset charset) {
+        responseBodyEncoding = charset;
+    }
+
     public String getResponseBody() {
         return responseBody;
     }
@@ -187,15 +195,14 @@ public abstract class SimpleHttpClient {
 
     public void connect(int connectTimeout, int soTimeout)
            throws UnknownHostException, IOException {
-        final String encoding = "ISO-8859-1";
         SocketAddress addr = new InetSocketAddress("localhost", port);
         socket = new Socket();
         socket.setSoTimeout(soTimeout);
         socket.connect(addr,connectTimeout);
         OutputStream os = createOutputStream(socket);
-        writer = new OutputStreamWriter(os, encoding);
+        writer = new OutputStreamWriter(os, requestBodyEncoding);
         InputStream is = socket.getInputStream();
-        Reader r = new InputStreamReader(is, encoding);
+        Reader r = new InputStreamReader(is, responseBodyEncoding);
         reader = new BufferedReader(r);
     }
     public void connect() throws UnknownHostException, IOException {
@@ -316,8 +323,8 @@ public abstract class SimpleHttpClient {
             if (useContentLength && (contentLength > -1)) {
                 char[] body = new char[contentLength];
                 int read = reader.read(body);
-                Assert.assertEquals(contentLength, read);
-                builder.append(body);
+                builder.append(body, 0 , read);
+                Assert.assertEquals(contentLength, builder.toString().getBytes(responseBodyEncoding).length);
             } else {
                 // not using content length, so just read it line by line
                 String line = null;

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


[tomcat] 01/02: Add option to reject "suspicious" URIs

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 7095d1b9c8910c3bb647f0c496eba13b3aa7410a
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Thu Oct 14 13:44:52 2021 +0100

    Add option to reject "suspicious" URIs
---
 java/org/apache/catalina/connector/Connector.java  | 13 ++++
 .../apache/catalina/connector/CoyoteAdapter.java   | 91 ++++++++++++++++++++++
 webapps/docs/changelog.xml                         |  7 +-
 webapps/docs/config/http.xml                       |  6 ++
 4 files changed, 116 insertions(+), 1 deletion(-)

diff --git a/java/org/apache/catalina/connector/Connector.java b/java/org/apache/catalina/connector/Connector.java
index 235aa1c..1284d87 100644
--- a/java/org/apache/catalina/connector/Connector.java
+++ b/java/org/apache/catalina/connector/Connector.java
@@ -296,6 +296,9 @@ public class Connector extends LifecycleMBeanBase  {
     protected boolean useBodyEncodingForURI = false;
 
 
+    private boolean rejectSuspiciousURIs;
+
+
     // ------------------------------------------------------------- Properties
 
     /**
@@ -914,6 +917,16 @@ public class Connector extends LifecycleMBeanBase  {
     }
 
 
+    public boolean getRejectSuspiciousURIs() {
+        return rejectSuspiciousURIs;
+    }
+
+
+    public void setRejectSuspiciousURIs(boolean rejectSuspiciousURIs) {
+        this.rejectSuspiciousURIs = rejectSuspiciousURIs;
+    }
+
+
     // --------------------------------------------------------- Public Methods
 
     /**
diff --git a/java/org/apache/catalina/connector/CoyoteAdapter.java b/java/org/apache/catalina/connector/CoyoteAdapter.java
index f1db80f..771cc50 100644
--- a/java/org/apache/catalina/connector/CoyoteAdapter.java
+++ b/java/org/apache/catalina/connector/CoyoteAdapter.java
@@ -46,6 +46,7 @@ import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.HexUtils;
 import org.apache.tomcat.util.buf.MessageBytes;
 import org.apache.tomcat.util.http.ServerCookie;
 import org.apache.tomcat.util.http.ServerCookies;
@@ -631,6 +632,12 @@ public class CoyoteAdapter implements Adapter {
         MessageBytes decodedURI = req.decodedURI();
 
         if (undecodedURI.getType() == MessageBytes.T_BYTES) {
+            if (connector.getRejectSuspiciousURIs()) {
+                if (checkSuspiciousURIs(undecodedURI.getByteChunk())) {
+                    response.sendError(400, "Invalid URI");
+                }
+            }
+
             // Copy the raw URI to the decodedURI
             decodedURI.duplicate(undecodedURI);
 
@@ -661,6 +668,8 @@ public class CoyoteAdapter implements Adapter {
              *   non-normalized URI
              * - req.decodedURI() has been set to the decoded, normalized form
              *   of req.requestURI()
+             * - 'suspicious' URI filtering - if required - has already been
+             *   performed
              */
             decodedURI.toChars();
             // Remove all path parameters; any needed path parameter should be set
@@ -1268,4 +1277,86 @@ public class CoyoteAdapter implements Adapter {
     protected static void copyBytes(byte[] b, int dest, int src, int len) {
         System.arraycopy(b, src, b, dest, len);
     }
+
+
+    /*
+     * Examine URI segment by segment for 'suspicious' URIs.
+     */
+    private static boolean checkSuspiciousURIs(ByteChunk undecodedURI) {
+        byte[] bytes = undecodedURI.getBytes();
+        int start = undecodedURI.getStart();
+        int end = undecodedURI.getEnd();
+        int segmentStart = -1;
+        int segmentEnd = -1;
+
+        // Find first segment
+        segmentStart = undecodedURI.indexOf('/', 0);
+        if (segmentStart > -1) {
+            segmentEnd = undecodedURI.indexOf('/', segmentStart + 1);
+        }
+
+        while (segmentStart > -1) {
+            int pos = start + segmentStart + 1;
+
+            // Empty segment other than final segment with path parameters
+            if (segmentEnd > 0 && bytes[pos] == ';') {
+                return true;
+            }
+
+            // encoded dot-segments and/or dot-segments with path parameters
+            int dotCount = 0;
+            boolean encodedDot = false;
+            while (pos < end) {
+                if (bytes[pos] == '.') {
+                    dotCount++;
+                    pos++;
+                } else if (pos + 2 < end && bytes[pos] == '%' && bytes[pos + 1] == '2' && (bytes[pos+2] == 'e' || bytes[pos+2] == 'E')) {
+                    encodedDot = true;
+                    dotCount++;
+                    pos += 3;
+                } else if (bytes[pos] == ';') {
+                    if (dotCount > 0) {
+                        return true;
+                    }
+                    break;
+                } else if (bytes[pos] == '/') {
+                    break;
+                } else {
+                    dotCount = 0;
+                    break;
+                }
+            }
+            if (dotCount > 0 && encodedDot) {
+                return true;
+            }
+
+            // %nn encoded controls or '/'
+            pos = start + segmentStart + 1;
+            while (pos < end) {
+                if (pos + 2 < end && bytes[pos] == '%') {
+                    byte b1 = bytes[pos + 1];
+                    byte b2 = bytes[pos + 2];
+                    pos += 3;
+                    int decoded = (HexUtils.getDec(b1) << 4) + HexUtils.getDec(b2);
+                    if (decoded < 20 || decoded == 0x7F || decoded == 0x2F) {
+                        return true;
+                    }
+                } else {
+                    pos++;
+                }
+            }
+
+            // Move to next segment
+            if (segmentEnd == -1) {
+                segmentStart = -1;
+            } else {
+                segmentStart = segmentEnd;
+                if (segmentStart > -1) {
+                    segmentEnd = undecodedURI.indexOf('/', segmentStart + 1);
+                }
+            }
+        }
+
+        return false;
+    }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index abdcfdf..ab48789 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -131,8 +131,13 @@
         not valid for the given URI encoding now trigger a 400 response. (markt)
       </fix>
       <fix>
-        Ensure that a requets URI must start with a <code>/</code>. (markt)
+        Ensure that a request URI starts with a <code>/</code>. (markt)
       </fix>
+      <add>
+        Add a new Connector option, <code>rejectSuspiciousURIs</code> that will
+        causes 'suspicious' (see the Servlet 6.0 specification) URIs to be
+        rejected with a 400 response. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Coyote">
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index 9a800ed..f6e8d08 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -261,6 +261,12 @@
       number specified here.</p>
     </attribute>
 
+    <attribute name="rejectSuspiciousURIs" required="false">
+      <p>Should this <strong>Connector</strong> reject a requests if the URI
+      matches one of the suspicious URIs patterns identified by the Servlet 6.0
+      specification? The default value is <code>false</code>.</p>
+    </attribute>
+
     <attribute name="scheme" required="false">
       <p>Set this attribute to the name of the protocol you wish to have
       returned by calls to <code>request.getScheme()</code>.  For

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org