1 /*
2 * Copyright (c) 2009, 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
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.File;
33 import java.io.FilterOutputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.OutputStream;
37 import java.nio.ByteBuffer;
38 import java.nio.MappedByteBuffer;
39 import java.nio.channels.*;
40 import java.nio.file.*;
41 import java.nio.file.attribute.*;
42 import java.nio.file.spi.*;
43 import java.security.AccessController;
44 import java.security.PrivilegedAction;
45 import java.security.PrivilegedActionException;
46 import java.security.PrivilegedExceptionAction;
47 import java.util.*;
48 import java.util.concurrent.locks.ReadWriteLock;
49 import java.util.concurrent.locks.ReentrantReadWriteLock;
50 import java.util.regex.Pattern;
51 import java.util.zip.CRC32;
52 import java.util.zip.Inflater;
53 import java.util.zip.Deflater;
54 import java.util.zip.InflaterInputStream;
55 import java.util.zip.DeflaterOutputStream;
56 import java.util.zip.ZipException;
57 import static java.lang.Boolean.*;
58 import static jdk.nio.zipfs.ZipConstants.*;
59 import static jdk.nio.zipfs.ZipUtils.*;
60 import static java.nio.file.StandardOpenOption.*;
61 import static java.nio.file.StandardCopyOption.*;
62
63 /**
64 * A FileSystem built on a zip file
65 *
66 * @author Xueming Shen
67 */
68
69 class ZipFileSystem extends FileSystem {
70
71 private final ZipFileSystemProvider provider;
72 private final Path zfpath;
73 final ZipCoder zc;
74 private final ZipPath rootdir;
75 private boolean readOnly = false; // readonly file system
76
77 // configurable by env map
78 private final boolean noExtt; // see readExtra()
79 private final boolean useTempFile; // use a temp file for newOS, default
80 // is to use BAOS for better performance
81 private static final boolean isWindows = AccessController.doPrivileged(
82 (PrivilegedAction<Boolean>) () -> System.getProperty("os.name")
83 .startsWith("Windows"));
84 private final boolean forceEnd64;
85 private final int defaultMethod; // METHOD_STORED if "noCompression=true"
86 // METHOD_DEFLATED otherwise
87
88 ZipFileSystem(ZipFileSystemProvider provider,
89 Path zfpath,
90 Map<String, ?> env) throws IOException
252 return new PathMatcher() {
253 @Override
254 public boolean matches(Path path) {
255 return pattern.matcher(path.toString()).matches();
256 }
257 };
258 }
259
260 @Override
261 public void close() throws IOException {
262 beginWrite();
263 try {
264 if (!isOpen)
265 return;
266 isOpen = false; // set closed
267 } finally {
268 endWrite();
269 }
270 if (!streams.isEmpty()) { // unlock and close all remaining streams
271 Set<InputStream> copy = new HashSet<>(streams);
272 for (InputStream is: copy)
273 is.close();
274 }
275 beginWrite(); // lock and sync
276 try {
277 AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
278 sync(); return null;
279 });
280 ch.close(); // close the ch just in case no update
281 // and sync didn't close the ch
282 } catch (PrivilegedActionException e) {
283 throw (IOException)e.getException();
284 } finally {
285 endWrite();
286 }
287
288 synchronized (inflaters) {
289 for (Inflater inf : inflaters)
290 inf.end();
291 }
292 synchronized (deflaters) {
293 for (Deflater def : deflaters)
294 def.end();
295 }
296
297 IOException ioe = null;
298 synchronized (tmppaths) {
299 for (Path p: tmppaths) {
300 try {
301 AccessController.doPrivileged(
302 (PrivilegedExceptionAction<Boolean>)() -> Files.deleteIfExists(p));
303 } catch (PrivilegedActionException e) {
304 IOException x = (IOException)e.getException();
305 if (ioe == null)
306 ioe = x;
307 else
308 ioe.addSuppressed(x);
309 }
310 }
311 }
312 provider.removeFileSystem(zfpath, this);
313 if (ioe != null)
314 throw ioe;
315 }
316
317 ZipFileAttributes getFileAttributes(byte[] path)
318 throws IOException
319 {
427 list.add(zpath);
428 child = child.sibling;
429 }
430 return list.iterator();
431 } finally {
432 endWrite();
433 }
434 }
435
436 void createDirectory(byte[] dir, FileAttribute<?>... attrs)
437 throws IOException
438 {
439 checkWritable();
440 // dir = toDirectoryPath(dir);
441 beginWrite();
442 try {
443 ensureOpen();
444 if (dir.length == 0 || exists(dir)) // root dir, or exiting dir
445 throw new FileAlreadyExistsException(getString(dir));
446 checkParents(dir);
447 Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED);
448 update(e);
449 } finally {
450 endWrite();
451 }
452 }
453
454 void copyFile(boolean deletesrc, byte[]src, byte[] dst, CopyOption... options)
455 throws IOException
456 {
457 checkWritable();
458 if (Arrays.equals(src, dst))
459 return; // do nothing, src and dst are the same
460
461 beginWrite();
462 try {
463 ensureOpen();
464 Entry eSrc = getEntry(src); // ensureOpen checked
465
466 if (eSrc == null)
467 throw new NoSuchFileException(getString(src));
504 if (!hasCopyAttrs)
505 u.mtime = u.atime= u.ctime = System.currentTimeMillis();
506 update(u);
507 if (deletesrc)
508 updateDelete(eSrc);
509 } finally {
510 endWrite();
511 }
512 }
513
514 // Returns an output stream for writing the contents into the specified
515 // entry.
516 OutputStream newOutputStream(byte[] path, OpenOption... options)
517 throws IOException
518 {
519 checkWritable();
520 boolean hasCreateNew = false;
521 boolean hasCreate = false;
522 boolean hasAppend = false;
523 boolean hasTruncate = false;
524 for (OpenOption opt: options) {
525 if (opt == READ)
526 throw new IllegalArgumentException("READ not allowed");
527 if (opt == CREATE_NEW)
528 hasCreateNew = true;
529 if (opt == CREATE)
530 hasCreate = true;
531 if (opt == APPEND)
532 hasAppend = true;
533 if (opt == TRUNCATE_EXISTING)
534 hasTruncate = true;
535 }
536 if (hasAppend && hasTruncate)
537 throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
538 beginRead(); // only need a readlock, the "update()" will
539 try { // try to obtain a writelock when the os is
540 ensureOpen(); // being closed.
541 Entry e = getEntry(path);
542 if (e != null) {
543 if (e.isDir() || hasCreateNew)
544 throw new FileAlreadyExistsException(getString(path));
649 SeekableByteChannel sbc =
650 new EntryOutputChannel(new Entry(e, Entry.NEW));
651 if (options.contains(APPEND)) {
652 try (InputStream is = getInputStream(e)) { // copyover
653 byte[] buf = new byte[8192];
654 ByteBuffer bb = ByteBuffer.wrap(buf);
655 int n;
656 while ((n = is.read(buf)) != -1) {
657 bb.position(0);
658 bb.limit(n);
659 sbc.write(bb);
660 }
661 }
662 }
663 return sbc;
664 }
665 if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
666 throw new NoSuchFileException(getString(path));
667 checkParents(path);
668 return new EntryOutputChannel(
669 new Entry(path, Entry.NEW, false, getCompressMethod(attrs)));
670
671 } finally {
672 endRead();
673 }
674 } else {
675 beginRead();
676 try {
677 ensureOpen();
678 Entry e = getEntry(path);
679 if (e == null || e.isDir())
680 throw new NoSuchFileException(getString(path));
681 try (InputStream is = getInputStream(e)) {
682 // TBD: if (e.size < NNNNN);
683 return new ByteArrayChannel(is.readAllBytes(), true);
684 }
685 } finally {
686 endRead();
687 }
688 }
689 }
714 }
715 } else {
716 if (options.contains(StandardOpenOption.CREATE_NEW)) {
717 throw new FileAlreadyExistsException(getString(path));
718 }
719 if (e.isDir())
720 throw new FileAlreadyExistsException("directory <"
721 + getString(path) + "> exists");
722 }
723 options = new HashSet<>(options);
724 options.remove(StandardOpenOption.CREATE_NEW); // for tmpfile
725 } else if (e == null || e.isDir()) {
726 throw new NoSuchFileException(getString(path));
727 }
728
729 final boolean isFCH = (e != null && e.type == Entry.FILECH);
730 final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
731 final FileChannel fch = tmpfile.getFileSystem()
732 .provider()
733 .newFileChannel(tmpfile, options, attrs);
734 final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH);
735 if (forWrite) {
736 u.flag = FLAG_DATADESCR;
737 u.method = getCompressMethod(attrs);
738 }
739 // is there a better way to hook into the FileChannel's close method?
740 return new FileChannel() {
741 public int write(ByteBuffer src) throws IOException {
742 return fch.write(src);
743 }
744 public long write(ByteBuffer[] srcs, int offset, int length)
745 throws IOException
746 {
747 return fch.write(srcs, offset, length);
748 }
749 public long position() throws IOException {
750 return fch.position();
751 }
752 public FileChannel position(long newPosition)
753 throws IOException
754 {
1460
1461 private InputStream getInputStream(Entry e)
1462 throws IOException
1463 {
1464 InputStream eis = null;
1465
1466 if (e.type == Entry.NEW) {
1467 // now bytes & file is uncompressed.
1468 if (e.bytes != null)
1469 return new ByteArrayInputStream(e.bytes);
1470 else if (e.file != null)
1471 return Files.newInputStream(e.file);
1472 else
1473 throw new ZipException("update entry data is missing");
1474 } else if (e.type == Entry.FILECH) {
1475 // FILECH result is un-compressed.
1476 eis = Files.newInputStream(e.file);
1477 // TBD: wrap to hook close()
1478 // streams.add(eis);
1479 return eis;
1480 } else { // untouced CEN or COPY
1481 eis = new EntryInputStream(e, ch);
1482 }
1483 if (e.method == METHOD_DEFLATED) {
1484 // MORE: Compute good size for inflater stream:
1485 long bufSize = e.size + 2; // Inflater likes a bit of slack
1486 if (bufSize > 65536)
1487 bufSize = 8192;
1488 final long size = e.size;
1489 eis = new InflaterInputStream(eis, getInflater(), (int)bufSize) {
1490 private boolean isClosed = false;
1491 public void close() throws IOException {
1492 if (!isClosed) {
1493 releaseInflater(inf);
1494 this.in.close();
1495 isClosed = true;
1496 streams.remove(this);
1497 }
1498 }
1499 // Override fill() method to provide an extra "dummy" byte
1500 // at the end of the input stream. This is required when
1522 return avail > (long) Integer.MAX_VALUE ?
1523 Integer.MAX_VALUE : (int) avail;
1524 }
1525 };
1526 } else if (e.method == METHOD_STORED) {
1527 // TBD: wrap/ it does not seem necessary
1528 } else {
1529 throw new ZipException("invalid compression method");
1530 }
1531 streams.add(eis);
1532 return eis;
1533 }
1534
1535 // Inner class implementing the input stream used to read
1536 // a (possibly compressed) zip file entry.
1537 private class EntryInputStream extends InputStream {
1538 private final SeekableByteChannel zfch; // local ref to zipfs's "ch". zipfs.ch might
1539 // point to a new channel after sync()
1540 private long pos; // current position within entry data
1541 protected long rem; // number of remaining bytes within entry
1542 protected final long size; // uncompressed size of this entry
1543
1544 EntryInputStream(Entry e, SeekableByteChannel zfch)
1545 throws IOException
1546 {
1547 this.zfch = zfch;
1548 rem = e.csize;
1549 size = e.size;
1550 pos = e.locoff;
1551 if (pos == -1) {
1552 Entry e2 = getEntry(e.name);
1553 if (e2 == null) {
1554 throw new ZipException("invalid loc for entry <" + e.name + ">");
1555 }
1556 pos = e2.locoff;
1557 }
1558 pos = -pos; // lazy initialize the real data offset
1559 }
1560
1561 public int read(byte b[], int off, int len) throws IOException {
1562 ensureOpen();
1563 initDataPos();
1564 if (rem == 0) {
1565 return -1;
1566 }
1567 if (len <= 0) {
1568 return 0;
1569 }
1596 return -1;
1597 }
1598 }
1599
1600 public long skip(long n) throws IOException {
1601 ensureOpen();
1602 if (n > rem)
1603 n = rem;
1604 pos += n;
1605 rem -= n;
1606 if (rem == 0) {
1607 close();
1608 }
1609 return n;
1610 }
1611
1612 public int available() {
1613 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
1614 }
1615
1616 public long size() {
1617 return size;
1618 }
1619
1620 public void close() {
1621 rem = 0;
1622 streams.remove(this);
1623 }
1624
1625 private void initDataPos() throws IOException {
1626 if (pos <= 0) {
1627 pos = -pos + locpos;
1628 byte[] buf = new byte[LOCHDR];
1629 if (readFullyAt(buf, 0, buf.length, pos) != LOCHDR) {
1630 throw new ZipException("invalid loc " + pos + " for entry reading");
1631 }
1632 pos += LOCHDR + LOCNAM(buf) + LOCEXT(buf);
1633 }
1634 }
1635 }
1636
1637 static void zerror(String msg) throws ZipException {
1638 throw new ZipException(msg);
1639 }
1655 return new Inflater(true);
1656 }
1657 }
1658 }
1659
1660 // Releases the specified inflater to the list of available inflaters.
1661 private void releaseInflater(Inflater inf) {
1662 synchronized (inflaters) {
1663 if (inflaters.size() < MAX_FLATER) {
1664 inf.reset();
1665 inflaters.add(inf);
1666 } else {
1667 inf.end();
1668 }
1669 }
1670 }
1671
1672 // List of available Deflater objects for compression
1673 private final List<Deflater> deflaters = new ArrayList<>();
1674
1675 // Gets an deflater from the list of available deflaters or allocates
1676 // a new one.
1677 private Deflater getDeflater() {
1678 synchronized (deflaters) {
1679 int size = deflaters.size();
1680 if (size > 0) {
1681 Deflater def = deflaters.remove(size - 1);
1682 return def;
1683 } else {
1684 return new Deflater(Deflater.DEFAULT_COMPRESSION, true);
1685 }
1686 }
1687 }
1688
1689 // Releases the specified inflater to the list of available inflaters.
1690 private void releaseDeflater(Deflater def) {
1691 synchronized (deflaters) {
1692 if (inflaters.size() < MAX_FLATER) {
1693 def.reset();
1694 deflaters.add(def);
1695 } else {
1696 def.end();
1697 }
1698 }
1699 }
1700
1701 // End of central directory record
1702 static class END {
1703 // these 2 fields are not used by anyone and write() uses "0"
1704 // int disknum;
1705 // int sdisknum;
1706 int endsub; // endsub
1707 int centot; // 4 bytes
1708 long cenlen; // 4 bytes
1709 long cenoff; // 4 bytes
1710 int comlen; // comment length
1711 byte[] comment;
1712
1713 /* members of Zip64 end of central directory locator */
1714 // int diskNum;
1715 long endpos;
1716 // int disktot;
1717
1718 void write(OutputStream os, long offset, boolean forceEnd64) throws IOException {
1719 boolean hasZip64 = forceEnd64; // false;
1720 long xlen = cenlen;
1848
1849 IndexNode() {}
1850 IndexNode sibling;
1851 IndexNode child; // 1st child
1852 }
1853
1854 static class Entry extends IndexNode implements ZipFileAttributes {
1855
1856 static final int CEN = 1; // entry read from cen
1857 static final int NEW = 2; // updated contents in bytes or file
1858 static final int FILECH = 3; // fch update in "file"
1859 static final int COPY = 4; // copy of a CEN entry
1860
1861 byte[] bytes; // updated content bytes
1862 Path file; // use tmp file to store bytes;
1863 int type = CEN; // default is the entry read from cen
1864
1865 // entry attributes
1866 int version;
1867 int flag;
1868 int method = -1; // compression method
1869 long mtime = -1; // last modification time (in DOS time)
1870 long atime = -1; // last access time
1871 long ctime = -1; // create time
1872 long crc = -1; // crc-32 of entry data
1873 long csize = -1; // compressed size of entry data
1874 long size = -1; // uncompressed size of entry data
1875 byte[] extra;
1876
1877 // cen
1878
1879 // these fields are not used by anyone and writeCEN uses "0"
1880 // int versionMade;
1881 // int disk;
1882 // int attrs;
1883 // long attrsEx;
1884 long locoff;
1885 byte[] comment;
1886
1887 Entry() {}
1888
1889 Entry(byte[] name, boolean isdir, int method) {
1890 name(name);
1891 this.isdir = isdir;
1892 this.mtime = this.ctime = this.atime = System.currentTimeMillis();
1893 this.crc = 0;
1894 this.size = 0;
1895 this.csize = 0;
1896 this.method = method;
1897 }
1898
1899 Entry(byte[] name, int type, boolean isdir, int method) {
1900 this(name, isdir, method);
1901 this.type = type;
1902 }
1903
1904 Entry (Entry e, int type) {
1905 name(e.name);
1906 this.isdir = e.isdir;
1907 this.version = e.version;
1908 this.ctime = e.ctime;
1909 this.atime = e.atime;
1910 this.mtime = e.mtime;
1911 this.crc = e.crc;
1912 this.size = e.size;
1913 this.csize = e.csize;
1914 this.method = e.method;
1915 this.extra = e.extra;
1916 /*
1917 this.versionMade = e.versionMade;
1918 this.disk = e.disk;
1919 this.attrs = e.attrs;
1920 this.attrsEx = e.attrsEx;
1921 */
1922 this.locoff = e.locoff;
1923 this.comment = e.comment;
1924 this.type = type;
1925 }
1926
1927 Entry (byte[] name, Path file, int type) {
1928 this(name, type, false, METHOD_STORED);
1929 this.file = file;
1930 }
1931
1932 int version() throws ZipException {
1933 if (method == METHOD_DEFLATED)
1934 return 20;
1935 else if (method == METHOD_STORED)
1936 return 10;
1937 throw new ZipException("unsupported compression method");
1938 }
1939
1940 ///////////////////// CEN //////////////////////
1941 static Entry readCEN(ZipFileSystem zipfs, IndexNode inode)
1942 throws IOException
1943 {
1944 return new Entry().cen(zipfs, inode);
1945 }
1946
1947 private Entry cen(ZipFileSystem zipfs, IndexNode inode)
1948 throws IOException
1949 {
1950 byte[] cen = zipfs.cen;
1951 int pos = inode.pos;
1952 if (!cenSigAt(cen, pos))
1953 zerror("invalid CEN header (bad signature)");
1954 version = CENVER(cen, pos);
1955 flag = CENFLG(cen, pos);
1956 method = CENHOW(cen, pos);
1957 mtime = dosToJavaTime(CENTIM(cen, pos));
1958 crc = CENCRC(cen, pos);
1959 csize = CENSIZ(cen, pos);
1960 size = CENLEN(cen, pos);
1961 int nlen = CENNAM(cen, pos);
1962 int elen = CENEXT(cen, pos);
1963 int clen = CENCOM(cen, pos);
1964 /*
1965 versionMade = CENVEM(cen, pos);
1966 disk = CENDSK(cen, pos);
1967 attrs = CENATT(cen, pos);
1968 attrsEx = CENATX(cen, pos);
1969 */
1970 locoff = CENOFF(cen, pos);
1971 pos += CENHDR;
1972 this.name = inode.name;
1973 this.isdir = inode.isdir;
1974 this.hashcode = inode.hashcode;
1975
1976 pos += nlen;
1977 if (elen > 0) {
1978 extra = Arrays.copyOfRange(cen, pos, pos + elen);
1979 pos += elen;
1980 readExtra(zipfs);
1981 }
1982 if (clen > 0) {
1983 comment = Arrays.copyOfRange(cen, pos, pos + clen);
1984 }
1985 return this;
1986 }
1987
1988 int writeCEN(OutputStream os) throws IOException
1989 {
1990 int written = CENHDR;
1991 int version0 = version();
1992 long csize0 = csize;
1993 long size0 = size;
1994 long locoff0 = locoff;
1995 int elen64 = 0; // extra for ZIP64
1996 int elenNTFS = 0; // extra for NTFS (a/c/mtime)
1997 int elenEXTT = 0; // extra for Extended Timestamp
1998 boolean foundExtraTime = false; // if time stamp NTFS, EXTT present
1999
2000 byte[] zname = isdir ? toDirectoryPath(name) : name;
2001
2002 // confirm size/length
2003 int nlen = (zname != null) ? zname.length - 1 : 0; // name has [0] as "slash"
2004 int elen = (extra != null) ? extra.length : 0;
2005 int eoff = 0;
2006 int clen = (comment != null) ? comment.length : 0;
2007 if (csize >= ZIP64_MINVAL) {
2008 csize0 = ZIP64_MINVAL;
2009 elen64 += 8; // csize(8)
2010 }
2011 if (size >= ZIP64_MINVAL) {
2012 size0 = ZIP64_MINVAL; // size(8)
2013 elen64 += 8;
2014 }
2015 if (locoff >= ZIP64_MINVAL) {
2016 locoff0 = ZIP64_MINVAL;
2017 elen64 += 8; // offset(8)
2018 }
2019 if (elen64 != 0) {
2020 elen64 += 4; // header and data sz 4 bytes
2021 }
2022 while (eoff + 4 < elen) {
2023 int tag = SH(extra, eoff);
2024 int sz = SH(extra, eoff + 2);
2025 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2026 foundExtraTime = true;
2027 }
2028 eoff += (4 + sz);
2029 }
2030 if (!foundExtraTime) {
2031 if (isWindows) { // use NTFS
2032 elenNTFS = 36; // total 36 bytes
2033 } else { // Extended Timestamp otherwise
2034 elenEXTT = 9; // only mtime in cen
2035 }
2036 }
2037 writeInt(os, CENSIG); // CEN header signature
2038 if (elen64 != 0) {
2039 writeShort(os, 45); // ver 4.5 for zip64
2040 writeShort(os, 45);
2041 } else {
2042 writeShort(os, version0); // version made by
2043 writeShort(os, version0); // version needed to extract
2044 }
2045 writeShort(os, flag); // general purpose bit flag
2046 writeShort(os, method); // compression method
2047 // last modification time
2048 writeInt(os, (int)javaToDosTime(mtime));
2049 writeInt(os, crc); // crc-32
2050 writeInt(os, csize0); // compressed size
2051 writeInt(os, size0); // uncompressed size
2052 writeShort(os, nlen);
2053 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2054
2055 if (comment != null) {
2056 writeShort(os, Math.min(clen, 0xffff));
2057 } else {
2058 writeShort(os, 0);
2059 }
2060 writeShort(os, 0); // starting disk number
2061 writeShort(os, 0); // internal file attributes (unused)
2062 writeInt(os, 0); // external file attributes (unused)
2063 writeInt(os, locoff0); // relative offset of local header
2064 writeBytes(os, zname, 1, nlen);
2065 if (elen64 != 0) {
2066 writeShort(os, EXTID_ZIP64);// Zip64 extra
2067 writeShort(os, elen64 - 4); // size of "this" extra block
2068 if (size0 == ZIP64_MINVAL)
2069 writeLong(os, size);
2070 if (csize0 == ZIP64_MINVAL)
2071 writeLong(os, csize);
2072 if (locoff0 == ZIP64_MINVAL)
2073 writeLong(os, locoff);
2074 }
2075 if (elenNTFS != 0) {
2076 writeShort(os, EXTID_NTFS);
2077 writeShort(os, elenNTFS - 4);
2078 writeInt(os, 0); // reserved
2079 writeShort(os, 0x0001); // NTFS attr tag
2080 writeShort(os, 24);
2081 writeLong(os, javaToWinTime(mtime));
2082 writeLong(os, javaToWinTime(atime));
2083 writeLong(os, javaToWinTime(ctime));
2084 }
2085 if (elenEXTT != 0) {
2086 writeShort(os, EXTID_EXTT);
2087 writeShort(os, elenEXTT - 4);
2088 if (ctime == -1)
2089 os.write(0x3); // mtime and atime
2090 else
2091 os.write(0x7); // mtime, atime and ctime
2092 writeInt(os, javaToUnixTime(mtime));
2093 }
2094 if (extra != null) // whatever not recognized
2095 writeBytes(os, extra);
2096 if (comment != null) //TBD: 0, Math.min(commentBytes.length, 0xffff));
2097 writeBytes(os, comment);
2098 return CENHDR + nlen + elen + clen + elen64 + elenNTFS + elenEXTT;
2099 }
2100
2101 ///////////////////// LOC //////////////////////
2102
2103 int writeLOC(OutputStream os) throws IOException {
2104 writeInt(os, LOCSIG); // LOC header signature
2105 int version = version();
2106
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 if ((flag & FLAG_DATADESCR) != 0) {
2116 writeShort(os, version()); // version needed to extract
2117 writeShort(os, flag); // general purpose bit flag
2118 writeShort(os, method); // compression method
2119 // last modification time
2120 writeInt(os, (int)javaToDosTime(mtime));
2121 // store size, uncompressed size, and crc-32 in data descriptor
2122 // immediately following compressed entry data
2123 writeInt(os, 0);
2124 writeInt(os, 0);
2125 writeInt(os, 0);
2126 } else {
2127 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2128 elen64 = 20; //headid(2) + size(2) + size(8) + csize(8)
2129 writeShort(os, 45); // ver 4.5 for zip64
2130 } else {
2131 writeShort(os, version()); // version needed to extract
2132 }
2133 writeShort(os, flag); // general purpose bit flag
2134 writeShort(os, method); // compression method
2135 // last modification time
2136 writeInt(os, (int)javaToDosTime(mtime));
2137 writeInt(os, crc); // crc-32
2138 if (elen64 != 0) {
2139 writeInt(os, ZIP64_MINVAL);
2140 writeInt(os, ZIP64_MINVAL);
2141 } else {
2142 writeInt(os, csize); // compressed size
2143 writeInt(os, size); // uncompressed size
2144 }
2145 }
2146 while (eoff + 4 < elen) {
2147 int tag = SH(extra, eoff);
2148 int sz = SH(extra, eoff + 2);
2149 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2150 foundExtraTime = true;
2151 }
2152 eoff += (4 + sz);
2153 }
2154 if (!foundExtraTime) {
2155 if (isWindows) {
2156 elenNTFS = 36; // NTFS, total 36 bytes
2157 } else { // on unix use "ext time"
2158 elenEXTT = 9;
2159 if (atime != -1)
2160 elenEXTT += 4;
2161 if (ctime != -1)
2162 elenEXTT += 4;
2163 }
2164 }
2165 writeShort(os, nlen);
2166 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2167 writeBytes(os, zname, 1, nlen);
2168 if (elen64 != 0) {
2169 writeShort(os, EXTID_ZIP64);
2170 writeShort(os, 16);
2171 writeLong(os, size);
2172 writeLong(os, csize);
2173 }
2174 if (elenNTFS != 0) {
2175 writeShort(os, EXTID_NTFS);
2176 writeShort(os, elenNTFS - 4);
2177 writeInt(os, 0); // reserved
2178 writeShort(os, 0x0001); // NTFS attr tag
2179 writeShort(os, 24);
2180 writeLong(os, javaToWinTime(mtime));
2181 writeLong(os, javaToWinTime(atime));
2182 writeLong(os, javaToWinTime(ctime));
2183 }
2184 if (elenEXTT != 0) {
2185 writeShort(os, EXTID_EXTT);
2186 writeShort(os, elenEXTT - 4);// size for the folowing data block
2187 int fbyte = 0x1;
2188 if (atime != -1) // mtime and atime
2401 return Arrays.copyOf(comment, comment.length);
2402 return null;
2403 }
2404
2405 public String toString() {
2406 StringBuilder sb = new StringBuilder(1024);
2407 Formatter fm = new Formatter(sb);
2408 fm.format(" name : %s%n", new String(name));
2409 fm.format(" creationTime : %tc%n", creationTime().toMillis());
2410 fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis());
2411 fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
2412 fm.format(" isRegularFile : %b%n", isRegularFile());
2413 fm.format(" isDirectory : %b%n", isDirectory());
2414 fm.format(" isSymbolicLink : %b%n", isSymbolicLink());
2415 fm.format(" isOther : %b%n", isOther());
2416 fm.format(" fileKey : %s%n", fileKey());
2417 fm.format(" size : %d%n", size());
2418 fm.format(" compressedSize : %d%n", compressedSize());
2419 fm.format(" crc : %x%n", crc());
2420 fm.format(" method : %d%n", method());
2421 fm.close();
2422 return sb.toString();
2423 }
2424 }
2425
2426 // ZIP directory has two issues:
2427 // (1) ZIP spec does not require the ZIP file to include
2428 // directory entry
2429 // (2) all entries are not stored/organized in a "tree"
2430 // structure.
2431 // A possible solution is to build the node tree ourself as
2432 // implemented below.
2433 private IndexNode root;
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 {
2444 IndexNode last = child;
2445 while ((child = child.sibling) != null) {
2446 if (child.equals(inode)) {
2447 last.sibling = child.sibling;
2448 break;
2449 } else {
2450 last = child;
2451 }
2452 }
2453 }
|
1 /*
2 * Copyright (c) 2009, 2018, 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.nio.zipfs;
27
28 import static java.lang.Boolean.TRUE;
29 import static jdk.nio.zipfs.ZipConstants.*;
30 import static jdk.nio.zipfs.ZipUtils.*;
31 import static java.nio.file.StandardOpenOption.*;
32 import static java.nio.file.StandardCopyOption.*;
33
34 import java.io.BufferedOutputStream;
35 import java.io.ByteArrayInputStream;
36 import java.io.ByteArrayOutputStream;
37 import java.io.EOFException;
38 import java.io.FilterOutputStream;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.OutputStream;
42 import java.nio.ByteBuffer;
43 import java.nio.MappedByteBuffer;
44 import java.nio.channels.FileChannel;
45 import java.nio.channels.FileLock;
46 import java.nio.channels.ReadableByteChannel;
47 import java.nio.channels.SeekableByteChannel;
48 import java.nio.channels.WritableByteChannel;
49 import java.nio.file.*;
50 import java.nio.file.attribute.FileAttribute;
51 import java.nio.file.attribute.FileTime;
52 import java.nio.file.attribute.GroupPrincipal;
53 import java.nio.file.attribute.PosixFilePermission;
54 import java.nio.file.attribute.UserPrincipal;
55 import java.nio.file.attribute.UserPrincipalLookupService;
56 import java.nio.file.spi.FileSystemProvider;
57 import java.security.AccessController;
58 import java.security.PrivilegedAction;
59 import java.security.PrivilegedActionException;
60 import java.security.PrivilegedExceptionAction;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collections;
64 import java.util.Formatter;
65 import java.util.HashSet;
66 import java.util.Iterator;
67 import java.util.LinkedHashMap;
68 import java.util.List;
69 import java.util.Map;
70 import java.util.Objects;
71 import java.util.Set;
72 import java.util.concurrent.locks.ReadWriteLock;
73 import java.util.concurrent.locks.ReentrantReadWriteLock;
74 import java.util.regex.Pattern;
75 import java.util.zip.CRC32;
76 import java.util.zip.Deflater;
77 import java.util.zip.DeflaterOutputStream;
78 import java.util.zip.Inflater;
79 import java.util.zip.InflaterInputStream;
80 import java.util.zip.ZipException;
81
82 /**
83 * A FileSystem built on a zip file
84 *
85 * @author Xueming Shen
86 */
87 class ZipFileSystem extends FileSystem {
88 private final ZipFileSystemProvider provider;
89 private final Path zfpath;
90 final ZipCoder zc;
91 private final ZipPath rootdir;
92 private boolean readOnly = false; // readonly file system
93
94 // configurable by env map
95 private final boolean noExtt; // see readExtra()
96 private final boolean useTempFile; // use a temp file for newOS, default
97 // is to use BAOS for better performance
98 private static final boolean isWindows = AccessController.doPrivileged(
99 (PrivilegedAction<Boolean>) () -> System.getProperty("os.name")
100 .startsWith("Windows"));
101 private final boolean forceEnd64;
102 private final int defaultMethod; // METHOD_STORED if "noCompression=true"
103 // METHOD_DEFLATED otherwise
104
105 ZipFileSystem(ZipFileSystemProvider provider,
106 Path zfpath,
107 Map<String, ?> env) throws IOException
269 return new PathMatcher() {
270 @Override
271 public boolean matches(Path path) {
272 return pattern.matcher(path.toString()).matches();
273 }
274 };
275 }
276
277 @Override
278 public void close() throws IOException {
279 beginWrite();
280 try {
281 if (!isOpen)
282 return;
283 isOpen = false; // set closed
284 } finally {
285 endWrite();
286 }
287 if (!streams.isEmpty()) { // unlock and close all remaining streams
288 Set<InputStream> copy = new HashSet<>(streams);
289 for (InputStream is : copy)
290 is.close();
291 }
292 beginWrite(); // lock and sync
293 try {
294 AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
295 sync(); return null;
296 });
297 ch.close(); // close the ch just in case no update
298 // and sync didn't close the ch
299 } catch (PrivilegedActionException e) {
300 throw (IOException)e.getException();
301 } finally {
302 endWrite();
303 }
304
305 synchronized (inflaters) {
306 for (Inflater inf : inflaters)
307 inf.end();
308 }
309 synchronized (deflaters) {
310 for (Deflater def : deflaters)
311 def.end();
312 }
313
314 IOException ioe = null;
315 synchronized (tmppaths) {
316 for (Path p : tmppaths) {
317 try {
318 AccessController.doPrivileged(
319 (PrivilegedExceptionAction<Boolean>)() -> Files.deleteIfExists(p));
320 } catch (PrivilegedActionException e) {
321 IOException x = (IOException)e.getException();
322 if (ioe == null)
323 ioe = x;
324 else
325 ioe.addSuppressed(x);
326 }
327 }
328 }
329 provider.removeFileSystem(zfpath, this);
330 if (ioe != null)
331 throw ioe;
332 }
333
334 ZipFileAttributes getFileAttributes(byte[] path)
335 throws IOException
336 {
444 list.add(zpath);
445 child = child.sibling;
446 }
447 return list.iterator();
448 } finally {
449 endWrite();
450 }
451 }
452
453 void createDirectory(byte[] dir, FileAttribute<?>... attrs)
454 throws IOException
455 {
456 checkWritable();
457 // dir = toDirectoryPath(dir);
458 beginWrite();
459 try {
460 ensureOpen();
461 if (dir.length == 0 || exists(dir)) // root dir, or exiting dir
462 throw new FileAlreadyExistsException(getString(dir));
463 checkParents(dir);
464 Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED, attrs);
465 update(e);
466 } finally {
467 endWrite();
468 }
469 }
470
471 void copyFile(boolean deletesrc, byte[]src, byte[] dst, CopyOption... options)
472 throws IOException
473 {
474 checkWritable();
475 if (Arrays.equals(src, dst))
476 return; // do nothing, src and dst are the same
477
478 beginWrite();
479 try {
480 ensureOpen();
481 Entry eSrc = getEntry(src); // ensureOpen checked
482
483 if (eSrc == null)
484 throw new NoSuchFileException(getString(src));
521 if (!hasCopyAttrs)
522 u.mtime = u.atime= u.ctime = System.currentTimeMillis();
523 update(u);
524 if (deletesrc)
525 updateDelete(eSrc);
526 } finally {
527 endWrite();
528 }
529 }
530
531 // Returns an output stream for writing the contents into the specified
532 // entry.
533 OutputStream newOutputStream(byte[] path, OpenOption... options)
534 throws IOException
535 {
536 checkWritable();
537 boolean hasCreateNew = false;
538 boolean hasCreate = false;
539 boolean hasAppend = false;
540 boolean hasTruncate = false;
541 for (OpenOption opt : options) {
542 if (opt == READ)
543 throw new IllegalArgumentException("READ not allowed");
544 if (opt == CREATE_NEW)
545 hasCreateNew = true;
546 if (opt == CREATE)
547 hasCreate = true;
548 if (opt == APPEND)
549 hasAppend = true;
550 if (opt == TRUNCATE_EXISTING)
551 hasTruncate = true;
552 }
553 if (hasAppend && hasTruncate)
554 throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
555 beginRead(); // only need a readlock, the "update()" will
556 try { // try to obtain a writelock when the os is
557 ensureOpen(); // being closed.
558 Entry e = getEntry(path);
559 if (e != null) {
560 if (e.isDir() || hasCreateNew)
561 throw new FileAlreadyExistsException(getString(path));
666 SeekableByteChannel sbc =
667 new EntryOutputChannel(new Entry(e, Entry.NEW));
668 if (options.contains(APPEND)) {
669 try (InputStream is = getInputStream(e)) { // copyover
670 byte[] buf = new byte[8192];
671 ByteBuffer bb = ByteBuffer.wrap(buf);
672 int n;
673 while ((n = is.read(buf)) != -1) {
674 bb.position(0);
675 bb.limit(n);
676 sbc.write(bb);
677 }
678 }
679 }
680 return sbc;
681 }
682 if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
683 throw new NoSuchFileException(getString(path));
684 checkParents(path);
685 return new EntryOutputChannel(
686 new Entry(path, Entry.NEW, false, getCompressMethod(attrs), attrs));
687
688 } finally {
689 endRead();
690 }
691 } else {
692 beginRead();
693 try {
694 ensureOpen();
695 Entry e = getEntry(path);
696 if (e == null || e.isDir())
697 throw new NoSuchFileException(getString(path));
698 try (InputStream is = getInputStream(e)) {
699 // TBD: if (e.size < NNNNN);
700 return new ByteArrayChannel(is.readAllBytes(), true);
701 }
702 } finally {
703 endRead();
704 }
705 }
706 }
731 }
732 } else {
733 if (options.contains(StandardOpenOption.CREATE_NEW)) {
734 throw new FileAlreadyExistsException(getString(path));
735 }
736 if (e.isDir())
737 throw new FileAlreadyExistsException("directory <"
738 + getString(path) + "> exists");
739 }
740 options = new HashSet<>(options);
741 options.remove(StandardOpenOption.CREATE_NEW); // for tmpfile
742 } else if (e == null || e.isDir()) {
743 throw new NoSuchFileException(getString(path));
744 }
745
746 final boolean isFCH = (e != null && e.type == Entry.FILECH);
747 final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
748 final FileChannel fch = tmpfile.getFileSystem()
749 .provider()
750 .newFileChannel(tmpfile, options, attrs);
751 final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH, attrs);
752 if (forWrite) {
753 u.flag = FLAG_DATADESCR;
754 u.method = getCompressMethod(attrs);
755 }
756 // is there a better way to hook into the FileChannel's close method?
757 return new FileChannel() {
758 public int write(ByteBuffer src) throws IOException {
759 return fch.write(src);
760 }
761 public long write(ByteBuffer[] srcs, int offset, int length)
762 throws IOException
763 {
764 return fch.write(srcs, offset, length);
765 }
766 public long position() throws IOException {
767 return fch.position();
768 }
769 public FileChannel position(long newPosition)
770 throws IOException
771 {
1477
1478 private InputStream getInputStream(Entry e)
1479 throws IOException
1480 {
1481 InputStream eis = null;
1482
1483 if (e.type == Entry.NEW) {
1484 // now bytes & file is uncompressed.
1485 if (e.bytes != null)
1486 return new ByteArrayInputStream(e.bytes);
1487 else if (e.file != null)
1488 return Files.newInputStream(e.file);
1489 else
1490 throw new ZipException("update entry data is missing");
1491 } else if (e.type == Entry.FILECH) {
1492 // FILECH result is un-compressed.
1493 eis = Files.newInputStream(e.file);
1494 // TBD: wrap to hook close()
1495 // streams.add(eis);
1496 return eis;
1497 } else { // untouched CEN or COPY
1498 eis = new EntryInputStream(e, ch);
1499 }
1500 if (e.method == METHOD_DEFLATED) {
1501 // MORE: Compute good size for inflater stream:
1502 long bufSize = e.size + 2; // Inflater likes a bit of slack
1503 if (bufSize > 65536)
1504 bufSize = 8192;
1505 final long size = e.size;
1506 eis = new InflaterInputStream(eis, getInflater(), (int)bufSize) {
1507 private boolean isClosed = false;
1508 public void close() throws IOException {
1509 if (!isClosed) {
1510 releaseInflater(inf);
1511 this.in.close();
1512 isClosed = true;
1513 streams.remove(this);
1514 }
1515 }
1516 // Override fill() method to provide an extra "dummy" byte
1517 // at the end of the input stream. This is required when
1539 return avail > (long) Integer.MAX_VALUE ?
1540 Integer.MAX_VALUE : (int) avail;
1541 }
1542 };
1543 } else if (e.method == METHOD_STORED) {
1544 // TBD: wrap/ it does not seem necessary
1545 } else {
1546 throw new ZipException("invalid compression method");
1547 }
1548 streams.add(eis);
1549 return eis;
1550 }
1551
1552 // Inner class implementing the input stream used to read
1553 // a (possibly compressed) zip file entry.
1554 private class EntryInputStream extends InputStream {
1555 private final SeekableByteChannel zfch; // local ref to zipfs's "ch". zipfs.ch might
1556 // point to a new channel after sync()
1557 private long pos; // current position within entry data
1558 protected long rem; // number of remaining bytes within entry
1559
1560 EntryInputStream(Entry e, SeekableByteChannel zfch)
1561 throws IOException
1562 {
1563 this.zfch = zfch;
1564 rem = e.csize;
1565 pos = e.locoff;
1566 if (pos == -1) {
1567 Entry e2 = getEntry(e.name);
1568 if (e2 == null) {
1569 throw new ZipException("invalid loc for entry <" + e.name + ">");
1570 }
1571 pos = e2.locoff;
1572 }
1573 pos = -pos; // lazy initialize the real data offset
1574 }
1575
1576 public int read(byte b[], int off, int len) throws IOException {
1577 ensureOpen();
1578 initDataPos();
1579 if (rem == 0) {
1580 return -1;
1581 }
1582 if (len <= 0) {
1583 return 0;
1584 }
1611 return -1;
1612 }
1613 }
1614
1615 public long skip(long n) throws IOException {
1616 ensureOpen();
1617 if (n > rem)
1618 n = rem;
1619 pos += n;
1620 rem -= n;
1621 if (rem == 0) {
1622 close();
1623 }
1624 return n;
1625 }
1626
1627 public int available() {
1628 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
1629 }
1630
1631 public void close() {
1632 rem = 0;
1633 streams.remove(this);
1634 }
1635
1636 private void initDataPos() throws IOException {
1637 if (pos <= 0) {
1638 pos = -pos + locpos;
1639 byte[] buf = new byte[LOCHDR];
1640 if (readFullyAt(buf, 0, buf.length, pos) != LOCHDR) {
1641 throw new ZipException("invalid loc " + pos + " for entry reading");
1642 }
1643 pos += LOCHDR + LOCNAM(buf) + LOCEXT(buf);
1644 }
1645 }
1646 }
1647
1648 static void zerror(String msg) throws ZipException {
1649 throw new ZipException(msg);
1650 }
1666 return new Inflater(true);
1667 }
1668 }
1669 }
1670
1671 // Releases the specified inflater to the list of available inflaters.
1672 private void releaseInflater(Inflater inf) {
1673 synchronized (inflaters) {
1674 if (inflaters.size() < MAX_FLATER) {
1675 inf.reset();
1676 inflaters.add(inf);
1677 } else {
1678 inf.end();
1679 }
1680 }
1681 }
1682
1683 // List of available Deflater objects for compression
1684 private final List<Deflater> deflaters = new ArrayList<>();
1685
1686 // Gets a deflater from the list of available deflaters or allocates
1687 // a new one.
1688 private Deflater getDeflater() {
1689 synchronized (deflaters) {
1690 int size = deflaters.size();
1691 if (size > 0) {
1692 Deflater def = deflaters.remove(size - 1);
1693 return def;
1694 } else {
1695 return new Deflater(Deflater.DEFAULT_COMPRESSION, true);
1696 }
1697 }
1698 }
1699
1700 // End of central directory record
1701 static class END {
1702 // these 2 fields are not used by anyone and write() uses "0"
1703 // int disknum;
1704 // int sdisknum;
1705 int endsub; // endsub
1706 int centot; // 4 bytes
1707 long cenlen; // 4 bytes
1708 long cenoff; // 4 bytes
1709 int comlen; // comment length
1710 byte[] comment;
1711
1712 /* members of Zip64 end of central directory locator */
1713 // int diskNum;
1714 long endpos;
1715 // int disktot;
1716
1717 void write(OutputStream os, long offset, boolean forceEnd64) throws IOException {
1718 boolean hasZip64 = forceEnd64; // false;
1719 long xlen = cenlen;
1847
1848 IndexNode() {}
1849 IndexNode sibling;
1850 IndexNode child; // 1st child
1851 }
1852
1853 static class Entry extends IndexNode implements ZipFileAttributes {
1854
1855 static final int CEN = 1; // entry read from cen
1856 static final int NEW = 2; // updated contents in bytes or file
1857 static final int FILECH = 3; // fch update in "file"
1858 static final int COPY = 4; // copy of a CEN entry
1859
1860 byte[] bytes; // updated content bytes
1861 Path file; // use tmp file to store bytes;
1862 int type = CEN; // default is the entry read from cen
1863
1864 // entry attributes
1865 int version;
1866 int flag;
1867 int posixPerms = -1; // posix permissions
1868 int method = -1; // compression method
1869 long mtime = -1; // last modification time (in DOS time)
1870 long atime = -1; // last access time
1871 long ctime = -1; // create time
1872 long crc = -1; // crc-32 of entry data
1873 long csize = -1; // compressed size of entry data
1874 long size = -1; // uncompressed size of entry data
1875 byte[] extra;
1876
1877 // cen
1878
1879 // these fields are not used
1880 // int versionMade;
1881 // int disk;
1882 // int attrs;
1883 // long attrsEx;
1884 long locoff;
1885 byte[] comment;
1886
1887 Entry() {}
1888
1889 Entry(byte[] name, boolean isdir, int method) {
1890 name(name);
1891 this.isdir = isdir;
1892 this.mtime = this.ctime = this.atime = System.currentTimeMillis();
1893 this.crc = 0;
1894 this.size = 0;
1895 this.csize = 0;
1896 this.method = method;
1897 }
1898
1899 @SuppressWarnings("unchecked")
1900 Entry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) {
1901 this(name, isdir, method);
1902 this.type = type;
1903 for (FileAttribute<?> attr : attrs) {
1904 String attrName = attr.name();
1905 if (attrName.equals("posix:permissions") || attrName.equals("unix:permissions")) {
1906 posixPerms = ZipUtils.permsToFlags((Set<PosixFilePermission>)attr.value());
1907 }
1908 }
1909 }
1910
1911 Entry(Entry e, int type) {
1912 name(e.name);
1913 this.isdir = e.isdir;
1914 this.version = e.version;
1915 this.ctime = e.ctime;
1916 this.atime = e.atime;
1917 this.mtime = e.mtime;
1918 this.crc = e.crc;
1919 this.size = e.size;
1920 this.csize = e.csize;
1921 this.method = e.method;
1922 this.extra = e.extra;
1923 /*
1924 this.versionMade = e.versionMade;
1925 this.disk = e.disk;
1926 this.attrs = e.attrs;
1927 this.attrsEx = e.attrsEx;
1928 */
1929 this.locoff = e.locoff;
1930 this.comment = e.comment;
1931 this.posixPerms = e.posixPerms;
1932 this.type = type;
1933 }
1934
1935 @SuppressWarnings("unchecked")
1936 Entry(byte[] name, Path file, int type, FileAttribute<?>... attrs) {
1937 this(name, type, false, METHOD_STORED);
1938 this.file = file;
1939 for (FileAttribute<?> attr : attrs) {
1940 String attrName = attr.name();
1941 if (attrName.equals("posix:permissions") || attrName.equals("unix:permissions")) {
1942 posixPerms = ZipUtils.permsToFlags((Set<PosixFilePermission>)attr.value());
1943 }
1944 }
1945 }
1946
1947 int version(boolean zip64) throws ZipException {
1948 if (zip64) {
1949 return 45;
1950 }
1951 if (method == METHOD_DEFLATED)
1952 return 20;
1953 else if (method == METHOD_STORED)
1954 return 10;
1955 throw new ZipException("unsupported compression method");
1956 }
1957
1958 /**
1959 * Adds information about compatibility of file attribute information
1960 * to a version value.
1961 */
1962 int versionMadeBy(int version) {
1963 return (posixPerms < 0) ? version :
1964 VERSION_BASE_UNIX | (version & 0xff);
1965 }
1966
1967 ///////////////////// CEN //////////////////////
1968 static Entry readCEN(ZipFileSystem zipfs, IndexNode inode)
1969 throws IOException
1970 {
1971 return new Entry().cen(zipfs, inode);
1972 }
1973
1974 private Entry cen(ZipFileSystem zipfs, IndexNode inode)
1975 throws IOException
1976 {
1977 byte[] cen = zipfs.cen;
1978 int pos = inode.pos;
1979 if (!cenSigAt(cen, pos))
1980 zerror("invalid CEN header (bad signature)");
1981 version = CENVER(cen, pos);
1982 flag = CENFLG(cen, pos);
1983 method = CENHOW(cen, pos);
1984 mtime = dosToJavaTime(CENTIM(cen, pos));
1985 crc = CENCRC(cen, pos);
1986 csize = CENSIZ(cen, pos);
1987 size = CENLEN(cen, pos);
1988 int nlen = CENNAM(cen, pos);
1989 int elen = CENEXT(cen, pos);
1990 int clen = CENCOM(cen, pos);
1991 /*
1992 versionMade = CENVEM(cen, pos);
1993 disk = CENDSK(cen, pos);
1994 attrs = CENATT(cen, pos);
1995 attrsEx = CENATX(cen, pos);
1996 */
1997 if (CENVEM_FA(cen, pos) == FILE_ATTRIBUTES_UNIX) {
1998 posixPerms = CENATX_PERMS(cen, pos) & 0xFFF; // 12 bits for setuid, setgid, sticky + perms
1999 }
2000 locoff = CENOFF(cen, pos);
2001 pos += CENHDR;
2002 this.name = inode.name;
2003 this.isdir = inode.isdir;
2004 this.hashcode = inode.hashcode;
2005
2006 pos += nlen;
2007 if (elen > 0) {
2008 extra = Arrays.copyOfRange(cen, pos, pos + elen);
2009 pos += elen;
2010 readExtra(zipfs);
2011 }
2012 if (clen > 0) {
2013 comment = Arrays.copyOfRange(cen, pos, pos + clen);
2014 }
2015 return this;
2016 }
2017
2018 int writeCEN(OutputStream os) throws IOException {
2019 long csize0 = csize;
2020 long size0 = size;
2021 long locoff0 = locoff;
2022 int elen64 = 0; // extra for ZIP64
2023 int elenNTFS = 0; // extra for NTFS (a/c/mtime)
2024 int elenEXTT = 0; // extra for Extended Timestamp
2025 boolean foundExtraTime = false; // if time stamp NTFS, EXTT present
2026
2027 byte[] zname = isdir ? toDirectoryPath(name) : name;
2028
2029 // confirm size/length
2030 int nlen = (zname != null) ? zname.length - 1 : 0; // name has [0] as "slash"
2031 int elen = (extra != null) ? extra.length : 0;
2032 int eoff = 0;
2033 int clen = (comment != null) ? comment.length : 0;
2034 if (csize >= ZIP64_MINVAL) {
2035 csize0 = ZIP64_MINVAL;
2036 elen64 += 8; // csize(8)
2037 }
2038 if (size >= ZIP64_MINVAL) {
2039 size0 = ZIP64_MINVAL; // size(8)
2040 elen64 += 8;
2041 }
2042 if (locoff >= ZIP64_MINVAL) {
2043 locoff0 = ZIP64_MINVAL;
2044 elen64 += 8; // offset(8)
2045 }
2046 if (elen64 != 0) {
2047 elen64 += 4; // header and data sz 4 bytes
2048 }
2049 boolean zip64 = (elen64 != 0);
2050 int version0 = version(zip64);
2051 while (eoff + 4 < elen) {
2052 int tag = SH(extra, eoff);
2053 int sz = SH(extra, eoff + 2);
2054 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2055 foundExtraTime = true;
2056 }
2057 eoff += (4 + sz);
2058 }
2059 if (!foundExtraTime) {
2060 if (isWindows) { // use NTFS
2061 elenNTFS = 36; // total 36 bytes
2062 } else { // Extended Timestamp otherwise
2063 elenEXTT = 9; // only mtime in cen
2064 }
2065 }
2066 writeInt(os, CENSIG); // CEN header signature
2067 writeShort(os, versionMadeBy(version0)); // version made by
2068 writeShort(os, version0); // version needed to extract
2069 writeShort(os, flag); // general purpose bit flag
2070 writeShort(os, method); // compression method
2071 // last modification time
2072 writeInt(os, (int)javaToDosTime(mtime));
2073 writeInt(os, crc); // crc-32
2074 writeInt(os, csize0); // compressed size
2075 writeInt(os, size0); // uncompressed size
2076 writeShort(os, nlen);
2077 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2078
2079 if (comment != null) {
2080 writeShort(os, Math.min(clen, 0xffff));
2081 } else {
2082 writeShort(os, 0);
2083 }
2084 writeShort(os, 0); // starting disk number
2085 writeShort(os, 0); // internal file attributes (unused)
2086 writeInt(os, posixPerms > 0 ? posixPerms << 16 : 0); // external file
2087 // attributes, used for storing posix
2088 // permissions
2089 writeInt(os, locoff0); // relative offset of local header
2090 writeBytes(os, zname, 1, nlen);
2091 if (zip64) {
2092 writeShort(os, EXTID_ZIP64);// Zip64 extra
2093 writeShort(os, elen64 - 4); // size of "this" extra block
2094 if (size0 == ZIP64_MINVAL)
2095 writeLong(os, size);
2096 if (csize0 == ZIP64_MINVAL)
2097 writeLong(os, csize);
2098 if (locoff0 == ZIP64_MINVAL)
2099 writeLong(os, locoff);
2100 }
2101 if (elenNTFS != 0) {
2102 writeShort(os, EXTID_NTFS);
2103 writeShort(os, elenNTFS - 4);
2104 writeInt(os, 0); // reserved
2105 writeShort(os, 0x0001); // NTFS attr tag
2106 writeShort(os, 24);
2107 writeLong(os, javaToWinTime(mtime));
2108 writeLong(os, javaToWinTime(atime));
2109 writeLong(os, javaToWinTime(ctime));
2110 }
2111 if (elenEXTT != 0) {
2112 writeShort(os, EXTID_EXTT);
2113 writeShort(os, elenEXTT - 4);
2114 if (ctime == -1)
2115 os.write(0x3); // mtime and atime
2116 else
2117 os.write(0x7); // mtime, atime and ctime
2118 writeInt(os, javaToUnixTime(mtime));
2119 }
2120 if (extra != null) // whatever not recognized
2121 writeBytes(os, extra);
2122 if (comment != null) //TBD: 0, Math.min(commentBytes.length, 0xffff));
2123 writeBytes(os, comment);
2124 return CENHDR + nlen + elen + clen + elen64 + elenNTFS + elenEXTT;
2125 }
2126
2127 ///////////////////// LOC //////////////////////
2128
2129 int writeLOC(OutputStream os) throws IOException {
2130 writeInt(os, LOCSIG); // LOC header signature
2131 byte[] zname = isdir ? toDirectoryPath(name) : name;
2132 int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
2133 int elen = (extra != null) ? extra.length : 0;
2134 boolean foundExtraTime = false; // if extra timestamp present
2135 int eoff = 0;
2136 int elen64 = 0;
2137 boolean zip64 = false;
2138 int elenEXTT = 0;
2139 int elenNTFS = 0;
2140 if ((flag & FLAG_DATADESCR) != 0) {
2141 writeShort(os, version(zip64)); // version needed to extract
2142 writeShort(os, flag); // general purpose bit flag
2143 writeShort(os, method); // compression method
2144 // last modification time
2145 writeInt(os, (int)javaToDosTime(mtime));
2146 // store size, uncompressed size, and crc-32 in data descriptor
2147 // immediately following compressed entry data
2148 writeInt(os, 0);
2149 writeInt(os, 0);
2150 writeInt(os, 0);
2151 } else {
2152 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2153 elen64 = 20; //headid(2) + size(2) + size(8) + csize(8)
2154 zip64 = true;
2155 }
2156 writeShort(os, version(zip64)); // version needed to extract
2157 writeShort(os, flag); // general purpose bit flag
2158 writeShort(os, method); // compression method
2159 // last modification time
2160 writeInt(os, (int)javaToDosTime(mtime));
2161 writeInt(os, crc); // crc-32
2162 if (zip64) {
2163 writeInt(os, ZIP64_MINVAL);
2164 writeInt(os, ZIP64_MINVAL);
2165 } else {
2166 writeInt(os, csize); // compressed size
2167 writeInt(os, size); // uncompressed size
2168 }
2169 }
2170 while (eoff + 4 < elen) {
2171 int tag = SH(extra, eoff);
2172 int sz = SH(extra, eoff + 2);
2173 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2174 foundExtraTime = true;
2175 }
2176 eoff += (4 + sz);
2177 }
2178 if (!foundExtraTime) {
2179 if (isWindows) {
2180 elenNTFS = 36; // NTFS, total 36 bytes
2181 } else { // on unix use "ext time"
2182 elenEXTT = 9;
2183 if (atime != -1)
2184 elenEXTT += 4;
2185 if (ctime != -1)
2186 elenEXTT += 4;
2187 }
2188 }
2189 writeShort(os, nlen);
2190 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2191 writeBytes(os, zname, 1, nlen);
2192 if (zip64) {
2193 writeShort(os, EXTID_ZIP64);
2194 writeShort(os, 16);
2195 writeLong(os, size);
2196 writeLong(os, csize);
2197 }
2198 if (elenNTFS != 0) {
2199 writeShort(os, EXTID_NTFS);
2200 writeShort(os, elenNTFS - 4);
2201 writeInt(os, 0); // reserved
2202 writeShort(os, 0x0001); // NTFS attr tag
2203 writeShort(os, 24);
2204 writeLong(os, javaToWinTime(mtime));
2205 writeLong(os, javaToWinTime(atime));
2206 writeLong(os, javaToWinTime(ctime));
2207 }
2208 if (elenEXTT != 0) {
2209 writeShort(os, EXTID_EXTT);
2210 writeShort(os, elenEXTT - 4);// size for the folowing data block
2211 int fbyte = 0x1;
2212 if (atime != -1) // mtime and atime
2425 return Arrays.copyOf(comment, comment.length);
2426 return null;
2427 }
2428
2429 public String toString() {
2430 StringBuilder sb = new StringBuilder(1024);
2431 Formatter fm = new Formatter(sb);
2432 fm.format(" name : %s%n", new String(name));
2433 fm.format(" creationTime : %tc%n", creationTime().toMillis());
2434 fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis());
2435 fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
2436 fm.format(" isRegularFile : %b%n", isRegularFile());
2437 fm.format(" isDirectory : %b%n", isDirectory());
2438 fm.format(" isSymbolicLink : %b%n", isSymbolicLink());
2439 fm.format(" isOther : %b%n", isOther());
2440 fm.format(" fileKey : %s%n", fileKey());
2441 fm.format(" size : %d%n", size());
2442 fm.format(" compressedSize : %d%n", compressedSize());
2443 fm.format(" crc : %x%n", crc());
2444 fm.format(" method : %d%n", method());
2445 if (posixPerms != -1) {
2446 fm.format(" permissions : %s%n", permissions());
2447 }
2448 fm.close();
2449 return sb.toString();
2450 }
2451
2452 @Override
2453 public UserPrincipal owner() {
2454 throw new UnsupportedOperationException(
2455 "ZipFileSystem does not support owner.");
2456 }
2457
2458 @Override
2459 public GroupPrincipal group() {
2460 throw new UnsupportedOperationException(
2461 "ZipFileSystem does not support group.");
2462 }
2463
2464 @Override
2465 public Set<PosixFilePermission> permissions() {
2466 if (posixPerms == -1) {
2467 // in case there are no Posix permissions associated with the
2468 // entry, we should not return an empty set of permissions
2469 // because that would be an explicit set of permissions meaning
2470 // no permissions for anyone
2471 throw new UnsupportedOperationException(
2472 "No posix permissions associated with zip entry.");
2473 }
2474 return ZipUtils.permsFromFlags(posixPerms);
2475 }
2476
2477 @Override
2478 public void setPermissions(Set<PosixFilePermission> perms) {
2479 posixPerms = ZipUtils.permsToFlags(perms);
2480 }
2481 }
2482
2483 // ZIP directory has two issues:
2484 // (1) ZIP spec does not require the ZIP file to include
2485 // directory entry
2486 // (2) all entries are not stored/organized in a "tree"
2487 // structure.
2488 // A possible solution is to build the node tree ourself as
2489 // implemented below.
2490
2491 // default time stamp for pseudo entries
2492 private long zfsDefaultTimeStamp = System.currentTimeMillis();
2493
2494 private void removeFromTree(IndexNode inode) {
2495 IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(inode.name)));
2496 IndexNode child = parent.child;
2497 if (child.equals(inode)) {
2498 parent.child = child.sibling;
2499 } else {
2500 IndexNode last = child;
2501 while ((child = child.sibling) != null) {
2502 if (child.equals(inode)) {
2503 last.sibling = child.sibling;
2504 break;
2505 } else {
2506 last = child;
2507 }
2508 }
2509 }
|