/*
* Copyright 2002-2017 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
*
* 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.springframework.web.servlet.resource;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.http.HttpServletResponse;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockServletContext;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
import org.springframework.web.servlet.HandlerMapping;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Unit tests for ResourceHttpRequestHandler.
*
* @author Keith Donald
* @author Jeremy Grelle
* @author Rossen Stoyanchev
* @author Brian Clozel
*/
public class ResourceHttpRequestHandlerTests {
private SimpleDateFormat dateFormat;
private ResourceHttpRequestHandler handler;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@Before
public void setUp() throws Exception {
dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
List<Resource> paths = new ArrayList<>(2);
paths.add(new ClassPathResource("test/", getClass()));
paths.add(new ClassPathResource("testalternatepath/", getClass()));
paths.add(new ClassPathResource("META-INF/resources/webjars/"));
this.handler = new ResourceHttpRequestHandler();
this.handler.setLocations(paths);
this.handler.setCacheSeconds(3600);
this.handler.setServletContext(new TestServletContext());
this.handler.afterPropertiesSet();
this.request = new MockHttpServletRequest("GET", "");
this.response = new MockHttpServletResponse();
}
@Test
public void getResource() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.handler.handleRequest(this.request, this.response);
assertEquals("text/css", this.response.getContentType());
assertEquals(17, this.response.getContentLength());
assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(this.response.getHeader("Last-Modified"), resourceLastModifiedDate("test/foo.css"));
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
assertEquals("h1 { color:red; }", this.response.getContentAsString());
}
@Test
public void getResourceHttpHeader() throws Exception {
this.request.setMethod("HEAD");
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.handler.handleRequest(this.request, this.response);
assertEquals(200, this.response.getStatus());
assertEquals("text/css", this.response.getContentType());
assertEquals(17, this.response.getContentLength());
assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(this.response.getHeader("Last-Modified"), resourceLastModifiedDate("test/foo.css"));
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
assertEquals(0, this.response.getContentAsByteArray().length);
}
@Test
public void getResourceHttpOptions() throws Exception {
this.request.setMethod("OPTIONS");
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.handler.handleRequest(this.request, this.response);
assertEquals(200, this.response.getStatus());
assertEquals("GET,HEAD,OPTIONS", this.response.getHeader("Allow"));
}
@Test
public void getResourceNoCache() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.handler.setCacheSeconds(0);
this.handler.handleRequest(this.request, this.response);
assertEquals("no-store", this.response.getHeader("Cache-Control"));
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(this.response.getHeader("Last-Modified"), resourceLastModifiedDate("test/foo.css"));
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
public void getVersionedResource() throws Exception {
VersionResourceResolver versionResolver = new VersionResourceResolver()
.addFixedVersionStrategy("versionString", "/**");
this.handler.setResourceResolvers(Arrays.asList(versionResolver, new PathResourceResolver()));
this.handler.afterPropertiesSet();
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "versionString/foo.css");
this.handler.handleRequest(this.request, this.response);
assertEquals("\"versionString\"", this.response.getHeader("ETag"));
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@SuppressWarnings("deprecation")
public void getResourceHttp10BehaviorCache() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.handler.setCacheSeconds(3600);
this.handler.setUseExpiresHeader(true);
this.handler.setUseCacheControlHeader(true);
this.handler.setAlwaysMustRevalidate(true);
this.handler.handleRequest(this.request, this.response);
assertEquals("max-age=3600, must-revalidate", this.response.getHeader("Cache-Control"));
assertTrue(dateHeaderAsLong("Expires") >= System.currentTimeMillis() - 1000 + (3600 * 1000));
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(this.response.getHeader("Last-Modified"), resourceLastModifiedDate("test/foo.css"));
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
@SuppressWarnings("deprecation")
public void getResourceHttp10BehaviorNoCache() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.handler.setCacheSeconds(0);
this.handler.setUseExpiresHeader(true);
this.handler.setUseCacheControlNoStore(false);
this.handler.setUseCacheControlHeader(true);
this.handler.handleRequest(this.request, this.response);
assertEquals("no-cache", this.response.getHeader("Pragma"));
assertThat(this.response.getHeaderValues("Cache-Control"), Matchers.iterableWithSize(1));
assertEquals("no-cache", this.response.getHeader("Cache-Control"));
assertTrue(dateHeaderAsLong("Expires") <= System.currentTimeMillis());
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(dateHeaderAsLong("Last-Modified") / 1000, resourceLastModified("test/foo.css") / 1000);
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
public void getResourceWithHtmlMediaType() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.html");
this.handler.handleRequest(this.request, this.response);
assertEquals("text/html", this.response.getContentType());
assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(this.response.getHeader("Last-Modified"), resourceLastModifiedDate("test/foo.html"));
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
public void getResourceFromAlternatePath() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "baz.css");
this.handler.handleRequest(this.request, this.response);
assertEquals("text/css", this.response.getContentType());
assertEquals(17, this.response.getContentLength());
assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(this.response.getHeader("Last-Modified"), resourceLastModifiedDate("testalternatepath/baz.css"));
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
assertEquals("h1 { color:red; }", this.response.getContentAsString());
}
@Test
public void getResourceFromSubDirectory() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "js/foo.js");
this.handler.handleRequest(this.request, this.response);
assertEquals("text/javascript", this.response.getContentType());
assertEquals("function foo() { console.log(\"hello world\"); }", this.response.getContentAsString());
}
@Test
public void getResourceFromSubDirectoryOfAlternatePath() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "js/baz.js");
this.handler.handleRequest(this.request, this.response);
assertEquals("text/javascript", this.response.getContentType());
assertEquals("function foo() { console.log(\"hello world\"); }", this.response.getContentAsString());
}
@Test // SPR-13658
public void getResourceWithRegisteredMediaType() throws Exception {
ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean();
factory.addMediaType("bar", new MediaType("foo", "bar"));
factory.afterPropertiesSet();
ContentNegotiationManager manager = factory.getObject();
List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setServletContext(new MockServletContext());
handler.setLocations(paths);
handler.setContentNegotiationManager(manager);
handler.afterPropertiesSet();
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.bar");
handler.handleRequest(this.request, this.response);
assertEquals("foo/bar", this.response.getContentType());
assertEquals("h1 { color:red; }", this.response.getContentAsString());
}
@Test // SPR-14577
public void getMediaTypeWithFavorPathExtensionOff() throws Exception {
ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean();
factory.setFavorPathExtension(false);
factory.afterPropertiesSet();
ContentNegotiationManager manager = factory.getObject();
List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setServletContext(new MockServletContext());
handler.setLocations(paths);
handler.setContentNegotiationManager(manager);
handler.afterPropertiesSet();
this.request.addHeader("Accept", "application/json,text/plain,*/*");
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.html");
handler.handleRequest(this.request, this.response);
assertEquals("text/html", this.response.getContentType());
}
@Test // SPR-14368
public void getResourceWithMediaTypeResolvedThroughServletContext() throws Exception {
MockServletContext servletContext = new MockServletContext() {
@Override
public String getMimeType(String filePath) {
return "foo/bar";
}
@Override
public String getVirtualServerName() {
return null;
}
};
List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setServletContext(servletContext);
handler.setLocations(paths);
handler.afterPropertiesSet();
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
handler.handleRequest(this.request, this.response);
assertEquals("foo/bar", this.response.getContentType());
assertEquals("h1 { color:red; }", this.response.getContentAsString());
}
@Test
public void invalidPath() throws Exception {
for (HttpMethod method : HttpMethod.values()) {
this.request = new MockHttpServletRequest("GET", "");
this.response = new MockHttpServletResponse();
testInvalidPath(method);
}
}
private void testInvalidPath(HttpMethod httpMethod) throws Exception {
this.request.setMethod(httpMethod.name());
Resource location = new ClassPathResource("test/", getClass());
this.handler.setLocations(Collections.singletonList(location));
testInvalidPath(location, "../testsecret/secret.txt");
testInvalidPath(location, "test/../../testsecret/secret.txt");
testInvalidPath(location, ":/../../testsecret/secret.txt");
location = new UrlResource(getClass().getResource("./test/"));
this.handler.setLocations(Collections.singletonList(location));
Resource secretResource = new UrlResource(getClass().getResource("testsecret/secret.txt"));
String secretPath = secretResource.getURL().getPath();
testInvalidPath(location, "file:" + secretPath);
testInvalidPath(location, "/file:" + secretPath);
testInvalidPath(location, "url:" + secretPath);
testInvalidPath(location, "/url:" + secretPath);
testInvalidPath(location, "/" + secretPath);
testInvalidPath(location, "////../.." + secretPath);
testInvalidPath(location, "/%2E%2E/testsecret/secret.txt");
testInvalidPath(location, "/ " + secretPath);
testInvalidPath(location, "url:" + secretPath);
}
private void testInvalidPath(Resource location, String requestPath) throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, requestPath);
this.response = new MockHttpServletResponse();
this.handler.handleRequest(this.request, this.response);
if (!location.createRelative(requestPath).exists() && !requestPath.contains(":")) {
fail(requestPath + " doesn't actually exist as a relative path");
}
assertEquals(HttpStatus.NOT_FOUND.value(), this.response.getStatus());
}
@Test
public void ignoreInvalidEscapeSequence() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/%foo%/bar.txt");
this.response = new MockHttpServletResponse();
this.handler.handleRequest(this.request, this.response);
assertEquals(404, this.response.getStatus());
}
@Test
public void processPath() throws Exception {
assertSame("/foo/bar", this.handler.processPath("/foo/bar"));
assertSame("foo/bar", this.handler.processPath("foo/bar"));
// leading whitespace control characters (00-1F)
assertEquals("/foo/bar", this.handler.processPath(" /foo/bar"));
assertEquals("/foo/bar", this.handler.processPath((char) 1 + "/foo/bar"));
assertEquals("/foo/bar", this.handler.processPath((char) 31 + "/foo/bar"));
assertEquals("foo/bar", this.handler.processPath(" foo/bar"));
assertEquals("foo/bar", this.handler.processPath((char) 31 + "foo/bar"));
// leading control character 0x7F (DEL)
assertEquals("/foo/bar", this.handler.processPath((char) 127 + "/foo/bar"));
assertEquals("/foo/bar", this.handler.processPath((char) 127 + "/foo/bar"));
// leading control and '/' characters
assertEquals("/foo/bar", this.handler.processPath(" / foo/bar"));
assertEquals("/foo/bar", this.handler.processPath(" / / foo/bar"));
assertEquals("/foo/bar", this.handler.processPath(" // /// //// foo/bar"));
assertEquals("/foo/bar", this.handler.processPath((char) 1 + " / " + (char) 127 + " // foo/bar"));
// root or empty path
assertEquals("", this.handler.processPath(" "));
assertEquals("/", this.handler.processPath("/"));
assertEquals("/", this.handler.processPath("///"));
assertEquals("/", this.handler.processPath("/ / / "));
}
@Test
public void initAllowedLocations() throws Exception {
PathResourceResolver resolver = (PathResourceResolver) this.handler.getResourceResolvers().get(0);
Resource[] locations = resolver.getAllowedLocations();
assertEquals(3, locations.length);
assertEquals("test/", ((ClassPathResource) locations[0]).getPath());
assertEquals("testalternatepath/", ((ClassPathResource) locations[1]).getPath());
assertEquals("META-INF/resources/webjars/", ((ClassPathResource) locations[2]).getPath());
}
@Test
public void initAllowedLocationsWithExplicitConfiguration() throws Exception {
ClassPathResource location1 = new ClassPathResource("test/", getClass());
ClassPathResource location2 = new ClassPathResource("testalternatepath/", getClass());
PathResourceResolver pathResolver = new PathResourceResolver();
pathResolver.setAllowedLocations(location1);
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setResourceResolvers(Collections.singletonList(pathResolver));
handler.setServletContext(new MockServletContext());
handler.setLocations(Arrays.asList(location1, location2));
handler.afterPropertiesSet();
Resource[] locations = pathResolver.getAllowedLocations();
assertEquals(1, locations.length);
assertEquals("test/", ((ClassPathResource) locations[0]).getPath());
}
@Test
public void notModified() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.request.addHeader("If-Modified-Since", resourceLastModified("test/foo.css"));
this.handler.handleRequest(this.request, this.response);
assertEquals(HttpServletResponse.SC_NOT_MODIFIED, this.response.getStatus());
}
@Test
public void modified() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.request.addHeader("If-Modified-Since", resourceLastModified("test/foo.css") / 1000 * 1000 - 1);
this.handler.handleRequest(this.request, this.response);
assertEquals(HttpServletResponse.SC_OK, this.response.getStatus());
assertEquals("h1 { color:red; }", this.response.getContentAsString());
}
@Test
public void directory() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "js/");
this.handler.handleRequest(this.request, this.response);
assertEquals(404, this.response.getStatus());
}
@Test
public void directoryInJarFile() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "underscorejs/");
this.handler.handleRequest(this.request, this.response);
assertEquals(200, this.response.getStatus());
assertEquals(0, this.response.getContentLength());
}
@Test
public void missingResourcePath() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "");
this.handler.handleRequest(this.request, this.response);
assertEquals(404, this.response.getStatus());
}
@Test(expected = IllegalStateException.class)
public void noPathWithinHandlerMappingAttribute() throws Exception {
this.handler.handleRequest(this.request, this.response);
}
@Test(expected = HttpRequestMethodNotSupportedException.class)
public void unsupportedHttpMethod() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.request.setMethod("POST");
this.handler.handleRequest(this.request, this.response);
}
@Test
public void resourceNotFound() throws Exception {
for (HttpMethod method : HttpMethod.values()) {
this.request = new MockHttpServletRequest("GET", "");
this.response = new MockHttpServletResponse();
resourceNotFound(method);
}
}
private void resourceNotFound(HttpMethod httpMethod) throws Exception {
this.request.setMethod(httpMethod.name());
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "not-there.css");
this.handler.handleRequest(this.request, this.response);
assertEquals(HttpStatus.NOT_FOUND.value(), this.response.getStatus());
}
@Test
public void partialContentByteRange() throws Exception {
this.request.addHeader("Range", "bytes=0-1");
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
this.handler.handleRequest(this.request, this.response);
assertEquals(206, this.response.getStatus());
assertEquals("text/plain", this.response.getContentType());
assertEquals(2, this.response.getContentLength());
assertEquals("bytes 0-1/10", this.response.getHeader("Content-Range"));
assertEquals("So", this.response.getContentAsString());
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
public void partialContentByteRangeNoEnd() throws Exception {
this.request.addHeader("Range", "bytes=9-");
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
this.handler.handleRequest(this.request, this.response);
assertEquals(206, this.response.getStatus());
assertEquals("text/plain", this.response.getContentType());
assertEquals(1, this.response.getContentLength());
assertEquals("bytes 9-9/10", this.response.getHeader("Content-Range"));
assertEquals(".", this.response.getContentAsString());
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
public void partialContentByteRangeLargeEnd() throws Exception {
this.request.addHeader("Range", "bytes=9-10000");
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
this.handler.handleRequest(this.request, this.response);
assertEquals(206, this.response.getStatus());
assertEquals("text/plain", this.response.getContentType());
assertEquals(1, this.response.getContentLength());
assertEquals("bytes 9-9/10", this.response.getHeader("Content-Range"));
assertEquals(".", this.response.getContentAsString());
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
public void partialContentSuffixRange() throws Exception {
this.request.addHeader("Range", "bytes=-1");
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
this.handler.handleRequest(this.request, this.response);
assertEquals(206, this.response.getStatus());
assertEquals("text/plain", this.response.getContentType());
assertEquals(1, this.response.getContentLength());
assertEquals("bytes 9-9/10", this.response.getHeader("Content-Range"));
assertEquals(".", this.response.getContentAsString());
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
public void partialContentSuffixRangeLargeSuffix() throws Exception {
this.request.addHeader("Range", "bytes=-11");
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
this.handler.handleRequest(this.request, this.response);
assertEquals(206, this.response.getStatus());
assertEquals("text/plain", this.response.getContentType());
assertEquals(10, this.response.getContentLength());
assertEquals("bytes 0-9/10", this.response.getHeader("Content-Range"));
assertEquals("Some text.", this.response.getContentAsString());
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
public void partialContentInvalidRangeHeader() throws Exception {
this.request.addHeader("Range", "bytes= foo bar");
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
this.handler.handleRequest(this.request, this.response);
assertEquals(416, this.response.getStatus());
assertEquals("bytes */10", this.response.getHeader("Content-Range"));
assertEquals("bytes", this.response.getHeader("Accept-Ranges"));
assertEquals(1, this.response.getHeaders("Accept-Ranges").size());
}
@Test
public void partialContentMultipleByteRanges() throws Exception {
this.request.addHeader("Range", "bytes=0-1, 4-5, 8-9");
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
this.handler.handleRequest(this.request, this.response);
assertEquals(206, this.response.getStatus());
assertTrue(this.response.getContentType().startsWith("multipart/byteranges; boundary="));
String boundary = "--" + this.response.getContentType().substring(31);
String content = this.response.getContentAsString();
String[] ranges = StringUtils.tokenizeToStringArray(content, "\r\n", false, true);
assertEquals(boundary, ranges[0]);
assertEquals("Content-Type: text/plain", ranges[1]);
assertEquals("Content-Range: bytes 0-1/10", ranges[2]);
assertEquals("So", ranges[3]);
assertEquals(boundary, ranges[4]);
assertEquals("Content-Type: text/plain", ranges[5]);
assertEquals("Content-Range: bytes 4-5/10", ranges[6]);
assertEquals(" t", ranges[7]);
assertEquals(boundary, ranges[8]);
assertEquals("Content-Type: text/plain", ranges[9]);
assertEquals("Content-Range: bytes 8-9/10", ranges[10]);
assertEquals("t.", ranges[11]);
}
// SPR-14005
@Test
public void doOverwriteExistingCacheControlHeaders() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.response.setHeader("Cache-Control", "no-store");
this.handler.handleRequest(this.request, this.response);
assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
}
private long dateHeaderAsLong(String responseHeaderName) throws Exception {
return dateFormat.parse(this.response.getHeader(responseHeaderName)).getTime();
}
private long resourceLastModified(String resourceName) throws IOException {
return new ClassPathResource(resourceName, getClass()).getFile().lastModified();
}
private String resourceLastModifiedDate(String resourceName) throws IOException {
long lastModified = new ClassPathResource(resourceName, getClass()).getFile().lastModified();
return dateFormat.format(lastModified);
}
private static class TestServletContext extends MockServletContext {
@Override
public String getMimeType(String filePath) {
if (filePath.endsWith(".css")) {
return "text/css";
}
else if (filePath.endsWith(".js")) {
return "text/javascript";
}
else {
return super.getMimeType(filePath);
}
}
}
}