1/*
2 * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/**
25 * @test
26 * @bug 4759491 6303183 7012868 8015666 8023713 8068790 8076641 8075526 8130914 8161942
27 * @summary Test ZOS and ZIS timestamp in extra field correctly
28 */
29
30import java.io.*;
31import java.nio.file.Files;
32import java.nio.file.Path;
33import java.nio.file.Paths;
34import java.nio.file.attribute.BasicFileAttributeView;
35import java.nio.file.attribute.FileOwnerAttributeView;
36import java.nio.file.attribute.FileTime;
37import java.nio.file.attribute.PosixFilePermission;
38import java.time.Instant;
39import java.util.Arrays;
40import java.util.Set;
41import java.util.TimeZone;
42import java.util.concurrent.TimeUnit;
43import java.util.zip.ZipEntry;
44import java.util.zip.ZipFile;
45import java.util.zip.ZipInputStream;
46import java.util.zip.ZipOutputStream;
47
48
49
50public class TestExtraTime {
51
52    public static void main(String[] args) throws Throwable{
53
54        File src = new File(System.getProperty("test.src", "."), "TestExtraTime.java");
55        if (!src.exists()) {
56            return;
57        }
58
59        TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");
60
61        for (byte[] extra : new byte[][] { null, new byte[] {1, 2, 3}}) {
62
63            // ms-dos 1980 epoch problem
64            test0(FileTime.from(10, TimeUnit.MILLISECONDS), null, null, null, extra);
65            // negative epoch time
66            test0(FileTime.from(-100, TimeUnit.DAYS), null, null, null, extra);
67
68            long time = src.lastModified();
69            test(FileTime.from(time, TimeUnit.MILLISECONDS),
70                 FileTime.from(time + 300000, TimeUnit.MILLISECONDS),
71                 FileTime.from(time - 300000, TimeUnit.MILLISECONDS),
72                 tz, extra);
73
74            // now
75            time = Instant.now().toEpochMilli();
76            test(FileTime.from(time, TimeUnit.MILLISECONDS),
77                 FileTime.from(time + 300000, TimeUnit.MILLISECONDS),
78                 FileTime.from(time - 300000, TimeUnit.MILLISECONDS),
79                 tz, extra);
80
81            // unix 2038
82            time = 0x80000000L;
83            test(FileTime.from(time, TimeUnit.SECONDS),
84                 FileTime.from(time, TimeUnit.SECONDS),
85                 FileTime.from(time, TimeUnit.SECONDS),
86                 tz, extra);
87
88            // mtime < unix 2038
89            time = 0x7fffffffL;
90            test(FileTime.from(time, TimeUnit.SECONDS),
91                 FileTime.from(time + 30000, TimeUnit.SECONDS),
92                 FileTime.from(time + 30000, TimeUnit.SECONDS),
93                 tz, extra);
94        }
95
96        testNullHandling();
97        testTagOnlyHandling();
98        testTimeConversions();
99    }
100
101    static void test(FileTime mtime, FileTime atime, FileTime ctime,
102                     TimeZone tz, byte[] extra) throws Throwable {
103        test0(mtime, null, null, null, extra);
104        test0(mtime, null, null, tz, extra);    // non-default tz
105        test0(mtime, atime, null, null, extra);
106        test0(mtime, null, ctime, null, extra);
107        test0(mtime, atime, ctime, null, extra);
108        test0(mtime, atime, null, tz, extra);
109        test0(mtime, null, ctime, tz, extra);
110        test0(mtime, atime, ctime, tz, extra);
111    }
112
113    static void test0(FileTime mtime, FileTime atime, FileTime ctime,
114                     TimeZone tz, byte[] extra) throws Throwable {
115        System.out.printf("--------------------%nTesting: [%s]/[%s]/[%s]%n",
116                          mtime, atime, ctime);
117        TimeZone tz0 = TimeZone.getDefault();
118        if (tz != null) {
119            TimeZone.setDefault(tz);
120        }
121        ByteArrayOutputStream baos = new ByteArrayOutputStream();
122        ZipOutputStream zos = new ZipOutputStream(baos);
123        ZipEntry ze = new ZipEntry("TestExtraTime.java");
124        ze.setExtra(extra);
125        ze.setLastModifiedTime(mtime);
126        if (atime != null)
127            ze.setLastAccessTime(atime);
128        if (ctime != null)
129            ze.setCreationTime(ctime);
130        zos.putNextEntry(ze);
131        zos.write(new byte[] { 1,2 ,3, 4});
132
133        // append an extra entry to help check if the length and data
134        // of the extra field are being correctly written (in previous
135        // entry).
136        if (extra != null) {
137            ze = new ZipEntry("TestExtraEntry");
138            zos.putNextEntry(ze);
139        }
140        zos.close();
141        if (tz != null) {
142            TimeZone.setDefault(tz0);
143        }
144        // ZipInputStream
145        ZipInputStream zis = new ZipInputStream(
146                                 new ByteArrayInputStream(baos.toByteArray()));
147        ze = zis.getNextEntry();
148        zis.close();
149        check(mtime, atime, ctime, ze, extra);
150
151        // ZipFile
152        Path zpath = Paths.get(System.getProperty("test.dir", "."),
153                               "TestExtraTime.zip");
154        Path zparent = zpath.getParent();
155        if (zparent != null && !Files.isWritable(zparent)) {
156            System.err.format("zpath %s parent %s is not writable%n",
157                zpath, zparent);
158        }
159        if (Files.exists(zpath)) {
160            System.err.format("zpath %s already exists%n", zpath);
161            if (Files.isDirectory(zpath)) {
162                System.err.format("%n%s contents:%n", zpath);
163                Files.list(zpath).forEach(System.err::println);
164            }
165            FileOwnerAttributeView foav = Files.getFileAttributeView(zpath,
166                FileOwnerAttributeView.class);
167            System.err.format("zpath %s owner: %s%n", zpath, foav.getOwner());
168            System.err.format("zpath %s permissions:%n", zpath);
169            Set<PosixFilePermission> perms =
170                Files.getPosixFilePermissions(zpath);
171            perms.stream().forEach(System.err::println);
172        }
173        if (Files.isSymbolicLink(zpath)) {
174            System.err.format("zpath %s is a symbolic link%n", zpath);
175        }
176        Files.copy(new ByteArrayInputStream(baos.toByteArray()), zpath);
177        try (ZipFile zf = new ZipFile(zpath.toFile())) {
178            ze = zf.getEntry("TestExtraTime.java");
179            // ZipFile read entry from cen, which does not have a/ctime,
180            // for now.
181            check(mtime, null, null, ze, extra);
182        } finally {
183            Files.delete(zpath);
184        }
185    }
186
187    static void check(FileTime mtime, FileTime atime, FileTime ctime,
188                      ZipEntry ze, byte[] extra) {
189        /*
190        System.out.printf("    mtime [%tc]: [%tc]/[%tc]%n",
191                          mtime.to(TimeUnit.MILLISECONDS),
192                          ze.getTime(),
193                          ze.getLastModifiedTime().to(TimeUnit.MILLISECONDS));
194         */
195        if (mtime.to(TimeUnit.SECONDS) !=
196            ze.getLastModifiedTime().to(TimeUnit.SECONDS))
197            throw new RuntimeException("Timestamp: storing mtime failed!");
198        if (atime != null &&
199            atime.to(TimeUnit.SECONDS) !=
200            ze.getLastAccessTime().to(TimeUnit.SECONDS))
201            throw new RuntimeException("Timestamp: storing atime failed!");
202        if (ctime != null &&
203            ctime.to(TimeUnit.SECONDS) !=
204            ze.getCreationTime().to(TimeUnit.SECONDS))
205            throw new RuntimeException("Timestamp: storing ctime failed!");
206        if (extra != null) {
207            // if extra data exists, the current implementation put it at
208            // the end of the extra data array (implementation detail)
209            byte[] extra1 = ze.getExtra();
210            if (extra1 == null || extra1.length < extra.length ||
211                !Arrays.equals(Arrays.copyOfRange(extra1,
212                                                  extra1.length - extra.length,
213                                                  extra1.length),
214                               extra)) {
215                throw new RuntimeException("Timestamp: storing extra field failed!");
216            }
217        }
218    }
219
220    static void testNullHandling() {
221        ZipEntry ze = new ZipEntry("TestExtraTime.java");
222        try {
223            ze.setLastAccessTime(null);
224            throw new RuntimeException("setLastAccessTime(null) should throw NPE");
225        } catch (NullPointerException ignored) {
226            // pass
227        }
228        try {
229            ze.setCreationTime(null);
230            throw new RuntimeException("setCreationTime(null) should throw NPE");
231        } catch (NullPointerException ignored) {
232            // pass
233        }
234        try {
235            ze.setLastModifiedTime(null);
236            throw new RuntimeException("setLastModifiedTime(null) should throw NPE");
237        } catch (NullPointerException ignored) {
238            // pass
239        }
240    }
241
242    // verify that setting and getting any time is possible as per the intent
243    // of 4759491
244    static void testTimeConversions() {
245        // Sample across the entire range
246        long step = Long.MAX_VALUE / 100L;
247        testTimeConversions(Long.MIN_VALUE, Long.MAX_VALUE - step, step);
248
249        // Samples through the near future
250        long currentTime = System.currentTimeMillis();
251        testTimeConversions(currentTime, currentTime + 1_000_000, 10_000);
252    }
253
254    static void testTimeConversions(long from, long to, long step) {
255        ZipEntry ze = new ZipEntry("TestExtraTime.java");
256        for (long time = from; time <= to; time += step) {
257            ze.setTime(time);
258            FileTime lastModifiedTime = ze.getLastModifiedTime();
259            if (lastModifiedTime.toMillis() != time) {
260                throw new RuntimeException("setTime should make getLastModifiedTime " +
261                        "return the specified instant: " + time +
262                        " got: " + lastModifiedTime.toMillis());
263            }
264            if (ze.getTime() != time) {
265                throw new RuntimeException("getTime after setTime, expected: " +
266                        time + " got: " + ze.getTime());
267            }
268        }
269    }
270
271    static void check(ZipEntry ze, byte[] extra) {
272        if (extra != null) {
273            byte[] extra1 = ze.getExtra();
274            if (extra1 == null || extra1.length < extra.length ||
275                !Arrays.equals(Arrays.copyOfRange(extra1,
276                                                  extra1.length - extra.length,
277                                                  extra1.length),
278                               extra)) {
279                throw new RuntimeException("Timestamp: storing extra field failed!");
280            }
281        }
282    }
283
284    static void testTagOnlyHandling() throws Throwable {
285        ByteArrayOutputStream baos = new ByteArrayOutputStream();
286        byte[] extra = new byte[] { 0x0a, 0, 4, 0, 0, 0, 0, 0 };
287        try (ZipOutputStream zos = new ZipOutputStream(baos)) {
288            ZipEntry ze = new ZipEntry("TestExtraTime.java");
289            ze.setExtra(extra);
290            zos.putNextEntry(ze);
291            zos.write(new byte[] { 1,2 ,3, 4});
292        }
293        try (ZipInputStream zis = new ZipInputStream(
294                 new ByteArrayInputStream(baos.toByteArray()))) {
295            ZipEntry ze = zis.getNextEntry();
296            check(ze, extra);
297        }
298        Path zpath = Paths.get(System.getProperty("test.dir", "."),
299                               "TestExtraTime.zip");
300        Files.copy(new ByteArrayInputStream(baos.toByteArray()), zpath);
301        try (ZipFile zf = new ZipFile(zpath.toFile())) {
302            ZipEntry ze = zf.getEntry("TestExtraTime.java");
303            check(ze, extra);
304        } finally {
305            Files.delete(zpath);
306        }
307    }
308}
309