From aa55e621c3a0ca01ef9aba3d9b7ef2696d98b45a Mon Sep 17 00:00:00 2001 From: Chenjp Date: Fri, 27 Jun 2025 17:06:01 +0800 Subject: [PATCH 1/3] Fitler to limit target file size of ranged put request target file maxSize is configurable now. --- .../catalina/filters/LocalStrings.properties | 2 + .../filters/RangedPutRequestBoundsFilter.java | 129 ++++++++++++++++++ webapps/docs/config/filter.xml | 34 +++++ 3 files changed, 165 insertions(+) create mode 100644 java/org/apache/catalina/filters/RangedPutRequestBoundsFilter.java diff --git a/java/org/apache/catalina/filters/LocalStrings.properties b/java/org/apache/catalina/filters/LocalStrings.properties index 63a5c1485daf..200b1db5c9b7 100644 --- a/java/org/apache/catalina/filters/LocalStrings.properties +++ b/java/org/apache/catalina/filters/LocalStrings.properties @@ -81,3 +81,5 @@ restCsrfPreventionFilter.multipleNonce.debug=Different CSRF nonces are sent as r webDavFilter.xpProblem=WebdavFixFilter: the XP-x64-SP2 client is known not to work with WebDAV Servlet webDavFilter.xpRootContext=WebdavFixFilter: the XP-x64-SP2 client will only work with the root context + +rangedRequestBoundsFilter.maxSizeExceeded=[{0}] Requested [{1}] from [{2}] with Content-Range header value [{3}] exceeds the maximum allowed [{4}] bytes. diff --git a/java/org/apache/catalina/filters/RangedPutRequestBoundsFilter.java b/java/org/apache/catalina/filters/RangedPutRequestBoundsFilter.java new file mode 100644 index 000000000000..c66643c9cf02 --- /dev/null +++ b/java/org/apache/catalina/filters/RangedPutRequestBoundsFilter.java @@ -0,0 +1,129 @@ +/* + * 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.filters; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Enumeration; +import java.util.regex.Pattern; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.http.parser.ContentRange; +import org.apache.tomcat.util.res.StringManager; + +/** + *

+ * Servlet filter that can help mitigate Denial of Service (DoS) by limiting the maximum file size of a partial PUT + * request's Content-Range header that are allowed. + */ +public class RangedPutRequestBoundsFilter extends FilterBase { + protected static final StringManager sm = StringManager.getManager(FilterBase.class); + + /** + * default value for maximum size of a ranged PUT target resource. + */ + private static final long DEFAULT_MAX_BOUND_UP = 1L << 32; + private static final String PARAMETER_MAX_SIZE = "maxSize"; + + /** + * Maximum size of a ranged PUT target resource. 4GB by default. + */ + private long maxSize = DEFAULT_MAX_BOUND_UP; + + public long getMaxSize() { + return maxSize; + } + + public void setMaxSize(long maxSize) { + this.maxSize = maxSize; + } + + private final transient Log log = LogFactory.getLog(RangedPutRequestBoundsFilter.class); + private String filterName = null; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + this.filterName = filterConfig.getFilterName(); + for (Enumeration names = filterConfig.getInitParameterNames(); names.hasMoreElements();) { + String name = names.nextElement(); + String value = filterConfig.getInitParameter(name); + if (PARAMETER_MAX_SIZE.equals(name)) { + setMaxSize(Long.valueOf(value)); + } + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (request instanceof HttpServletRequest req) { + if ("PUT".equals(req.getMethod())) { + Enumeration values = req.getHeaders("Content-Range"); + while (values.hasMoreElements()) { + String cr = values.nextElement(); + if (!checkContentRangeBounds(req, cr)) { + + ((HttpServletResponse) response).sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE); + return; + } + } + } + } + chain.doFilter(request, response); + } + + /** + * Checks the content-range header, returns false only if the content-range parsed successfully and the range + * exceeds bounds. + * + * @param req The servlet request we are processing. + * @param contentRangeHeader The contentRange header from request + * + * @return false only if the content-range parsed successfully and the range exceeds bounds. + */ + private boolean checkContentRangeBounds(HttpServletRequest req, String contentRangeHeader) { + try { + ContentRange contentRange = ContentRange.parse(new StringReader(contentRangeHeader)); + if (contentRange != null && getMaxSize() >= 0 && + (getMaxSize() <= contentRange.getEnd() || getMaxSize() < (contentRange.getLength()))) { + if (log.isWarnEnabled()) { + log.warn(sm.getString("rangedRequestBoundsFilter.maxSizeExceeded", filterName, req.getRequestURI(), + req.getRemoteAddr(), contentRangeHeader, getMaxSize())); + } + return false; + } + } catch (IOException e) { + // Ignore + } + return true; + } + + @Override + protected Log getLogger() { + return log; + } + +} diff --git a/webapps/docs/config/filter.xml b/webapps/docs/config/filter.xml index c3d1c98bf4ba..dbdb3d1f1e76 100644 --- a/webapps/docs/config/filter.xml +++ b/webapps/docs/config/filter.xml @@ -1991,6 +1991,40 @@ org.apache.catalina.filters.RequestDumperFilter.handlers = \ + +

+ + + +

The filter can help mitigate Denial of Service (DoS) by limiting + the maximum file size of a partial PUT request that is allowed.

+ +
+ + + +

The filter class name for the Ranged Put Request Bounds Limit Filter is + org.apache.catalina.filters.RangedPutRequestBoundsFilter + .

+ +
+ + + +

The Ranged Put Request Bounds Limit Filter supports the following initialization + parameters:

+ + + + +

Maximum file size of a partial PUT request that is allowed. Default is 4GB.

+
+ +
+ +
+ +
From ba5ce6758691349dbab1f2c2628108343a8b9526 Mon Sep 17 00:00:00 2001 From: Chenjp Date: Fri, 4 Jul 2025 11:32:12 +0800 Subject: [PATCH 2/3] Enhance Enhance for javadoc, code sytle, and filter init --- .../catalina/filters/LocalStrings.properties | 2 + .../filters/RangedPutRequestBoundsFilter.java | 40 ++++++++++++++----- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/java/org/apache/catalina/filters/LocalStrings.properties b/java/org/apache/catalina/filters/LocalStrings.properties index 200b1db5c9b7..24718761c5e2 100644 --- a/java/org/apache/catalina/filters/LocalStrings.properties +++ b/java/org/apache/catalina/filters/LocalStrings.properties @@ -83,3 +83,5 @@ webDavFilter.xpProblem=WebdavFixFilter: the XP-x64-SP2 client is known not to wo webDavFilter.xpRootContext=WebdavFixFilter: the XP-x64-SP2 client will only work with the root context rangedRequestBoundsFilter.maxSizeExceeded=[{0}] Requested [{1}] from [{2}] with Content-Range header value [{3}] exceeds the maximum allowed [{4}] bytes. +rangedRequestBoundsFilter.invalidParameter=[{0}] Invalid value for parameter [{1}]: [{2}] +rangedRequestBoundsFilter.noSuchParameter=[{0}] No such parameter [{1}] diff --git a/java/org/apache/catalina/filters/RangedPutRequestBoundsFilter.java b/java/org/apache/catalina/filters/RangedPutRequestBoundsFilter.java index c66643c9cf02..f582752c3fba 100644 --- a/java/org/apache/catalina/filters/RangedPutRequestBoundsFilter.java +++ b/java/org/apache/catalina/filters/RangedPutRequestBoundsFilter.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.io.StringReader; import java.util.Enumeration; -import java.util.regex.Pattern; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; @@ -45,23 +44,39 @@ public class RangedPutRequestBoundsFilter extends FilterBase { /** * default value for maximum size of a ranged PUT target resource. */ - private static final long DEFAULT_MAX_BOUND_UP = 1L << 32; + private static final long DEFAULT_MAX = 1L << 32; private static final String PARAMETER_MAX_SIZE = "maxSize"; /** - * Maximum size of a ranged PUT target resource. 4GB by default. + * Maximum size of a ranged PUT target resource, 4GB by default. A value of -1 indicates no maximum. */ - private long maxSize = DEFAULT_MAX_BOUND_UP; + private long maxSize = DEFAULT_MAX; + /** + * Gets the maximum size of destination resource that is allowed. + * + * @return The maximum size of destination resource that is allowed in bytes. A value of -1 indicates no maximum. + */ public long getMaxSize() { return maxSize; } + /** + * Sets the maximum size of destination resource that is allowed. + * + * @param maxSize The maximum size of destination resource that is allowed in bytes. A value of -1 indicates no + * maximum. + * + * @throws IllegalArgumentException if the maxSize is unsupported + */ public void setMaxSize(long maxSize) { + if (maxSize < 0 && maxSize != -1) { + throw new IllegalArgumentException("Unsupported value: " + maxSize); + } this.maxSize = maxSize; } - private final transient Log log = LogFactory.getLog(RangedPutRequestBoundsFilter.class); + private transient Log log = LogFactory.getLog(RangedPutRequestBoundsFilter.class); private String filterName = null; @Override @@ -71,7 +86,15 @@ public void init(FilterConfig filterConfig) throws ServletException { String name = names.nextElement(); String value = filterConfig.getInitParameter(name); if (PARAMETER_MAX_SIZE.equals(name)) { - setMaxSize(Long.valueOf(value)); + try { + setMaxSize(Long.valueOf(value)); + } catch (IllegalArgumentException e) { + throw new ServletException(sm.getString("rangedRequestBoundsFilter.invalidParameter", filterName, + PARAMETER_MAX_SIZE, value), e); + } + } else { + throw new ServletException( + sm.getString("rangedRequestBoundsFilter.noSuchParameter", filterName, PARAMETER_MAX_SIZE)); } } } @@ -79,13 +102,12 @@ public void init(FilterConfig filterConfig) throws ServletException { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - if (request instanceof HttpServletRequest req) { + if (getMaxSize() != -1 && request instanceof HttpServletRequest req) { if ("PUT".equals(req.getMethod())) { Enumeration values = req.getHeaders("Content-Range"); while (values.hasMoreElements()) { String cr = values.nextElement(); if (!checkContentRangeBounds(req, cr)) { - ((HttpServletResponse) response).sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE); return; } @@ -107,7 +129,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha private boolean checkContentRangeBounds(HttpServletRequest req, String contentRangeHeader) { try { ContentRange contentRange = ContentRange.parse(new StringReader(contentRangeHeader)); - if (contentRange != null && getMaxSize() >= 0 && + if (contentRange != null && getMaxSize() != -1 && (getMaxSize() <= contentRange.getEnd() || getMaxSize() < (contentRange.getLength()))) { if (log.isWarnEnabled()) { log.warn(sm.getString("rangedRequestBoundsFilter.maxSizeExceeded", filterName, req.getRequestURI(), From ef3e0161732d09b18016b06a4c115bba3efbcb45 Mon Sep 17 00:00:00 2001 From: Chenjp Date: Fri, 4 Jul 2025 13:48:52 +0800 Subject: [PATCH 3/3] add testcase and message --- .../catalina/filters/LocalStrings.properties | 2 +- .../filters/LocalStrings_zh_CN.properties | 4 + .../filters/RangedPutRequestBoundsFilter.java | 6 +- .../catalina/filters/TestRangedPutFilter.java | 366 ++++++++++++++++++ 4 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 test/org/apache/catalina/filters/TestRangedPutFilter.java diff --git a/java/org/apache/catalina/filters/LocalStrings.properties b/java/org/apache/catalina/filters/LocalStrings.properties index 24718761c5e2..0b61875fb7be 100644 --- a/java/org/apache/catalina/filters/LocalStrings.properties +++ b/java/org/apache/catalina/filters/LocalStrings.properties @@ -82,6 +82,6 @@ restCsrfPreventionFilter.multipleNonce.debug=Different CSRF nonces are sent as r webDavFilter.xpProblem=WebdavFixFilter: the XP-x64-SP2 client is known not to work with WebDAV Servlet webDavFilter.xpRootContext=WebdavFixFilter: the XP-x64-SP2 client will only work with the root context -rangedRequestBoundsFilter.maxSizeExceeded=[{0}] Requested [{1}] from [{2}] with Content-Range header value [{3}] exceeds the maximum allowed [{4}] bytes. +rangedRequestBoundsFilter.maxSizeExceeded=[{0}] Request [{1}] from [{2}] is rejected due to Content-Range header value [{3}] exceeds the maximum allowed [{4}] bytes. rangedRequestBoundsFilter.invalidParameter=[{0}] Invalid value for parameter [{1}]: [{2}] rangedRequestBoundsFilter.noSuchParameter=[{0}] No such parameter [{1}] diff --git a/java/org/apache/catalina/filters/LocalStrings_zh_CN.properties b/java/org/apache/catalina/filters/LocalStrings_zh_CN.properties index b4569f985dbb..d6699f976782 100644 --- a/java/org/apache/catalina/filters/LocalStrings_zh_CN.properties +++ b/java/org/apache/catalina/filters/LocalStrings_zh_CN.properties @@ -75,3 +75,7 @@ restCsrfPreventionFilter.multipleNonce.debug=不同的 CSRF 随机数作为请 webDavFilter.xpProblem=WebdavFixFilter:已知XP-x64-SP2客户端不使用WebDAV Servlet webDavFilter.xpRootContext=WebdavFixFilter:XP-x64-SP2客户端将仅与根上下文一起工作 + +rangedRequestBoundsFilter.maxSizeExceeded=[{0}] 来自 [{2}] 的请求[{1}]被拒绝。其Content-Range头信息 [{3}] 超过了允许的最大长度[{4}]。 +rangedRequestBoundsFilter.invalidParameter=[{0}] 不合法的参数设置 [{1}]: [{2}] +rangedRequestBoundsFilter.noSuchParameter=[{0}] 不支持参数 [{1}] \ No newline at end of file diff --git a/java/org/apache/catalina/filters/RangedPutRequestBoundsFilter.java b/java/org/apache/catalina/filters/RangedPutRequestBoundsFilter.java index f582752c3fba..75bc5136476c 100644 --- a/java/org/apache/catalina/filters/RangedPutRequestBoundsFilter.java +++ b/java/org/apache/catalina/filters/RangedPutRequestBoundsFilter.java @@ -39,7 +39,7 @@ * request's Content-Range header that are allowed. */ public class RangedPutRequestBoundsFilter extends FilterBase { - protected static final StringManager sm = StringManager.getManager(FilterBase.class); + protected static final StringManager sm = StringManager.getManager(RangedPutRequestBoundsFilter.class); /** * default value for maximum size of a ranged PUT target resource. @@ -148,4 +148,8 @@ protected Log getLogger() { return log; } + @Override + protected boolean isConfigProblemFatal() { + return true; + } } diff --git a/test/org/apache/catalina/filters/TestRangedPutFilter.java b/test/org/apache/catalina/filters/TestRangedPutFilter.java new file mode 100644 index 000000000000..4f85394a32b4 --- /dev/null +++ b/test/org/apache/catalina/filters/TestRangedPutFilter.java @@ -0,0 +1,366 @@ +/* + * 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.filters; + +import static org.apache.catalina.startup.SimpleHttpClient.CRLF; + +import java.io.File; +import java.nio.file.Files; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.Context; +import org.apache.catalina.LifecycleState; +import org.apache.catalina.Wrapper; +import org.apache.catalina.servlets.DefaultServlet; +import org.apache.catalina.startup.SimpleHttpClient; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.descriptor.web.FilterDef; +import org.apache.tomcat.util.descriptor.web.FilterMap; + +public class TestRangedPutFilter extends TomcatBaseTest { + + private File tempDocBase; + + @Override + public void setUp() throws Exception { + super.setUp(); + tempDocBase = Files.createTempDirectory(getTemporaryDirectory().toPath(), "put").toFile(); + } + + @Test + public void testConfiguration() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + Context root = tomcat.addContext("", tempDocBase.getAbsolutePath()); + + FilterDef filterDef = new FilterDef(); + long maxSize = 1024L; + filterDef.addInitParameter("maxSize", String.valueOf(maxSize)); + + filterDef.setFilter(new RangedPutRequestBoundsFilter()); + filterDef.setFilterClass(RangedPutRequestBoundsFilter.class.getName()); + filterDef.setFilterName("rangedPutFilter01"); + root.addFilterDef(filterDef); + + FilterMap filterMap = new FilterMap(); + filterMap.setFilterName("rangedPutFilter01"); + filterMap.addURLPatternDecoded("*"); + root.addFilterMap(filterMap); + + tomcat.start(); + + RangedPutRequestBoundsFilter filter = (RangedPutRequestBoundsFilter) filterDef.getFilter(); + try { + Assert.assertTrue(filter.getMaxSize() != -1); + Assert.assertEquals(maxSize, filter.getMaxSize()); + } finally { + tomcat.stop(); + } + } + + @Test + public void testConfigurationInvalidParameter() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + Context root = tomcat.addContext("", tempDocBase.getAbsolutePath()); + + FilterDef filterDef = new FilterDef(); + long maxSize = -2; + filterDef.addInitParameter("maxSize", String.valueOf(maxSize)); + + filterDef.setFilterClass(RangedPutRequestBoundsFilter.class.getName()); + filterDef.setFilterName("rangedPutFilter01"); + root.addFilterDef(filterDef); + + FilterMap filterMap = new FilterMap(); + filterMap.setFilterName("rangedPutFilter01"); + filterMap.addURLPatternDecoded("*"); + root.addFilterMap(filterMap); + + try { + tomcat.start(); + Assert.assertEquals("Context should be stopped due to filter configuration exception", LifecycleState.STOPPED, root.getState()); + } finally { + tomcat.stop(); + } + } + + + @Test + public void testConfigurationNoSuchParameter() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + Context root = tomcat.addContext("", tempDocBase.getAbsolutePath()); + + FilterDef filterDef = new FilterDef(); + filterDef.addInitParameter("maxPostSize", "1024"); + + filterDef.setFilterClass(RangedPutRequestBoundsFilter.class.getName()); + filterDef.setFilterName("rangedPutFilter01"); + root.addFilterDef(filterDef); + + FilterMap filterMap = new FilterMap(); + filterMap.setFilterName("rangedPutFilter01"); + filterMap.addURLPatternDecoded("*"); + root.addFilterMap(filterMap); + + try { + tomcat.start(); + Assert.assertEquals("Context should be stopped due to filter configuration exception", LifecycleState.STOPPED, root.getState()); + } finally { + tomcat.stop(); + } + } + + @Test + public void testRangePutTooBig() throws Exception { + Tomcat tomcat = getTomcatInstance(); + Context root = tomcat.addContext("", tempDocBase.getAbsolutePath()); + + FilterDef filterDef = new FilterDef(); + int maxSize = 1024; + filterDef.addInitParameter("maxSize", String.valueOf(maxSize)); + + filterDef.setFilterClass(RangedPutRequestBoundsFilter.class.getName()); + filterDef.setFilterName("rangedPutFilter01"); + root.addFilterDef(filterDef); + + FilterMap filterMap = new FilterMap(); + filterMap.setFilterName("rangedPutFilter01"); + filterMap.addURLPatternDecoded("*"); + root.addFilterMap(filterMap); + + Wrapper w = Tomcat.addServlet(root, "default", DefaultServlet.class.getName()); + w.addInitParameter("readonly", "false"); + w.addInitParameter("allowPartialPut", Boolean.toString(true)); + root.addServletMappingDecoded("/", "default"); + + tomcat.start(); + + + String text = "a".repeat(maxSize+1); + + // Full PUT + SimpleHttpClient putClient = new SimpleHttpClient() { + + @Override + public boolean isResponseBodyOK() { + // TODO Auto-generated method stub + return false; + } + }; + putClient.setPort(getPort()); + + putClient.setRequest(new String[] { + "PUT /test.txt HTTP/1.1" + CRLF + + "Host: localhost:" + getPort() + CRLF + + "Content-Length: " + text.length() + CRLF + + CRLF + + text + }); + putClient.connect(); + putClient.processRequest(false); + Assert.assertTrue(putClient.isResponse201()); + putClient.disconnect(); + + putClient.reset(); + + // Partial PUT + putClient.connect(); + putClient.setRequest(new String[] { + "PUT /test.txt HTTP/1.1" + CRLF + + "Host: localhost:" + getPort() + CRLF + + "Content-Range: bytes 0-0/" +text.length()+CRLF+ + "Content-Length: " + 1 + CRLF + + CRLF + + "1" + }); + putClient.processRequest(false); + Assert.assertTrue(putClient.isResponse413()); + putClient.disconnect(); + putClient.reset(); + + + // Check for the final resource + String path = "http://localhost:" + getPort() + "/test.txt"; + ByteChunk responseBody = new ByteChunk(); + + int rc = getUrl(path, responseBody, null); + + Assert.assertEquals(200, rc); + Assert.assertEquals(text, responseBody.toString()); + tomcat.stop(); + } + + @Test + public void testRangePutUnlimited() throws Exception { + Tomcat tomcat = getTomcatInstance(); + Context root = tomcat.addContext("", tempDocBase.getAbsolutePath()); + + FilterDef filterDef = new FilterDef(); + int maxSize = -1; + filterDef.addInitParameter("maxSize", String.valueOf(maxSize)); + + filterDef.setFilterClass(RangedPutRequestBoundsFilter.class.getName()); + filterDef.setFilterName("rangedPutFilter01"); + root.addFilterDef(filterDef); + + FilterMap filterMap = new FilterMap(); + filterMap.setFilterName("rangedPutFilter01"); + filterMap.addURLPatternDecoded("*"); + root.addFilterMap(filterMap); + + Wrapper w = Tomcat.addServlet(root, "default", DefaultServlet.class.getName()); + w.addInitParameter("readonly", "false"); + w.addInitParameter("allowPartialPut", Boolean.toString(true)); + root.addServletMappingDecoded("/", "default"); + + tomcat.start(); + + String text = "a".repeat(1024); + // Full PUT + SimpleHttpClient putClient = new SimpleHttpClient() { + + @Override + public boolean isResponseBodyOK() { + // TODO Auto-generated method stub + return false; + } + }; + putClient.setPort(getPort()); + + putClient.setRequest(new String[] { + "PUT /test.txt HTTP/1.1" + CRLF + + "Host: localhost:" + getPort() + CRLF + + "Content-Length: " + text.length() + CRLF + + CRLF + + text + }); + putClient.connect(); + putClient.processRequest(false); + Assert.assertTrue(putClient.isResponse201()); + putClient.disconnect(); + putClient.reset(); + + // Partial PUT + putClient.connect(); + putClient.setRequest(new String[] { + "PUT /test.txt HTTP/1.1" + CRLF + + "Host: localhost:" + getPort() + CRLF + + "Content-Range: bytes 10241024-10241024/10241025"+CRLF+ + "Content-Length: " + 1 + CRLF + + CRLF + + "a" + }); + putClient.processRequest(false); + Assert.assertTrue(putClient.isResponse204()); + putClient.disconnect(); + putClient.reset(); + + + // Check for the final resource + String path = "http://localhost:" + getPort() + "/test.txt"; + ByteChunk responseBody = new ByteChunk(); + + int rc = getUrl(path, responseBody, null); + + Assert.assertEquals(200, rc); + Assert.assertEquals(10241025, responseBody.getLength()); + tomcat.stop(); + } + + @Test + public void testRangePutLimited() throws Exception { + Tomcat tomcat = getTomcatInstance(); + Context root = tomcat.addContext("", tempDocBase.getAbsolutePath()); + + FilterDef filterDef = new FilterDef(); + int maxSize = 1025; + filterDef.addInitParameter("maxSize", String.valueOf(maxSize)); + + filterDef.setFilterClass(RangedPutRequestBoundsFilter.class.getName()); + filterDef.setFilterName("rangedPutFilter01"); + root.addFilterDef(filterDef); + + FilterMap filterMap = new FilterMap(); + filterMap.setFilterName("rangedPutFilter01"); + filterMap.addURLPatternDecoded("*"); + root.addFilterMap(filterMap); + + Wrapper w = Tomcat.addServlet(root, "default", DefaultServlet.class.getName()); + w.addInitParameter("readonly", "false"); + w.addInitParameter("allowPartialPut", Boolean.toString(true)); + root.addServletMappingDecoded("/", "default"); + + tomcat.start(); + + String text = "a".repeat(maxSize-1); + // Full PUT + SimpleHttpClient putClient = new SimpleHttpClient() { + + @Override + public boolean isResponseBodyOK() { + // TODO Auto-generated method stub + return false; + } + }; + putClient.setPort(getPort()); + + putClient.setRequest(new String[] { + "PUT /test.txt HTTP/1.1" + CRLF + + "Host: localhost:" + getPort() + CRLF + + "Content-Length: " + text.length() + CRLF + + CRLF + + text + }); + putClient.connect(); + putClient.processRequest(false); + Assert.assertTrue(putClient.isResponse201()); + putClient.disconnect(); + putClient.reset(); + + // Partial PUT + putClient.connect(); + putClient.setRequest(new String[] { + "PUT /test.txt HTTP/1.1" + CRLF + + "Host: localhost:" + getPort() + CRLF + + "Content-Range: bytes "+text.length()+"-"+text.length()+"/"+(text.length()+1)+CRLF+ + "Content-Length: " + 1 + CRLF + + CRLF + + "a" + }); + putClient.processRequest(false); + Assert.assertTrue(putClient.isResponse204()); + putClient.disconnect(); + putClient.reset(); + + + // Check for the final resource + String path = "http://localhost:" + getPort() + "/test.txt"; + ByteChunk responseBody = new ByteChunk(); + + int rc = getUrl(path, responseBody, null); + + Assert.assertEquals(200, rc); + Assert.assertEquals(text+"a", responseBody.toString()); + tomcat.stop(); + } +}