protected List<ResourceResolver> getResourceResolvers() { if (!this.hasPathResolver) { List<ResourceResolver> result = new ArrayList<>(this.resolvers); if (isWebJarsAssetLocatorPresent && !this.hasWebjarsResolver) { result.add(new WebJarsResourceResolver()); } result.add(new PathResourceResolver()); return result; } return this.resolvers; }
/** * Find the resource under the given location. * <p>The default implementation checks if there is a readable * {@code Resource} for the given path relative to the location. * @param resourcePath the path to the resource * @param location the location to check * @return the resource, or {@code null} if none found */ @Nullable protected Resource getResource(String resourcePath, Resource location) throws IOException { Resource resource = location.createRelative(resourcePath); if (resource.isReadable()) { if (checkResource(resource, location)) { return resource; } else if (logger.isWarnEnabled()) { Resource[] allowedLocations = getAllowedLocations(); logger.warn("Resource path \"" + resourcePath + "\" was successfully resolved " + "but resource \"" + resource.getURL() + "\" is neither under the " + "current location \"" + location.getURL() + "\" nor under any of the " + "allowed locations " + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]")); } } return null; }
/** * Look for a {@code PathResourceResolver} among the configured resource * resolvers and set its {@code allowedLocations} property (if empty) to * match the {@link #setLocations locations} configured on this class. */ protected void initAllowedLocations() { if (CollectionUtils.isEmpty(this.locations)) { return; } for (int i = getResourceResolvers().size() - 1; i >= 0; i--) { if (getResourceResolvers().get(i) instanceof PathResourceResolver) { PathResourceResolver pathResolver = (PathResourceResolver) getResourceResolvers().get(i); if (ObjectUtils.isEmpty(pathResolver.getAllowedLocations())) { pathResolver.setAllowedLocations(getLocations().toArray(new Resource[0])); } if (this.urlPathHelper != null) { pathResolver.setLocationCharsets(this.locationCharsets); pathResolver.setUrlPathHelper(this.urlPathHelper); } break; } } }
/** * Perform additional checks on a resolved resource beyond checking whether the * resources exists and is readable. The default implementation also verifies * the resource is either under the location relative to which it was found or * is under one of the {@link #setAllowedLocations allowed locations}. * @param resource the resource to check * @param location the location relative to which the resource was found * @return "true" if resource is in a valid location, "false" otherwise. * @since 4.1.2 */ protected boolean checkResource(Resource resource, Resource location) throws IOException { if (isResourceUnderLocation(resource, location)) { return true; } Resource[] allowedLocations = getAllowedLocations(); if (allowedLocations != null) { for (Resource current : allowedLocations) { if (isResourceUnderLocation(resource, current)) { return true; } } } return false; }
@Nullable private Resource getResource(String resourcePath, @Nullable HttpServletRequest request, List<? extends Resource> locations) { for (Resource location : locations) { try { String pathToUse = encodeIfNecessary(resourcePath, request, location); Resource resource = getResource(pathToUse, location); if (resource != null) { return resource; } } catch (IOException ex) { if (logger.isDebugEnabled()) { String error = "Skip location [" + location + "] due to error"; if (logger.isTraceEnabled()) { logger.trace(error, ex); } else { logger.debug(error + ": " + ex.getMessage()); } } } } return null; }
@Before public void createFilter() { VersionResourceResolver versionResolver = new VersionResourceResolver(); versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy())); PathResourceResolver pathResolver = new PathResourceResolver(); pathResolver.setAllowedLocations(new ClassPathResource("test/", getClass())); List<ResourceResolver> resolvers = new ArrayList<>(); resolvers.add(versionResolver); resolvers.add(pathResolver); this.filter = new ResourceUrlEncodingFilter(); this.urlProvider = createResourceUrlProvider(resolvers); }
@Test public void initAllowedLocationsWithExplicitConfiguration() throws Exception { ClassPathResource location1 = new ClassPathResource("test/", getClass()); ClassPathResource location2 = new ClassPathResource("testalternatepath/", getClass()); PathResourceResolver pathResolver = new PathResourceResolver(); pathResolver.setAllowedLocations(location1); ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler(); handler.setResourceResolvers(Collections.singletonList(pathResolver)); handler.setServletContext(new MockServletContext()); handler.setLocations(Arrays.asList(location1, location2)); handler.afterPropertiesSet(); Resource[] locations = pathResolver.getAllowedLocations(); assertEquals(1, locations.length); assertEquals("test/", ((ClassPathResource) locations[0]).getPath()); }
@Test public void relativePathEncodedForUrlResource() throws Exception { TestUrlResource location = new TestUrlResource("file:///tmp"); List<TestUrlResource> locations = Collections.singletonList(location); // ISO-8859-1 this.resolver.setUrlPathHelper(new UrlPathHelper()); this.resolver.setLocationCharsets(Collections.singletonMap(location, StandardCharsets.ISO_8859_1)); this.resolver.resolveResource(new MockHttpServletRequest(), "/Ä ;ä.txt", locations, null); assertEquals("%C4%20%3B%E4.txt", location.getSavedRelativePath()); // UTF-8 this.resolver.setLocationCharsets(Collections.singletonMap(location, StandardCharsets.UTF_8)); this.resolver.resolveResource(new MockHttpServletRequest(), "/Ä ;ä.txt", locations, null); assertEquals("%C3%84%20%3B%C3%A4.txt", location.getSavedRelativePath()); // UTF-8 by default this.resolver.setLocationCharsets(Collections.emptyMap()); this.resolver.resolveResource(new MockHttpServletRequest(), "/Ä ;ä.txt", locations, null); assertEquals("%C3%84%20%3B%C3%A4.txt", location.getSavedRelativePath()); }
@Override protected Resource resolveResourceInternal(@Nullable HttpServletRequest request, String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) { return getResource(requestPath, request, locations); }
@Test public void checkResourceWithAllowedLocations() { this.resolver.setAllowedLocations( new ClassPathResource("test/", PathResourceResolver.class), new ClassPathResource("testalternatepath/", PathResourceResolver.class) ); Resource location = getResource("main.css"); List<Resource> locations = Collections.singletonList(location); String actual = this.resolver.resolveUrlPath("../testalternatepath/bar.css", locations, null); assertEquals("../testalternatepath/bar.css", actual); }
@Test public void checkServletContextResource() throws Exception { Resource classpathLocation = new ClassPathResource("test/", PathResourceResolver.class); MockServletContext context = new MockServletContext(); ServletContextResource servletContextLocation = new ServletContextResource(context, "/webjars/"); ServletContextResource resource = new ServletContextResource(context, "/webjars/webjar-foo/1.0/foo.js"); assertFalse(this.resolver.checkResource(resource, classpathLocation)); assertTrue(this.resolver.checkResource(resource, servletContextLocation)); }
private boolean isResourceUnderLocation(Resource resource, Resource location) throws IOException { if (resource.getClass() != location.getClass()) { return false; } String resourcePath; String locationPath; if (resource instanceof UrlResource) { resourcePath = resource.getURL().toExternalForm(); locationPath = StringUtils.cleanPath(location.getURL().toString()); } else if (resource instanceof ClassPathResource) { resourcePath = ((ClassPathResource) resource).getPath(); locationPath = StringUtils.cleanPath(((ClassPathResource) location).getPath()); } else if (resource instanceof ServletContextResource) { resourcePath = ((ServletContextResource) resource).getPath(); locationPath = StringUtils.cleanPath(((ServletContextResource) location).getPath()); } else { resourcePath = resource.getURL().getPath(); locationPath = StringUtils.cleanPath(location.getURL().getPath()); } if (locationPath.equals(resourcePath)) { return true; } locationPath = (locationPath.endsWith("/") || locationPath.isEmpty() ? locationPath : locationPath + "/"); return (resourcePath.startsWith(locationPath) && !isInvalidEncodedPath(resourcePath)); }
@Test public void initAllowedLocations() { PathResourceResolver resolver = (PathResourceResolver) this.handler.getResourceResolvers().get(0); Resource[] locations = resolver.getAllowedLocations(); assertEquals(3, locations.length); assertEquals("test/", ((ClassPathResource) locations[0]).getPath()); assertEquals("testalternatepath/", ((ClassPathResource) locations[1]).getPath()); assertEquals("META-INF/resources/webjars/", ((ClassPathResource) locations[2]).getPath()); }
@Before public void setUp() { VersionResourceResolver versionResolver = new VersionResourceResolver(); versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy())); PathResourceResolver pathResolver = new PathResourceResolver(); pathResolver.setAllowedLocations(new ClassPathResource("test/", getClass())); List<ResourceResolver> resolvers = new ArrayList<>(); resolvers.add(versionResolver); resolvers.add(new PathResourceResolver()); ResourceUrlProvider resourceUrlProvider = createUrlProvider(resolvers); CssLinkResourceTransformer cssLinkTransformer = new CssLinkResourceTransformer(); cssLinkTransformer.setResourceUrlProvider(resourceUrlProvider); this.transformerChain = new DefaultResourceTransformerChain( new DefaultResourceResolverChain(resolvers), Collections.singletonList(cssLinkTransformer)); }
/** * Perform additional checks on a resolved resource beyond checking whether the * resources exists and is readable. The default implementation also verifies * the resource is either under the location relative to which it was found or * is under one of the {@link #setAllowedLocations allowed locations}. * @param resource the resource to check * @param location the location relative to which the resource was found * @return "true" if resource is in a valid location, "false" otherwise. * @since 4.1.2 */ protected boolean checkResource(Resource resource, Resource location) throws IOException { if (isResourceUnderLocation(resource, location)) { return true; } Resource[] allowedLocations = getAllowedLocations(); if (allowedLocations != null) { for (Resource current : allowedLocations) { if (isResourceUnderLocation(resource, current)) { return true; } } } return false; }
@Nullable private Resource getResource(String resourcePath, @Nullable HttpServletRequest request, List<? extends Resource> locations) { for (Resource location : locations) { try { String pathToUse = encodeIfNecessary(resourcePath, request, location); Resource resource = getResource(pathToUse, location); if (resource != null) { return resource; } } catch (IOException ex) { if (logger.isDebugEnabled()) { String error = "Skip location [" + location + "] due to error"; if (logger.isTraceEnabled()) { logger.trace(error, ex); } else { logger.debug(error + ": " + ex.getMessage()); } } } } return null; }
@Override protected String resolveUrlPathInternal(String resourcePath, List<? extends Resource> locations, ResourceResolverChain chain) { return (StringUtils.hasText(resourcePath) && getResource(resourcePath, null, locations) != null ? resourcePath : null); }
@Test public void checkFileLocation() throws Exception { Resource resource = getResource("main.css"); assertTrue(this.resolver.checkResource(resource, resource)); }
private boolean isResourceUnderLocation(Resource resource, Resource location) throws IOException { if (resource.getClass() != location.getClass()) { return false; } String resourcePath; String locationPath; if (resource instanceof UrlResource) { resourcePath = resource.getURL().toExternalForm(); locationPath = StringUtils.cleanPath(location.getURL().toString()); } else if (resource instanceof ClassPathResource) { resourcePath = ((ClassPathResource) resource).getPath(); locationPath = StringUtils.cleanPath(((ClassPathResource) location).getPath()); } else if (resource instanceof ServletContextResource) { resourcePath = ((ServletContextResource) resource).getPath(); locationPath = StringUtils.cleanPath(((ServletContextResource) location).getPath()); } else { resourcePath = resource.getURL().getPath(); locationPath = StringUtils.cleanPath(location.getURL().getPath()); } if (locationPath.equals(resourcePath)) { return true; } locationPath = (locationPath.endsWith("/") || locationPath.isEmpty() ? locationPath : locationPath + "/"); return (resourcePath.startsWith(locationPath) && !isInvalidEncodedPath(resourcePath)); }
/** * Look for a {@code PathResourceResolver} among the configured resource * resolvers and set its {@code allowedLocations} property (if empty) to * match the {@link #setLocations locations} configured on this class. */ protected void initAllowedLocations() { if (CollectionUtils.isEmpty(this.locations)) { return; } for (int i = getResourceResolvers().size() - 1; i >= 0; i--) { if (getResourceResolvers().get(i) instanceof PathResourceResolver) { PathResourceResolver pathResolver = (PathResourceResolver) getResourceResolvers().get(i); if (ObjectUtils.isEmpty(pathResolver.getAllowedLocations())) { pathResolver.setAllowedLocations(getLocations().toArray(new Resource[0])); } if (this.urlPathHelper != null) { pathResolver.setLocationCharsets(this.locationCharsets); pathResolver.setUrlPathHelper(this.urlPathHelper); } break; } } }