Skip to content

Commit 7ae7294

Browse files
committed
Resolve absolute resource links in ResourceTransformers
This commit adapts the fix for SPR-14597 commited to spring-webmvc to the spring-web-reactive module. Issue: SPR-14597
1 parent 679b661 commit 7ae7294

File tree

5 files changed

+87
-27
lines changed

5 files changed

+87
-27
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ private Mono<LineOutput> processLine(LineInfo info, ServerWebExchange exchange,
144144
return Mono.just(new LineOutput(info.getLine(), null));
145145
}
146146

147-
Mono<String> pathMono = resolveUrlPath(info.getLine(), exchange, resource, chain)
147+
String link = toAbsolutePath(info.getLine(), exchange.getRequest());
148+
Mono<String> pathMono = resolveUrlPath(link, exchange, resource, chain)
148149
.doOnNext(path -> {
149150
if (logger.isTraceEnabled()) {
150151
logger.trace("Link modified: " + path + " (original: " + info.getLine() + ")");

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ public Mono<Resource> transform(ServerWebExchange exchange, Resource resource,
102102
.concatMap(segment -> {
103103
String segmentContent = segment.getContent(fullContent);
104104
if (segment.isLink() && !hasScheme(segmentContent)) {
105-
return resolveUrlPath(segmentContent, exchange, newResource, transformerChain)
105+
String link = toAbsolutePath(segmentContent, exchange.getRequest());
106+
return resolveUrlPath(link, exchange, newResource, transformerChain)
106107
.defaultIfEmpty(segmentContent);
107108
}
108109
else {

spring-web-reactive/src/main/java/org/springframework/web/reactive/resource/ResourceTransformerSupport.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import reactor.core.publisher.Mono;
2222

2323
import org.springframework.core.io.Resource;
24+
import org.springframework.http.server.reactive.ServerHttpRequest;
25+
import org.springframework.util.StringUtils;
2426
import org.springframework.web.server.ServerWebExchange;
2527

2628
/**
@@ -72,7 +74,7 @@ protected Mono<String> resolveUrlPath(String resourcePath, ServerWebExchange exc
7274
if (resourcePath.startsWith("/")) {
7375
// full resource path
7476
ResourceUrlProvider urlProvider = getResourceUrlProvider();
75-
return (urlProvider != null ? urlProvider.getForRequestUrl(exchange, resourcePath) : null);
77+
return (urlProvider != null ? urlProvider.getForRequestUrl(exchange, resourcePath) : Mono.empty());
7678
}
7779
else {
7880
// try resolving as relative path
@@ -81,4 +83,19 @@ protected Mono<String> resolveUrlPath(String resourcePath, ServerWebExchange exc
8183
}
8284
}
8385

86+
/**
87+
* Transform the given relative request path to an absolute path,
88+
* taking the path of the given request as a point of reference.
89+
* The resulting path is also cleaned from sequences like "path/..".
90+
*
91+
* @param path the relative path to transform
92+
* @param request the referer request
93+
* @return the absolute request path for the given resource path
94+
*/
95+
protected String toAbsolutePath(String path, ServerHttpRequest request) {
96+
String requestPath = request.getURI().getPath();
97+
String absolutePath = StringUtils.applyRelativePath(requestPath, path);
98+
return StringUtils.cleanPath(absolutePath);
99+
}
100+
84101
}

spring-web-reactive/src/test/java/org/springframework/web/reactive/resource/AppCacheManifestTransformerTests.java

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,35 @@ public class AppCacheManifestTransformerTests {
6161

6262
@Before
6363
public void setup() {
64+
ClassPathResource allowedLocation = new ClassPathResource("test/", getClass());
65+
ResourceWebHandler resourceHandler = new ResourceWebHandler();
66+
ResourceUrlProvider resourceUrlProvider = new ResourceUrlProvider();
67+
resourceUrlProvider.setHandlerMap(Collections.singletonMap("/static/**", resourceHandler));
68+
69+
VersionResourceResolver versionResolver = new VersionResourceResolver();
70+
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
71+
PathResourceResolver pathResolver = new PathResourceResolver();
72+
pathResolver.setAllowedLocations(allowedLocation);
73+
List<ResourceResolver> resolvers = Arrays.asList(versionResolver, pathResolver);
74+
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
75+
76+
CssLinkResourceTransformer cssLinkResourceTransformer = new CssLinkResourceTransformer();
77+
cssLinkResourceTransformer.setResourceUrlProvider(resourceUrlProvider);
78+
List<ResourceTransformer> transformers = Arrays.asList(cssLinkResourceTransformer);
79+
this.chain = new DefaultResourceTransformerChain(resolverChain, transformers);
6480
this.transformer = new AppCacheManifestTransformer();
65-
this.chain = mock(ResourceTransformerChain.class);
81+
this.transformer.setResourceUrlProvider(resourceUrlProvider);
6682

67-
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "");
68-
ServerHttpResponse response = new MockServerHttpResponse();
69-
WebSessionManager manager = new DefaultWebSessionManager();
70-
this.exchange = new DefaultServerWebExchange(request, response, manager);
83+
resourceHandler.setResourceResolvers(resolvers);
84+
resourceHandler.setResourceTransformers(transformers);
85+
resourceHandler.setLocations(Collections.singletonList(allowedLocation));
7186
}
7287

7388

7489
@Test
7590
public void noTransformIfExtensionNoMatch() throws Exception {
91+
initExchange(HttpMethod.GET, "/static/foobar.file");
92+
this.chain = mock(ResourceTransformerChain.class);
7693
Resource resource = mock(Resource.class);
7794
given(resource.getFilename()).willReturn("foobar.file");
7895
given(this.chain.transform(this.exchange, resource)).willReturn(Mono.just(resource));
@@ -83,6 +100,8 @@ public void noTransformIfExtensionNoMatch() throws Exception {
83100

84101
@Test
85102
public void syntaxErrorInManifest() throws Exception {
103+
initExchange(HttpMethod.GET, "/static/error.appcache");
104+
this.chain = mock(ResourceTransformerChain.class);
86105
Resource resource = new ClassPathResource("test/error.appcache", getClass());
87106
given(this.chain.transform(this.exchange, resource)).willReturn(Mono.just(resource));
88107

@@ -92,7 +111,7 @@ public void syntaxErrorInManifest() throws Exception {
92111

93112
@Test
94113
public void transformManifest() throws Exception {
95-
114+
initExchange(HttpMethod.GET, "/static/test.appcache");
96115
VersionResourceResolver versionResolver = new VersionResourceResolver();
97116
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
98117

@@ -112,11 +131,11 @@ public void transformManifest() throws Exception {
112131
String content = new String(bytes, "UTF-8");
113132

114133
assertThat("should rewrite resource links", content,
115-
Matchers.containsString("foo-e36d2e05253c6c7085a91522ce43a0b4.css"));
134+
Matchers.containsString("/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css"));
116135
assertThat("should rewrite resource links", content,
117-
Matchers.containsString("bar-11e16cf79faee7ac698c805cf28248d2.css"));
136+
Matchers.containsString("/static/bar-11e16cf79faee7ac698c805cf28248d2.css"));
118137
assertThat("should rewrite resource links", content,
119-
Matchers.containsString("js/bar-bd508c62235b832d960298ca6c0b7645.js"));
138+
Matchers.containsString("/static/js/bar-bd508c62235b832d960298ca6c0b7645.js"));
120139

121140
assertThat("should not rewrite external resources", content,
122141
Matchers.containsString("//example.org/style.css"));
@@ -127,4 +146,10 @@ public void transformManifest() throws Exception {
127146
Matchers.containsString("# Hash: 4bf0338bcbeb0a5b3a4ec9ed8864107d"));
128147
}
129148

149+
private void initExchange(HttpMethod method, String url) {
150+
MockServerHttpRequest request = new MockServerHttpRequest(method, url);
151+
ServerHttpResponse response = new MockServerHttpResponse();
152+
WebSessionManager manager = new DefaultWebSessionManager();
153+
this.exchange = new DefaultServerWebExchange(request, response, manager);
154+
}
130155
}

spring-web-reactive/src/test/java/org/springframework/web/reactive/resource/CssLinkResourceTransformerTests.java

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,39 +52,45 @@ public class CssLinkResourceTransformerTests {
5252

5353
@Before
5454
public void setUp() {
55+
ClassPathResource allowedLocation = new ClassPathResource("test/", getClass());
56+
ResourceWebHandler resourceHandler = new ResourceWebHandler();
57+
58+
ResourceUrlProvider resourceUrlProvider = new ResourceUrlProvider();
59+
resourceUrlProvider.setHandlerMap(Collections.singletonMap("/static/**", resourceHandler));
60+
5561
VersionResourceResolver versionResolver = new VersionResourceResolver();
5662
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
57-
5863
PathResourceResolver pathResolver = new PathResourceResolver();
59-
pathResolver.setAllowedLocations(new ClassPathResource("test/", getClass()));
60-
64+
pathResolver.setAllowedLocations(allowedLocation);
6165
List<ResourceResolver> resolvers = Arrays.asList(versionResolver, pathResolver);
62-
List<ResourceTransformer> transformers = Collections.singletonList(new CssLinkResourceTransformer());
6366

67+
CssLinkResourceTransformer cssLinkResourceTransformer = new CssLinkResourceTransformer();
68+
cssLinkResourceTransformer.setResourceUrlProvider(resourceUrlProvider);
69+
List<ResourceTransformer> transformers = Collections.singletonList(cssLinkResourceTransformer);
70+
71+
resourceHandler.setResourceResolvers(resolvers);
72+
resourceHandler.setResourceTransformers(transformers);
73+
resourceHandler.setLocations(Collections.singletonList(allowedLocation));
6474
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
6575
this.transformerChain = new DefaultResourceTransformerChain(resolverChain, transformers);
66-
67-
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "");
68-
ServerHttpResponse response = new MockServerHttpResponse();
69-
WebSessionManager manager = new DefaultWebSessionManager();
70-
this.exchange = new DefaultServerWebExchange(request, response, manager);
7176
}
7277

7378

7479
@Test
7580
public void transform() throws Exception {
81+
initExchange(HttpMethod.GET, "/static/main.css");
7682
Resource css = new ClassPathResource("test/main.css", getClass());
7783
TransformedResource actual =
7884
(TransformedResource) this.transformerChain.transform(this.exchange, css)
7985
.blockMillis(5000);
8086

8187
String expected = "\n" +
82-
"@import url(\"bar-11e16cf79faee7ac698c805cf28248d2.css\");\n" +
83-
"@import url('bar-11e16cf79faee7ac698c805cf28248d2.css');\n" +
84-
"@import url(bar-11e16cf79faee7ac698c805cf28248d2.css);\n\n" +
85-
"@import \"foo-e36d2e05253c6c7085a91522ce43a0b4.css\";\n" +
86-
"@import 'foo-e36d2e05253c6c7085a91522ce43a0b4.css';\n\n" +
87-
"body { background: url(\"images/image-f448cd1d5dba82b774f3202c878230b3.png\") }\n";
88+
"@import url(\"/static/bar-11e16cf79faee7ac698c805cf28248d2.css\");\n" +
89+
"@import url('/static/bar-11e16cf79faee7ac698c805cf28248d2.css');\n" +
90+
"@import url(/static/bar-11e16cf79faee7ac698c805cf28248d2.css);\n\n" +
91+
"@import \"/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css\";\n" +
92+
"@import '/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css';\n\n" +
93+
"body { background: url(\"/static/images/image-f448cd1d5dba82b774f3202c878230b3.png\") }\n";
8894

8995
String result = new String(actual.getByteArray(), "UTF-8");
9096
result = StringUtils.deleteAny(result, "\r");
@@ -93,13 +99,15 @@ public void transform() throws Exception {
9399

94100
@Test
95101
public void transformNoLinks() throws Exception {
102+
initExchange(HttpMethod.GET, "/static/foo.css");
96103
Resource expected = new ClassPathResource("test/foo.css", getClass());
97104
Resource actual = this.transformerChain.transform(this.exchange, expected).blockMillis(5000);
98105
assertSame(expected, actual);
99106
}
100107

101108
@Test
102109
public void transformExtLinksNotAllowed() throws Exception {
110+
initExchange(HttpMethod.GET, "/static/external.css");
103111
ResourceResolverChain resolverChain = Mockito.mock(DefaultResourceResolverChain.class);
104112
ResourceTransformerChain transformerChain = new DefaultResourceTransformerChain(resolverChain,
105113
Collections.singletonList(new CssLinkResourceTransformer()));
@@ -125,9 +133,17 @@ public void transformExtLinksNotAllowed() throws Exception {
125133

126134
@Test
127135
public void transformWithNonCssResource() throws Exception {
136+
initExchange(HttpMethod.GET, "/static/images/image.png");
128137
Resource expected = new ClassPathResource("test/images/image.png", getClass());
129138
Resource actual = this.transformerChain.transform(this.exchange, expected).blockMillis(5000);
130139
assertSame(expected, actual);
131140
}
132141

142+
private void initExchange(HttpMethod method, String url) {
143+
MockServerHttpRequest request = new MockServerHttpRequest(method, url);
144+
ServerHttpResponse response = new MockServerHttpResponse();
145+
WebSessionManager manager = new DefaultWebSessionManager();
146+
this.exchange = new DefaultServerWebExchange(request, response, manager);
147+
}
148+
133149
}

0 commit comments

Comments
 (0)