File tomcat-9.0.36-CVE-2026-24733.patch of Package tomcat.43093
diff --git a/java/org/apache/coyote/Request.java b/java/org/apache/coyote/Request.java
index 7524da4..649f02c 100644
--- a/java/org/apache/coyote/Request.java
+++ b/java/org/apache/coyote/Request.java
@@ -233,6 +233,10 @@ public final class Request {
return methodMB;
}
+ public String getMethod() {
+ return methodMB.toStringType();
+ }
+
public MessageBytes requestURI() {
return uriMB;
}
diff --git a/java/org/apache/coyote/http11/Http11Processor.java b/java/org/apache/coyote/http11/Http11Processor.java
index 4a55bb9..29eafd4 100644
--- a/java/org/apache/coyote/http11/Http11Processor.java
+++ b/java/org/apache/coyote/http11/Http11Processor.java
@@ -51,6 +51,7 @@ import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.http.FastHttpDateFormat;
+import org.apache.tomcat.util.http.Method;
import org.apache.tomcat.util.http.MimeHeaders;
import org.apache.tomcat.util.http.parser.HttpParser;
import org.apache.tomcat.util.http.parser.TokenList;
@@ -541,6 +542,11 @@ public class Http11Processor extends AbstractProcessor {
http09 = true;
http11 = false;
keepAlive = false;
+ if (!Method.GET.equals(request.getMethod())) {
+ // Send 400, GET is the only allowed method for HTTP/0.9
+ response.setStatus(400);
+ setErrorState(ErrorState.CLOSE_CLEAN, null);
+ }
} else {
// Unsupported protocol
http09 = false;
diff --git a/java/org/apache/tomcat/util/buf/MessageBytes.java b/java/org/apache/tomcat/util/buf/MessageBytes.java
index 64e87d0..1110fd6 100644
--- a/java/org/apache/tomcat/util/buf/MessageBytes.java
+++ b/java/org/apache/tomcat/util/buf/MessageBytes.java
@@ -178,6 +178,28 @@ public final class MessageBytes implements Cloneable, Serializable {
return null;
}
+ /**
+ * Convert to String (if not already of the String type) and then return the String value.
+ *
+ * @return The current value as a String
+ */
+ public String toStringType() {
+ switch (type) {
+ case T_NULL:
+ case T_STR:
+ // No conversion required
+ break;
+ case T_BYTES:
+ setString(byteC.toString());
+ break;
+ case T_CHARS:
+ setString(charC.toString());
+ break;
+ }
+
+ return strValue;
+ }
+
//----------------------------------------
/**
* Return the type of the original content. Can be
diff --git a/java/org/apache/tomcat/util/http/Method.java b/java/org/apache/tomcat/util/http/Method.java
new file mode 100644
index 0000000..982d7bb
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/Method.java
@@ -0,0 +1,164 @@
+/*
+ * 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.tomcat.util.http;
+
+public class Method {
+
+ /*
+ * This class was originally created to hold the bytes to String conversion method. It turns out that these
+ * constants are just as much of a benefit to performance - if used consistently.
+ *
+ * If the String constants for the methods are used throughout the code-base, that allows String.equals() to use the
+ * 'same object shortcut' when checking if a request is (or is not) using a particular method. That is faster than a
+ * character by character comparison. That results in a further performance improvement that is as big - or possibly
+ * slightly bigger - than the improvement obtained by using the optimised conversion.
+ */
+
+ // Standard HTTP methods supported by HttpServlet
+ public static final String GET = "GET";
+ public static final String POST = "POST";
+ public static final String PUT = "PUT";
+ public static final String PATCH = "PATCH";
+ public static final String HEAD = "HEAD";
+ public static final String OPTIONS = "OPTIONS";
+ public static final String DELETE = "DELETE";
+ public static final String TRACE = "TRACE";
+ // Additional WebDAV methods
+ public static final String PROPFIND = "PROPFIND";
+ public static final String PROPPATCH = "PROPPATCH";
+ public static final String MKCOL = "MKCOL";
+ public static final String COPY = "COPY";
+ public static final String MOVE = "MOVE";
+ public static final String LOCK = "LOCK";
+ public static final String UNLOCK = "UNLOCK";
+ // Other methods recognised by Tomcat
+ public static final String CONNECT = "CONNECT";
+
+
+ /**
+ * Provides optimised conversion from bytes to Strings for known HTTP methods. The bytes are assumed to be an
+ * ISO-8859-1 encoded representation of an HTTP method. The method is not validated as being a token, but only valid
+ * HTTP method names will be returned.
+ * <p>
+ * Doing it this way is ~10x faster than using MessageBytes.toStringType() saving ~40ns per request which is ~1% of
+ * the processing time for a minimal "Hello World" type servlet. For non-standard methods there is an additional
+ * overhead of ~2.5ns per request.
+ * <p>
+ * Pretty much every request ends up converting the method to a String so it is more efficient to do this straight
+ * away and always use Strings.
+ *
+ * @param buf The byte buffer containing the HTTP method to convert
+ * @param start The first byte of the HTTP method
+ * @param len The number of bytes to convert
+ *
+ * @return The HTTP method as a String or {@code null} if the method is not recognised.
+ */
+ public static String bytesToString(byte[] buf, int start, int len) {
+ switch (buf[start]) {
+ case 'G': {
+ if (len == 3 && buf[start + 1] == 'E' && buf[start + 2] == 'T') {
+ return GET;
+ }
+ break;
+ }
+ case 'P': {
+ if (len == 4 && buf[start + 1] == 'O' && buf[start + 2] == 'S' && buf[start + 3] == 'T') {
+ return POST;
+ } else if (len == 3 && buf[start + 1] == 'U' && buf[start + 2] == 'T') {
+ return PUT;
+ } else if (len == 5 && buf[start + 1] == 'A' && buf[start + 2] == 'T' && buf[start + 3] == 'C' &&
+ buf[start + 4] == 'H') {
+ return PATCH;
+ } else if (len == 8 && buf[start + 1] == 'R' && buf[start + 2] == 'O' && buf[start + 3] == 'P' &&
+ buf[start + 4] == 'F' && buf[start + 5] == 'I' && buf[start + 6] == 'N' &&
+ buf[start + 7] == 'D') {
+ return PROPFIND;
+ } else if (len == 9 && buf[start + 1] == 'R' && buf[start + 2] == 'O' && buf[start + 3] == 'P' &&
+ buf[start + 4] == 'P' && buf[start + 5] == 'A' && buf[start + 6] == 'T' &&
+ buf[start + 7] == 'C' && buf[start + 8] == 'H') {
+ return PROPPATCH;
+ }
+ break;
+ }
+ case 'H': {
+ if (len == 4 && buf[start + 1] == 'E' && buf[start + 2] == 'A' && buf[start + 3] == 'D') {
+ return HEAD;
+ }
+ break;
+ }
+ case 'O': {
+ if (len == 7 && buf[start + 1] == 'P' && buf[start + 2] == 'T' && buf[start + 3] == 'I' &&
+ buf[start + 4] == 'O' && buf[start + 5] == 'N' && buf[start + 6] == 'S') {
+ return OPTIONS;
+ }
+ break;
+ }
+ case 'D': {
+ if (len == 6 && buf[start + 1] == 'E' && buf[start + 2] == 'L' && buf[start + 3] == 'E' &&
+ buf[start + 4] == 'T' && buf[start + 5] == 'E') {
+ return DELETE;
+ }
+ break;
+ }
+ case 'T': {
+ if (len == 5 && buf[start + 1] == 'R' && buf[start + 2] == 'A' && buf[start + 3] == 'C' &&
+ buf[start + 4] == 'E') {
+ return TRACE;
+ }
+ break;
+ }
+ case 'M': {
+ if (len == 5 && buf[start + 1] == 'K' && buf[start + 2] == 'C' && buf[start + 3] == 'O' &&
+ buf[start + 4] == 'L') {
+ return MKCOL;
+ } else if (len == 4 && buf[start + 1] == 'O' && buf[start + 2] == 'V' && buf[start + 3] == 'E') {
+ return MOVE;
+ }
+ break;
+ }
+ case 'C': {
+ if (len == 4 && buf[start + 1] == 'O' && buf[start + 2] == 'P' && buf[start + 3] == 'Y') {
+ return COPY;
+ } else if (len == 7 && buf[start + 1] == 'O' && buf[start + 2] == 'N' && buf[start + 3] == 'N' &&
+ buf[start + 4] == 'E' && buf[start + 5] == 'C' && buf[start + 6] == 'T') {
+ return CONNECT;
+ }
+ break;
+ }
+ case 'L': {
+ if (len == 4 && buf[start + 1] == 'O' && buf[start + 2] == 'C' && buf[start + 3] == 'K') {
+ return LOCK;
+ }
+ break;
+ }
+ case 'U': {
+ if (len == 6 && buf[start + 1] == 'N' && buf[start + 2] == 'L' && buf[start + 3] == 'O' &&
+ buf[start + 4] == 'C' && buf[start + 5] == 'K') {
+ return UNLOCK;
+ }
+ break;
+ }
+ }
+
+ return null;
+ }
+
+
+ private Method() {
+ // Utility class - hide default constructor
+ }
+}
diff --git a/test/org/apache/coyote/http11/TestHttp11InputBuffer.java b/test/org/apache/coyote/http11/TestHttp11InputBuffer.java
index 94b9a17..b5514e4 100644
--- a/test/org/apache/coyote/http11/TestHttp11InputBuffer.java
+++ b/test/org/apache/coyote/http11/TestHttp11InputBuffer.java
@@ -669,6 +669,21 @@ public class TestHttp11InputBuffer extends TomcatBaseTest {
}
+ @Test
+ public void testInvalidHttp09Method() {
+
+ String[] request = new String[1];
+ request[0] = "POST /test" + CRLF;
+
+ InvalidClient client = new InvalidClient(request);
+
+ client.doRequest();
+ // The response in that case is HTTP/0.9 so only the body
+ Assert.assertTrue(client.getResponseLine(), client.getResponseLine().contains("400"));
+ Assert.assertTrue(client.isResponseBodyOK());
+ }
+
+
@Test
public void testInvalidEndOfRequestLine01() {
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 38055bd..1182831 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -151,6 +151,9 @@
Align size tracking for multipart requests with FileUpload's use of
<code>long</code>. (schultz)
</fix>
+ <fix>
+ HTTP/0.9 only allows GET as the HTTP method. (remm)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">