Browse Source

ClassPathResource.isReadable() checks InputStream (for jar directories)

Resource.isReadable() is defined to semantically imply exists() now.

Issue: SPR-16832
pull/1842/head
Juergen Hoeller 7 years ago
parent
commit
69f14a2038
  1. 37
      spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java
  2. 10
      spring-core/src/main/java/org/springframework/core/io/AbstractResource.java
  3. 10
      spring-core/src/main/java/org/springframework/core/io/Resource.java
  4. 13
      spring-core/src/test/java/org/springframework/core/io/ClassPathResourceTests.java
  5. 2
      spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java
  6. 2
      spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java

37
spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java

@ -19,7 +19,6 @@ package org.springframework.core.io;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
@ -70,19 +69,18 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
return true; return true;
} }
if (httpCon != null) { if (httpCon != null) {
// no HTTP OK status, and no content-length header: give up // No HTTP OK status, and no content-length header: give up
httpCon.disconnect(); httpCon.disconnect();
return false; return false;
} }
else { else {
// Fall back to stream existence: can we open the stream? // Fall back to stream existence: can we open the stream?
InputStream is = getInputStream(); getInputStream().close();
is.close();
return true; return true;
} }
} }
} }
catch (IOException ex) { catch (Exception ex) {
return false; return false;
} }
} }
@ -97,10 +95,33 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
return (file.canRead() && !file.isDirectory()); return (file.canRead() && !file.isDirectory());
} }
else { else {
// Try InputStream resolution for jar resources
URLConnection con = url.openConnection();
customizeConnection(con);
if (con instanceof HttpURLConnection) {
HttpURLConnection httpCon = (HttpURLConnection) con;
int code = httpCon.getResponseCode();
if (code != HttpURLConnection.HTTP_OK) {
httpCon.disconnect();
return false;
}
}
int contentLength = con.getContentLength();
if (contentLength > 0) {
return true;
}
else if (contentLength < 0) {
return false;
}
// 0 length: either an empty file or a directory...
// On current JDKs, this will trigger an NPE from within the close() call
// for directories, only returning true for actual files with 0 length.
getInputStream().close();
return true; return true;
} }
} }
catch (IOException ex) { catch (Exception ex) {
// Usually an IOException but potentially a NullPointerException (see above)
return false; return false;
} }
} }
@ -114,7 +135,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
} }
return ResourceUtils.URL_PROTOCOL_FILE.equals(url.getProtocol()); return ResourceUtils.URL_PROTOCOL_FILE.equals(url.getProtocol());
} }
catch (IOException ex) { catch (Exception ex) {
return false; return false;
} }
} }
@ -165,7 +186,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
} }
return ResourceUtils.URL_PROTOCOL_FILE.equals(uri.getScheme()); return ResourceUtils.URL_PROTOCOL_FILE.equals(uri.getScheme());
} }
catch (IOException ex) { catch (Exception ex) {
return false; return false;
} }
} }

10
spring-core/src/main/java/org/springframework/core/io/AbstractResource.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -57,8 +57,7 @@ public abstract class AbstractResource implements Resource {
catch (IOException ex) { catch (IOException ex) {
// Fall back to stream existence: can we open the stream? // Fall back to stream existence: can we open the stream?
try { try {
InputStream is = getInputStream(); getInputStream().close();
is.close();
return true; return true;
} }
catch (Throwable isEx) { catch (Throwable isEx) {
@ -68,11 +67,12 @@ public abstract class AbstractResource implements Resource {
} }
/** /**
* This implementation always returns {@code true}. * This implementation always returns {@code true} for a resource
* that {@link #exists() exists} (revised as of 5.1).
*/ */
@Override @Override
public boolean isReadable() { public boolean isReadable() {
return true; return exists();
} }
/** /**

10
spring-core/src/main/java/org/springframework/core/io/Resource.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -62,14 +62,16 @@ public interface Resource extends InputStreamSource {
/** /**
* Indicate whether the contents of this resource can be read via * Indicate whether the contents of this resource can be read via
* {@link #getInputStream()}. * {@link #getInputStream()}.
* <p>Will be {@code true} for typical resource descriptors; * <p>Will be {@code true} for typical resource descriptors that exist
* note that actual content reading may still fail when attempted. * since it strictly implies {@link #exists()} semantics as of 5.1.
* Note that actual content reading may still fail when attempted.
* However, a value of {@code false} is a definitive indication * However, a value of {@code false} is a definitive indication
* that the resource content cannot be read. * that the resource content cannot be read.
* @see #getInputStream() * @see #getInputStream()
* @see #exists()
*/ */
default boolean isReadable() { default boolean isReadable() {
return true; return exists();
} }
/** /**

13
spring-core/src/test/java/org/springframework/core/io/ClassPathResourceTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -109,6 +109,17 @@ public class ClassPathResourceTests {
assertEquals("/test.html", ((ClassPathResource) new ClassPathResource("", getClass()).createRelative("/test.html")).getPath()); assertEquals("/test.html", ((ClassPathResource) new ClassPathResource("", getClass()).createRelative("/test.html")).getPath());
} }
@Test
public void directoryNotReadable() {
Resource fileDir = new ClassPathResource("org/springframework/core");
assertTrue(fileDir.exists());
assertFalse(fileDir.isReadable());
Resource jarDir = new ClassPathResource("reactor/core");
assertTrue(jarDir.exists());
assertFalse(jarDir.isReadable());
}
private void assertDescriptionContainsExpectedPath(ClassPathResource resource, String expectedPath) { private void assertDescriptionContainsExpectedPath(ClassPathResource resource, String expectedPath) {
Matcher matcher = DESCRIPTION_PATTERN.matcher(resource.getDescription()); Matcher matcher = DESCRIPTION_PATTERN.matcher(resource.getDescription());

2
spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java

@ -109,7 +109,7 @@ public class PathResourceResolver extends AbstractResourceResolver {
protected Mono<Resource> getResource(String resourcePath, Resource location) { protected Mono<Resource> getResource(String resourcePath, Resource location) {
try { try {
Resource resource = location.createRelative(resourcePath); Resource resource = location.createRelative(resourcePath);
if (resource.exists() && resource.isReadable()) { if (resource.isReadable()) {
if (checkResource(resource, location)) { if (checkResource(resource, location)) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Found match: " + resource); logger.trace("Found match: " + resource);

2
spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java

@ -183,7 +183,7 @@ public class PathResourceResolver extends AbstractResourceResolver {
@Nullable @Nullable
protected Resource getResource(String resourcePath, Resource location) throws IOException { protected Resource getResource(String resourcePath, Resource location) throws IOException {
Resource resource = location.createRelative(resourcePath); Resource resource = location.createRelative(resourcePath);
if (resource.exists() && resource.isReadable()) { if (resource.isReadable()) {
if (checkResource(resource, location)) { if (checkResource(resource, location)) {
return resource; return resource;
} }

Loading…
Cancel
Save