private Mono<? extends Resource> transform(String content, Resource resource, ResourceTransformerChain chain, ServerWebExchange exchange) { if (!content.startsWith(MANIFEST_HEADER)) { if (logger.isTraceEnabled()) { logger.trace(exchange.getLogPrefix() + "Skipping " + resource + ": Manifest does not start with 'CACHE MANIFEST'"); } return Mono.just(resource); } return Flux.generate(new LineInfoGenerator(content)) .concatMap(info -> processLine(info, exchange, resource, chain)) .reduce(new ByteArrayOutputStream(), (out, line) -> { writeToByteArrayOutputStream(out, line + "\n"); return out; }) .map(out -> { String hash = DigestUtils.md5DigestAsHex(out.toByteArray()); writeToByteArrayOutputStream(out, "\n" + "# Hash: " + hash); return new TransformedResource(resource, out.toByteArray()); }); }
@Test public void transformExtLinksNotAllowed() { MockServerWebExchange exchange = MockServerWebExchange.from(get("/static/external.css")); List<ResourceTransformer> transformers = Collections.singletonList(new CssLinkResourceTransformer()); ResourceResolverChain mockChain = Mockito.mock(DefaultResourceResolverChain.class); ResourceTransformerChain chain = new DefaultResourceTransformerChain(mockChain, transformers); Resource resource = getResource("external.css"); String expected = "@import url(\"http://example.org/fonts/css\");\n" + "body { background: url(\"file:///home/spring/image.png\") }\n" + "figure { background: url(\"//example.org/style.css\")}"; StepVerifier.create(chain.transform(exchange, resource) .cast(TransformedResource.class)) .consumeNextWith(transformedResource -> { String result = new String(transformedResource.getByteArray(), StandardCharsets.UTF_8); result = StringUtils.deleteAny(result, "\r"); assertEquals(expected, result); }) .expectComplete() .verify(); List<Resource> locations = Collections.singletonList(resource); Mockito.verify(mockChain, Mockito.never()).resolveUrlPath("http://example.org/fonts/css", locations); Mockito.verify(mockChain, Mockito.never()).resolveUrlPath("file:///home/spring/image.png", locations); Mockito.verify(mockChain, Mockito.never()).resolveUrlPath("//example.org/style.css", locations); }
@Test public void transform() { MockServerWebExchange exchange = MockServerWebExchange.from(get("/static/main.css")); Resource css = getResource("main.css"); String expected = "\n" + "@import url(\"/static/bar-11e16cf79faee7ac698c805cf28248d2.css?#iefix\");\n" + "@import url('/static/bar-11e16cf79faee7ac698c805cf28248d2.css#bla-normal');\n" + "@import url(/static/bar-11e16cf79faee7ac698c805cf28248d2.css);\n\n" + "@import \"/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css\";\n" + "@import '/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css';\n\n" + "body { background: url(\"/static/images/image-f448cd1d5dba82b774f3202c878230b3.png?#iefix\") }\n"; StepVerifier.create(this.transformerChain.transform(exchange, css) .cast(TransformedResource.class)) .consumeNextWith(transformedResource -> { String result = new String(transformedResource.getByteArray(), StandardCharsets.UTF_8); result = StringUtils.deleteAny(result, "\r"); assertEquals(expected, result); }) .expectComplete() .verify(); }
private Mono<? extends Resource> transformContent(String cssContent, Resource resource, ResourceTransformerChain chain, ServerWebExchange exchange) { List<ContentChunkInfo> contentChunkInfos = parseContent(cssContent); if (contentChunkInfos.isEmpty()) { return Mono.just(resource); } return Flux.fromIterable(contentChunkInfos) .concatMap(contentChunkInfo -> { String contentChunk = contentChunkInfo.getContent(cssContent); if (contentChunkInfo.isLink() && !hasScheme(contentChunk)) { String link = toAbsolutePath(contentChunk, exchange); return resolveUrlPath(link, exchange, resource, chain).defaultIfEmpty(contentChunk); } else { return Mono.just(contentChunk); } }) .reduce(new StringWriter(), (writer, chunk) -> { writer.write(chunk); return writer; }) .map(writer -> { byte[] newContent = writer.toString().getBytes(DEFAULT_CHARSET); return new TransformedResource(resource, newContent); }); }
public TransformedResource createResource() { String hash = DigestUtils.md5DigestAsHex(this.baos.toByteArray()); this.writer.write("\n" + "# Hash: " + hash); if (logger.isTraceEnabled()) { logger.trace("AppCache file: [" + resource.getFilename()+ "] hash: [" + hash + "]"); } byte[] bytes = this.writer.toString().getBytes(DEFAULT_CHARSET); return new TransformedResource(this.resource, bytes); } }
.then(writer -> { byte[] newContent = writer.toString().getBytes(DEFAULT_CHARSET); return Mono.just(new TransformedResource(resource, newContent)); }); });