Skip to content

Commit 9c9f5ac

Browse files
fix: Make StyleSheet for AppShell support non-root context path (#22441)
* fix: Make StyleSheet for AppShell support non-root context path * test modules use custom servlet mapping --------- Co-authored-by: Marco Collovati <marco@vaadin.com>
1 parent f375480 commit 9c9f5ac

File tree

3 files changed

+33
-38
lines changed

3 files changed

+33
-38
lines changed

flow-server/src/main/java/com/vaadin/flow/server/AppShellRegistry.java

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public String validateClass(Class<?> clz) {
190190
return error;
191191
}
192192

193-
private AppShellSettings createSettings(String contextPath) {
193+
private AppShellSettings createSettings(VaadinRequest request) {
194194
AppShellSettings settings = new AppShellSettings();
195195

196196
getAnnotations(Meta.class).forEach(
@@ -225,7 +225,7 @@ private AppShellSettings createSettings(String contextPath) {
225225

226226
Set<String> stylesheets = new LinkedHashSet<>();
227227
for (StyleSheet sheet : getAnnotations(StyleSheet.class)) {
228-
String href = resolveStyleSheetHref(sheet.value(), contextPath);
228+
String href = resolveStyleSheetHref(sheet.value(), request);
229229
if (href != null && !href.isBlank()) {
230230
stylesheets.add(href);
231231
}
@@ -235,41 +235,43 @@ private AppShellSettings createSettings(String contextPath) {
235235
return settings;
236236
}
237237

238-
private String resolveStyleSheetHref(String href, String contextPath) {
238+
private String resolveStyleSheetHref(String href, VaadinRequest request) {
239239
if (href == null || href.isBlank()) {
240240
return null;
241241
}
242+
if (HandlerHelper
243+
.isPathUnsafe(href.startsWith("/") ? href : "/" + href)) {
244+
log.warn(
245+
"@StyleSheet href containing traversals ('../') are not allowed, ignored: {}",
246+
href);
247+
return null;
248+
}
242249
href = href.trim();
243250
// Accept absolute http(s) URLs unchanged
244251
String lower = href.toLowerCase();
245252
if (lower.startsWith("http://") || lower.startsWith("https://")) {
246253
return href;
247254
}
248-
// Accept context-relative URLs: context://path -> /path
249-
String contextProtocol = ApplicationConstants.CONTEXT_PROTOCOL_PREFIX;
250-
if (lower.startsWith(contextProtocol)) {
251-
String path = href.substring(contextProtocol.length());
252-
if (!path.startsWith("/")) {
253-
path = "/" + path;
254-
}
255-
if (contextPath != null && !contextPath.isEmpty()) {
256-
return contextPath + path;
257-
}
258-
return path;
259-
}
260255
// Treat ./ as relative path to static resources location
261256
if (href.startsWith("./")) {
262257
href = href.substring(2);
263258
}
264259
// Accept bare paths beginning with '/' as-is
265-
href = href.startsWith("/") ? href : "/" + href;
266-
if (HandlerHelper.isPathUnsafe(href)) {
267-
log.warn(
268-
"@StyleSheet href containing traversals ('../') are not allowed, ignored: "
269-
+ href);
270-
return null;
260+
if (href.startsWith("/")) {
261+
return href;
262+
}
263+
264+
String contextPath = request.getContextPath();
265+
if (!contextPath.isEmpty()) {
266+
String contextProtocol = ApplicationConstants.CONTEXT_PROTOCOL_PREFIX;
267+
if (!lower.startsWith(contextProtocol)) {
268+
// Prepend context protocol so URL is resolved with context path
269+
href = contextProtocol + href;
270+
}
271271
}
272-
return href;
272+
BootstrapHandler.BootstrapUriResolver resolver = new BootstrapHandler.BootstrapUriResolver(
273+
contextPath + "/", null);
274+
return resolver.resolveVaadinUri(href);
273275
}
274276

275277
/**
@@ -283,7 +285,7 @@ private String resolveStyleSheetHref(String href, String contextPath) {
283285
* The request to handle
284286
*/
285287
public void modifyIndexHtml(Document document, VaadinRequest request) {
286-
AppShellSettings settings = createSettings(request.getContextPath());
288+
AppShellSettings settings = createSettings(request);
287289
if (appShellClass != null) {
288290
VaadinService.getCurrent().getInstantiator()
289291
.getOrCreate(appShellClass).configurePage(settings);

flow-server/src/test/java/com/vaadin/flow/server/startup/VaadinAppShellInitializerTest.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,6 @@ public static class MyAppShellWithDuplicateStyles
225225
@StyleSheet("foo/bar.css")
226226
@StyleSheet("HTTP://cdn.Example.com/u.css")
227227
@StyleSheet("context://assets/site.css")
228-
@StyleSheet("context:///already-absolute.css")
229228
public static class MyAppShellWithVariousStyleSheets
230229
implements AppShellConfigurator {
231230
}
@@ -564,12 +563,11 @@ public void styleSheetResolution_variousScenarios() throws Exception {
564563
createVaadinRequest("/", "/ctx"));
565564

566565
List<Element> links = document.head().select("link[rel=stylesheet]");
567-
assertEquals(5, links.size());
566+
assertEquals(4, links.size());
568567
assertEquals("/trimmed.css", links.get(0).attr("href"));
569-
assertEquals("/foo/bar.css", links.get(1).attr("href"));
568+
assertEquals("/ctx/foo/bar.css", links.get(1).attr("href"));
570569
assertEquals("HTTP://cdn.Example.com/u.css", links.get(2).attr("href"));
571570
assertEquals("/ctx/assets/site.css", links.get(3).attr("href"));
572-
assertEquals("/ctx/already-absolute.css", links.get(4).attr("href"));
573571
}
574572

575573
@Test
@@ -582,7 +580,7 @@ public void styleSheetResolution_handlesDotSlash() throws Exception {
582580

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

588586
@Test
@@ -602,15 +600,6 @@ private VaadinServletRequest createVaadinRequest(String pathInfo) {
602600
return new VaadinServletRequest(request, service);
603601
}
604602

605-
private HttpServletRequest createRequest(String pathInfo) {
606-
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
607-
Mockito.when(request.getServletPath()).thenReturn("");
608-
Mockito.when(request.getPathInfo()).thenReturn(pathInfo);
609-
Mockito.when(request.getRequestURL())
610-
.thenReturn(new StringBuffer(pathInfo));
611-
return request;
612-
}
613-
614603
private Logger mockLog(Class clz) throws Exception {
615604
// wrap logger for clz in an spy
616605
Logger spy = Mockito.spy(LoggerFactory.getLogger(clz.getName()));
@@ -650,6 +639,10 @@ private VaadinServletRequest createVaadinRequest(String pathInfo,
650639
return new VaadinServletRequest(request, service);
651640
}
652641

642+
private HttpServletRequest createRequest(String pathInfo) {
643+
return createRequest(pathInfo, "");
644+
}
645+
653646
private HttpServletRequest createRequest(String pathInfo,
654647
String contextPath) {
655648
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);

flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/AppShell.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@
2727
@NpmPackage(value = "@fortawesome/fontawesome-free", version = TestVersion.FONTAWESOME, assets = {
2828
"svgs/regular/**:npm/icons" })
2929
@LoadDependenciesOnStartup
30-
@StyleSheet("@vaadin/aura/fake-aura.css")
30+
@StyleSheet("context://@vaadin/aura/fake-aura.css")
3131
public class AppShell implements AppShellConfigurator {
3232
}

0 commit comments

Comments
 (0)