Skip to content

Commit

Permalink
Return getLastModified result from JarUrlConnection
Browse files Browse the repository at this point in the history
Update `JarUrlConnection` and `NestedUrlConnection` so that calls
to `getLastModified()` and `getHeaderFieldDate("last-modified", 0)`
always return a result.

Fixes gh-38204
  • Loading branch information
philwebb committed Nov 5, 2023
1 parent d6c28b3 commit c0f8b90
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ private String deduceContentTypeFromEntryName() {
return guessContentTypeFromName(this.entryName);
}

@Override
public long getLastModified() {
return (this.jarFileConnection != null) ? this.jarFileConnection.getLastModified() : super.getLastModified();
}

@Override
public String getHeaderField(String name) {
return (this.jarFileConnection != null) ? this.jarFileConnection.getHeaderField(name) : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
import java.net.URLConnection;
import java.nio.file.Files;
import java.security.Permission;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.springframework.boot.loader.ref.Cleaner;

Expand All @@ -39,6 +48,9 @@
*/
class NestedUrlConnection extends URLConnection {

private static final DateTimeFormatter RFC_1123_DATE_TIME = DateTimeFormatter.RFC_1123_DATE_TIME
.withZone(ZoneId.of("GMT"));

private static final String CONTENT_TYPE = "x-java/jar";

private final NestedUrlConnectionResources resources;
Expand All @@ -49,6 +61,8 @@ class NestedUrlConnection extends URLConnection {

private FilePermission permission;

private Map<String, List<String>> headerFields;

NestedUrlConnection(URL url) throws MalformedURLException {
this(url, Cleaner.instance);
}
Expand All @@ -69,6 +83,60 @@ private NestedLocation parseNestedLocation(URL url) throws MalformedURLException
}
}

@Override
public String getHeaderField(String name) {
List<String> values = getHeaderFields().get(name);
return (values != null && !values.isEmpty()) ? values.get(0) : null;
}

@Override
public String getHeaderField(int n) {
Entry<String, List<String>> entry = getHeaderEntry(n);
List<String> values = (entry != null) ? entry.getValue() : null;
return (values != null && !values.isEmpty()) ? values.get(0) : null;
}

@Override
public String getHeaderFieldKey(int n) {
Entry<String, List<String>> entry = getHeaderEntry(n);
return (entry != null) ? entry.getKey() : null;
}

private Entry<String, List<String>> getHeaderEntry(int n) {
Iterator<Entry<String, List<String>>> iterator = getHeaderFields().entrySet().iterator();
Entry<String, List<String>> entry = null;
for (int i = 0; i < n; i++) {
entry = (!iterator.hasNext()) ? null : iterator.next();
}
return entry;
}

@Override
public Map<String, List<String>> getHeaderFields() {
try {
connect();
}
catch (IOException ex) {
return Collections.emptyMap();
}
Map<String, List<String>> headerFields = this.headerFields;
if (headerFields == null) {
headerFields = new LinkedHashMap<>();
long contentLength = getContentLengthLong();
long lastModified = getLastModified();
if (contentLength > 0) {
headerFields.put("content-length", List.of(String.valueOf(contentLength)));
}
if (getLastModified() > 0) {
headerFields.put("last-modified",
List.of(RFC_1123_DATE_TIME.format(Instant.ofEpochMilli(lastModified))));
}
headerFields = Collections.unmodifiableMap(headerFields);
this.headerFields = headerFields;
}
return headerFields;
}

@Override
public int getContentLength() {
long contentLength = getContentLengthLong();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.Permission;
import java.time.Instant;
import java.time.temporal.ChronoField;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
Expand Down Expand Up @@ -490,4 +492,22 @@ void openReturnsConnection() throws Exception {
assertThat(connection).isNotNull();
}

@Test // gh-38204
void getLastModifiedReturnsFileModifiedTime() throws Exception {
JarUrlConnection connection = JarUrlConnection.open(this.url);
assertThat(connection.getLastModified()).isEqualTo(this.file.lastModified());
}

@Test // gh-38204
void getLastModifiedHeaderReturnsFileModifiedTime() throws IOException {
JarUrlConnection connection = JarUrlConnection.open(this.url);
URLConnection fileConnection = this.file.toURI().toURL().openConnection();
assertThat(connection.getHeaderFieldDate("last-modified", 0)).isEqualTo(withoutNanos(this.file.lastModified()))
.isEqualTo(fileConnection.getHeaderFieldDate("last-modified", 0));
}

private long withoutNanos(long epochMilli) {
return Instant.ofEpochMilli(epochMilli).with(ChronoField.NANO_OF_SECOND, 0).toEpochMilli();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@

import java.io.File;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.Cleaner.Cleanable;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.Permission;
import java.time.Instant;
import java.time.temporal.ChronoField;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -148,4 +152,23 @@ void inputStreamCloseCleansResource() throws Exception {
actionCaptor.getValue().run();
}

@Test // gh-38204
void getLastModifiedReturnsFileModifiedTime() throws Exception {
NestedUrlConnection connection = new NestedUrlConnection(this.url);
assertThat(connection.getLastModified()).isEqualTo(this.jarFile.lastModified());
}

@Test // gh-38204
void getLastModifiedHeaderReturnsFileModifiedTime() throws IOException {
NestedUrlConnection connection = new NestedUrlConnection(this.url);
URLConnection fileConnection = this.jarFile.toURI().toURL().openConnection();
assertThat(connection.getHeaderFieldDate("last-modified", 0))
.isEqualTo(withoutNanos(this.jarFile.lastModified()))
.isEqualTo(fileConnection.getHeaderFieldDate("last-modified", 0));
}

private long withoutNanos(long epochMilli) {
return Instant.ofEpochMilli(epochMilli).with(ChronoField.NANO_OF_SECOND, 0).toEpochMilli();
}

}

0 comments on commit c0f8b90

Please sign in to comment.