Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -305,16 +305,22 @@ private static String resolveStyleSheetHref(String href,
return href;
}

String contextPath = request.getContextPath();
if (!contextPath.isEmpty()) {
String contextProtocol = ApplicationConstants.CONTEXT_PROTOCOL_PREFIX;
if (!lower.startsWith(contextProtocol)) {
// Prepend context protocol so URL is resolved with context path
href = contextProtocol + href;
}
String contextProtocol = ApplicationConstants.CONTEXT_PROTOCOL_PREFIX;
if (!lower.startsWith(contextProtocol)) {
// Prepend context protocol so URL is resolved against the
// context root by the bootstrap URI resolver below.
href = contextProtocol + href;
}
// Use the servlet-relative path (e.g. "./", "../") rather than the
// absolute context path. The emitted href is then resolved by the
// browser against <base>, which Vaadin sets from the actual request
// URL (honoring X-Forwarded-* headers). This works correctly behind
// reverse proxies that rewrite or strip the context path in the
// public URL.
String servletPathToContextRoot = request.getService()
.getContextRootRelativePath(request);
BootstrapHandler.BootstrapUriResolver resolver = new BootstrapHandler.BootstrapUriResolver(
contextPath + "/", null);
servletPathToContextRoot, null);
return resolver.resolveVaadinUri(href);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ void setup() {
Mockito.when(service.getInstantiator()).thenReturn(
Mockito.mock(com.vaadin.flow.di.Instantiator.class));
Mockito.when(service.getContext()).thenReturn(context);
Mockito.when(service.getContextRootRelativePath(Mockito.any()))
.thenReturn("./");
}

@AfterEach
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,25 @@
List<Element> links = document.head().select("link[rel=stylesheet]");
assertEquals(4, links.size());

// The href values are servlet-relative (resolved through <base> by
// the browser). With the test request's empty servletPath, the
// context:// prefix expands to "./".

// 1) Absolute path: href preserved, data-file-path drops leading '/'
Element abs = links.get(0);
assertEquals("/absolute.css", abs.attr("href"));
assertEquals("absolute.css", abs.attr("data-file-path"));

// 2) Relative with './': href resolved with context path,
// 2) Relative with './': href is servlet-relative,
// data-file-path drops './'
Element rel = links.get(1);
assertEquals("/ctx/relative/path.css", rel.attr("href"));
assertEquals("./relative/path.css", rel.attr("href"));
assertEquals("relative/path.css", rel.attr("data-file-path"));

// 3) context:// should resolve to context path in href, and
// data-file-path strips context protocol prefix
// 3) context:// expands to servlet-relative path; data-file-path
// strips the context protocol prefix
Element ctx = links.get(2);
assertEquals("/ctx/from-context.css", ctx.attr("href"));
assertEquals("./from-context.css", ctx.attr("href"));
assertEquals("from-context.css", ctx.attr("data-file-path"));

// 4) Remote http(s) URL unchanged, data-file-path remains original
Expand Down Expand Up @@ -138,20 +142,22 @@
"Absolute href should start with /absolute.css");
assertEquals("/absolute.css", abs.attr("data-file-path"));

// 2) Relative path: href has hash appended, data-file-path unchanged
// 2) Relative path: href is servlet-relative, hash appended,
// data-file-path unchanged
Element rel = links.get(1);
assertTrue(hashPattern.matcher(rel.attr("href")).find(),
"Relative href should contain hash parameter");
assertTrue(rel.attr("href").startsWith("/ctx/relative/path.css"),
"Relative href should start with /ctx/");
assertTrue(rel.attr("href").startsWith("./relative/path.css"),
"Relative href should start with ./");
assertEquals("./relative/path.css", rel.attr("data-file-path"));

// 3) Context path: href has hash appended, data-file-path unchanged
// 3) Context path: href is servlet-relative (context:// expanded),
// hash appended, data-file-path unchanged
Element ctx = links.get(2);
assertTrue(hashPattern.matcher(ctx.attr("href")).find(),
"Context href should contain hash parameter");
assertTrue(ctx.attr("href").startsWith("/ctx/from-context.css"),
"Context href should start with /ctx/");
assertTrue(ctx.attr("href").startsWith("./from-context.css"),
"Context href should start with ./");
assertEquals("context://from-context.css", ctx.attr("data-file-path"));

// 4) External URL: no hash appended, data-file-path unchanged
Expand All @@ -163,6 +169,32 @@
remote.attr("data-file-path"));
}

@Test
void modifyIndex_customServletMapping_hrefIsServletRelative()
throws Exception {

Check warning on line 174 in flow-server/src/test/java/com/vaadin/flow/server/AppShellRegistryStyleSheetDataFilePathTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the declaration of thrown exception 'java.lang.Exception', as it cannot be thrown from method's body.

See more on https://sonarcloud.io/project/issues?id=vaadin_flow&issues=AZ3xzH0ydmAH6R6U17Sj&open=AZ3xzH0ydmAH6R6U17Sj&pullRequest=24233
AppShellRegistry registry = AppShellRegistry.getInstance(context);
registry.setShell(MyShell.class);

// Servlet mapped to "/myservlet/*", context path "/ctx".
// contextRootRelativePath becomes "./../" so relative and
// context:// hrefs must step one level up from the servlet path.
VaadinServletRequest request = createRequest("/", "/ctx", "/myservlet");
registry.modifyIndexHtml(document, request);

List<Element> links = document.head().select("link[rel=stylesheet]");
assertEquals(4, links.size());

// Absolute path remains unchanged
assertEquals("/absolute.css", links.get(0).attr("href"));
// Relative href steps up out of the servlet path
assertEquals("./../relative/path.css", links.get(1).attr("href"));
// context:// expands the same way
assertEquals("./../from-context.css", links.get(2).attr("href"));
// Remote URL untouched
assertEquals("https://cdn.example.com/remote.css",
links.get(3).attr("href"));
}

@Test
void productionMode_missingResource_fallsBackToOriginalUrl()
throws Exception {
Expand All @@ -188,9 +220,14 @@

private VaadinServletRequest createRequest(String pathInfo,
String contextPath) {
return createRequest(pathInfo, contextPath, "");
}

private VaadinServletRequest createRequest(String pathInfo,
String contextPath, String servletPath) {
jakarta.servlet.http.HttpServletRequest req = Mockito
.mock(jakarta.servlet.http.HttpServletRequest.class);
Mockito.when(req.getServletPath()).thenReturn("");
Mockito.when(req.getServletPath()).thenReturn(servletPath);
Mockito.when(req.getPathInfo()).thenReturn(pathInfo);
Mockito.when(req.getRequestURL())
.thenReturn(new StringBuffer(pathInfo));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ public void styleSheetOnAppShell_injectedAsLinksInOrder() throws Exception {

List<Element> links = document.head().select("link[rel=stylesheet]");
assertEquals(2, links.size());
assertEquals("/my-styles.css", links.get(0).attr("href"));
assertEquals("./my-styles.css", links.get(0).attr("href"));
assertEquals("https://cdn.example.com/ui.css",
links.get(1).attr("href"));
}
Expand All @@ -517,7 +517,7 @@ public void duplicateStyleSheets_deduplicated() throws Exception {

List<Element> links = document.head().select("link[rel=stylesheet]");
assertEquals(1, links.size());
assertEquals("/theme-base.css", links.get(0).attr("href"));
assertEquals("./theme-base.css", links.get(0).attr("href"));
}

@Test
Expand Down Expand Up @@ -579,9 +579,9 @@ public void styleSheetResolution_variousScenarios() throws Exception {
List<Element> links = document.head().select("link[rel=stylesheet]");
assertEquals(4, links.size());
assertEquals("/trimmed.css", links.get(0).attr("href"));
assertEquals("/ctx/foo/bar.css", links.get(1).attr("href"));
assertEquals("./foo/bar.css", links.get(1).attr("href"));
assertEquals("HTTP://cdn.Example.com/u.css", links.get(2).attr("href"));
assertEquals("/ctx/assets/site.css", links.get(3).attr("href"));
assertEquals("./assets/site.css", links.get(3).attr("href"));
}

@Test
Expand All @@ -594,7 +594,7 @@ public void styleSheetResolution_handlesDotSlash() throws Exception {

List<Element> links = document.head().select("link[rel=stylesheet]");
assertEquals(1, links.size());
assertEquals("/ctx/local.css", links.get(0).attr("href"));
assertEquals("./local.css", links.get(0).attr("href"));
}

@Test
Expand Down
Loading