26 package jdk.nio.zipfs;
27
28 import java.io.BufferedOutputStream;
29 import java.io.ByteArrayInputStream;
30 import java.io.ByteArrayOutputStream;
31 import java.io.EOFException;
32 import java.io.FilterOutputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.io.OutputStream;
36 import java.nio.ByteBuffer;
37 import java.nio.MappedByteBuffer;
38 import java.nio.channels.FileChannel;
39 import java.nio.channels.FileLock;
40 import java.nio.channels.ReadableByteChannel;
41 import java.nio.channels.SeekableByteChannel;
42 import java.nio.channels.WritableByteChannel;
43 import java.nio.file.*;
44 import java.nio.file.attribute.FileAttribute;
45 import java.nio.file.attribute.FileTime;
46 import java.nio.file.attribute.UserPrincipalLookupService;
47 import java.nio.file.spi.FileSystemProvider;
48 import java.security.AccessController;
49 import java.security.PrivilegedAction;
50 import java.security.PrivilegedActionException;
51 import java.security.PrivilegedExceptionAction;
52 import java.util.*;
53 import java.util.concurrent.locks.ReadWriteLock;
54 import java.util.concurrent.locks.ReentrantReadWriteLock;
55 import java.util.regex.Pattern;
56 import java.util.zip.CRC32;
57 import java.util.zip.Deflater;
58 import java.util.zip.DeflaterOutputStream;
59 import java.util.zip.Inflater;
60 import java.util.zip.InflaterInputStream;
61 import java.util.zip.ZipException;
62
63 import static java.lang.Boolean.TRUE;
64 import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
65 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
66 import static java.nio.file.StandardOpenOption.APPEND;
67 import static java.nio.file.StandardOpenOption.CREATE;
68 import static java.nio.file.StandardOpenOption.CREATE_NEW;
69 import static java.nio.file.StandardOpenOption.READ;
70 import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
71 import static java.nio.file.StandardOpenOption.WRITE;
72 import static jdk.nio.zipfs.ZipConstants.*;
73 import static jdk.nio.zipfs.ZipUtils.*;
74
75 /**
76 * A FileSystem built on a zip file
77 *
78 * @author Xueming Shen
79 */
80 class ZipFileSystem extends FileSystem {
81 private final ZipFileSystemProvider provider;
82 private final Path zfpath;
83 final ZipCoder zc;
84 private final ZipPath rootdir;
85 private boolean readOnly = false; // readonly file system
86
87 // configurable by env map
88 private final boolean noExtt; // see readExtra()
89 private final boolean useTempFile; // use a temp file for newOS, default
90 // is to use BAOS for better performance
91 private static final boolean isWindows = AccessController.doPrivileged(
92 (PrivilegedAction<Boolean>)() -> System.getProperty("os.name")
93 .startsWith("Windows"));
94 private final boolean forceEnd64;
95 private final int defaultMethod; // METHOD_STORED if "noCompression=true"
96 // METHOD_DEFLATED otherwise
97
98 ZipFileSystem(ZipFileSystemProvider provider,
99 Path zfpath,
100 Map<String, ?> env) throws IOException
101 {
102 // default encoding for name/comment
103 String nameEncoding = env.containsKey("encoding") ?
104 (String)env.get("encoding") : "UTF-8";
105 this.noExtt = "false".equals(env.get("zipinfo-time"));
106 this.useTempFile = isTrue(env, "useTempFile");
107 this.forceEnd64 = isTrue(env, "forceZIP64End");
108 this.defaultMethod = isTrue(env, "noCompression") ? METHOD_STORED: METHOD_DEFLATED;
109 if (Files.notExists(zfpath)) {
110 // create a new zip if not exists
111 if (isTrue(env, "create")) {
112 try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
113 new END().write(os, 0, forceEnd64);
114 }
115 } else {
116 throw new FileSystemNotFoundException(zfpath.toString());
117 }
118 }
119 // sm and existence check
120 zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
121 boolean writeable = AccessController.doPrivileged(
122 (PrivilegedAction<Boolean>) () -> Files.isWritable(zfpath));
123 this.readOnly = !writeable;
124 this.zc = ZipCoder.get(nameEncoding);
125 this.rootdir = new ZipPath(this, new byte[]{'/'});
126 this.ch = Files.newByteChannel(zfpath, READ);
127 try {
128 this.cen = initCEN();
129 } catch (IOException x) {
130 try {
131 this.ch.close();
132 } catch (IOException xx) {
133 x.addSuppressed(xx);
134 }
135 throw x;
136 }
137 this.provider = provider;
138 this.zfpath = zfpath;
139 }
140
141 // returns true if there is a name=true/"true" setting in env
142 private static boolean isTrue(Map<String, ?> env, String name) {
143 return "true".equals(env.get(name)) || TRUE.equals(env.get(name));
144 }
145
146 @Override
147 public FileSystemProvider provider() {
148 return provider;
149 }
150
151 @Override
152 public String getSeparator() {
153 return "/";
154 }
155
156 @Override
157 public boolean isOpen() {
158 return isOpen;
159 }
160
161 @Override
162 public boolean isReadOnly() {
163 return readOnly;
164 }
165
201
202 @Override
203 public UserPrincipalLookupService getUserPrincipalLookupService() {
204 throw new UnsupportedOperationException();
205 }
206
207 @Override
208 public WatchService newWatchService() {
209 throw new UnsupportedOperationException();
210 }
211
212 FileStore getFileStore(ZipPath path) {
213 return new ZipFileStore(path);
214 }
215
216 @Override
217 public Iterable<FileStore> getFileStores() {
218 return List.of(new ZipFileStore(rootdir));
219 }
220
221 private static final Set<String> supportedFileAttributeViews =
222 Set.of("basic", "zip");
223
224 @Override
225 public Set<String> supportedFileAttributeViews() {
226 return supportedFileAttributeViews;
227 }
228
229 @Override
230 public String toString() {
231 return zfpath.toString();
232 }
233
234 Path getZipFile() {
235 return zfpath;
236 }
237
238 private static final String GLOB_SYNTAX = "glob";
239 private static final String REGEX_SYNTAX = "regex";
240
241 @Override
242 public PathMatcher getPathMatcher(String syntaxAndInput) {
243 int pos = syntaxAndInput.indexOf(':');
354 if (getInode(path) == null) {
355 throw new NoSuchFileException(toString());
356 }
357
358 } finally {
359 endRead();
360 }
361 }
362
363 void setTimes(byte[] path, FileTime mtime, FileTime atime, FileTime ctime)
364 throws IOException
365 {
366 checkWritable();
367 beginWrite();
368 try {
369 ensureOpen();
370 Entry e = getEntry(path); // ensureOpen checked
371 if (e == null)
372 throw new NoSuchFileException(getString(path));
373 if (e.type == Entry.CEN)
374 e.type = Entry.COPY; // copy e
375 if (mtime != null)
376 e.mtime = mtime.toMillis();
377 if (atime != null)
378 e.atime = atime.toMillis();
379 if (ctime != null)
380 e.ctime = ctime.toMillis();
381 update(e);
382 } finally {
383 endWrite();
384 }
385 }
386
387 boolean exists(byte[] path)
388 throws IOException
389 {
390 beginRead();
391 try {
392 ensureOpen();
393 return getInode(path) != null;
394 } finally {
395 endRead();
396 }
397 }
398
399 boolean isDirectory(byte[] path)
400 throws IOException
401 {
402 beginRead();
403 try {
404 IndexNode n = getInode(path);
405 return n != null && n.isDir();
406 } finally {
436 list.add(zpath);
437 child = child.sibling;
438 }
439 return list.iterator();
440 } finally {
441 endWrite();
442 }
443 }
444
445 void createDirectory(byte[] dir, FileAttribute<?>... attrs)
446 throws IOException
447 {
448 checkWritable();
449 // dir = toDirectoryPath(dir);
450 beginWrite();
451 try {
452 ensureOpen();
453 if (dir.length == 0 || exists(dir)) // root dir, or exiting dir
454 throw new FileAlreadyExistsException(getString(dir));
455 checkParents(dir);
456 Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED);
457 update(e);
458 } finally {
459 endWrite();
460 }
461 }
462
463 void copyFile(boolean deletesrc, byte[]src, byte[] dst, CopyOption... options)
464 throws IOException
465 {
466 checkWritable();
467 if (Arrays.equals(src, dst))
468 return; // do nothing, src and dst are the same
469
470 beginWrite();
471 try {
472 ensureOpen();
473 Entry eSrc = getEntry(src); // ensureOpen checked
474
475 if (eSrc == null)
476 throw new NoSuchFileException(getString(src));
617 // store size, compressed size, and crc-32 in datadescriptor
618 e.flag = FLAG_DATADESCR;
619 if (zc.isUTF8())
620 e.flag |= FLAG_USE_UTF8;
621 }
622
623 @Override
624 public void close() throws IOException {
625 e.bytes = toByteArray();
626 e.size = e.bytes.length;
627 e.crc = -1;
628 super.close();
629 update(e);
630 }
631 }
632
633 private int getCompressMethod(FileAttribute<?>... attrs) {
634 return defaultMethod;
635 }
636
637 // Returns a Writable/ReadByteChannel for now. Might consdier to use
638 // newFileChannel() instead, which dump the entry data into a regular
639 // file on the default file system and create a FileChannel on top of
640 // it.
641 SeekableByteChannel newByteChannel(byte[] path,
642 Set<? extends OpenOption> options,
643 FileAttribute<?>... attrs)
644 throws IOException
645 {
646 checkOptions(options);
647 if (options.contains(StandardOpenOption.WRITE) ||
648 options.contains(StandardOpenOption.APPEND)) {
649 checkWritable();
650 beginRead(); // only need a readlock, the "update()" will obtain
651 // thewritelock when the channel is closed
652 try {
653 ensureOpen();
654 Entry e = getEntry(path);
655 if (e != null) {
656 if (e.isDir() || options.contains(CREATE_NEW))
657 throw new FileAlreadyExistsException(getString(path));
658 SeekableByteChannel sbc =
659 new EntryOutputChannel(new Entry(e, Entry.NEW));
660 if (options.contains(APPEND)) {
661 try (InputStream is = getInputStream(e)) { // copyover
662 byte[] buf = new byte[8192];
663 ByteBuffer bb = ByteBuffer.wrap(buf);
664 int n;
665 while ((n = is.read(buf)) != -1) {
666 bb.position(0);
667 bb.limit(n);
668 sbc.write(bb);
669 }
670 }
671 }
672 return sbc;
673 }
674 if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
675 throw new NoSuchFileException(getString(path));
676 checkParents(path);
677 return new EntryOutputChannel(
678 new Entry(path, Entry.NEW, false, getCompressMethod(attrs)));
679
680 } finally {
681 endRead();
682 }
683 } else {
684 beginRead();
685 try {
686 ensureOpen();
687 Entry e = getEntry(path);
688 if (e == null || e.isDir())
689 throw new NoSuchFileException(getString(path));
690 try (InputStream is = getInputStream(e)) {
691 // TBD: if (e.size < NNNNN);
692 return new ByteArrayChannel(is.readAllBytes(), true);
693 }
694 } finally {
695 endRead();
696 }
697 }
698 }
723 }
724 } else {
725 if (options.contains(StandardOpenOption.CREATE_NEW)) {
726 throw new FileAlreadyExistsException(getString(path));
727 }
728 if (e.isDir())
729 throw new FileAlreadyExistsException("directory <"
730 + getString(path) + "> exists");
731 }
732 options = new HashSet<>(options);
733 options.remove(StandardOpenOption.CREATE_NEW); // for tmpfile
734 } else if (e == null || e.isDir()) {
735 throw new NoSuchFileException(getString(path));
736 }
737
738 final boolean isFCH = (e != null && e.type == Entry.FILECH);
739 final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
740 final FileChannel fch = tmpfile.getFileSystem()
741 .provider()
742 .newFileChannel(tmpfile, options, attrs);
743 final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH);
744 if (forWrite) {
745 u.flag = FLAG_DATADESCR;
746 u.method = getCompressMethod(attrs);
747 }
748 // is there a better way to hook into the FileChannel's close method?
749 return new FileChannel() {
750 public int write(ByteBuffer src) throws IOException {
751 return fch.write(src);
752 }
753 public long write(ByteBuffer[] srcs, int offset, int length)
754 throws IOException
755 {
756 return fch.write(srcs, offset, length);
757 }
758 public long position() throws IOException {
759 return fch.position();
760 }
761 public FileChannel position(long newPosition)
762 throws IOException
763 {
1252 // entry copy: the only thing changed is the "name"
1253 // and "nlen" in LOC header, so we udpate/rewrite the
1254 // LOC in new file and simply copy the rest (data and
1255 // ext) without enflating/deflating from the old zip
1256 // file LOC entry.
1257 written += copyLOCEntry(e, true, os, written, buf);
1258 } else { // NEW, FILECH or CEN
1259 e.locoff = written;
1260 written += e.writeLOC(os); // write loc header
1261 written += writeEntry(e, os, buf);
1262 }
1263 elist.add(e);
1264 } catch (IOException x) {
1265 x.printStackTrace(); // skip any in-accurate entry
1266 }
1267 } else { // unchanged inode
1268 if (inode.pos == -1) {
1269 continue; // pseudo directory node
1270 }
1271 if (inode.name.length == 1 && inode.name[0] == '/') {
1272 continue; // no root '/' directory even it
1273 // exits in original zip/jar file.
1274 }
1275 e = Entry.readCEN(this, inode);
1276 try {
1277 written += copyLOCEntry(e, false, os, written, buf);
1278 elist.add(e);
1279 } catch (IOException x) {
1280 x.printStackTrace(); // skip any wrong entry
1281 }
1282 }
1283 }
1284
1285 // now write back the cen and end table
1286 end.cenoff = written;
1287 for (Entry entry : elist) {
1288 written += entry.writeCEN(os);
1289 }
1290 end.centot = elist.size();
1291 end.cenlen = written - end.cenoff;
1292 end.write(os, written, forceEnd64);
1293 }
1294
1295 ch.close();
1296 Files.delete(zfpath);
1297 Files.move(tmpFile, zfpath, REPLACE_EXISTING);
1298 hasUpdate = false; // clear
1299 }
1300
1301 IndexNode getInode(byte[] path) {
1302 if (path == null)
1303 throw new NullPointerException("path");
1304 return inodes.get(IndexNode.keyOf(path));
1305 }
1306
1307 Entry getEntry(byte[] path) throws IOException {
1308 IndexNode inode = getInode(path);
1309 if (inode instanceof Entry)
1310 return (Entry)inode;
1311 if (inode == null || inode.pos == -1)
1312 return null;
1313 return Entry.readCEN(this, inode);
1314 }
1315
1316 public void deleteFile(byte[] path, boolean failIfNotExists)
1317 throws IOException
1318 {
1319 checkWritable();
1320
1321 IndexNode inode = getInode(path);
1322 if (inode == null) {
1323 if (path != null && path.length == 0)
1324 throw new ZipException("root directory </> can't not be delete");
1325 if (failIfNotExists)
1326 throw new NoSuchFileException(getString(path));
1327 } else {
1328 if (inode.isDir() && inode.child != null)
1329 throw new DirectoryNotEmptyException(getString(path));
1330 updateDelete(inode);
1331 }
1332 }
1333
1761 writeShort(os, 0); // central directory start disk
1762 writeShort(os, count); // number of directory entries on disk
1763 writeShort(os, count); // total number of directory entries
1764 writeInt(os, xlen); // length of central directory
1765 writeInt(os, xoff); // offset of central directory
1766 if (comment != null) { // zip file comment
1767 writeShort(os, comment.length);
1768 writeBytes(os, comment);
1769 } else {
1770 writeShort(os, 0);
1771 }
1772 }
1773 }
1774
1775 // Internal node that links a "name" to its pos in cen table.
1776 // The node itself can be used as a "key" to lookup itself in
1777 // the HashMap inodes.
1778 static class IndexNode {
1779 byte[] name;
1780 int hashcode; // node is hashable/hashed by its name
1781 int pos = -1; // position in cen table, -1 menas the
1782 // entry does not exists in zip file
1783 boolean isdir;
1784
1785 IndexNode(byte[] name, boolean isdir) {
1786 name(name);
1787 this.isdir = isdir;
1788 this.pos = -1;
1789 }
1790
1791 IndexNode(byte[] name, int pos) {
1792 name(name);
1793 this.pos = pos;
1794 }
1795
1796 // constructor for cenInit() (1) remove tailing '/' (2) pad leading '/'
1797 IndexNode(byte[] cen, int pos, int nlen) {
1798 int noff = pos + CENHDR;
1799 if (cen[noff + nlen - 1] == '/') {
1800 isdir = true;
1801 nlen--;
1802 }
1838
1839 public boolean equals(Object other) {
1840 if (!(other instanceof IndexNode)) {
1841 return false;
1842 }
1843 if (other instanceof ParentLookup) {
1844 return ((ParentLookup)other).equals(this);
1845 }
1846 return Arrays.equals(name, ((IndexNode)other).name);
1847 }
1848
1849 public int hashCode() {
1850 return hashcode;
1851 }
1852
1853 IndexNode() {}
1854 IndexNode sibling;
1855 IndexNode child; // 1st child
1856 }
1857
1858 static class Entry extends IndexNode implements ZipFileAttributes {
1859
1860 static final int CEN = 1; // entry read from cen
1861 static final int NEW = 2; // updated contents in bytes or file
1862 static final int FILECH = 3; // fch update in "file"
1863 static final int COPY = 4; // copy of a CEN entry
1864
1865 byte[] bytes; // updated content bytes
1866 Path file; // use tmp file to store bytes;
1867 int type = CEN; // default is the entry read from cen
1868
1869 // entry attributes
1870 int version;
1871 int flag;
1872 int method = -1; // compression method
1873 long mtime = -1; // last modification time (in DOS time)
1874 long atime = -1; // last access time
1875 long ctime = -1; // create time
1876 long crc = -1; // crc-32 of entry data
1877 long csize = -1; // compressed size of entry data
1878 long size = -1; // uncompressed size of entry data
1879 byte[] extra;
1880
1881 // cen
1882
1883 // these fields are not used by anyone and writeCEN uses "0"
1884 // int versionMade;
1885 // int disk;
1886 // int attrs;
1887 // long attrsEx;
1888 long locoff;
1889 byte[] comment;
1890
1891 Entry() {}
1892
1893 Entry(byte[] name, boolean isdir, int method) {
1894 name(name);
1895 this.isdir = isdir;
1896 this.mtime = this.ctime = this.atime = System.currentTimeMillis();
1897 this.crc = 0;
1898 this.size = 0;
1899 this.csize = 0;
1900 this.method = method;
1901 }
1902
1903 Entry(byte[] name, int type, boolean isdir, int method) {
1904 this(name, isdir, method);
1905 this.type = type;
1906 }
1907
1908 Entry (Entry e, int type) {
1909 name(e.name);
1910 this.isdir = e.isdir;
1911 this.version = e.version;
1912 this.ctime = e.ctime;
1913 this.atime = e.atime;
1914 this.mtime = e.mtime;
1915 this.crc = e.crc;
1916 this.size = e.size;
1917 this.csize = e.csize;
1918 this.method = e.method;
1919 this.extra = e.extra;
1920 /*
1921 this.versionMade = e.versionMade;
1922 this.disk = e.disk;
1923 this.attrs = e.attrs;
1924 this.attrsEx = e.attrsEx;
1925 */
1926 this.locoff = e.locoff;
1927 this.comment = e.comment;
1928 this.type = type;
1929 }
1930
1931 Entry (byte[] name, Path file, int type) {
1932 this(name, type, false, METHOD_STORED);
1933 this.file = file;
1934 }
1935
1936 int version() throws ZipException {
1937 if (method == METHOD_DEFLATED)
1938 return 20;
1939 else if (method == METHOD_STORED)
1940 return 10;
1941 throw new ZipException("unsupported compression method");
1942 }
1943
1944 ///////////////////// CEN //////////////////////
1945 static Entry readCEN(ZipFileSystem zipfs, IndexNode inode)
1946 throws IOException
1947 {
1948 return new Entry().cen(zipfs, inode);
1949 }
1950
1951 private Entry cen(ZipFileSystem zipfs, IndexNode inode)
1952 throws IOException
1953 {
1954 byte[] cen = zipfs.cen;
1955 int pos = inode.pos;
1956 if (!cenSigAt(cen, pos))
1957 zerror("invalid CEN header (bad signature)");
1958 version = CENVER(cen, pos);
1959 flag = CENFLG(cen, pos);
1960 method = CENHOW(cen, pos);
1961 mtime = dosToJavaTime(CENTIM(cen, pos));
1962 crc = CENCRC(cen, pos);
1963 csize = CENSIZ(cen, pos);
1964 size = CENLEN(cen, pos);
1965 int nlen = CENNAM(cen, pos);
1966 int elen = CENEXT(cen, pos);
1967 int clen = CENCOM(cen, pos);
1968 /*
1969 versionMade = CENVEM(cen, pos);
1970 disk = CENDSK(cen, pos);
1971 attrs = CENATT(cen, pos);
1972 attrsEx = CENATX(cen, pos);
1973 */
1974 locoff = CENOFF(cen, pos);
1975 pos += CENHDR;
1976 this.name = inode.name;
1977 this.isdir = inode.isdir;
1978 this.hashcode = inode.hashcode;
1979
1980 pos += nlen;
1981 if (elen > 0) {
1982 extra = Arrays.copyOfRange(cen, pos, pos + elen);
1983 pos += elen;
1984 readExtra(zipfs);
1985 }
1986 if (clen > 0) {
1987 comment = Arrays.copyOfRange(cen, pos, pos + clen);
1988 }
1989 return this;
1990 }
1991
1992 int writeCEN(OutputStream os) throws IOException {
1993 int version0 = version();
1994 long csize0 = csize;
1995 long size0 = size;
1996 long locoff0 = locoff;
1997 int elen64 = 0; // extra for ZIP64
1998 int elenNTFS = 0; // extra for NTFS (a/c/mtime)
1999 int elenEXTT = 0; // extra for Extended Timestamp
2000 boolean foundExtraTime = false; // if time stamp NTFS, EXTT present
2001
2002 byte[] zname = isdir ? toDirectoryPath(name) : name;
2003
2004 // confirm size/length
2005 int nlen = (zname != null) ? zname.length - 1 : 0; // name has [0] as "slash"
2006 int elen = (extra != null) ? extra.length : 0;
2007 int eoff = 0;
2008 int clen = (comment != null) ? comment.length : 0;
2009 if (csize >= ZIP64_MINVAL) {
2010 csize0 = ZIP64_MINVAL;
2011 elen64 += 8; // csize(8)
2012 }
2013 if (size >= ZIP64_MINVAL) {
2014 size0 = ZIP64_MINVAL; // size(8)
2015 elen64 += 8;
2016 }
2017 if (locoff >= ZIP64_MINVAL) {
2018 locoff0 = ZIP64_MINVAL;
2019 elen64 += 8; // offset(8)
2020 }
2021 if (elen64 != 0) {
2022 elen64 += 4; // header and data sz 4 bytes
2023 }
2024 while (eoff + 4 < elen) {
2025 int tag = SH(extra, eoff);
2026 int sz = SH(extra, eoff + 2);
2027 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2028 foundExtraTime = true;
2029 }
2030 eoff += (4 + sz);
2031 }
2032 if (!foundExtraTime) {
2033 if (isWindows) { // use NTFS
2034 elenNTFS = 36; // total 36 bytes
2035 } else { // Extended Timestamp otherwise
2036 elenEXTT = 9; // only mtime in cen
2037 }
2038 }
2039 writeInt(os, CENSIG); // CEN header signature
2040 if (elen64 != 0) {
2041 writeShort(os, 45); // ver 4.5 for zip64
2042 writeShort(os, 45);
2043 } else {
2044 writeShort(os, version0); // version made by
2045 writeShort(os, version0); // version needed to extract
2046 }
2047 writeShort(os, flag); // general purpose bit flag
2048 writeShort(os, method); // compression method
2049 // last modification time
2050 writeInt(os, (int)javaToDosTime(mtime));
2051 writeInt(os, crc); // crc-32
2052 writeInt(os, csize0); // compressed size
2053 writeInt(os, size0); // uncompressed size
2054 writeShort(os, nlen);
2055 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2056
2057 if (comment != null) {
2058 writeShort(os, Math.min(clen, 0xffff));
2059 } else {
2060 writeShort(os, 0);
2061 }
2062 writeShort(os, 0); // starting disk number
2063 writeShort(os, 0); // internal file attributes (unused)
2064 writeInt(os, 0); // external file attributes (unused)
2065 writeInt(os, locoff0); // relative offset of local header
2066 writeBytes(os, zname, 1, nlen);
2067 if (elen64 != 0) {
2068 writeShort(os, EXTID_ZIP64);// Zip64 extra
2069 writeShort(os, elen64 - 4); // size of "this" extra block
2070 if (size0 == ZIP64_MINVAL)
2071 writeLong(os, size);
2072 if (csize0 == ZIP64_MINVAL)
2073 writeLong(os, csize);
2074 if (locoff0 == ZIP64_MINVAL)
2075 writeLong(os, locoff);
2076 }
2077 if (elenNTFS != 0) {
2078 writeShort(os, EXTID_NTFS);
2079 writeShort(os, elenNTFS - 4);
2080 writeInt(os, 0); // reserved
2081 writeShort(os, 0x0001); // NTFS attr tag
2082 writeShort(os, 24);
2083 writeLong(os, javaToWinTime(mtime));
2084 writeLong(os, javaToWinTime(atime));
2085 writeLong(os, javaToWinTime(ctime));
2086 }
2087 if (elenEXTT != 0) {
2088 writeShort(os, EXTID_EXTT);
2089 writeShort(os, elenEXTT - 4);
2090 if (ctime == -1)
2091 os.write(0x3); // mtime and atime
2092 else
2093 os.write(0x7); // mtime, atime and ctime
2094 writeInt(os, javaToUnixTime(mtime));
2095 }
2096 if (extra != null) // whatever not recognized
2097 writeBytes(os, extra);
2098 if (comment != null) //TBD: 0, Math.min(commentBytes.length, 0xffff));
2099 writeBytes(os, comment);
2100 return CENHDR + nlen + elen + clen + elen64 + elenNTFS + elenEXTT;
2101 }
2102
2103 ///////////////////// LOC //////////////////////
2104
2105 int writeLOC(OutputStream os) throws IOException {
2106 int version0 = version();
2107 byte[] zname = isdir ? toDirectoryPath(name) : name;
2108 int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
2109 int elen = (extra != null) ? extra.length : 0;
2110 boolean foundExtraTime = false; // if extra timestamp present
2111 int eoff = 0;
2112 int elen64 = 0;
2113 int elenEXTT = 0;
2114 int elenNTFS = 0;
2115 writeInt(os, LOCSIG); // LOC header signature
2116 if ((flag & FLAG_DATADESCR) != 0) {
2117 writeShort(os, version0); // version needed to extract
2118 writeShort(os, flag); // general purpose bit flag
2119 writeShort(os, method); // compression method
2120 // last modification time
2121 writeInt(os, (int)javaToDosTime(mtime));
2122 // store size, uncompressed size, and crc-32 in data descriptor
2123 // immediately following compressed entry data
2124 writeInt(os, 0);
2125 writeInt(os, 0);
2126 writeInt(os, 0);
2127 } else {
2128 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2129 elen64 = 20; //headid(2) + size(2) + size(8) + csize(8)
2130 writeShort(os, 45); // ver 4.5 for zip64
2131 } else {
2132 writeShort(os, version0); // version needed to extract
2133 }
2134 writeShort(os, flag); // general purpose bit flag
2135 writeShort(os, method); // compression method
2136 // last modification time
2137 writeInt(os, (int)javaToDosTime(mtime));
2138 writeInt(os, crc); // crc-32
2139 if (elen64 != 0) {
2140 writeInt(os, ZIP64_MINVAL);
2141 writeInt(os, ZIP64_MINVAL);
2142 } else {
2143 writeInt(os, csize); // compressed size
2144 writeInt(os, size); // uncompressed size
2145 }
2146 }
2147 while (eoff + 4 < elen) {
2148 int tag = SH(extra, eoff);
2149 int sz = SH(extra, eoff + 2);
2150 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2151 foundExtraTime = true;
2152 }
2153 eoff += (4 + sz);
2154 }
2155 if (!foundExtraTime) {
2156 if (isWindows) {
2157 elenNTFS = 36; // NTFS, total 36 bytes
2158 } else { // on unix use "ext time"
2159 elenEXTT = 9;
2160 if (atime != -1)
2161 elenEXTT += 4;
2162 if (ctime != -1)
2163 elenEXTT += 4;
2164 }
2165 }
2166 writeShort(os, nlen);
2167 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2168 writeBytes(os, zname, 1, nlen);
2169 if (elen64 != 0) {
2170 writeShort(os, EXTID_ZIP64);
2171 writeShort(os, 16);
2172 writeLong(os, size);
2173 writeLong(os, csize);
2174 }
2175 if (elenNTFS != 0) {
2176 writeShort(os, EXTID_NTFS);
2177 writeShort(os, elenNTFS - 4);
2178 writeInt(os, 0); // reserved
2179 writeShort(os, 0x0001); // NTFS attr tag
2180 writeShort(os, 24);
2181 writeLong(os, javaToWinTime(mtime));
2182 writeLong(os, javaToWinTime(atime));
2183 writeLong(os, javaToWinTime(ctime));
2184 }
2185 if (elenEXTT != 0) {
2186 writeShort(os, EXTID_EXTT);
2187 writeShort(os, elenEXTT - 4);// size for the folowing data block
2188 int fbyte = 0x1;
2189 if (atime != -1) // mtime and atime
2190 fbyte |= 0x2;
2191 if (ctime != -1) // mtime, atime and ctime
2192 fbyte |= 0x4;
2193 os.write(fbyte); // flags byte
2194 writeInt(os, javaToUnixTime(mtime));
2195 if (atime != -1)
2196 writeInt(os, javaToUnixTime(atime));
2197 if (ctime != -1)
2198 writeInt(os, javaToUnixTime(ctime));
2199 }
2200 if (extra != null) {
2201 writeBytes(os, extra);
2202 }
2203 return LOCHDR + nlen + elen + elen64 + elenNTFS + elenEXTT;
2204 }
2205
2206 // Data Descriptior
2207 int writeEXT(OutputStream os) throws IOException {
2208 writeInt(os, EXTSIG); // EXT header signature
2209 writeInt(os, crc); // crc-32
2210 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2211 writeLong(os, csize);
2212 writeLong(os, size);
2213 return 24;
2214 } else {
2215 writeInt(os, csize); // compressed size
2216 writeInt(os, size); // uncompressed size
2217 return 16;
2218 }
2219 }
2220
2221 // read NTFS, UNIX and ZIP64 data from cen.extra
2222 void readExtra(ZipFileSystem zipfs) throws IOException {
2223 if (extra == null)
2224 return;
2225 int elen = extra.length;
2226 int off = 0;
2263 if (SH(extra, pos + 2) != 24)
2264 break;
2265 // override the loc field, datatime here is
2266 // more "accurate"
2267 mtime = winToJavaTime(LL(extra, pos + 4));
2268 atime = winToJavaTime(LL(extra, pos + 12));
2269 ctime = winToJavaTime(LL(extra, pos + 20));
2270 break;
2271 case EXTID_EXTT:
2272 // spec says the Extened timestamp in cen only has mtime
2273 // need to read the loc to get the extra a/ctime, if flag
2274 // "zipinfo-time" is not specified to false;
2275 // there is performance cost (move up to loc and read) to
2276 // access the loc table foreach entry;
2277 if (zipfs.noExtt) {
2278 if (sz == 5)
2279 mtime = unixToJavaTime(LG(extra, pos + 1));
2280 break;
2281 }
2282 byte[] buf = new byte[LOCHDR];
2283 if (zipfs.readFullyAt(buf, 0, buf.length , locoff)
2284 != buf.length)
2285 throw new ZipException("loc: reading failed");
2286 if (!locSigAt(buf, 0))
2287 throw new ZipException("loc: wrong sig ->"
2288 + Long.toString(getSig(buf, 0), 16));
2289 int locElen = LOCEXT(buf);
2290 if (locElen < 9) // EXTT is at lease 9 bytes
2291 break;
2292 int locNlen = LOCNAM(buf);
2293 buf = new byte[locElen];
2294 if (zipfs.readFullyAt(buf, 0, buf.length , locoff + LOCHDR + locNlen)
2295 != buf.length)
2296 throw new ZipException("loc extra: reading failed");
2297 int locPos = 0;
2298 while (locPos + 4 < buf.length) {
2299 int locTag = SH(buf, locPos);
2300 int locSZ = SH(buf, locPos + 2);
2301 locPos += 4;
2302 if (locTag != EXTID_EXTT) {
2303 locPos += locSZ;
2304 continue;
2305 }
2306 int end = locPos + locSZ - 4;
2307 int flag = CH(buf, locPos++);
2308 if ((flag & 0x1) != 0 && locPos <= end) {
2309 mtime = unixToJavaTime(LG(buf, locPos));
2310 locPos += 4;
2311 }
2312 if ((flag & 0x2) != 0 && locPos <= end) {
2313 atime = unixToJavaTime(LG(buf, locPos));
2314 locPos += 4;
2315 }
2316 if ((flag & 0x4) != 0 && locPos <= end) {
2317 ctime = unixToJavaTime(LG(buf, locPos));
2318 locPos += 4;
2319 }
2320 break;
2321 }
2322 break;
2323 default: // unknown tag
2324 System.arraycopy(extra, off, extra, newOff, sz + 4);
2325 newOff += (sz + 4);
2326 }
2327 off += (sz + 4);
2328 }
2329 if (newOff != 0 && newOff != extra.length)
2330 extra = Arrays.copyOf(extra, newOff);
2331 else
2332 extra = null;
2333 }
2334
2335 ///////// basic file attributes ///////////
2336 @Override
2337 public FileTime creationTime() {
2338 return FileTime.fromMillis(ctime == -1 ? mtime : ctime);
2339 }
2340
2341 @Override
2342 public boolean isDirectory() {
2343 return isDir();
2344 }
2345
2346 @Override
2347 public boolean isOther() {
2348 return false;
2349 }
2350
2351 @Override
2352 public boolean isRegularFile() {
2353 return !isDir();
2354 }
2361 @Override
2362 public FileTime lastModifiedTime() {
2363 return FileTime.fromMillis(mtime);
2364 }
2365
2366 @Override
2367 public long size() {
2368 return size;
2369 }
2370
2371 @Override
2372 public boolean isSymbolicLink() {
2373 return false;
2374 }
2375
2376 @Override
2377 public Object fileKey() {
2378 return null;
2379 }
2380
2381 ///////// zip entry attributes ///////////
2382 public long compressedSize() {
2383 return csize;
2384 }
2385
2386 public long crc() {
2387 return crc;
2388 }
2389
2390 public int method() {
2391 return method;
2392 }
2393
2394 public byte[] extra() {
2395 if (extra != null)
2396 return Arrays.copyOf(extra, extra.length);
2397 return null;
2398 }
2399
2400 public byte[] comment() {
2401 if (comment != null)
2402 return Arrays.copyOf(comment, comment.length);
2403 return null;
2404 }
2405
2406 public String toString() {
2407 StringBuilder sb = new StringBuilder(1024);
2408 Formatter fm = new Formatter(sb);
2409 fm.format(" name : %s%n", new String(name));
2410 fm.format(" creationTime : %tc%n", creationTime().toMillis());
2411 fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis());
2412 fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
2413 fm.format(" isRegularFile : %b%n", isRegularFile());
2414 fm.format(" isDirectory : %b%n", isDirectory());
2415 fm.format(" isSymbolicLink : %b%n", isSymbolicLink());
2416 fm.format(" isOther : %b%n", isOther());
2417 fm.format(" fileKey : %s%n", fileKey());
2418 fm.format(" size : %d%n", size());
2419 fm.format(" compressedSize : %d%n", compressedSize());
2420 fm.format(" crc : %x%n", crc());
2421 fm.format(" method : %d%n", method());
2422 fm.close();
2423 return sb.toString();
2424 }
2425 }
2426
2427 // ZIP directory has two issues:
2428 // (1) ZIP spec does not require the ZIP file to include
2429 // directory entry
2430 // (2) all entries are not stored/organized in a "tree"
2431 // structure.
2432 // A possible solution is to build the node tree ourself as
2433 // implemented below.
2434
2435 // default time stamp for pseudo entries
2436 private long zfsDefaultTimeStamp = System.currentTimeMillis();
2437
2438 private void removeFromTree(IndexNode inode) {
2439 IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(inode.name)));
2440 IndexNode child = parent.child;
2441 if (child.equals(inode)) {
2442 parent.child = child.sibling;
2443 } else {
|
26 package jdk.nio.zipfs;
27
28 import java.io.BufferedOutputStream;
29 import java.io.ByteArrayInputStream;
30 import java.io.ByteArrayOutputStream;
31 import java.io.EOFException;
32 import java.io.FilterOutputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.io.OutputStream;
36 import java.nio.ByteBuffer;
37 import java.nio.MappedByteBuffer;
38 import java.nio.channels.FileChannel;
39 import java.nio.channels.FileLock;
40 import java.nio.channels.ReadableByteChannel;
41 import java.nio.channels.SeekableByteChannel;
42 import java.nio.channels.WritableByteChannel;
43 import java.nio.file.*;
44 import java.nio.file.attribute.FileAttribute;
45 import java.nio.file.attribute.FileTime;
46 import java.nio.file.attribute.GroupPrincipal;
47 import java.nio.file.attribute.PosixFilePermission;
48 import java.nio.file.attribute.PosixFilePermissions;
49 import java.nio.file.attribute.UserPrincipal;
50 import java.nio.file.attribute.UserPrincipalLookupService;
51 import java.nio.file.spi.FileSystemProvider;
52 import java.security.AccessController;
53 import java.security.PrivilegedAction;
54 import java.security.PrivilegedActionException;
55 import java.security.PrivilegedExceptionAction;
56 import java.util.*;
57 import java.util.concurrent.locks.ReadWriteLock;
58 import java.util.concurrent.locks.ReentrantReadWriteLock;
59 import java.util.regex.Pattern;
60 import java.util.zip.CRC32;
61 import java.util.zip.Deflater;
62 import java.util.zip.DeflaterOutputStream;
63 import java.util.zip.Inflater;
64 import java.util.zip.InflaterInputStream;
65 import java.util.zip.ZipException;
66
67 import static java.lang.Boolean.TRUE;
68 import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
69 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
70 import static java.nio.file.StandardOpenOption.APPEND;
71 import static java.nio.file.StandardOpenOption.CREATE;
72 import static java.nio.file.StandardOpenOption.CREATE_NEW;
73 import static java.nio.file.StandardOpenOption.READ;
74 import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
75 import static java.nio.file.StandardOpenOption.WRITE;
76 import static jdk.nio.zipfs.ZipConstants.*;
77 import static jdk.nio.zipfs.ZipUtils.*;
78
79 /**
80 * A FileSystem built on a zip file
81 *
82 * @author Xueming Shen
83 */
84 class ZipFileSystem extends FileSystem {
85 // statics
86 private static final boolean isWindows = AccessController.doPrivileged(
87 (PrivilegedAction<Boolean>)()->System.getProperty("os.name")
88 .startsWith("Windows"));
89 private static final String OPT_POSIX = "enablePosixFileAttributes";
90 private static final String OPT_DEFAULT_OWNER = "defaultOwner";
91 private static final String OPT_DEFAULT_GROUP = "defaultGroup";
92 private static final String OPT_DEFAULT_PERMISSIONS = "defaultPermissions";
93
94 private static final String DEFAULT_PRINCIPAL_NAME = "<zipfs_default>";
95 private static final Set<PosixFilePermission> DEFAULT_PERMISSIONS =
96 PosixFilePermissions.fromString("rwxrwxrwx");
97
98 private final ZipFileSystemProvider provider;
99 private final Path zfpath;
100 final ZipCoder zc;
101 private final ZipPath rootdir;
102 private boolean readOnly = false; // readonly file system
103
104 // configurable by env map
105 private final boolean noExtt; // see readExtra()
106 private final boolean useTempFile; // use a temp file for newOS, default
107 // is to use BAOS for better performance
108
109 private final boolean forceEnd64;
110 private final int defaultMethod; // METHOD_STORED if "noCompression=true"
111 // METHOD_DEFLATED otherwise
112
113 // POSIX support
114 final boolean supportPosix;
115 private final UserPrincipal defaultOwner;
116 private final GroupPrincipal defaultGroup;
117 private final Set<PosixFilePermission> defaultPermissions;
118
119 private final Set<String> supportedFileAttributeViews;
120
121 ZipFileSystem(ZipFileSystemProvider provider,
122 Path zfpath,
123 Map<String, ?> env) throws IOException
124 {
125 // default encoding for name/comment
126 String nameEncoding = env.containsKey("encoding") ?
127 (String)env.get("encoding") : "UTF-8";
128 this.noExtt = "false".equals(env.get("zipinfo-time"));
129 this.useTempFile = isTrue(env, "useTempFile");
130 this.forceEnd64 = isTrue(env, "forceZIP64End");
131 this.defaultMethod = isTrue(env, "noCompression") ? METHOD_STORED : METHOD_DEFLATED;
132 this.supportPosix = isTrue(env, OPT_POSIX);
133 this.defaultOwner = initOwner(zfpath, env);
134 this.defaultGroup = initGroup(env);
135 this.defaultPermissions = initPermissions(env);
136 this.supportedFileAttributeViews = supportPosix ?
137 Set.of("basic", "posix", "zip") : Set.of("basic", "zip");
138 if (Files.notExists(zfpath)) {
139 // create a new zip if it doesn't exist
140 if (isTrue(env, "create")) {
141 try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
142 new END().write(os, 0, forceEnd64);
143 }
144 } else {
145 throw new FileSystemNotFoundException(zfpath.toString());
146 }
147 }
148 // sm and existence check
149 zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
150 boolean writeable = AccessController.doPrivileged(
151 (PrivilegedAction<Boolean>)()->Files.isWritable(zfpath));
152 this.readOnly = !writeable;
153 this.zc = ZipCoder.get(nameEncoding);
154 this.rootdir = new ZipPath(this, new byte[]{'/'});
155 this.ch = Files.newByteChannel(zfpath, READ);
156 try {
157 this.cen = initCEN();
158 } catch (IOException x) {
159 try {
160 this.ch.close();
161 } catch (IOException xx) {
162 x.addSuppressed(xx);
163 }
164 throw x;
165 }
166 this.provider = provider;
167 this.zfpath = zfpath;
168 }
169
170 // returns true if there is a name=true/"true" setting in env
171 private static boolean isTrue(Map<String, ?> env, String name) {
172 return "true".equals(env.get(name)) || TRUE.equals(env.get(name));
173 }
174
175 // Initialize the default owner for files inside the zip archive.
176 // If not specified in env, it is the owner of the archive. If no owner can
177 // be determined, we try to go with system property "user.name". If that's not
178 // accessible, we return "<zipfs_default>".
179 private UserPrincipal initOwner(Path zfpath, Map<String, ?> env) {
180 Object o = env.get(OPT_DEFAULT_OWNER);
181 if (o == null) {
182 try {
183 return Files.getOwner(zfpath);
184 } catch (Exception e) {
185 try {
186 String userName = AccessController.doPrivileged(
187 (PrivilegedAction<String>)()->System.getProperty("user.name"));
188 return ()->userName;
189 } catch (Exception e2) {
190 return ()->DEFAULT_PRINCIPAL_NAME;
191 }
192 }
193 }
194 if (o instanceof String) {
195 if (((String)o).isEmpty()) {
196 throw new IllegalArgumentException("Value for property " +
197 OPT_DEFAULT_OWNER + " must not be empty.");
198 }
199 return ()->(String)o;
200 }
201 if (o instanceof UserPrincipal) {
202 return (UserPrincipal)o;
203 }
204 throw new IllegalArgumentException("Value for property " +
205 OPT_DEFAULT_OWNER + " must be of type " + String.class +
206 " or " + UserPrincipal.class);
207 }
208
209 // Initialize the default group for files inside the zip archive.
210 // If not specified in env, it will return a group principal going
211 // by the same name as the default owner.
212 private GroupPrincipal initGroup(Map<String, ?> env) {
213 Object o = env.get(OPT_DEFAULT_GROUP);
214 if (o == null) {
215 return ()->defaultOwner.getName();
216 }
217 if (o instanceof String) {
218 if (((String)o).isEmpty()) {
219 throw new IllegalArgumentException("Value for property " +
220 OPT_DEFAULT_GROUP + " must not be empty.");
221 }
222 return ()->(String)o;
223 }
224 if (o instanceof GroupPrincipal) {
225 return (GroupPrincipal)o;
226 }
227 throw new IllegalArgumentException("Value for property " +
228 OPT_DEFAULT_GROUP + " must be of type " + String.class +
229 " or " + GroupPrincipal.class);
230 }
231
232 // Initialize the default permissions for files inside the zip archive.
233 // If not specified in env, it will return 777.
234 private Set<PosixFilePermission> initPermissions(Map<String, ?> env) {
235 Object o = env.get(OPT_DEFAULT_PERMISSIONS);
236 if (o == null) {
237 return DEFAULT_PERMISSIONS;
238 }
239 if (o instanceof String) {
240 return PosixFilePermissions.fromString((String)o);
241 }
242 if (!(o instanceof Set)) {
243 throw new IllegalArgumentException("Value for property " +
244 OPT_DEFAULT_PERMISSIONS + " must be of type " + String.class +
245 " or " + Set.class);
246 }
247 Set<PosixFilePermission> perms = new HashSet<PosixFilePermission>();
248 for (Object o2 : (Set<?>)o) {
249 if (o2 instanceof PosixFilePermission) {
250 perms.add((PosixFilePermission)o2);
251 } else {
252 throw new IllegalArgumentException(OPT_DEFAULT_PERMISSIONS +
253 " must only contain objects of type " + PosixFilePermission.class);
254 }
255 }
256 return perms;
257 }
258
259 @Override
260 public FileSystemProvider provider() {
261 return provider;
262 }
263
264 @Override
265 public String getSeparator() {
266 return "/";
267 }
268
269 @Override
270 public boolean isOpen() {
271 return isOpen;
272 }
273
274 @Override
275 public boolean isReadOnly() {
276 return readOnly;
277 }
278
314
315 @Override
316 public UserPrincipalLookupService getUserPrincipalLookupService() {
317 throw new UnsupportedOperationException();
318 }
319
320 @Override
321 public WatchService newWatchService() {
322 throw new UnsupportedOperationException();
323 }
324
325 FileStore getFileStore(ZipPath path) {
326 return new ZipFileStore(path);
327 }
328
329 @Override
330 public Iterable<FileStore> getFileStores() {
331 return List.of(new ZipFileStore(rootdir));
332 }
333
334 @Override
335 public Set<String> supportedFileAttributeViews() {
336 return supportedFileAttributeViews;
337 }
338
339 @Override
340 public String toString() {
341 return zfpath.toString();
342 }
343
344 Path getZipFile() {
345 return zfpath;
346 }
347
348 private static final String GLOB_SYNTAX = "glob";
349 private static final String REGEX_SYNTAX = "regex";
350
351 @Override
352 public PathMatcher getPathMatcher(String syntaxAndInput) {
353 int pos = syntaxAndInput.indexOf(':');
464 if (getInode(path) == null) {
465 throw new NoSuchFileException(toString());
466 }
467
468 } finally {
469 endRead();
470 }
471 }
472
473 void setTimes(byte[] path, FileTime mtime, FileTime atime, FileTime ctime)
474 throws IOException
475 {
476 checkWritable();
477 beginWrite();
478 try {
479 ensureOpen();
480 Entry e = getEntry(path); // ensureOpen checked
481 if (e == null)
482 throw new NoSuchFileException(getString(path));
483 if (e.type == Entry.CEN)
484 e.type = Entry.COPY; // copy e
485 if (mtime != null)
486 e.mtime = mtime.toMillis();
487 if (atime != null)
488 e.atime = atime.toMillis();
489 if (ctime != null)
490 e.ctime = ctime.toMillis();
491 update(e);
492 } finally {
493 endWrite();
494 }
495 }
496
497 void setOwner(byte[] path, UserPrincipal owner) throws IOException {
498 checkWritable();
499 beginWrite();
500 try {
501 ensureOpen();
502 Entry e = getEntry(path); // ensureOpen checked
503 if (e == null) {
504 throw new NoSuchFileException(getString(path));
505 }
506 // as the owner information is not persistent, we don't need to
507 // change e.type to Entry.COPY
508 e.owner = owner;
509 update(e);
510 } finally {
511 endWrite();
512 }
513 }
514
515 void setPermissions(byte[] path, Set<PosixFilePermission> perms)
516 throws IOException
517 {
518 checkWritable();
519 beginWrite();
520 try {
521 ensureOpen();
522 Entry e = getEntry(path); // ensureOpen checked
523 if (e == null) {
524 throw new NoSuchFileException(getString(path));
525 }
526 if (e.type == Entry.CEN) {
527 e.type = Entry.COPY; // copy e
528 }
529 e.posixPerms = perms == null ? -1 : ZipUtils.permsToFlags(perms);
530 update(e);
531 } finally {
532 endWrite();
533 }
534 }
535
536 void setGroup(byte[] path, GroupPrincipal group) throws IOException {
537 checkWritable();
538 beginWrite();
539 try {
540 ensureOpen();
541 Entry e = getEntry(path); // ensureOpen checked
542 if (e == null) {
543 throw new NoSuchFileException(getString(path));
544 }
545 // as the group information is not persistent, we don't need to
546 // change e.type to Entry.COPY
547 e.group = group;
548 update(e);
549 } finally {
550 endWrite();
551 }
552 }
553
554 boolean exists(byte[] path)
555 throws IOException
556 {
557 beginRead();
558 try {
559 ensureOpen();
560 return getInode(path) != null;
561 } finally {
562 endRead();
563 }
564 }
565
566 boolean isDirectory(byte[] path)
567 throws IOException
568 {
569 beginRead();
570 try {
571 IndexNode n = getInode(path);
572 return n != null && n.isDir();
573 } finally {
603 list.add(zpath);
604 child = child.sibling;
605 }
606 return list.iterator();
607 } finally {
608 endWrite();
609 }
610 }
611
612 void createDirectory(byte[] dir, FileAttribute<?>... attrs)
613 throws IOException
614 {
615 checkWritable();
616 // dir = toDirectoryPath(dir);
617 beginWrite();
618 try {
619 ensureOpen();
620 if (dir.length == 0 || exists(dir)) // root dir, or exiting dir
621 throw new FileAlreadyExistsException(getString(dir));
622 checkParents(dir);
623 Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED, attrs);
624 update(e);
625 } finally {
626 endWrite();
627 }
628 }
629
630 void copyFile(boolean deletesrc, byte[]src, byte[] dst, CopyOption... options)
631 throws IOException
632 {
633 checkWritable();
634 if (Arrays.equals(src, dst))
635 return; // do nothing, src and dst are the same
636
637 beginWrite();
638 try {
639 ensureOpen();
640 Entry eSrc = getEntry(src); // ensureOpen checked
641
642 if (eSrc == null)
643 throw new NoSuchFileException(getString(src));
784 // store size, compressed size, and crc-32 in datadescriptor
785 e.flag = FLAG_DATADESCR;
786 if (zc.isUTF8())
787 e.flag |= FLAG_USE_UTF8;
788 }
789
790 @Override
791 public void close() throws IOException {
792 e.bytes = toByteArray();
793 e.size = e.bytes.length;
794 e.crc = -1;
795 super.close();
796 update(e);
797 }
798 }
799
800 private int getCompressMethod(FileAttribute<?>... attrs) {
801 return defaultMethod;
802 }
803
804 // Returns a Writable/ReadByteChannel for now. Might consider to use
805 // newFileChannel() instead, which dump the entry data into a regular
806 // file on the default file system and create a FileChannel on top of it.
807 SeekableByteChannel newByteChannel(byte[] path,
808 Set<? extends OpenOption> options,
809 FileAttribute<?>... attrs)
810 throws IOException
811 {
812 checkOptions(options);
813 if (options.contains(StandardOpenOption.WRITE) ||
814 options.contains(StandardOpenOption.APPEND)) {
815 checkWritable();
816 beginRead(); // only need a readlock, the "update()" will obtain
817 // thewritelock when the channel is closed
818 try {
819 ensureOpen();
820 Entry e = getEntry(path);
821 if (e != null) {
822 if (e.isDir() || options.contains(CREATE_NEW))
823 throw new FileAlreadyExistsException(getString(path));
824 SeekableByteChannel sbc =
825 new EntryOutputChannel(new Entry(e, Entry.NEW));
826 if (options.contains(APPEND)) {
827 try (InputStream is = getInputStream(e)) { // copyover
828 byte[] buf = new byte[8192];
829 ByteBuffer bb = ByteBuffer.wrap(buf);
830 int n;
831 while ((n = is.read(buf)) != -1) {
832 bb.position(0);
833 bb.limit(n);
834 sbc.write(bb);
835 }
836 }
837 }
838 return sbc;
839 }
840 if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
841 throw new NoSuchFileException(getString(path));
842 checkParents(path);
843 return new EntryOutputChannel(
844 new Entry(path, Entry.NEW, false, getCompressMethod(attrs), attrs));
845
846 } finally {
847 endRead();
848 }
849 } else {
850 beginRead();
851 try {
852 ensureOpen();
853 Entry e = getEntry(path);
854 if (e == null || e.isDir())
855 throw new NoSuchFileException(getString(path));
856 try (InputStream is = getInputStream(e)) {
857 // TBD: if (e.size < NNNNN);
858 return new ByteArrayChannel(is.readAllBytes(), true);
859 }
860 } finally {
861 endRead();
862 }
863 }
864 }
889 }
890 } else {
891 if (options.contains(StandardOpenOption.CREATE_NEW)) {
892 throw new FileAlreadyExistsException(getString(path));
893 }
894 if (e.isDir())
895 throw new FileAlreadyExistsException("directory <"
896 + getString(path) + "> exists");
897 }
898 options = new HashSet<>(options);
899 options.remove(StandardOpenOption.CREATE_NEW); // for tmpfile
900 } else if (e == null || e.isDir()) {
901 throw new NoSuchFileException(getString(path));
902 }
903
904 final boolean isFCH = (e != null && e.type == Entry.FILECH);
905 final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
906 final FileChannel fch = tmpfile.getFileSystem()
907 .provider()
908 .newFileChannel(tmpfile, options, attrs);
909 final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH, attrs);
910 if (forWrite) {
911 u.flag = FLAG_DATADESCR;
912 u.method = getCompressMethod(attrs);
913 }
914 // is there a better way to hook into the FileChannel's close method?
915 return new FileChannel() {
916 public int write(ByteBuffer src) throws IOException {
917 return fch.write(src);
918 }
919 public long write(ByteBuffer[] srcs, int offset, int length)
920 throws IOException
921 {
922 return fch.write(srcs, offset, length);
923 }
924 public long position() throws IOException {
925 return fch.position();
926 }
927 public FileChannel position(long newPosition)
928 throws IOException
929 {
1418 // entry copy: the only thing changed is the "name"
1419 // and "nlen" in LOC header, so we udpate/rewrite the
1420 // LOC in new file and simply copy the rest (data and
1421 // ext) without enflating/deflating from the old zip
1422 // file LOC entry.
1423 written += copyLOCEntry(e, true, os, written, buf);
1424 } else { // NEW, FILECH or CEN
1425 e.locoff = written;
1426 written += e.writeLOC(os); // write loc header
1427 written += writeEntry(e, os, buf);
1428 }
1429 elist.add(e);
1430 } catch (IOException x) {
1431 x.printStackTrace(); // skip any in-accurate entry
1432 }
1433 } else { // unchanged inode
1434 if (inode.pos == -1) {
1435 continue; // pseudo directory node
1436 }
1437 if (inode.name.length == 1 && inode.name[0] == '/') {
1438 continue; // no root '/' directory even if it
1439 // exists in original zip/jar file.
1440 }
1441 e = new Entry(inode);
1442 try {
1443 written += copyLOCEntry(e, false, os, written, buf);
1444 elist.add(e);
1445 } catch (IOException x) {
1446 x.printStackTrace(); // skip any wrong entry
1447 }
1448 }
1449 }
1450
1451 // now write back the cen and end table
1452 end.cenoff = written;
1453 for (Entry entry : elist) {
1454 written += entry.writeCEN(os);
1455 }
1456 end.centot = elist.size();
1457 end.cenlen = written - end.cenoff;
1458 end.write(os, written, forceEnd64);
1459 }
1460
1461 ch.close();
1462 Files.delete(zfpath);
1463 Files.move(tmpFile, zfpath, REPLACE_EXISTING);
1464 hasUpdate = false; // clear
1465 }
1466
1467 IndexNode getInode(byte[] path) {
1468 if (path == null)
1469 throw new NullPointerException("path");
1470 return inodes.get(IndexNode.keyOf(path));
1471 }
1472
1473 Entry getEntry(byte[] path) throws IOException {
1474 IndexNode inode = getInode(path);
1475 if (inode instanceof Entry)
1476 return (Entry)inode;
1477 if (inode == null || inode.pos == -1)
1478 return null;
1479 return new Entry(inode);
1480 }
1481
1482 public void deleteFile(byte[] path, boolean failIfNotExists)
1483 throws IOException
1484 {
1485 checkWritable();
1486
1487 IndexNode inode = getInode(path);
1488 if (inode == null) {
1489 if (path != null && path.length == 0)
1490 throw new ZipException("root directory </> can't not be delete");
1491 if (failIfNotExists)
1492 throw new NoSuchFileException(getString(path));
1493 } else {
1494 if (inode.isDir() && inode.child != null)
1495 throw new DirectoryNotEmptyException(getString(path));
1496 updateDelete(inode);
1497 }
1498 }
1499
1927 writeShort(os, 0); // central directory start disk
1928 writeShort(os, count); // number of directory entries on disk
1929 writeShort(os, count); // total number of directory entries
1930 writeInt(os, xlen); // length of central directory
1931 writeInt(os, xoff); // offset of central directory
1932 if (comment != null) { // zip file comment
1933 writeShort(os, comment.length);
1934 writeBytes(os, comment);
1935 } else {
1936 writeShort(os, 0);
1937 }
1938 }
1939 }
1940
1941 // Internal node that links a "name" to its pos in cen table.
1942 // The node itself can be used as a "key" to lookup itself in
1943 // the HashMap inodes.
1944 static class IndexNode {
1945 byte[] name;
1946 int hashcode; // node is hashable/hashed by its name
1947 int pos = -1; // position in cen table, -1 means the
1948 // entry does not exist in zip file
1949 boolean isdir;
1950
1951 IndexNode(byte[] name, boolean isdir) {
1952 name(name);
1953 this.isdir = isdir;
1954 this.pos = -1;
1955 }
1956
1957 IndexNode(byte[] name, int pos) {
1958 name(name);
1959 this.pos = pos;
1960 }
1961
1962 // constructor for cenInit() (1) remove tailing '/' (2) pad leading '/'
1963 IndexNode(byte[] cen, int pos, int nlen) {
1964 int noff = pos + CENHDR;
1965 if (cen[noff + nlen - 1] == '/') {
1966 isdir = true;
1967 nlen--;
1968 }
2004
2005 public boolean equals(Object other) {
2006 if (!(other instanceof IndexNode)) {
2007 return false;
2008 }
2009 if (other instanceof ParentLookup) {
2010 return ((ParentLookup)other).equals(this);
2011 }
2012 return Arrays.equals(name, ((IndexNode)other).name);
2013 }
2014
2015 public int hashCode() {
2016 return hashcode;
2017 }
2018
2019 IndexNode() {}
2020 IndexNode sibling;
2021 IndexNode child; // 1st child
2022 }
2023
2024 class Entry extends IndexNode implements ZipFileAttributes {
2025
2026 static final int CEN = 1; // entry read from cen
2027 static final int NEW = 2; // updated contents in bytes or file
2028 static final int FILECH = 3; // fch update in "file"
2029 static final int COPY = 4; // copy of a CEN entry
2030
2031 byte[] bytes; // updated content bytes
2032 Path file; // use tmp file to store bytes;
2033 int type = CEN; // default is the entry read from cen
2034
2035 // entry attributes
2036 int version;
2037 int flag;
2038 int posixPerms = -1; // posix permissions
2039 int method = -1; // compression method
2040 long mtime = -1; // last modification time (in DOS time)
2041 long atime = -1; // last access time
2042 long ctime = -1; // create time
2043 long crc = -1; // crc-32 of entry data
2044 long csize = -1; // compressed size of entry data
2045 long size = -1; // uncompressed size of entry data
2046 byte[] extra;
2047
2048 // cen
2049
2050 // these fields are not used by anyone and writeCEN uses "0"
2051 // int versionMade;
2052 // int disk;
2053 // int attrs;
2054 // long attrsEx;
2055 long locoff;
2056 byte[] comment;
2057
2058 // posix support
2059 private UserPrincipal owner = defaultOwner;
2060 private GroupPrincipal group = defaultGroup;
2061
2062 Entry() {}
2063
2064 Entry(byte[] name, boolean isdir, int method) {
2065 name(name);
2066 this.isdir = isdir;
2067 this.mtime = this.ctime = this.atime = System.currentTimeMillis();
2068 this.crc = 0;
2069 this.size = 0;
2070 this.csize = 0;
2071 this.method = method;
2072 }
2073
2074 @SuppressWarnings("unchecked")
2075 Entry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) {
2076 this(name, isdir, method);
2077 this.type = type;
2078 for (FileAttribute<?> attr : attrs) {
2079 String attrName = attr.name();
2080 if (attrName.equals("posix:permissions")) {
2081 posixPerms = ZipUtils.permsToFlags((Set<PosixFilePermission>)attr.value());
2082 }
2083 }
2084 }
2085
2086 Entry(Entry e, int type) {
2087 name(e.name);
2088 this.isdir = e.isdir;
2089 this.version = e.version;
2090 this.ctime = e.ctime;
2091 this.atime = e.atime;
2092 this.mtime = e.mtime;
2093 this.crc = e.crc;
2094 this.size = e.size;
2095 this.csize = e.csize;
2096 this.method = e.method;
2097 this.extra = e.extra;
2098 /*
2099 this.versionMade = e.versionMade;
2100 this.disk = e.disk;
2101 this.attrs = e.attrs;
2102 this.attrsEx = e.attrsEx;
2103 */
2104 this.locoff = e.locoff;
2105 this.comment = e.comment;
2106 this.posixPerms = e.posixPerms;
2107 this.owner = e.owner;
2108 this.group = e.group;
2109 this.type = type;
2110 }
2111
2112 @SuppressWarnings("unchecked")
2113 Entry(byte[] name, Path file, int type, FileAttribute<?>... attrs) {
2114 this(name, type, false, METHOD_STORED);
2115 this.file = file;
2116 for (FileAttribute<?> attr : attrs) {
2117 String attrName = attr.name();
2118 if (attrName.equals("posix:permissions")) {
2119 posixPerms = ZipUtils.permsToFlags((Set<PosixFilePermission>)attr.value());
2120 }
2121 }
2122 }
2123
2124 // reads the full entry from an IndexNode
2125 Entry(IndexNode inode) throws IOException {
2126 int pos = inode.pos;
2127 if (!cenSigAt(cen, pos))
2128 zerror("invalid CEN header (bad signature)");
2129 version = CENVER(cen, pos);
2130 flag = CENFLG(cen, pos);
2131 method = CENHOW(cen, pos);
2132 mtime = dosToJavaTime(CENTIM(cen, pos));
2133 crc = CENCRC(cen, pos);
2134 csize = CENSIZ(cen, pos);
2135 size = CENLEN(cen, pos);
2136 int nlen = CENNAM(cen, pos);
2137 int elen = CENEXT(cen, pos);
2138 int clen = CENCOM(cen, pos);
2139 /*
2140 versionMade = CENVEM(cen, pos);
2141 disk = CENDSK(cen, pos);
2142 attrs = CENATT(cen, pos);
2143 attrsEx = CENATX(cen, pos);
2144 */
2145 if (CENVEM_FA(cen, pos) == FILE_ATTRIBUTES_UNIX) {
2146 posixPerms = CENATX_PERMS(cen, pos) & 0xFFF; // 12 bits for setuid, setgid, sticky + perms
2147 }
2148 locoff = CENOFF(cen, pos);
2149 pos += CENHDR;
2150 this.name = inode.name;
2151 this.isdir = inode.isdir;
2152 this.hashcode = inode.hashcode;
2153
2154 pos += nlen;
2155 if (elen > 0) {
2156 extra = Arrays.copyOfRange(cen, pos, pos + elen);
2157 pos += elen;
2158 readExtra(ZipFileSystem.this);
2159 }
2160 if (clen > 0) {
2161 comment = Arrays.copyOfRange(cen, pos, pos + clen);
2162 }
2163 }
2164
2165 int version(boolean zip64) throws ZipException {
2166 if (zip64) {
2167 return 45;
2168 }
2169 if (method == METHOD_DEFLATED)
2170 return 20;
2171 else if (method == METHOD_STORED)
2172 return 10;
2173 throw new ZipException("unsupported compression method");
2174 }
2175
2176 /**
2177 * Adds information about compatibility of file attribute information
2178 * to a version value.
2179 */
2180 int versionMadeBy(int version) {
2181 return (posixPerms < 0) ? version :
2182 VERSION_BASE_UNIX | (version & 0xff);
2183 }
2184
2185 ///////////////////// CEN //////////////////////
2186 int writeCEN(OutputStream os) throws IOException {
2187 long csize0 = csize;
2188 long size0 = size;
2189 long locoff0 = locoff;
2190 int elen64 = 0; // extra for ZIP64
2191 int elenNTFS = 0; // extra for NTFS (a/c/mtime)
2192 int elenEXTT = 0; // extra for Extended Timestamp
2193 boolean foundExtraTime = false; // if time stamp NTFS, EXTT present
2194
2195 byte[] zname = isdir ? toDirectoryPath(name) : name;
2196
2197 // confirm size/length
2198 int nlen = (zname != null) ? zname.length - 1 : 0; // name has [0] as "slash"
2199 int elen = (extra != null) ? extra.length : 0;
2200 int eoff = 0;
2201 int clen = (comment != null) ? comment.length : 0;
2202 if (csize >= ZIP64_MINVAL) {
2203 csize0 = ZIP64_MINVAL;
2204 elen64 += 8; // csize(8)
2205 }
2206 if (size >= ZIP64_MINVAL) {
2207 size0 = ZIP64_MINVAL; // size(8)
2208 elen64 += 8;
2209 }
2210 if (locoff >= ZIP64_MINVAL) {
2211 locoff0 = ZIP64_MINVAL;
2212 elen64 += 8; // offset(8)
2213 }
2214 if (elen64 != 0) {
2215 elen64 += 4; // header and data sz 4 bytes
2216 }
2217 boolean zip64 = (elen64 != 0);
2218 int version0 = version(zip64);
2219 while (eoff + 4 < elen) {
2220 int tag = SH(extra, eoff);
2221 int sz = SH(extra, eoff + 2);
2222 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2223 foundExtraTime = true;
2224 }
2225 eoff += (4 + sz);
2226 }
2227 if (!foundExtraTime) {
2228 if (isWindows) { // use NTFS
2229 elenNTFS = 36; // total 36 bytes
2230 } else { // Extended Timestamp otherwise
2231 elenEXTT = 9; // only mtime in cen
2232 }
2233 }
2234 writeInt(os, CENSIG); // CEN header signature
2235 writeShort(os, versionMadeBy(version0)); // version made by
2236 writeShort(os, version0); // version needed to extract
2237 writeShort(os, flag); // general purpose bit flag
2238 writeShort(os, method); // compression method
2239 // last modification time
2240 writeInt(os, (int)javaToDosTime(mtime));
2241 writeInt(os, crc); // crc-32
2242 writeInt(os, csize0); // compressed size
2243 writeInt(os, size0); // uncompressed size
2244 writeShort(os, nlen);
2245 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2246
2247 if (comment != null) {
2248 writeShort(os, Math.min(clen, 0xffff));
2249 } else {
2250 writeShort(os, 0);
2251 }
2252 writeShort(os, 0); // starting disk number
2253 writeShort(os, 0); // internal file attributes (unused)
2254 writeInt(os, posixPerms > 0 ? posixPerms << 16 : 0); // external file
2255 // attributes, used for storing posix
2256 // permissions
2257 writeInt(os, locoff0); // relative offset of local header
2258 writeBytes(os, zname, 1, nlen);
2259 if (zip64) {
2260 writeShort(os, EXTID_ZIP64);// Zip64 extra
2261 writeShort(os, elen64 - 4); // size of "this" extra block
2262 if (size0 == ZIP64_MINVAL)
2263 writeLong(os, size);
2264 if (csize0 == ZIP64_MINVAL)
2265 writeLong(os, csize);
2266 if (locoff0 == ZIP64_MINVAL)
2267 writeLong(os, locoff);
2268 }
2269 if (elenNTFS != 0) {
2270 writeShort(os, EXTID_NTFS);
2271 writeShort(os, elenNTFS - 4);
2272 writeInt(os, 0); // reserved
2273 writeShort(os, 0x0001); // NTFS attr tag
2274 writeShort(os, 24);
2275 writeLong(os, javaToWinTime(mtime));
2276 writeLong(os, javaToWinTime(atime));
2277 writeLong(os, javaToWinTime(ctime));
2278 }
2279 if (elenEXTT != 0) {
2280 writeShort(os, EXTID_EXTT);
2281 writeShort(os, elenEXTT - 4);
2282 if (ctime == -1)
2283 os.write(0x3); // mtime and atime
2284 else
2285 os.write(0x7); // mtime, atime and ctime
2286 writeInt(os, javaToUnixTime(mtime));
2287 }
2288 if (extra != null) // whatever not recognized
2289 writeBytes(os, extra);
2290 if (comment != null) //TBD: 0, Math.min(commentBytes.length, 0xffff));
2291 writeBytes(os, comment);
2292 return CENHDR + nlen + elen + clen + elen64 + elenNTFS + elenEXTT;
2293 }
2294
2295 ///////////////////// LOC //////////////////////
2296
2297 int writeLOC(OutputStream os) throws IOException {
2298 byte[] zname = isdir ? toDirectoryPath(name) : name;
2299 int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
2300 int elen = (extra != null) ? extra.length : 0;
2301 boolean foundExtraTime = false; // if extra timestamp present
2302 int eoff = 0;
2303 int elen64 = 0;
2304 boolean zip64 = false;
2305 int elenEXTT = 0;
2306 int elenNTFS = 0;
2307 writeInt(os, LOCSIG); // LOC header signature
2308 if ((flag & FLAG_DATADESCR) != 0) {
2309 writeShort(os, version(zip64)); // version needed to extract
2310 writeShort(os, flag); // general purpose bit flag
2311 writeShort(os, method); // compression method
2312 // last modification time
2313 writeInt(os, (int)javaToDosTime(mtime));
2314 // store size, uncompressed size, and crc-32 in data descriptor
2315 // immediately following compressed entry data
2316 writeInt(os, 0);
2317 writeInt(os, 0);
2318 writeInt(os, 0);
2319 } else {
2320 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2321 elen64 = 20; //headid(2) + size(2) + size(8) + csize(8)
2322 zip64 = true;
2323 }
2324 writeShort(os, version(zip64)); // version needed to extract
2325 writeShort(os, flag); // general purpose bit flag
2326 writeShort(os, method); // compression method
2327 // last modification time
2328 writeInt(os, (int)javaToDosTime(mtime));
2329 writeInt(os, crc); // crc-32
2330 if (zip64) {
2331 writeInt(os, ZIP64_MINVAL);
2332 writeInt(os, ZIP64_MINVAL);
2333 } else {
2334 writeInt(os, csize); // compressed size
2335 writeInt(os, size); // uncompressed size
2336 }
2337 }
2338 while (eoff + 4 < elen) {
2339 int tag = SH(extra, eoff);
2340 int sz = SH(extra, eoff + 2);
2341 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2342 foundExtraTime = true;
2343 }
2344 eoff += (4 + sz);
2345 }
2346 if (!foundExtraTime) {
2347 if (isWindows) {
2348 elenNTFS = 36; // NTFS, total 36 bytes
2349 } else { // on unix use "ext time"
2350 elenEXTT = 9;
2351 if (atime != -1)
2352 elenEXTT += 4;
2353 if (ctime != -1)
2354 elenEXTT += 4;
2355 }
2356 }
2357 writeShort(os, nlen);
2358 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2359 writeBytes(os, zname, 1, nlen);
2360 if (zip64) {
2361 writeShort(os, EXTID_ZIP64);
2362 writeShort(os, 16);
2363 writeLong(os, size);
2364 writeLong(os, csize);
2365 }
2366 if (elenNTFS != 0) {
2367 writeShort(os, EXTID_NTFS);
2368 writeShort(os, elenNTFS - 4);
2369 writeInt(os, 0); // reserved
2370 writeShort(os, 0x0001); // NTFS attr tag
2371 writeShort(os, 24);
2372 writeLong(os, javaToWinTime(mtime));
2373 writeLong(os, javaToWinTime(atime));
2374 writeLong(os, javaToWinTime(ctime));
2375 }
2376 if (elenEXTT != 0) {
2377 writeShort(os, EXTID_EXTT);
2378 writeShort(os, elenEXTT - 4);// size for the folowing data block
2379 int fbyte = 0x1;
2380 if (atime != -1) // mtime and atime
2381 fbyte |= 0x2;
2382 if (ctime != -1) // mtime, atime and ctime
2383 fbyte |= 0x4;
2384 os.write(fbyte); // flags byte
2385 writeInt(os, javaToUnixTime(mtime));
2386 if (atime != -1)
2387 writeInt(os, javaToUnixTime(atime));
2388 if (ctime != -1)
2389 writeInt(os, javaToUnixTime(ctime));
2390 }
2391 if (extra != null) {
2392 writeBytes(os, extra);
2393 }
2394 return LOCHDR + nlen + elen + elen64 + elenNTFS + elenEXTT;
2395 }
2396
2397 // Data Descriptor
2398 int writeEXT(OutputStream os) throws IOException {
2399 writeInt(os, EXTSIG); // EXT header signature
2400 writeInt(os, crc); // crc-32
2401 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2402 writeLong(os, csize);
2403 writeLong(os, size);
2404 return 24;
2405 } else {
2406 writeInt(os, csize); // compressed size
2407 writeInt(os, size); // uncompressed size
2408 return 16;
2409 }
2410 }
2411
2412 // read NTFS, UNIX and ZIP64 data from cen.extra
2413 void readExtra(ZipFileSystem zipfs) throws IOException {
2414 if (extra == null)
2415 return;
2416 int elen = extra.length;
2417 int off = 0;
2454 if (SH(extra, pos + 2) != 24)
2455 break;
2456 // override the loc field, datatime here is
2457 // more "accurate"
2458 mtime = winToJavaTime(LL(extra, pos + 4));
2459 atime = winToJavaTime(LL(extra, pos + 12));
2460 ctime = winToJavaTime(LL(extra, pos + 20));
2461 break;
2462 case EXTID_EXTT:
2463 // spec says the Extened timestamp in cen only has mtime
2464 // need to read the loc to get the extra a/ctime, if flag
2465 // "zipinfo-time" is not specified to false;
2466 // there is performance cost (move up to loc and read) to
2467 // access the loc table foreach entry;
2468 if (zipfs.noExtt) {
2469 if (sz == 5)
2470 mtime = unixToJavaTime(LG(extra, pos + 1));
2471 break;
2472 }
2473 byte[] buf = new byte[LOCHDR];
2474 if (zipfs.readFullyAt(buf, 0, buf.length, locoff)
2475 != buf.length)
2476 throw new ZipException("loc: reading failed");
2477 if (!locSigAt(buf, 0))
2478 throw new ZipException("loc: wrong sig ->"
2479 + Long.toString(getSig(buf, 0), 16));
2480 int locElen = LOCEXT(buf);
2481 if (locElen < 9) // EXTT is at lease 9 bytes
2482 break;
2483 int locNlen = LOCNAM(buf);
2484 buf = new byte[locElen];
2485 if (zipfs.readFullyAt(buf, 0, buf.length, locoff + LOCHDR + locNlen)
2486 != buf.length)
2487 throw new ZipException("loc extra: reading failed");
2488 int locPos = 0;
2489 while (locPos + 4 < buf.length) {
2490 int locTag = SH(buf, locPos);
2491 int locSZ = SH(buf, locPos + 2);
2492 locPos += 4;
2493 if (locTag != EXTID_EXTT) {
2494 locPos += locSZ;
2495 continue;
2496 }
2497 int end = locPos + locSZ - 4;
2498 int flag = CH(buf, locPos++);
2499 if ((flag & 0x1) != 0 && locPos <= end) {
2500 mtime = unixToJavaTime(LG(buf, locPos));
2501 locPos += 4;
2502 }
2503 if ((flag & 0x2) != 0 && locPos <= end) {
2504 atime = unixToJavaTime(LG(buf, locPos));
2505 locPos += 4;
2506 }
2507 if ((flag & 0x4) != 0 && locPos <= end) {
2508 ctime = unixToJavaTime(LG(buf, locPos));
2509 locPos += 4;
2510 }
2511 break;
2512 }
2513 break;
2514 default: // unknown tag
2515 System.arraycopy(extra, off, extra, newOff, sz + 4);
2516 newOff += (sz + 4);
2517 }
2518 off += (sz + 4);
2519 }
2520 if (newOff != 0 && newOff != extra.length)
2521 extra = Arrays.copyOf(extra, newOff);
2522 else
2523 extra = null;
2524 }
2525
2526 @Override
2527 public String toString() {
2528 StringBuilder sb = new StringBuilder(1024);
2529 Formatter fm = new Formatter(sb);
2530 fm.format(" name : %s%n", new String(name));
2531 fm.format(" creationTime : %tc%n", creationTime().toMillis());
2532 fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis());
2533 fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
2534 fm.format(" isRegularFile : %b%n", isRegularFile());
2535 fm.format(" isDirectory : %b%n", isDirectory());
2536 fm.format(" isSymbolicLink : %b%n", isSymbolicLink());
2537 fm.format(" isOther : %b%n", isOther());
2538 fm.format(" fileKey : %s%n", fileKey());
2539 fm.format(" size : %d%n", size());
2540 fm.format(" compressedSize : %d%n", compressedSize());
2541 fm.format(" crc : %x%n", crc());
2542 fm.format(" method : %d%n", method());
2543 if (posixPerms != -1) {
2544 fm.format(" permissions : %s%n", permissions());
2545 }
2546 fm.close();
2547 return sb.toString();
2548 }
2549
2550 ///////// basic file attributes ///////////
2551 @Override
2552 public FileTime creationTime() {
2553 return FileTime.fromMillis(ctime == -1 ? mtime : ctime);
2554 }
2555
2556 @Override
2557 public boolean isDirectory() {
2558 return isDir();
2559 }
2560
2561 @Override
2562 public boolean isOther() {
2563 return false;
2564 }
2565
2566 @Override
2567 public boolean isRegularFile() {
2568 return !isDir();
2569 }
2576 @Override
2577 public FileTime lastModifiedTime() {
2578 return FileTime.fromMillis(mtime);
2579 }
2580
2581 @Override
2582 public long size() {
2583 return size;
2584 }
2585
2586 @Override
2587 public boolean isSymbolicLink() {
2588 return false;
2589 }
2590
2591 @Override
2592 public Object fileKey() {
2593 return null;
2594 }
2595
2596 ///////// posix file attributes ///////////
2597
2598 @Override
2599 public UserPrincipal owner() {
2600 return owner;
2601 }
2602
2603 @Override
2604 public GroupPrincipal group() {
2605 return group;
2606 }
2607
2608 @Override
2609 public Set<PosixFilePermission> permissions() {
2610 return storedPermissions().orElse(Set.copyOf(defaultPermissions));
2611 }
2612
2613 ///////// zip file attributes ///////////
2614
2615 @Override
2616 public long compressedSize() {
2617 return csize;
2618 }
2619
2620 @Override
2621 public long crc() {
2622 return crc;
2623 }
2624
2625 @Override
2626 public int method() {
2627 return method;
2628 }
2629
2630 @Override
2631 public byte[] extra() {
2632 if (extra != null)
2633 return Arrays.copyOf(extra, extra.length);
2634 return null;
2635 }
2636
2637 @Override
2638 public byte[] comment() {
2639 if (comment != null)
2640 return Arrays.copyOf(comment, comment.length);
2641 return null;
2642 }
2643
2644 @Override
2645 public Optional<Set<PosixFilePermission>> storedPermissions() {
2646 Set<PosixFilePermission> perms = null;
2647 if (posixPerms != -1) {
2648 perms = new HashSet<>(PosixFilePermission.values().length);
2649 for (PosixFilePermission perm : PosixFilePermission.values()) {
2650 if ((posixPerms & ZipUtils.permToFlag(perm)) != 0) {
2651 perms.add(perm);
2652 }
2653 }
2654 }
2655 return Optional.ofNullable(perms);
2656 }
2657 }
2658
2659 // ZIP directory has two issues:
2660 // (1) ZIP spec does not require the ZIP file to include
2661 // directory entry
2662 // (2) all entries are not stored/organized in a "tree"
2663 // structure.
2664 // A possible solution is to build the node tree ourself as
2665 // implemented below.
2666
2667 // default time stamp for pseudo entries
2668 private long zfsDefaultTimeStamp = System.currentTimeMillis();
2669
2670 private void removeFromTree(IndexNode inode) {
2671 IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(inode.name)));
2672 IndexNode child = parent.child;
2673 if (child.equals(inode)) {
2674 parent.child = child.sibling;
2675 } else {
|