Skip to content

Commit 0447726

Browse files
committed
HttpRange validates requested ranges
Issue: SPR-17318
1 parent d15abfd commit 0447726

File tree

2 files changed

+78
-8
lines changed

2 files changed

+78
-8
lines changed

spring-web/src/main/java/org/springframework/http/HttpRange.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -43,6 +43,9 @@
4343
*/
4444
public abstract class HttpRange {
4545

46+
/** Maximum ranges per request. */
47+
private static final int MAX_RANGES = 100;
48+
4649
private static final String BYTE_RANGE_PREFIX = "bytes=";
4750

4851

@@ -58,16 +61,23 @@ public ResourceRegion toResourceRegion(Resource resource) {
5861
// Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
5962
Assert.isTrue(resource.getClass() != InputStreamResource.class,
6063
"Cannot convert an InputStreamResource to a ResourceRegion");
64+
long contentLength = getLengthFor(resource);
65+
Assert.isTrue(contentLength > 0, "Resource content length should be > 0");
66+
long start = getRangeStart(contentLength);
67+
long end = getRangeEnd(contentLength);
68+
return new ResourceRegion(resource, start, end - start + 1);
69+
}
70+
71+
private static long getLengthFor(Resource resource) {
72+
long contentLength;
6173
try {
62-
long contentLength = resource.contentLength();
74+
contentLength = resource.contentLength();
6375
Assert.isTrue(contentLength > 0, "Resource content length should be > 0");
64-
long start = getRangeStart(contentLength);
65-
long end = getRangeEnd(contentLength);
66-
return new ResourceRegion(resource, start, end - start + 1);
6776
}
6877
catch (IOException ex) {
69-
throw new IllegalArgumentException("Failed to convert Resource to ResourceRegion", ex);
78+
throw new IllegalArgumentException("Failed to obtain Resource content length", ex);
7079
}
80+
return contentLength;
7181
}
7282

7383
/**
@@ -121,7 +131,8 @@ public static HttpRange createSuffixRange(long suffixLength) {
121131
* <p>This method can be used to parse an {@code Range} header.
122132
* @param ranges the string to parse
123133
* @return the list of ranges
124-
* @throws IllegalArgumentException if the string cannot be parsed
134+
* @throws IllegalArgumentException if the string cannot be parsed, or if
135+
* the number of ranges is greater than 100.
125136
*/
126137
public static List<HttpRange> parseRanges(String ranges) {
127138
if (!StringUtils.hasLength(ranges)) {
@@ -133,6 +144,7 @@ public static List<HttpRange> parseRanges(String ranges) {
133144
ranges = ranges.substring(BYTE_RANGE_PREFIX.length());
134145

135146
String[] tokens = StringUtils.tokenizeToStringArray(ranges, ",");
147+
Assert.isTrue(tokens.length <= MAX_RANGES, "Too many ranges " + tokens.length);
136148
List<HttpRange> result = new ArrayList<HttpRange>(tokens.length);
137149
for (String token : tokens) {
138150
result.add(parseRange(token));
@@ -169,6 +181,8 @@ else if (dashIdx == 0) {
169181
* @param resource the resource to select the regions from
170182
* @return the list of regions for the given resource
171183
* @since 4.3
184+
* @throws IllegalArgumentException if the sum of all ranges exceeds the
185+
* resource length.
172186
*/
173187
public static List<ResourceRegion> toResourceRegions(List<HttpRange> ranges, Resource resource) {
174188
if (CollectionUtils.isEmpty(ranges)) {
@@ -178,6 +192,15 @@ public static List<ResourceRegion> toResourceRegions(List<HttpRange> ranges, Res
178192
for (HttpRange range : ranges) {
179193
regions.add(range.toResourceRegion(resource));
180194
}
195+
if (ranges.size() > 1) {
196+
long length = getLengthFor(resource);
197+
long total = 0;
198+
for (ResourceRegion region : regions) {
199+
total += region.getCount();
200+
}
201+
Assert.isTrue(total < length, "The sum of all ranges (" + total + ") " +
202+
"should be less than the resource length (" + length + ")");
203+
}
181204
return regions;
182205
}
183206

spring-web/src/test/java/org/springframework/http/HttpRangeTests.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020
import java.nio.charset.Charset;
21+
import java.nio.charset.StandardCharsets;
2122
import java.util.ArrayList;
2223
import java.util.List;
2324

@@ -101,6 +102,31 @@ public void parseRanges() {
101102
assertEquals(999, ranges.get(2).getRangeEnd(1000));
102103
}
103104

105+
@Test
106+
public void parseRangesValidations() {
107+
108+
// 1. At limit..
109+
StringBuilder sb = new StringBuilder("bytes=0-0");
110+
for (int i=0; i < 99; i++) {
111+
sb.append(",").append(i).append("-").append(i + 1);
112+
}
113+
List<HttpRange> ranges = HttpRange.parseRanges(sb.toString());
114+
assertEquals(100, ranges.size());
115+
116+
// 2. Above limit..
117+
sb = new StringBuilder("bytes=0-0");
118+
for (int i=0; i < 100; i++) {
119+
sb.append(",").append(i).append("-").append(i + 1);
120+
}
121+
try {
122+
HttpRange.parseRanges(sb.toString());
123+
fail();
124+
}
125+
catch (IllegalArgumentException ex) {
126+
// Expected
127+
}
128+
}
129+
104130
@Test
105131
public void rangeToString() {
106132
List<HttpRange> ranges = new ArrayList<>();
@@ -145,4 +171,25 @@ public void toResourceRegionExceptionLength() throws IOException {
145171
range.toResourceRegion(resource);
146172
}
147173

174+
@Test
175+
public void toResourceRegionsValidations() {
176+
byte[] bytes = "12345".getBytes(StandardCharsets.UTF_8);
177+
ByteArrayResource resource = new ByteArrayResource(bytes);
178+
179+
// 1. Below full length
180+
List<HttpRange> ranges = HttpRange.parseRanges("bytes=0-1,2-3");
181+
List<ResourceRegion> regions = HttpRange.toResourceRegions(ranges, resource);
182+
assertEquals(2, regions.size());
183+
184+
// 2. At full length
185+
ranges = HttpRange.parseRanges("bytes=0-1,2-4");
186+
try {
187+
HttpRange.toResourceRegions(ranges, resource);
188+
fail();
189+
}
190+
catch (IllegalArgumentException ex) {
191+
// Expected..
192+
}
193+
}
194+
148195
}

0 commit comments

Comments
 (0)