Merge pull request #19595 from nosan
* pr/19595: Polish "Limit ChronoField values to their range" Limit ChronoField values to their range Closes gh-19595
This commit is contained in:
		
						commit
						f375277026
					
				|  | @ -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 <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 | ||||
| 	 */ | ||||
| 	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())); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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<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() { | ||||
| 		try { | ||||
| 			Object runtimeVersion = Runtime.class.getMethod("version").invoke(null); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue