Skip to content

Commit

Permalink
feat: Only send index.html when the browser requests a HTML page (#12571
Browse files Browse the repository at this point in the history
)

Fixes #12569
  • Loading branch information
Artur- authored and vaadin-bot committed Feb 9, 2022
1 parent b9aee76 commit faa7ef3
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> nonHtmlFetchDests;
static {
// Full list at
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Dest
Set<String> 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,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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));
Expand Down

0 comments on commit faa7ef3

Please sign in to comment.