Skip to content

Commit 423aa28

Browse files
committed
HttpRange validates requested ranges
Issue: SPR-17318
1 parent 5094941 commit 423aa28

File tree

2 files changed

+76
-8
lines changed

2 files changed

+76
-8
lines changed

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

Lines changed: 27 additions & 7 deletions
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.
@@ -44,6 +44,9 @@
4444
*/
4545
public abstract class HttpRange {
4646

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

4952

@@ -59,16 +62,22 @@ public ResourceRegion toResourceRegion(Resource resource) {
5962
// Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
6063
Assert.isTrue(resource.getClass() != InputStreamResource.class,
6164
"Cannot convert an InputStreamResource to a ResourceRegion");
65+
long contentLength = getLengthFor(resource);
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;
6273
try {
63-
long contentLength = resource.contentLength();
74+
contentLength = resource.contentLength();
6475
Assert.isTrue(contentLength > 0, "Resource content length should be > 0");
65-
long start = getRangeStart(contentLength);
66-
long end = getRangeEnd(contentLength);
67-
return new ResourceRegion(resource, start, end - start + 1);
6876
}
6977
catch (IOException ex) {
70-
throw new IllegalArgumentException("Failed to convert Resource to ResourceRegion", ex);
78+
throw new IllegalArgumentException("Failed to obtain Resource content length", ex);
7179
}
80+
return contentLength;
7281
}
7382

7483
/**
@@ -122,7 +131,8 @@ public static HttpRange createSuffixRange(long suffixLength) {
122131
* <p>This method can be used to parse an {@code Range} header.
123132
* @param ranges the string to parse
124133
* @return the list of ranges
125-
* @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.
126136
*/
127137
public static List<HttpRange> parseRanges(@Nullable String ranges) {
128138
if (!StringUtils.hasLength(ranges)) {
@@ -134,6 +144,7 @@ public static List<HttpRange> parseRanges(@Nullable String ranges) {
134144
ranges = ranges.substring(BYTE_RANGE_PREFIX.length());
135145

136146
String[] tokens = StringUtils.tokenizeToStringArray(ranges, ",");
147+
Assert.isTrue(tokens.length <= MAX_RANGES, () -> "Too many ranges " + tokens.length);
137148
List<HttpRange> result = new ArrayList<>(tokens.length);
138149
for (String token : tokens) {
139150
result.add(parseRange(token));
@@ -169,6 +180,8 @@ else if (dashIdx == 0) {
169180
* @param ranges the list of ranges
170181
* @param resource the resource to select the regions from
171182
* @return the list of regions for the given resource
183+
* @throws IllegalArgumentException if the sum of all ranges exceeds the
184+
* resource length.
172185
* @since 4.3
173186
*/
174187
public static List<ResourceRegion> toResourceRegions(List<HttpRange> ranges, Resource resource) {
@@ -179,6 +192,13 @@ public static List<ResourceRegion> toResourceRegions(List<HttpRange> ranges, Res
179192
for (HttpRange range : ranges) {
180193
regions.add(range.toResourceRegion(resource));
181194
}
195+
if (ranges.size() > 1) {
196+
long length = getLengthFor(resource);
197+
long total = regions.stream().map(ResourceRegion::getCount).reduce(0L, (count, sum) -> sum + count);
198+
Assert.isTrue(total < length,
199+
() -> "The sum of all ranges (" + total + ") " +
200+
"should be less than the resource length (" + length + ")");
201+
}
182202
return regions;
183203
}
184204

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

Lines changed: 49 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.
@@ -19,7 +19,9 @@
1919
import java.io.IOException;
2020
import java.nio.charset.StandardCharsets;
2121
import java.util.ArrayList;
22+
import java.util.Arrays;
2223
import java.util.List;
24+
import java.util.stream.Stream;
2325

2426
import org.junit.Test;
2527

@@ -100,6 +102,31 @@ public void parseRanges() {
100102
assertEquals(999, ranges.get(2).getRangeEnd(1000));
101103
}
102104

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+
103130
@Test
104131
public void rangeToString() {
105132
List<HttpRange> ranges = new ArrayList<>();
@@ -144,4 +171,25 @@ public void toResourceRegionExceptionLength() throws IOException {
144171
range.toResourceRegion(resource);
145172
}
146173

174+
@Test
175+
public void toResourceRegionsValidations() {
176+
byte[] bytes = "12345".getBytes(StandardCharsets.UTF_8);
177+
ByteArrayResource resource = new ByteArrayResource(bytes);
178+
179+
// 1. Below 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 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+
147195
}

0 commit comments

Comments
 (0)