From faa7ef38752e20acc7c5c8b50c7deded9ef212a1 Mon Sep 17 00:00:00 2001 From: Artur Date: Wed, 15 Dec 2021 11:58:13 +0200 Subject: [PATCH] feat: Only send index.html when the browser requests a HTML page (#12571) Fixes #12569 --- .../IndexHtmlRequestHandler.java | 54 ++++++++++++++++++- .../IndexHtmlRequestHandlerTest.java | 45 ++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandler.java index 21e8f01ce37..5df450a307f 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandler.java @@ -19,7 +19,10 @@ import java.io.IOException; import java.io.Serializable; import java.io.UncheckedIOException; +import java.util.Collections; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; import org.jsoup.Jsoup; import org.jsoup.nodes.DataNode; @@ -66,6 +69,30 @@ public class IndexHtmlRequestHandler extends JavaScriptBootstrapHandler { private static final String SCRIPT = "script"; private static final String SCRIPT_INITIAL = "initial"; + private static final Set nonHtmlFetchDests; + static { + // Full list at + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Dest + Set dests = new HashSet<>(); + dests.add("audio"); + dests.add("audioworklet"); + dests.add("font"); + dests.add("image"); + dests.add("manifest"); + dests.add("paintworklet"); + dests.add("script"); // NOSONAR + dests.add("serviceworker"); + dests.add("sharedworker"); + dests.add("style"); + dests.add("track"); + dests.add("video"); + dests.add("worker"); + dests.add("xslt"); + + // "empty" requests are used when service worker caches / so they need + // to be allowed + nonHtmlFetchDests = Collections.unmodifiableSet(dests); + } @Override public boolean synchronizedHandleRequest(VaadinSession session, @@ -227,8 +254,31 @@ private void includeInitialUidl(JsonObject initialJson, @Override protected boolean canHandleRequest(VaadinRequest request) { - return !BootstrapHandler.isFrameworkInternalRequest(request) && request - .getService().getBootstrapUrlPredicate().isValidUrl(request); + return isRequestForHtml(request) + && !BootstrapHandler.isFrameworkInternalRequest(request) + && request.getService().getBootstrapUrlPredicate() + .isValidUrl(request); + } + + /** + * Checks if the request is potentially a request for a HTML page. + * + * @param request + * the request to check + * @return {@code true} if the request is potentially for HTML, + * {@code false} if it is certain that it is a request for a script, + * image or something else + */ + protected boolean isRequestForHtml(VaadinRequest request) { + String fetchDest = request.getHeader("Sec-Fetch-Dest"); + if (fetchDest == null) { + // Old browsers do not send the header at all + return true; + } + if (nonHtmlFetchDests.contains(fetchDest)) { + return false; + } + return true; } @Override diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java index 6e6d8fa818e..2bc0f5a1e80 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java @@ -256,6 +256,36 @@ public void canHandleRequest_withBootstrapUrlPredicate() { .canHandleRequest(createVaadinRequest("/.htaccess"))); } + @Test + public void canHandleRequest_allow_oldBrowser() { + Assert.assertTrue(indexHtmlRequestHandler.canHandleRequest( + createRequestWithDestination("/", null, null))); + } + + @Test + public void canHandleRequest_handle_indexHtmlRequest() { + Assert.assertTrue(indexHtmlRequestHandler.canHandleRequest( + createRequestWithDestination("/", "document", "navigate"))); + } + + @Test + public void canHandleRequest_doNotHandle_scriptRequest() { + Assert.assertFalse(indexHtmlRequestHandler.canHandleRequest( + createRequestWithDestination("/", "script", "no-cors"))); + } + + @Test + public void canHandleRequest_doNotHandle_imageRequest() { + Assert.assertFalse(indexHtmlRequestHandler.canHandleRequest( + createRequestWithDestination("/", "image", "no-cors"))); + } + + @Test + public void canHandleRequest_handle_serviceWorkerDocumentRequest() { + Assert.assertTrue(indexHtmlRequestHandler.canHandleRequest( + createRequestWithDestination("/", "empty", "same-origin"))); + } + @Test public void bootstrapListener_addListener_responseIsModified() throws IOException { @@ -756,6 +786,21 @@ public void tearDown() throws Exception { mocks.cleanup(); } + private VaadinServletRequest createRequestWithDestination(String pathInfo, + String fetchDest, String fetchMode) { + VaadinServletRequest req = createVaadinRequest(pathInfo); + Mockito.when(req.getHeader(Mockito.anyString())).thenAnswer(arg -> { + if ("Sec-Fetch-Dest".equals(arg.getArgument(0))) { + return fetchDest; + } else if ("Sec-Fetch-Mode".equals(arg.getArgument(0))) { + return fetchMode; + } + return null; + }); + + return req; + } + private VaadinServletRequest createVaadinRequest(String pathInfo) { HttpServletRequest request = createRequest(pathInfo); return new VaadinServletRequest(request, Mockito.spy(service));