Skip invalid links in CssLinkResourceTransformer

Prior to this commit, both `CssLinkResourceTransformer` implementations
could fail at runtime in case of invalid CSS links (for example, with
out of bounds exceptions).

This commit skips invalid links and writes them out to the resulting CSS
without any transformation.

Fixes gh-37336
This commit is contained in:
Brian Clozel
2026-09-25 17:06:28 +02:00
parent 24645069f9
commit a2e3c0d81d
6 changed files with 57 additions and 2 deletions
@@ -176,9 +176,12 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
return;
}
position += getKeyword().length();
while (Character.isWhitespace(content.charAt(position))) {
while (position < content.length() && Character.isWhitespace(content.charAt(position))) {
position++;
}
if (position == content.length()) {
return;
}
if (content.charAt(position) == '\'') {
position = extractLink(position, '\'', content, result);
}
@@ -194,6 +197,12 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
protected int extractLink(int index, char endChar, String content, Set<ContentChunkInfo> result) {
int start = index + 1;
int end = content.indexOf(endChar, start);
if (end == -1) {
if (logger.isTraceEnabled()) {
logger.trace("Unterminated link at index " + start + ", no closing '" + endChar + "'");
}
return content.length();
}
result.add(new ContentChunkInfo(start, end, true));
return end + 1;
}
@@ -183,6 +183,25 @@ class CssLinkResourceTransformerTests {
.verify();
}
@Test
void transformUnclosedUrlFunction() {
MockServerWebExchange exchange = MockServerWebExchange.from(get("/static/unclosed_url_function.css"));
Resource css = getResource("unclosed_url_function.css");
String expected = """
body { background: url("/static/images/image-f448cd1d5dba82b774f3202c878230b3.png?#iefix") }
div { background: url(images/image.png
""";
StepVerifier.create(this.transformerChain.transform(exchange, css)
.cast(TransformedResource.class))
.consumeNextWith(transformedResource -> {
String result = new String(transformedResource.getByteArray(), UTF_8);
assertThat(result).isEqualToNormalizingNewlines(expected);
})
.expectComplete()
.verify();
}
private Resource getResource(String filePath) {
return new ClassPathResource("test/" + filePath, getClass());
}
@@ -0,0 +1,2 @@
body { background: url("images/image.png?#iefix") }
div { background: url(images/image.png
@@ -140,9 +140,12 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
return;
}
position += getKeyword().length();
while (Character.isWhitespace(content.charAt(position))) {
while (position < content.length() && Character.isWhitespace(content.charAt(position))) {
position++;
}
if (position == content.length()) {
return;
}
if (content.charAt(position) == '\'') {
position = extractLink(position, "'", content, result);
}
@@ -158,6 +161,12 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
protected int extractLink(int index, String endKey, String content, SortedSet<ContentChunkInfo> linksToAdd) {
int start = index + 1;
int end = content.indexOf(endKey, start);
if (end == -1) {
if (logger.isTraceEnabled()) {
logger.trace("Unterminated link at index " + start + ", no closing \"" + endKey + "\"");
}
return content.length();
}
linksToAdd.add(new ContentChunkInfo(start, end));
return end + endKey.length();
}
@@ -167,6 +167,20 @@ class CssLinkResourceTransformerTests {
assertThat(result).isEqualToNormalizingNewlines(expected);
}
@Test
void transformUnclosedUrlFunction() throws Exception {
this.request = new MockHttpServletRequest("GET", "/static/unclosed_url_function.css");
Resource css = getResource("unclosed_url_function.css");
String expected = """
body { background: url("/static/images/image-f448cd1d5dba82b774f3202c878230b3.png?#iefix") }
div { background: url(images/image.png
""";
TransformedResource actual = (TransformedResource) this.transformerChain.transform(this.request, css);
String result = new String(actual.getByteArray(), UTF_8);
assertThat(result).isEqualToNormalizingNewlines(expected);
}
private Resource getResource(String filePath) {
return new ClassPathResource("test/" + filePath, getClass());
}
@@ -0,0 +1,2 @@
body { background: url("images/image.png?#iefix") }
div { background: url(images/image.png