diff options
Diffstat (limited to 'spring-web')
5 files changed, 102 insertions, 24 deletions
diff --git a/spring-web/src/main/java/org/springframework/http/HttpRange.java b/spring-web/src/main/java/org/springframework/http/HttpRange.java index c11a57a0..ea3fef01 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpRange.java +++ b/spring-web/src/main/java/org/springframework/http/HttpRange.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,9 @@ import org.springframework.util.StringUtils; */ public abstract class HttpRange { + /** Maximum ranges per request. */ + private static final int MAX_RANGES = 100; + private static final String BYTE_RANGE_PREFIX = "bytes="; @@ -58,16 +61,23 @@ public abstract class HttpRange { // Note: custom InputStreamResource subclasses could provide a pre-calculated content length! Assert.isTrue(resource.getClass() != InputStreamResource.class, "Cannot convert an InputStreamResource to a ResourceRegion"); + long contentLength = getLengthFor(resource); + Assert.isTrue(contentLength > 0, "Resource content length should be > 0"); + long start = getRangeStart(contentLength); + long end = getRangeEnd(contentLength); + return new ResourceRegion(resource, start, end - start + 1); + } + + private static long getLengthFor(Resource resource) { + long contentLength; try { - long contentLength = resource.contentLength(); + contentLength = resource.contentLength(); Assert.isTrue(contentLength > 0, "Resource content length should be > 0"); - long start = getRangeStart(contentLength); - long end = getRangeEnd(contentLength); - return new ResourceRegion(resource, start, end - start + 1); } catch (IOException ex) { - throw new IllegalArgumentException("Failed to convert Resource to ResourceRegion", ex); + throw new IllegalArgumentException("Failed to obtain Resource content length", ex); } + return contentLength; } /** @@ -121,7 +131,8 @@ public abstract class HttpRange { * <p>This method can be used to parse an {@code Range} header. * @param ranges the string to parse * @return the list of ranges - * @throws IllegalArgumentException if the string cannot be parsed + * @throws IllegalArgumentException if the string cannot be parsed, or if + * the number of ranges is greater than 100. */ public static List<HttpRange> parseRanges(String ranges) { if (!StringUtils.hasLength(ranges)) { @@ -133,6 +144,7 @@ public abstract class HttpRange { ranges = ranges.substring(BYTE_RANGE_PREFIX.length()); String[] tokens = StringUtils.tokenizeToStringArray(ranges, ","); + Assert.isTrue(tokens.length <= MAX_RANGES, "Too many ranges " + tokens.length); List<HttpRange> result = new ArrayList<HttpRange>(tokens.length); for (String token : tokens) { result.add(parseRange(token)); @@ -169,6 +181,8 @@ public abstract class HttpRange { * @param resource the resource to select the regions from * @return the list of regions for the given resource * @since 4.3 + * @throws IllegalArgumentException if the sum of all ranges exceeds the + * resource length. */ public static List<ResourceRegion> toResourceRegions(List<HttpRange> ranges, Resource resource) { if (CollectionUtils.isEmpty(ranges)) { @@ -178,6 +192,15 @@ public abstract class HttpRange { for (HttpRange range : ranges) { regions.add(range.toResourceRegion(resource)); } + if (ranges.size() > 1) { + long length = getLengthFor(resource); + long total = 0; + for (ResourceRegion region : regions) { + total += region.getCount(); + } + Assert.isTrue(total < length, "The sum of all ranges (" + total + ") " + + "should be less than the resource length (" + length + ")"); + } return regions; } diff --git a/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestFactory.java index 3f3b4185..ec251fdf 100644 --- a/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import org.springframework.http.HttpMethod; * all outgoing and incoming streams in memory. * * <p>Using this wrapper allows for multiple reads of the - * @linkplain ClientHttpResponse#getBody() response body}. + * {@linkplain ClientHttpResponse#getBody() response body}. * * @author Arjen Poutsma * @since 3.1 diff --git a/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java index 877f6094..fc04869c 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java @@ -1,11 +1,11 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed 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 + * 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, @@ -63,13 +63,13 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ private static final List<String> SAFE_METHODS = Arrays.asList("GET", "HEAD"); /** - * Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match" + * Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match". * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a> */ private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?"); /** - * Date formats as specified in the HTTP RFC + * Date formats as specified in the HTTP RFC. * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a> */ private static final String[] DATE_FORMATS = new String[] { @@ -233,7 +233,6 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ } // Update response - boolean isHttpGetOrHead = SAFE_METHODS.contains(getRequest().getMethod()); if (this.notModified) { response.setStatus(isHttpGetOrHead ? diff --git a/spring-web/src/test/java/org/springframework/http/HttpRangeTests.java b/spring-web/src/test/java/org/springframework/http/HttpRangeTests.java index fe672951..8445cc66 100644 --- a/spring-web/src/test/java/org/springframework/http/HttpRangeTests.java +++ b/spring-web/src/test/java/org/springframework/http/HttpRangeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.http; import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -102,6 +103,31 @@ public class HttpRangeTests { } @Test + public void parseRangesValidations() { + + // 1. At limit.. + StringBuilder sb = new StringBuilder("bytes=0-0"); + for (int i=0; i < 99; i++) { + sb.append(",").append(i).append("-").append(i + 1); + } + List<HttpRange> ranges = HttpRange.parseRanges(sb.toString()); + assertEquals(100, ranges.size()); + + // 2. Above limit.. + sb = new StringBuilder("bytes=0-0"); + for (int i=0; i < 100; i++) { + sb.append(",").append(i).append("-").append(i + 1); + } + try { + HttpRange.parseRanges(sb.toString()); + fail(); + } + catch (IllegalArgumentException ex) { + // Expected + } + } + + @Test public void rangeToString() { List<HttpRange> ranges = new ArrayList<>(); ranges.add(HttpRange.createByteRange(0, 499)); @@ -145,4 +171,25 @@ public class HttpRangeTests { range.toResourceRegion(resource); } + @Test + public void toResourceRegionsValidations() { + byte[] bytes = "12345".getBytes(StandardCharsets.UTF_8); + ByteArrayResource resource = new ByteArrayResource(bytes); + + // 1. Below full length + List<HttpRange> ranges = HttpRange.parseRanges("bytes=0-1,2-3"); + List<ResourceRegion> regions = HttpRange.toResourceRegions(ranges, resource); + assertEquals(2, regions.size()); + + // 2. At full length + ranges = HttpRange.parseRanges("bytes=0-1,2-4"); + try { + HttpRange.toResourceRegions(ranges, resource); + fail(); + } + catch (IllegalArgumentException ex) { + // Expected.. + } + } + } diff --git a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java index c1b99345..733a2588 100644 --- a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java +++ b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -101,7 +101,7 @@ public class MockHttpServletRequest implements HttpServletRequest { new BufferedReader(new StringReader("")); /** - * Date formats as specified in the HTTP RFC + * Date formats as specified in the HTTP RFC. * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a> */ private static final String[] DATE_FORMATS = new String[] { @@ -510,7 +510,7 @@ public class MockHttpServletRequest implements HttpServletRequest { } /** - * Adds all provided parameters <strong>without</strong> replacing any + * Add all provided parameters <strong>without</strong> replacing any * existing values. To replace existing values, use * {@link #setParameters(java.util.Map)}. */ @@ -540,7 +540,7 @@ public class MockHttpServletRequest implements HttpServletRequest { } /** - * Removes all existing parameters. + * Remove all existing parameters. */ public void removeAllParameters() { this.parameters.clear(); @@ -702,8 +702,8 @@ public class MockHttpServletRequest implements HttpServletRequest { /** * Set the list of preferred locales, in descending order, effectively replacing * any existing locales. - * @see #addPreferredLocale * @since 3.2 + * @see #addPreferredLocale */ public void setPreferredLocales(List<Locale> locales) { Assert.notEmpty(locales, "Locale list must not be empty"); @@ -890,9 +890,9 @@ public class MockHttpServletRequest implements HttpServletRequest { } /** - * Add a header entry for the given name. - * <p>While this method can take any {@code Object} as a parameter, it - * is recommended to use the following types: + * Add an HTTP header entry for the given name. + * <p>While this method can take any {@code Object} as a parameter, + * it is recommended to use the following types: * <ul> * <li>String or any Object to be converted using {@code toString()}; see {@link #getHeader}.</li> * <li>String, Number, or Date for date headers; see {@link #getDateHeader}.</li> @@ -932,6 +932,15 @@ public class MockHttpServletRequest implements HttpServletRequest { } /** + * Remove already registered entries for the specified HTTP header, if any. + * @since 4.3.20 + */ + public void removeHeader(String name) { + Assert.notNull(name, "Header name must not be null"); + this.headers.remove(name); + } + + /** * Return the long timestamp for the date header with the given {@code name}. * <p>If the internal value representation is a String, this method will try * to parse it as a date using the supported date formats: @@ -1164,7 +1173,7 @@ public class MockHttpServletRequest implements HttpServletRequest { public String changeSessionId() { Assert.isTrue(this.session != null, "The request does not have a session"); if (this.session instanceof MockHttpSession) { - return ((MockHttpSession) session).changeSessionId(); + return ((MockHttpSession) this.session).changeSessionId(); } return this.session.getId(); } |