From 45c72e4f37a2f842568b97a2b114757d82c67066 Mon Sep 17 00:00:00 2001
From: rjung false to disable request data swallowing
+ * after an upload was aborted due to size constraints.
+ *
+ * @param swallowAbortedUploads false to disable
+ * swallowing, true otherwise (default).
+ */
+ public void setSwallowAbortedUploads(boolean swallowAbortedUploads);
+
+ /**
+ * Returns true if remaining request data will be read
+ * (swallowed) even the request violates a data size constraint.
+ *
+ * @return true if data will be swallowed (default),
+ * false otherwise.
+ */
+ public boolean getSwallowAbortedUploads();
+
+ /**
* Return the set of initialized application event listener objects,
* in the order they were specified in the web application deployment
* descriptor, for this application.
diff --git a/java/org/apache/catalina/connector/Request.java b/java/org/apache/catalina/connector/Request.java
index 231219499..30d55872a 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -800,6 +800,9 @@ public class Request
*/
public void finishRequest() throws IOException {
// The reader and input stream don't need to be closed
+ // TODO: Is this ever called?
+ // If so, move input swallow disabling from
+ // Response.finishResponse() to here
}
@@ -2450,6 +2453,16 @@ public class Request
return (inputBuffer.available() > 0);
}
+ /**
+ * Disable swallowing of remaining input if configured
+ */
+ protected void disableSwallowInput() {
+ Context context = getContext();
+ if (context != null && !context.getSwallowAbortedUploads()) {
+ coyoteRequest.action(ActionCode.DISABLE_SWALLOW_INPUT, null);
+ }
+ }
+
public void cometClose() {
coyoteRequest.action(ActionCode.COMET_CLOSE,getEvent());
}
@@ -2620,6 +2633,7 @@ public class Request
} catch (InvalidContentTypeException e) {
partsParseException = new ServletException(e);
} catch (FileUploadBase.SizeException e) {
+ disableSwallowInput();
partsParseException = new IllegalStateException(e);
} catch (FileUploadException e) {
partsParseException = new IOException(e);
@@ -2845,6 +2859,7 @@ public class Request
context.getLogger().debug(
sm.getString("coyoteRequest.postTooLarge"));
}
+ disableSwallowInput();
return;
}
byte[] formData = null;
@@ -2922,6 +2937,7 @@ public class Request
if (connector.getMaxPostSize() > 0 &&
(body.getLength() + len) > connector.getMaxPostSize()) {
// Too much data
+ disableSwallowInput();
throw new IllegalArgumentException(
sm.getString("coyoteRequest.chunkedPostTooLarge"));
}
diff --git a/java/org/apache/catalina/connector/Response.java b/java/org/apache/catalina/connector/Response.java
index 00eb84406..26cb5ba17 100644
--- a/java/org/apache/catalina/connector/Response.java
+++ b/java/org/apache/catalina/connector/Response.java
@@ -49,6 +49,7 @@ import org.apache.catalina.core.ApplicationSessionCookieConfig;
import org.apache.catalina.security.SecurityUtil;
import org.apache.catalina.util.CharsetMapper;
import org.apache.catalina.util.DateTool;
+import org.apache.coyote.ActionCode;
import org.apache.tomcat.util.buf.CharChunk;
import org.apache.tomcat.util.buf.UEncoder;
import org.apache.tomcat.util.http.FastHttpDateFormat;
@@ -497,6 +498,15 @@ public class Response
*/
public void finishResponse()
throws IOException {
+ // Optionally disable swallowing of additional request data.
+ // TODO: Should be in Request.finishRequest(), but that method
+ // seems to get called never.
+ Context context = getContext();
+ if (context != null
+ && getStatus() == HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE
+ && !context.getSwallowAbortedUploads()) {
+ coyoteResponse.action(ActionCode.DISABLE_SWALLOW_INPUT, null);
+ }
// Writing leftover bytes
outputBuffer.close();
}
diff --git a/java/org/apache/catalina/core/StandardContext.java b/java/org/apache/catalina/core/StandardContext.java
index cc1e588ed..d5d80e3d7 100644
--- a/java/org/apache/catalina/core/StandardContext.java
+++ b/java/org/apache/catalina/core/StandardContext.java
@@ -197,6 +197,12 @@ public class StandardContext extends ContainerBase
protected boolean allowCasualMultipartParsing = false;
/**
+ * Control whether remaining request data will be read
+ * (swallowed) even if the request violates a data size constraint.
+ */
+ public boolean swallowAbortedUploads = true;
+
+ /**
* The alternate deployment descriptor name.
*/
private String altDDName = null;
@@ -1066,6 +1072,30 @@ public class StandardContext extends ContainerBase
}
/**
+ * Set to false to disable request data swallowing
+ * after an upload was aborted due to size constraints.
+ *
+ * @param swallowAbortedUploads false to disable
+ * swallowing, true otherwise (default).
+ */
+ @Override
+ public void setSwallowAbortedUploads(boolean swallowAbortedUploads) {
+ this.swallowAbortedUploads = swallowAbortedUploads;
+ }
+
+ /**
+ * Returns true if remaining request data will be read
+ * (swallowed) even the request violates a data size constraint.
+ *
+ * @return true if data will be swallowed (default),
+ * false otherwise.
+ */
+ @Override
+ public boolean getSwallowAbortedUploads() {
+ return this.swallowAbortedUploads;
+ }
+
+ /**
* Set cache TTL.
*/
public void setCacheTTL(int cacheTTL) {
@@ -6440,4 +6470,4 @@ public class StandardContext extends ContainerBase
return false;
}
-}
+}
\ No newline at end of file
diff --git a/java/org/apache/coyote/ActionCode.java b/java/org/apache/coyote/ActionCode.java
index 31ee255b7..c268658f1 100644
--- a/java/org/apache/coyote/ActionCode.java
+++ b/java/org/apache/coyote/ActionCode.java
@@ -49,6 +49,13 @@ public enum ActionCode {
POST_REQUEST,
/**
+ * Hook called if swallowing request input should be disabled.
+ * Example: Cancel a large file upload.
+ *
+ */
+ DISABLE_SWALLOW_INPUT,
+
+ /**
* Callback for lazy evaluation - extract the remote host address.
*/
REQ_HOST_ATTRIBUTE,
diff --git a/java/org/apache/coyote/ajp/AbstractAjpProcessor.java b/java/org/apache/coyote/ajp/AbstractAjpProcessor.java
index 44b00e13b..7c3fdc9f7 100644
--- a/java/org/apache/coyote/ajp/AbstractAjpProcessor.java
+++ b/java/org/apache/coyote/ajp/AbstractAjpProcessor.java
@@ -266,6 +266,11 @@ public abstract class AbstractAjpProcessor implements ActionHook, Processor {
error = true;
}
+ } else if (actionCode == ActionCode.DISABLE_SWALLOW_INPUT) {
+ // TODO: Do not swallow request input but
+ // make sure we are closing the connection
+ error = true;
+
} else if (actionCode == ActionCode.CLOSE) {
// Close
// End the processing of the current request, and stop any further
diff --git a/java/org/apache/coyote/http11/AbstractHttp11Processor.java b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
index 765c79b7b..b212b51a3 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11Processor.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
@@ -767,6 +767,12 @@ public abstract class AbstractHttp11Processor implements ActionHook, Processor {
response.setErrorException(e);
}
+ } else if (actionCode == ActionCode.DISABLE_SWALLOW_INPUT) {
+ // Do not swallow request input but
+ // make sure we are closing the connection
+ error = true;
+ getInputBuffer().setSwallowInput(false);
+
} else if (actionCode == ActionCode.RESET) {
// Reset response
// Note: This must be called before the response is committed
diff --git a/test/org/apache/catalina/core/TestSwallowAbortedUploads.java b/test/org/apache/catalina/core/TestSwallowAbortedUploads.java
new file mode 100644
index 000000000..8ea33fbc4
--- /dev/null
+++ b/test/org/apache/catalina/core/TestSwallowAbortedUploads.java
@@ -0,0 +1,399 @@
+/*
+ * 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.core;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collection;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.MultipartConfig;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.Part;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.startup.SimpleHttpClient;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+public class TestSwallowAbortedUploads extends TomcatBaseTest {
+
+ private static Log log = LogFactory.getLog(TestSwallowAbortedUploads.class);
+
+ /**
+ * Test whether size limited uploads correctly handle connection draining.
+ */
+ public Exception doAbortedUploadTest(AbortedUploadClient client, boolean limited,
+ boolean swallow) {
+ client.setPort(getPort());
+ Exception ex = client.doRequest(limited, swallow);
+ if (log.isDebugEnabled()) {
+ log.debug("Response line: " + client.getResponseLine());
+ log.debug("Response headers: " + client.getResponseHeaders());
+ log.debug("Response body: " + client.getResponseBody());
+ if (ex != null) {
+ log.debug("Exception in client: ", ex);
+ }
+
+ }
+ return ex;
+ }
+
+ /**
+ * Test whether aborted POST correctly handle connection draining.
+ */
+ public Exception doAbortedPOSTTest(AbortedPOSTClient client, int status,
+ boolean swallow) {
+ client.setPort(getPort());
+ Exception ex = client.doRequest(status, swallow);
+ if (log.isDebugEnabled()) {
+ log.debug("Response line: " + client.getResponseLine());
+ log.debug("Response headers: " + client.getResponseHeaders());
+ log.debug("Response body: " + client.getResponseBody());
+ if (ex != null) {
+ log.info("Exception in client: ", ex);
+ }
+
+ }
+ return ex;
+ }
+
+ public void testAbortedUploadUnlimitedSwallow() {
+ log.info("Unlimited, swallow enabled");
+ AbortedUploadClient client = new AbortedUploadClient();
+ Exception ex = doAbortedUploadTest(client, false, true);
+ assertNull("Unlimited upload with swallow enabled generates client exception",
+ ex);
+ assertTrue("Unlimited upload with swallow enabled returns error status code",
+ client.isResponse200());
+ client.reset();
+ }
+
+ public void testAbortedUploadUnlimitedNoSwallow() {
+ log.info("Unlimited, swallow disabled");
+ AbortedUploadClient client = new AbortedUploadClient();
+ Exception ex = doAbortedUploadTest(client, false, false);
+ assertNull("Unlimited upload with swallow disabled generates client exception",
+ ex);
+ assertTrue("Unlimited upload with swallow disabled returns error status code",
+ client.isResponse200());
+ client.reset();
+ }
+
+ public void testAbortedUploadLimitedSwallow() {
+ log.info("Limited, swallow enabled");
+ AbortedUploadClient client = new AbortedUploadClient();
+ Exception ex = doAbortedUploadTest(client, true, true);
+ assertNull("Limited upload with swallow enabled generates client exception",
+ ex);
+ assertTrue("Limited upload with swallow enabled returns error status code",
+ client.isResponse500());
+ client.reset();
+ }
+
+ public void testAbortedUploadLimitedNoSwallow() {
+ log.info("Limited, swallow disabled");
+ AbortedUploadClient client = new AbortedUploadClient();
+ Exception ex = doAbortedUploadTest(client, true, false);
+ assertTrue("Limited upload with swallow disabled does not generate client exception",
+ ex != null && ex instanceof java.net.SocketException);
+ client.reset();
+ }
+
+ public void testAbortedPOSTOKSwallow() {
+ log.info("Aborted (OK), swallow enabled");
+ AbortedPOSTClient client = new AbortedPOSTClient();
+ Exception ex = doAbortedPOSTTest(client, HttpServletResponse.SC_OK, true);
+ assertNull("Unlimited upload with swallow enabled generates client exception",
+ ex);
+ assertTrue("Unlimited upload with swallow enabled returns error status code",
+ client.isResponse200());
+ client.reset();
+ }
+
+ public void testAbortedPOSTOKNoSwallow() {
+ log.info("Aborted (OK), swallow disabled");
+ AbortedPOSTClient client = new AbortedPOSTClient();
+ Exception ex = doAbortedPOSTTest(client, HttpServletResponse.SC_OK, false);
+ assertNull("Unlimited upload with swallow disabled generates client exception",
+ ex);
+ assertTrue("Unlimited upload with swallow disabled returns error status code",
+ client.isResponse200());
+ client.reset();
+ }
+
+ public void testAbortedPOST413Swallow() {
+ log.info("Aborted (413), swallow enabled");
+ AbortedPOSTClient client = new AbortedPOSTClient();
+ Exception ex = doAbortedPOSTTest(client, HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, true);
+ assertNull("Limited upload with swallow enabled generates client exception",
+ ex);
+ assertTrue("Limited upload with swallow enabled returns error status code",
+ client.isResponse413());
+ client.reset();
+ }
+
+ public void testAbortedPOST413NoSwallow() {
+ log.info("Aborted (413), swallow disabled");
+ AbortedPOSTClient client = new AbortedPOSTClient();
+ Exception ex = doAbortedPOSTTest(client, HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, false);
+ assertTrue("Limited upload with swallow disabled does not generate client exception",
+ ex != null && ex instanceof java.net.SocketException);
+ client.reset();
+ }
+
+ @MultipartConfig
+ private static class AbortedUploadServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ PrintWriter out = resp.getWriter();
+ resp.setContentType("text/plain");
+ resp.setCharacterEncoding("UTF-8");
+ StringBuilder sb = new StringBuilder();
+ try {
+ Collectiontrue will be used.
Set to false if Tomcat should not read any additional + request body data for aborted uploads and instead abort the client + connection. This setting is used in the following situations: +
maxPostSize configured in the connector
+ The default is true, so additional data is being read.
If the value of this flag is true, the bytes output to
System.out and System.err by the web application will be redirected to
--
2.11.0