Skip to content

Commit 125ae99

Browse files
committed
Transform absolute links in ResourceTransformers
Prior to this change, ResourceTransformers that transformed resources by updating the links to other resources, worked only if links were relative to the resource being transformed. For example, when the CssLinkResourceTransformer rewrote links within a "main.css" resource, only links such as "../css/other.css" were rewritten. Using relative links is a recommended approach, because it's totally independent from the application servlet path, context path, mappings... This change allows absolute links to be rewritten by those Transformers, provided those links are accurate and point to existing resources. Issue: SPR-12137
1 parent 06f1f49 commit 125ae99

File tree

4 files changed

+238
-13
lines changed

4 files changed

+238
-13
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/resource/AppCacheManifestTransformer.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
* applications spec</a>
5959
* @since 4.1
6060
*/
61-
public class AppCacheManifestTransformer implements ResourceTransformer {
61+
public class AppCacheManifestTransformer extends ResourceTransformerSupport {
6262

6363
private static final String MANIFEST_HEADER = "CACHE MANIFEST";
6464

@@ -129,7 +129,7 @@ public Resource transform(HttpServletRequest request, Resource resource, Resourc
129129
hashBuilder.appendString(line);
130130
}
131131
else {
132-
contentWriter.write(currentTransformer.transform(line, hashBuilder, resource, transformerChain) + "\n");
132+
contentWriter.write(currentTransformer.transform(line, hashBuilder, resource, transformerChain, request) + "\n");
133133
}
134134
}
135135

@@ -151,32 +151,33 @@ private static interface SectionTransformer {
151151
* for the current manifest section (CACHE, NETWORK, FALLBACK, etc).
152152
*/
153153
String transform(String line, HashBuilder builder, Resource resource,
154-
ResourceTransformerChain transformerChain) throws IOException;
154+
ResourceTransformerChain transformerChain, HttpServletRequest request) throws IOException;
155155
}
156156

157157

158158
private static class NoOpSection implements SectionTransformer {
159159

160-
public String transform(String line, HashBuilder builder, Resource resource, ResourceTransformerChain transformerChain)
161-
throws IOException {
160+
public String transform(String line, HashBuilder builder, Resource resource,
161+
ResourceTransformerChain transformerChain, HttpServletRequest request) throws IOException {
162162

163163
builder.appendString(line);
164164
return line;
165165
}
166166
}
167167

168168

169-
private static class CacheSection implements SectionTransformer {
169+
private class CacheSection implements SectionTransformer {
170170

171171
private final String COMMENT_DIRECTIVE = "#";
172172

173173
@Override
174-
public String transform(String line, HashBuilder builder,
175-
Resource resource, ResourceTransformerChain transformerChain) throws IOException {
174+
public String transform(String line, HashBuilder builder, Resource resource,
175+
ResourceTransformerChain transformerChain, HttpServletRequest request) throws IOException {
176176

177177
if (isLink(line) && !hasScheme(line)) {
178-
Resource appCacheResource = transformerChain.getResolverChain().resolveResource(null, line, Arrays.asList(resource));
179-
String path = transformerChain.getResolverChain().resolveUrlPath(line, Arrays.asList(resource));
178+
ResourceResolverChain resolverChain = transformerChain.getResolverChain();
179+
Resource appCacheResource = resolverChain.resolveResource(null, line, Arrays.asList(resource));
180+
String path = resolveUrlPath(line, request, resource, transformerChain);
180181
builder.appendResource(appCacheResource);
181182
if (logger.isTraceEnabled()) {
182183
logger.trace("Link modified: " + path + " (original: " + line + ")");

spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CssLinkResourceTransformer.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import java.io.StringWriter;
2828
import java.nio.charset.Charset;
2929
import java.util.ArrayList;
30-
import java.util.Arrays;
3130
import java.util.Collections;
3231
import java.util.HashSet;
3332
import java.util.List;
@@ -47,7 +46,7 @@
4746
* @author Rossen Stoyanchev
4847
* @since 4.1
4948
*/
50-
public class CssLinkResourceTransformer implements ResourceTransformer {
49+
public class CssLinkResourceTransformer extends ResourceTransformerSupport {
5150

5251
private static final Log logger = LogFactory.getLog(CssLinkResourceTransformer.class);
5352

@@ -103,7 +102,7 @@ public Resource transform(HttpServletRequest request, Resource resource, Resourc
103102
String link = content.substring(info.getStart(), info.getEnd());
104103
String newLink = null;
105104
if (!hasScheme(link)) {
106-
newLink = transformerChain.getResolverChain().resolveUrlPath(link, Arrays.asList(resource));
105+
newLink = resolveUrlPath(link, request, resource, transformerChain);
107106
}
108107
if (logger.isTraceEnabled()) {
109108
if (newLink != null && !link.equals(newLink)) {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.web.servlet.resource;
17+
18+
import java.util.Arrays;
19+
20+
import javax.servlet.http.HttpServletRequest;
21+
22+
import org.springframework.core.io.Resource;
23+
24+
/**
25+
* A base class for a {@code ResourceTransformer} with an optional helper method
26+
* for resolving public links within a transformed resource.
27+
*
28+
* @author Brian Clozel
29+
* @author Rossen Stoyanchev
30+
* @since 4.1
31+
*/
32+
public abstract class ResourceTransformerSupport implements ResourceTransformer {
33+
34+
private ResourceUrlProvider resourceUrlProvider;
35+
36+
37+
/**
38+
* Configure a {@link ResourceUrlProvider} to use when resolving the public
39+
* URL of links in a transformed resource (e.g. import links in a CSS file).
40+
* This is required only for links expressed as full paths, i.e. including
41+
* context and servlet path, and not for relative links.
42+
*
43+
* <p>By default this property is not set. In that case if a
44+
* {@code ResourceUrlProvider} is needed an attempt is made to find the
45+
* {@code ResourceUrlProvider} exposed through the
46+
* {@link org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor
47+
* ResourceUrlProviderExposingInterceptor} (configured by default in the MVC
48+
* Java config and XML namespace). Therefore explicitly configuring this
49+
* property should not be needed in most cases.
50+
* @param resourceUrlProvider the URL provider to use
51+
*/
52+
public void setResourceUrlProvider(ResourceUrlProvider resourceUrlProvider) {
53+
this.resourceUrlProvider = resourceUrlProvider;
54+
}
55+
56+
/**
57+
* @return the configured {@code ResourceUrlProvider}.
58+
*/
59+
public ResourceUrlProvider getResourceUrlProvider() {
60+
return this.resourceUrlProvider;
61+
}
62+
63+
64+
/**
65+
* A transformer can use this method when a resource being transformed
66+
* contains links to other resources. Such links need to be replaced with the
67+
* public facing link as determined by the resource resolver chain (e.g. the
68+
* public URL may have a version inserted).
69+
*
70+
* @param resourcePath the path to a resource that needs to be re-written
71+
* @param request the current request
72+
* @param resource the resource being transformed
73+
* @param transformerChain the transformer chain
74+
* @return the resolved URL or null
75+
*/
76+
protected String resolveUrlPath(String resourcePath, HttpServletRequest request,
77+
Resource resource, ResourceTransformerChain transformerChain) {
78+
79+
if (!resourcePath.startsWith("/")) {
80+
// try resolving as relative path
81+
return transformerChain.getResolverChain().resolveUrlPath(resourcePath, Arrays.asList(resource));
82+
}
83+
else {
84+
// full resource path
85+
ResourceUrlProvider urlProvider = findResourceUrlProvider(request);
86+
return (urlProvider != null ? urlProvider.getForRequestUrl(request, resourcePath) : null);
87+
}
88+
}
89+
90+
private ResourceUrlProvider findResourceUrlProvider(HttpServletRequest request) {
91+
if (this.resourceUrlProvider != null) {
92+
return this.resourceUrlProvider;
93+
}
94+
return (ResourceUrlProvider) request.getAttribute(
95+
ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR);
96+
}
97+
98+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.web.servlet.resource;
17+
18+
import static org.junit.Assert.assertEquals;
19+
20+
import java.util.ArrayList;
21+
import java.util.Collections;
22+
import java.util.List;
23+
24+
import org.junit.Before;
25+
import org.junit.Test;
26+
27+
import org.springframework.core.io.ClassPathResource;
28+
import org.springframework.core.io.Resource;
29+
import org.springframework.mock.web.test.MockHttpServletRequest;
30+
import org.springframework.web.servlet.HandlerMapping;
31+
32+
import javax.servlet.http.HttpServletRequest;
33+
34+
/**
35+
* Unit tests for {@code LinkRewriteTransformer}
36+
*
37+
* @author Brian Clozel
38+
* @author Rossen Stoyanchev
39+
*/
40+
public class LinkRewriteTransformerTests {
41+
42+
private ResourceTransformerChain transformerChain;
43+
44+
private TestTransformer transformer;
45+
46+
private MockHttpServletRequest request;
47+
48+
49+
@Before
50+
public void setUp() {
51+
VersionResourceResolver versionResolver = new VersionResourceResolver();
52+
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
53+
54+
List<ResourceResolver> resolvers = new ArrayList<>();
55+
resolvers.add(versionResolver);
56+
resolvers.add(new PathResourceResolver());
57+
this.transformerChain = new DefaultResourceTransformerChain(new DefaultResourceResolverChain(resolvers), null);
58+
59+
List<Resource> locations = new ArrayList<>();
60+
locations.add(new ClassPathResource("test/", getClass()));
61+
62+
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
63+
handler.setLocations(locations);
64+
handler.setResourceResolvers(resolvers);
65+
66+
ResourceUrlProvider urlProvider = new ResourceUrlProvider();
67+
urlProvider.setHandlerMap(Collections.singletonMap("/resources/**", handler));
68+
69+
this.transformer = new TestTransformer();
70+
this.transformer.setResourceUrlProvider(urlProvider);
71+
72+
this.request = new MockHttpServletRequest();
73+
}
74+
75+
@Test
76+
public void rewriteAbsolutePath() throws Exception {
77+
this.request.setRequestURI("/servlet/context/resources/main.css");
78+
this.request.setMethod("GET");
79+
this.request.setServletPath("/servlet");
80+
this.request.setContextPath("/context");
81+
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/resources/main.css");
82+
83+
String resourcePath = "/servlet/context/resources/bar.css";
84+
Resource mainCss = new ClassPathResource("test/main.css", getClass());
85+
String actual = this.transformer.resolveUrlPath(resourcePath, this.request, mainCss, this.transformerChain);
86+
assertEquals("/servlet/context/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
87+
88+
actual = this.transformer.resolveUrlPath("bar.css", this.request, mainCss, this.transformerChain);
89+
assertEquals("bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
90+
}
91+
92+
@Test
93+
public void rewriteRelativePath() throws Exception {
94+
this.request.setRequestURI("/servlet/context/resources/main.css");
95+
this.request.setMethod("GET");
96+
this.request.setServletPath("/servlet");
97+
this.request.setContextPath("/context");
98+
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/resources/main.css");
99+
100+
Resource mainCss = new ClassPathResource("test/main.css", getClass());
101+
String actual = this.transformer.resolveUrlPath("bar.css", this.request, mainCss, this.transformerChain);
102+
assertEquals("bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
103+
}
104+
105+
@Test
106+
public void rewriteRelativePathUpperLevel() throws Exception {
107+
this.request.setRequestURI("/servlet/context/resources/images/image.png");
108+
this.request.setMethod("GET");
109+
this.request.setServletPath("/servlet");
110+
this.request.setContextPath("/context");
111+
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/resources/images/image.png");
112+
113+
Resource imagePng = new ClassPathResource("test/images/image.png", getClass());
114+
String actual = this.transformer.resolveUrlPath("../bar.css", this.request, imagePng, this.transformerChain);
115+
assertEquals("../bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
116+
}
117+
118+
119+
private static class TestTransformer extends ResourceTransformerSupport {
120+
121+
@Override
122+
public Resource transform(HttpServletRequest request, Resource resource, ResourceTransformerChain chain) {
123+
throw new IllegalStateException("Should never be called");
124+
}
125+
}
126+
127+
}

0 commit comments

Comments
 (0)