diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java
index 5be09442025..25f1f73cda6 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,11 @@
package org.springframework.boot.loader.jar;
import java.io.IOException;
-import java.time.LocalDateTime;
import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.ValueRange;
import org.springframework.boot.loader.data.RandomAccessData;
@@ -27,6 +30,7 @@ import org.springframework.boot.loader.data.RandomAccessData;
*
* @author Phillip Webb
* @author Andy Wilkinson
+ * @author Dmytro Nosan
* @see Zip File Format
*/
@@ -124,10 +128,14 @@ final class CentralDirectoryFileHeader implements FileHeader {
* @return the date and time as milliseconds since the epoch
*/
private long decodeMsDosFormatDateTime(long datetime) {
- LocalDateTime localDateTime = LocalDateTime.of((int) (((datetime >> 25) & 0x7f) + 1980),
- (int) ((datetime >> 21) & 0x0f), (int) ((datetime >> 16) & 0x1f), (int) ((datetime >> 11) & 0x1f),
- (int) ((datetime >> 5) & 0x3f), (int) ((datetime << 1) & 0x3e));
- return localDateTime.toEpochSecond(ZoneId.systemDefault().getRules().getOffset(localDateTime)) * 1000;
+ int year = getChronoValue(((datetime >> 25) & 0x7f) + 1980, ChronoField.YEAR);
+ int month = getChronoValue((datetime >> 21) & 0x0f, ChronoField.MONTH_OF_YEAR);
+ int day = getChronoValue((datetime >> 16) & 0x1f, ChronoField.DAY_OF_MONTH);
+ int hour = getChronoValue((datetime >> 11) & 0x1f, ChronoField.HOUR_OF_DAY);
+ int minute = getChronoValue((datetime >> 5) & 0x3f, ChronoField.MINUTE_OF_HOUR);
+ int second = getChronoValue((datetime << 1) & 0x3e, ChronoField.SECOND_OF_MINUTE);
+ return ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneId.systemDefault()).toInstant()
+ .truncatedTo(ChronoUnit.SECONDS).toEpochMilli();
}
public long getCrc() {
@@ -172,4 +180,9 @@ final class CentralDirectoryFileHeader implements FileHeader {
return fileHeader;
}
+ private static int getChronoValue(long value, ChronoField field) {
+ ValueRange range = field.range();
+ return Math.toIntExact(Math.min(Math.max(value, range.getMinimum()), range.getMaximum()));
+ }
+
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java
index 416465c4733..97329cb9ef5 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java
@@ -25,9 +25,12 @@ import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -39,6 +42,7 @@ import org.junit.rules.TemporaryFolder;
import org.springframework.boot.loader.TestJarCreator;
import org.springframework.boot.loader.data.RandomAccessDataFile;
+import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StreamUtils;
@@ -54,6 +58,7 @@ import static org.mockito.Mockito.verify;
* @author Phillip Webb
* @author Martin Lau
* @author Andy Wilkinson
+ * @author Madhura Bhave
*/
public class JarFileTests {
@@ -493,6 +498,26 @@ public class JarFileTests {
assertThat(inputStream.read()).isEqualTo(getJavaVersion());
}
+ @Test
+ public void jarFileEntryWithEpochTimeOfZeroShouldNotFail() throws Exception {
+ File file = this.temporaryFolder.newFile();
+ FileOutputStream fileOutputStream = new FileOutputStream(file);
+ try (JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream)) {
+ jarOutputStream.setComment("outer");
+ JarEntry entry = new JarEntry("1.dat");
+ entry.setLastModifiedTime(FileTime.from(Instant.EPOCH));
+ ReflectionTestUtils.setField(entry, "xdostime", 0);
+ jarOutputStream.putNextEntry(entry);
+ jarOutputStream.write(new byte[] { (byte) 1 });
+ jarOutputStream.closeEntry();
+ }
+ JarFile jarFile = new JarFile(file);
+ Enumeration entries = jarFile.entries();
+ JarEntry entry = entries.nextElement();
+ assertThat(entry.getLastModifiedTime().toInstant()).isEqualTo(Instant.EPOCH);
+ assertThat(entry.getName()).isEqualTo("1.dat");
+ }
+
private int getJavaVersion() {
try {
Object runtimeVersion = Runtime.class.getMethod("version").invoke(null);