Skip to content

Commit edf65fe

Browse files
committed
Do not overwrite extra-field for JVM handling of LocalFileHeader when copying from CentralDirectoryFileHeader
1 parent 02951ff commit edf65fe

File tree

4 files changed

+133
-2
lines changed

4 files changed

+133
-2
lines changed

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>software.coley</groupId>
88
<artifactId>lljzip</artifactId>
9-
<version>2.1.3</version>
9+
<version>2.1.4</version>
1010

1111
<name>LL Java ZIP</name>
1212
<description>Lower level ZIP support for Java</description>

src/main/java/software/coley/lljzip/format/model/JvmLocalFileHeader.java

-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ public void adoptLinkedCentralDirectoryValues() {
105105
crc32 = linkedDirectoryFileHeader.crc32;
106106
fileNameLength = linkedDirectoryFileHeader.fileNameLength;
107107
fileName = linkedDirectoryFileHeader.fileName;
108-
extraField = linkedDirectoryFileHeader.extraField;
109108

110109
// The sizes are not used by the JVM parser.
111110
// It just says 'go until the next header'.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package software.coley.lljzip;
2+
3+
import org.junit.jupiter.params.ParameterizedTest;
4+
import org.junit.jupiter.params.provider.ValueSource;
5+
import software.coley.lljzip.format.model.LocalFileHeader;
6+
import software.coley.lljzip.format.model.ZipArchive;
7+
import software.coley.lljzip.util.ByteData;
8+
9+
import javax.annotation.Nonnull;
10+
import java.io.IOException;
11+
import java.nio.file.Path;
12+
import java.nio.file.Paths;
13+
import java.nio.file.attribute.FileTime;
14+
import java.util.concurrent.TimeUnit;
15+
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
import static org.junit.jupiter.api.Assertions.fail;
18+
19+
/**
20+
* Tests ensuring that the extra field can be read for custom/detailed windows time values.
21+
* This generally means the extra-field is assigned correctly.
22+
*
23+
* @author Matt Coley
24+
*/
25+
public class WindowsExtraFieldTimeTests {
26+
@ParameterizedTest
27+
@ValueSource(strings = {"standard", "naive", "jvm"})
28+
public void validity(@Nonnull String mode) {
29+
Path path = Paths.get("src/test/resources/content-with-windows-time.jar");
30+
try {
31+
long timeCreate = 1000000000000L;
32+
long timeModify = 1200000000000L;
33+
long timeAccess = 1400000000000L;
34+
ZipArchive archive;
35+
switch (mode) {
36+
case "standard":
37+
archive = ZipIO.readStandard(path);
38+
break;
39+
case "naive":
40+
archive = ZipIO.readNaive(path);
41+
break;
42+
case "jvm":
43+
archive = ZipIO.readJvm(path);
44+
break;
45+
default:
46+
throw new IllegalStateException();
47+
}
48+
Wrapper wrapper = read(archive);
49+
assertEquals(timeCreate, wrapper.creation);
50+
assertEquals(timeModify, wrapper.modify);
51+
assertEquals(timeAccess, wrapper.access);
52+
} catch (IOException ex) {
53+
fail(ex);
54+
}
55+
}
56+
57+
@Nonnull
58+
private Wrapper read(@Nonnull ZipArchive archive) {
59+
LocalFileHeader header = archive.getLocalFiles().get(0);
60+
Wrapper wrapper = new Wrapper();
61+
int extraLen = header.getExtraFieldLength();
62+
if (extraLen > 0 && extraLen < 0xFFFF) {
63+
// Reimplementation of 'java.util.zip.ZipEntry#setExtra0(...)'
64+
ByteData extra = header.getExtraField();
65+
int off = 0;
66+
int len = (int) extra.length();
67+
while (off + 4 < len) {
68+
int tag = extra.getShort(off);
69+
int size = extra.getShort(off + 2);
70+
off += 4;
71+
if (off + size > len)
72+
break;
73+
if (tag == /* EXTID_NTFS */ 0xA) {
74+
if (size < 32) // reserved 4 bytes + tag 2 bytes + size 2 bytes
75+
break; // m[a|c]time 24 bytes
76+
int pos = off + 4;
77+
if (extra.getShort(pos) != 0x0001 || extra.getShort(pos + 2) != 24)
78+
break;
79+
long wtime;
80+
wtime = extra.getInt(pos + 4) | ((long) extra.getInt(pos + 8) << 32);
81+
if (wtime != Long.MIN_VALUE) {
82+
wrapper.modify = winTimeToFileTime(wtime).toMillis();
83+
}
84+
wtime = extra.getInt(pos + 12) | ((long) extra.getInt(pos + 16) << 32);
85+
if (wtime != Long.MIN_VALUE) {
86+
wrapper.access = winTimeToFileTime(wtime).toMillis();
87+
}
88+
wtime = extra.getInt(pos + 20) | ((long) extra.getInt(pos + 8) << 24);
89+
if (wtime != Long.MIN_VALUE) {
90+
wrapper.creation = winTimeToFileTime(wtime).toMillis();
91+
}
92+
} else if (tag == /* EXTID_EXTT */ 0x5455) {
93+
int flag = extra.get(off);
94+
int localOff = 1;
95+
// The CEN-header extra field contains the modification
96+
// time only, or no timestamp at all. 'sz' is used to
97+
// flag its presence or absence. But if mtime is present
98+
// in LOC it must be present in CEN as well.
99+
if ((flag & 0x1) != 0 && (localOff + 4) <= size) {
100+
// get32S(extra, off + localOff)
101+
wrapper.modify = unixTimeToFileTime(extra.getInt(off + localOff)).toMillis();
102+
localOff += 4;
103+
}
104+
if ((flag & 0x2) != 0 && (localOff + 4) <= size) {
105+
wrapper.access = unixTimeToFileTime(extra.getInt(off + localOff)).toMillis();
106+
localOff += 4;
107+
}
108+
if ((flag & 0x4) != 0 && (localOff + 4) <= size) {
109+
wrapper.creation = unixTimeToFileTime(extra.getInt(off + localOff)).toMillis();
110+
localOff += 4;
111+
}
112+
}
113+
off += size;
114+
}
115+
}
116+
return wrapper;
117+
}
118+
119+
@Nonnull
120+
public static FileTime winTimeToFileTime(long time) {
121+
return FileTime.from(time / 10 + -11644473600000000L /* windows epoch */, TimeUnit.MICROSECONDS);
122+
}
123+
124+
@Nonnull
125+
public static FileTime unixTimeToFileTime(long utime) {
126+
return FileTime.from(utime, TimeUnit.SECONDS);
127+
}
128+
129+
private static class Wrapper {
130+
private long creation, access, modify;
131+
}
132+
}
204 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)