Merge branch '2.2.x'

Closes gh-19714
pull/19718/head
Madhura Bhave 5 years ago
commit aae1151f12

@ -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"); * 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.
@ -17,8 +17,11 @@
package org.springframework.boot.loader.jar; package org.springframework.boot.loader.jar;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId; 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; import org.springframework.boot.loader.data.RandomAccessData;
@ -27,6 +30,7 @@ import org.springframework.boot.loader.data.RandomAccessData;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Dmytro Nosan
* @see <a href="https://en.wikipedia.org/wiki/Zip_%28file_format%29">Zip File Format</a> * @see <a href="https://en.wikipedia.org/wiki/Zip_%28file_format%29">Zip File Format</a>
*/ */
@ -124,10 +128,14 @@ final class CentralDirectoryFileHeader implements FileHeader {
* @return the date and time as milliseconds since the epoch * @return the date and time as milliseconds since the epoch
*/ */
private long decodeMsDosFormatDateTime(long datetime) { private long decodeMsDosFormatDateTime(long datetime) {
LocalDateTime localDateTime = LocalDateTime.of((int) (((datetime >> 25) & 0x7f) + 1980), int year = getChronoValue(((datetime >> 25) & 0x7f) + 1980, ChronoField.YEAR);
(int) ((datetime >> 21) & 0x0f), (int) ((datetime >> 16) & 0x1f), (int) ((datetime >> 11) & 0x1f), int month = getChronoValue((datetime >> 21) & 0x0f, ChronoField.MONTH_OF_YEAR);
(int) ((datetime >> 5) & 0x3f), (int) ((datetime << 1) & 0x3e)); int day = getChronoValue((datetime >> 16) & 0x1f, ChronoField.DAY_OF_MONTH);
return localDateTime.toEpochSecond(ZoneId.systemDefault().getRules().getOffset(localDateTime)) * 1000; 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();
} }
long getCrc() { long getCrc() {
@ -176,4 +184,9 @@ final class CentralDirectoryFileHeader implements FileHeader {
return 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()));
}
} }

@ -28,6 +28,8 @@ import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List; import java.util.List;
@ -47,6 +49,7 @@ import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.loader.TestJarCreator; import org.springframework.boot.loader.TestJarCreator;
import org.springframework.boot.loader.data.RandomAccessDataFile; import org.springframework.boot.loader.data.RandomAccessDataFile;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
@ -62,6 +65,7 @@ import static org.mockito.Mockito.verify;
* @author Phillip Webb * @author Phillip Webb
* @author Martin Lau * @author Martin Lau
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Madhura Bhave
*/ */
@ExtendWith(JarUrlProtocolHandler.class) @ExtendWith(JarUrlProtocolHandler.class)
class JarFileTests { class JarFileTests {
@ -578,6 +582,26 @@ class JarFileTests {
return bytes.toByteArray(); return bytes.toByteArray();
} }
@Test
void jarFileEntryWithEpochTimeOfZeroShouldNotFail() throws Exception {
File file = new File(this.tempDir, "timed.jar");
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<java.util.jar.JarEntry> entries = jarFile.entries();
JarEntry entry = entries.nextElement();
assertThat(entry.getLastModifiedTime().toInstant()).isEqualTo(Instant.EPOCH);
assertThat(entry.getName()).isEqualTo("1.dat");
}
private int getJavaVersion() { private int getJavaVersion() {
try { try {
Object runtimeVersion = Runtime.class.getMethod("version").invoke(null); Object runtimeVersion = Runtime.class.getMethod("version").invoke(null);

Loading…
Cancel
Save