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
418 list.add(zp);
419 child = child.sibling;
420 }
421 return list.iterator();
422 } finally {
423 endWrite();
424 }
425 }
426
427 void createDirectory(byte[] dir, FileAttribute<?>... attrs)
428 throws IOException
429 {
430 checkWritable();
431 // dir = toDirectoryPath(dir);
432 beginWrite();
433 try {
434 ensureOpen();
435 if (dir.length == 0 || exists(dir)) // root dir, or exiting dir
436 throw new FileAlreadyExistsException(getString(dir));
437 checkParents(dir);
438 Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED);
439 update(e);
440 } finally {
441 endWrite();
442 }
443 }
444
445 void copyFile(boolean deletesrc, byte[]src, byte[] dst, CopyOption... options)
446 throws IOException
447 {
448 checkWritable();
449 if (Arrays.equals(src, dst))
450 return; // do nothing, src and dst are the same
451
452 beginWrite();
453 try {
454 ensureOpen();
455 Entry eSrc = getEntry(src); // ensureOpen checked
456
457 if (eSrc == null)
458 throw new NoSuchFileException(getString(src));
640 SeekableByteChannel sbc =
641 new EntryOutputChannel(new Entry(e, Entry.NEW));
642 if (options.contains(APPEND)) {
643 try (InputStream is = getInputStream(e)) { // copyover
644 byte[] buf = new byte[8192];
645 ByteBuffer bb = ByteBuffer.wrap(buf);
646 int n;
647 while ((n = is.read(buf)) != -1) {
648 bb.position(0);
649 bb.limit(n);
650 sbc.write(bb);
651 }
652 }
653 }
654 return sbc;
655 }
656 if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
657 throw new NoSuchFileException(getString(path));
658 checkParents(path);
659 return new EntryOutputChannel(
660 new Entry(path, Entry.NEW, false, getCompressMethod(attrs)));
661
662 } finally {
663 endRead();
664 }
665 } else {
666 beginRead();
667 try {
668 ensureOpen();
669 Entry e = getEntry(path);
670 if (e == null || e.isDir())
671 throw new NoSuchFileException(getString(path));
672 try (InputStream is = getInputStream(e)) {
673 // TBD: if (e.size < NNNNN);
674 return new ByteArrayChannel(is.readAllBytes(), true);
675 }
676 } finally {
677 endRead();
678 }
679 }
680 }
705 }
706 } else {
707 if (options.contains(StandardOpenOption.CREATE_NEW)) {
708 throw new FileAlreadyExistsException(getString(path));
709 }
710 if (e.isDir())
711 throw new FileAlreadyExistsException("directory <"
712 + getString(path) + "> exists");
713 }
714 options = new HashSet<>(options);
715 options.remove(StandardOpenOption.CREATE_NEW); // for tmpfile
716 } else if (e == null || e.isDir()) {
717 throw new NoSuchFileException(getString(path));
718 }
719
720 final boolean isFCH = (e != null && e.type == Entry.FILECH);
721 final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
722 final FileChannel fch = tmpfile.getFileSystem()
723 .provider()
724 .newFileChannel(tmpfile, options, attrs);
725 final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH);
726 if (forWrite) {
727 u.flag = FLAG_DATADESCR;
728 u.method = getCompressMethod(attrs);
729 }
730 // is there a better way to hook into the FileChannel's close method?
731 return new FileChannel() {
732 public int write(ByteBuffer src) throws IOException {
733 return fch.write(src);
734 }
735 public long write(ByteBuffer[] srcs, int offset, int length)
736 throws IOException
737 {
738 return fch.write(srcs, offset, length);
739 }
740 public long position() throws IOException {
741 return fch.position();
742 }
743 public FileChannel position(long newPosition)
744 throws IOException
745 {
1451
1452 private InputStream getInputStream(Entry e)
1453 throws IOException
1454 {
1455 InputStream eis = null;
1456
1457 if (e.type == Entry.NEW) {
1458 // now bytes & file is uncompressed.
1459 if (e.bytes != null)
1460 return new ByteArrayInputStream(e.bytes);
1461 else if (e.file != null)
1462 return Files.newInputStream(e.file);
1463 else
1464 throw new ZipException("update entry data is missing");
1465 } else if (e.type == Entry.FILECH) {
1466 // FILECH result is un-compressed.
1467 eis = Files.newInputStream(e.file);
1468 // TBD: wrap to hook close()
1469 // streams.add(eis);
1470 return eis;
1471 } else { // untouced CEN or COPY
1472 eis = new EntryInputStream(e, ch);
1473 }
1474 if (e.method == METHOD_DEFLATED) {
1475 // MORE: Compute good size for inflater stream:
1476 long bufSize = e.size + 2; // Inflater likes a bit of slack
1477 if (bufSize > 65536)
1478 bufSize = 8192;
1479 final long size = e.size;
1480 eis = new InflaterInputStream(eis, getInflater(), (int)bufSize) {
1481 private boolean isClosed = false;
1482 public void close() throws IOException {
1483 if (!isClosed) {
1484 releaseInflater(inf);
1485 this.in.close();
1486 isClosed = true;
1487 streams.remove(this);
1488 }
1489 }
1490 // Override fill() method to provide an extra "dummy" byte
1491 // at the end of the input stream. This is required when
1513 return avail > (long) Integer.MAX_VALUE ?
1514 Integer.MAX_VALUE : (int) avail;
1515 }
1516 };
1517 } else if (e.method == METHOD_STORED) {
1518 // TBD: wrap/ it does not seem necessary
1519 } else {
1520 throw new ZipException("invalid compression method");
1521 }
1522 streams.add(eis);
1523 return eis;
1524 }
1525
1526 // Inner class implementing the input stream used to read
1527 // a (possibly compressed) zip file entry.
1528 private class EntryInputStream extends InputStream {
1529 private final SeekableByteChannel zfch; // local ref to zipfs's "ch". zipfs.ch might
1530 // point to a new channel after sync()
1531 private long pos; // current position within entry data
1532 protected long rem; // number of remaining bytes within entry
1533 protected final long size; // uncompressed size of this entry
1534
1535 EntryInputStream(Entry e, SeekableByteChannel zfch)
1536 throws IOException
1537 {
1538 this.zfch = zfch;
1539 rem = e.csize;
1540 size = e.size;
1541 pos = e.locoff;
1542 if (pos == -1) {
1543 Entry e2 = getEntry(e.name);
1544 if (e2 == null) {
1545 throw new ZipException("invalid loc for entry <" + e.name + ">");
1546 }
1547 pos = e2.locoff;
1548 }
1549 pos = -pos; // lazy initialize the real data offset
1550 }
1551
1552 public int read(byte b[], int off, int len) throws IOException {
1553 ensureOpen();
1554 initDataPos();
1555 if (rem == 0) {
1556 return -1;
1557 }
1558 if (len <= 0) {
1559 return 0;
1560 }
1587 return -1;
1588 }
1589 }
1590
1591 public long skip(long n) throws IOException {
1592 ensureOpen();
1593 if (n > rem)
1594 n = rem;
1595 pos += n;
1596 rem -= n;
1597 if (rem == 0) {
1598 close();
1599 }
1600 return n;
1601 }
1602
1603 public int available() {
1604 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
1605 }
1606
1607 public long size() {
1608 return size;
1609 }
1610
1611 public void close() {
1612 rem = 0;
1613 streams.remove(this);
1614 }
1615
1616 private void initDataPos() throws IOException {
1617 if (pos <= 0) {
1618 pos = -pos + locpos;
1619 byte[] buf = new byte[LOCHDR];
1620 if (readFullyAt(buf, 0, buf.length, pos) != LOCHDR) {
1621 throw new ZipException("invalid loc " + pos + " for entry reading");
1622 }
1623 pos += LOCHDR + LOCNAM(buf) + LOCEXT(buf);
1624 }
1625 }
1626 }
1627
1628 static void zerror(String msg) throws ZipException {
1629 throw new ZipException(msg);
1630 }
1646 return new Inflater(true);
1647 }
1648 }
1649 }
1650
1651 // Releases the specified inflater to the list of available inflaters.
1652 private void releaseInflater(Inflater inf) {
1653 synchronized (inflaters) {
1654 if (inflaters.size() < MAX_FLATER) {
1655 inf.reset();
1656 inflaters.add(inf);
1657 } else {
1658 inf.end();
1659 }
1660 }
1661 }
1662
1663 // List of available Deflater objects for compression
1664 private final List<Deflater> deflaters = new ArrayList<>();
1665
1666 // Gets an deflater from the list of available deflaters or allocates
1667 // a new one.
1668 private Deflater getDeflater() {
1669 synchronized (deflaters) {
1670 int size = deflaters.size();
1671 if (size > 0) {
1672 Deflater def = deflaters.remove(size - 1);
1673 return def;
1674 } else {
1675 return new Deflater(Deflater.DEFAULT_COMPRESSION, true);
1676 }
1677 }
1678 }
1679
1680 // Releases the specified inflater to the list of available inflaters.
1681 private void releaseDeflater(Deflater def) {
1682 synchronized (deflaters) {
1683 if (inflaters.size() < MAX_FLATER) {
1684 def.reset();
1685 deflaters.add(def);
1686 } else {
1687 def.end();
1688 }
1689 }
1690 }
1691
1692 // End of central directory record
1693 static class END {
1694 // these 2 fields are not used by anyone and write() uses "0"
1695 // int disknum;
1696 // int sdisknum;
1697 int endsub; // endsub
1698 int centot; // 4 bytes
1699 long cenlen; // 4 bytes
1700 long cenoff; // 4 bytes
1701 int comlen; // comment length
1702 byte[] comment;
1703
1704 /* members of Zip64 end of central directory locator */
1705 // int diskNum;
1706 long endpos;
1707 // int disktot;
1708
1709 void write(OutputStream os, long offset, boolean forceEnd64) throws IOException {
1710 boolean hasZip64 = forceEnd64; // false;
1711 long xlen = cenlen;
1839
1840 IndexNode() {}
1841 IndexNode sibling;
1842 IndexNode child; // 1st child
1843 }
1844
1845 static class Entry extends IndexNode implements ZipFileAttributes {
1846
1847 static final int CEN = 1; // entry read from cen
1848 static final int NEW = 2; // updated contents in bytes or file
1849 static final int FILECH = 3; // fch update in "file"
1850 static final int COPY = 4; // copy of a CEN entry
1851
1852 byte[] bytes; // updated content bytes
1853 Path file; // use tmp file to store bytes;
1854 int type = CEN; // default is the entry read from cen
1855
1856 // entry attributes
1857 int version;
1858 int flag;
1859 int method = -1; // compression method
1860 long mtime = -1; // last modification time (in DOS time)
1861 long atime = -1; // last access time
1862 long ctime = -1; // create time
1863 long crc = -1; // crc-32 of entry data
1864 long csize = -1; // compressed size of entry data
1865 long size = -1; // uncompressed size of entry data
1866 byte[] extra;
1867
1868 // cen
1869
1870 // these fields are not used by anyone and writeCEN uses "0"
1871 // int versionMade;
1872 // int disk;
1873 // int attrs;
1874 // long attrsEx;
1875 long locoff;
1876 byte[] comment;
1877
1878 Entry() {}
1879
1880 Entry(byte[] name, boolean isdir, int method) {
1881 name(name);
1882 this.isdir = isdir;
1883 this.mtime = this.ctime = this.atime = System.currentTimeMillis();
1884 this.crc = 0;
1885 this.size = 0;
1886 this.csize = 0;
1887 this.method = method;
1888 }
1889
1890 Entry(byte[] name, int type, boolean isdir, int method) {
1891 this(name, isdir, method);
1892 this.type = type;
1893 }
1894
1895 Entry (Entry e, int type) {
1896 name(e.name);
1897 this.isdir = e.isdir;
1898 this.version = e.version;
1899 this.ctime = e.ctime;
1900 this.atime = e.atime;
1901 this.mtime = e.mtime;
1902 this.crc = e.crc;
1903 this.size = e.size;
1904 this.csize = e.csize;
1905 this.method = e.method;
1906 this.extra = e.extra;
1907 /*
1908 this.versionMade = e.versionMade;
1909 this.disk = e.disk;
1910 this.attrs = e.attrs;
1911 this.attrsEx = e.attrsEx;
1912 */
1913 this.locoff = e.locoff;
1914 this.comment = e.comment;
1915 this.type = type;
1916 }
1917
1918 Entry (byte[] name, Path file, int type) {
1919 this(name, type, false, METHOD_STORED);
1920 this.file = file;
1921 }
1922
1923 int version() throws ZipException {
1924 if (method == METHOD_DEFLATED)
1925 return 20;
1926 else if (method == METHOD_STORED)
1927 return 10;
1928 throw new ZipException("unsupported compression method");
1929 }
1930
1931 ///////////////////// CEN //////////////////////
1932 static Entry readCEN(ZipFileSystem zipfs, IndexNode inode)
1933 throws IOException
1934 {
1935 return new Entry().cen(zipfs, inode);
1936 }
1937
1938 private Entry cen(ZipFileSystem zipfs, IndexNode inode)
1939 throws IOException
1940 {
1941 byte[] cen = zipfs.cen;
1942 int pos = inode.pos;
1943 if (!cenSigAt(cen, pos))
1944 zerror("invalid CEN header (bad signature)");
1945 version = CENVER(cen, pos);
1946 flag = CENFLG(cen, pos);
1947 method = CENHOW(cen, pos);
1948 mtime = dosToJavaTime(CENTIM(cen, pos));
1949 crc = CENCRC(cen, pos);
1950 csize = CENSIZ(cen, pos);
1951 size = CENLEN(cen, pos);
1952 int nlen = CENNAM(cen, pos);
1953 int elen = CENEXT(cen, pos);
1954 int clen = CENCOM(cen, pos);
1955 /*
1956 versionMade = CENVEM(cen, pos);
1957 disk = CENDSK(cen, pos);
1958 attrs = CENATT(cen, pos);
1959 attrsEx = CENATX(cen, pos);
1960 */
1961 locoff = CENOFF(cen, pos);
1962 pos += CENHDR;
1963 this.name = inode.name;
1964 this.isdir = inode.isdir;
1965 this.hashcode = inode.hashcode;
1966
1967 pos += nlen;
1968 if (elen > 0) {
1969 extra = Arrays.copyOfRange(cen, pos, pos + elen);
1970 pos += elen;
1971 readExtra(zipfs);
1972 }
1973 if (clen > 0) {
1974 comment = Arrays.copyOfRange(cen, pos, pos + clen);
1975 }
1976 return this;
1977 }
1978
1979 int writeCEN(OutputStream os) throws IOException
1980 {
1981 int written = CENHDR;
1982 int version0 = version();
1983 long csize0 = csize;
1984 long size0 = size;
1985 long locoff0 = locoff;
1986 int elen64 = 0; // extra for ZIP64
1987 int elenNTFS = 0; // extra for NTFS (a/c/mtime)
1988 int elenEXTT = 0; // extra for Extended Timestamp
1989 boolean foundExtraTime = false; // if time stamp NTFS, EXTT present
1990
1991 byte[] zname = isdir ? toDirectoryPath(name) : name;
1992
1993 // confirm size/length
1994 int nlen = (zname != null) ? zname.length - 1 : 0; // name has [0] as "slash"
1995 int elen = (extra != null) ? extra.length : 0;
1996 int eoff = 0;
1997 int clen = (comment != null) ? comment.length : 0;
1998 if (csize >= ZIP64_MINVAL) {
1999 csize0 = ZIP64_MINVAL;
2000 elen64 += 8; // csize(8)
2001 }
2002 if (size >= ZIP64_MINVAL) {
2003 size0 = ZIP64_MINVAL; // size(8)
2004 elen64 += 8;
2005 }
2006 if (locoff >= ZIP64_MINVAL) {
2007 locoff0 = ZIP64_MINVAL;
2008 elen64 += 8; // offset(8)
2009 }
2010 if (elen64 != 0) {
2011 elen64 += 4; // header and data sz 4 bytes
2012 }
2013 while (eoff + 4 < elen) {
2014 int tag = SH(extra, eoff);
2015 int sz = SH(extra, eoff + 2);
2016 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2017 foundExtraTime = true;
2018 }
2019 eoff += (4 + sz);
2020 }
2021 if (!foundExtraTime) {
2022 if (isWindows) { // use NTFS
2023 elenNTFS = 36; // total 36 bytes
2024 } else { // Extended Timestamp otherwise
2025 elenEXTT = 9; // only mtime in cen
2026 }
2027 }
2028 writeInt(os, CENSIG); // CEN header signature
2029 if (elen64 != 0) {
2030 writeShort(os, 45); // ver 4.5 for zip64
2031 writeShort(os, 45);
2032 } else {
2033 writeShort(os, version0); // version made by
2034 writeShort(os, version0); // version needed to extract
2035 }
2036 writeShort(os, flag); // general purpose bit flag
2037 writeShort(os, method); // compression method
2038 // last modification time
2039 writeInt(os, (int)javaToDosTime(mtime));
2040 writeInt(os, crc); // crc-32
2041 writeInt(os, csize0); // compressed size
2042 writeInt(os, size0); // uncompressed size
2043 writeShort(os, nlen);
2044 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2045
2046 if (comment != null) {
2047 writeShort(os, Math.min(clen, 0xffff));
2048 } else {
2049 writeShort(os, 0);
2050 }
2051 writeShort(os, 0); // starting disk number
2052 writeShort(os, 0); // internal file attributes (unused)
2053 writeInt(os, 0); // external file attributes (unused)
2054 writeInt(os, locoff0); // relative offset of local header
2055 writeBytes(os, zname, 1, nlen);
2056 if (elen64 != 0) {
2057 writeShort(os, EXTID_ZIP64);// Zip64 extra
2058 writeShort(os, elen64 - 4); // size of "this" extra block
2059 if (size0 == ZIP64_MINVAL)
2060 writeLong(os, size);
2061 if (csize0 == ZIP64_MINVAL)
2062 writeLong(os, csize);
2063 if (locoff0 == ZIP64_MINVAL)
2064 writeLong(os, locoff);
2065 }
2066 if (elenNTFS != 0) {
2067 writeShort(os, EXTID_NTFS);
2068 writeShort(os, elenNTFS - 4);
2069 writeInt(os, 0); // reserved
2070 writeShort(os, 0x0001); // NTFS attr tag
2071 writeShort(os, 24);
2072 writeLong(os, javaToWinTime(mtime));
2073 writeLong(os, javaToWinTime(atime));
2074 writeLong(os, javaToWinTime(ctime));
2075 }
2076 if (elenEXTT != 0) {
2077 writeShort(os, EXTID_EXTT);
2078 writeShort(os, elenEXTT - 4);
2079 if (ctime == -1)
2080 os.write(0x3); // mtime and atime
2081 else
2082 os.write(0x7); // mtime, atime and ctime
2083 writeInt(os, javaToUnixTime(mtime));
2084 }
2085 if (extra != null) // whatever not recognized
2086 writeBytes(os, extra);
2087 if (comment != null) //TBD: 0, Math.min(commentBytes.length, 0xffff));
2088 writeBytes(os, comment);
2089 return CENHDR + nlen + elen + clen + elen64 + elenNTFS + elenEXTT;
2090 }
2091
2092 ///////////////////// LOC //////////////////////
2093
2094 int writeLOC(OutputStream os) throws IOException {
2095 writeInt(os, LOCSIG); // LOC header signature
2096 int version = version();
2097
2098 byte[] zname = isdir ? toDirectoryPath(name) : name;
2099 int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
2100 int elen = (extra != null) ? extra.length : 0;
2101 boolean foundExtraTime = false; // if extra timestamp present
2102 int eoff = 0;
2103 int elen64 = 0;
2104 int elenEXTT = 0;
2105 int elenNTFS = 0;
2106 if ((flag & FLAG_DATADESCR) != 0) {
2107 writeShort(os, version()); // version needed to extract
2108 writeShort(os, flag); // general purpose bit flag
2109 writeShort(os, method); // compression method
2110 // last modification time
2111 writeInt(os, (int)javaToDosTime(mtime));
2112 // store size, uncompressed size, and crc-32 in data descriptor
2113 // immediately following compressed entry data
2114 writeInt(os, 0);
2115 writeInt(os, 0);
2116 writeInt(os, 0);
2117 } else {
2118 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2119 elen64 = 20; //headid(2) + size(2) + size(8) + csize(8)
2120 writeShort(os, 45); // ver 4.5 for zip64
2121 } else {
2122 writeShort(os, version()); // version needed to extract
2123 }
2124 writeShort(os, flag); // general purpose bit flag
2125 writeShort(os, method); // compression method
2126 // last modification time
2127 writeInt(os, (int)javaToDosTime(mtime));
2128 writeInt(os, crc); // crc-32
2129 if (elen64 != 0) {
2130 writeInt(os, ZIP64_MINVAL);
2131 writeInt(os, ZIP64_MINVAL);
2132 } else {
2133 writeInt(os, csize); // compressed size
2134 writeInt(os, size); // uncompressed size
2135 }
2136 }
2137 while (eoff + 4 < elen) {
2138 int tag = SH(extra, eoff);
2139 int sz = SH(extra, eoff + 2);
2140 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2141 foundExtraTime = true;
2142 }
2143 eoff += (4 + sz);
2144 }
2145 if (!foundExtraTime) {
2146 if (isWindows) {
2147 elenNTFS = 36; // NTFS, total 36 bytes
2148 } else { // on unix use "ext time"
2149 elenEXTT = 9;
2150 if (atime != -1)
2151 elenEXTT += 4;
2152 if (ctime != -1)
2153 elenEXTT += 4;
2154 }
2155 }
2156 writeShort(os, nlen);
2157 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2158 writeBytes(os, zname, 1, nlen);
2159 if (elen64 != 0) {
2160 writeShort(os, EXTID_ZIP64);
2161 writeShort(os, 16);
2162 writeLong(os, size);
2163 writeLong(os, csize);
2164 }
2165 if (elenNTFS != 0) {
2166 writeShort(os, EXTID_NTFS);
2167 writeShort(os, elenNTFS - 4);
2168 writeInt(os, 0); // reserved
2169 writeShort(os, 0x0001); // NTFS attr tag
2170 writeShort(os, 24);
2171 writeLong(os, javaToWinTime(mtime));
2172 writeLong(os, javaToWinTime(atime));
2173 writeLong(os, javaToWinTime(ctime));
2174 }
2175 if (elenEXTT != 0) {
2176 writeShort(os, EXTID_EXTT);
2177 writeShort(os, elenEXTT - 4);// size for the folowing data block
2178 int fbyte = 0x1;
2179 if (atime != -1) // mtime and atime
2392 return Arrays.copyOf(comment, comment.length);
2393 return null;
2394 }
2395
2396 public String toString() {
2397 StringBuilder sb = new StringBuilder(1024);
2398 Formatter fm = new Formatter(sb);
2399 fm.format(" name : %s%n", new String(name));
2400 fm.format(" creationTime : %tc%n", creationTime().toMillis());
2401 fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis());
2402 fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
2403 fm.format(" isRegularFile : %b%n", isRegularFile());
2404 fm.format(" isDirectory : %b%n", isDirectory());
2405 fm.format(" isSymbolicLink : %b%n", isSymbolicLink());
2406 fm.format(" isOther : %b%n", isOther());
2407 fm.format(" fileKey : %s%n", fileKey());
2408 fm.format(" size : %d%n", size());
2409 fm.format(" compressedSize : %d%n", compressedSize());
2410 fm.format(" crc : %x%n", crc());
2411 fm.format(" method : %d%n", method());
2412 fm.close();
2413 return sb.toString();
2414 }
2415 }
2416
2417 // ZIP directory has two issues:
2418 // (1) ZIP spec does not require the ZIP file to include
2419 // directory entry
2420 // (2) all entries are not stored/organized in a "tree"
2421 // structure.
2422 // A possible solution is to build the node tree ourself as
2423 // implemented below.
2424 private IndexNode root;
2425
2426 // default time stamp for pseudo entries
2427 private long zfsDefaultTimeStamp = System.currentTimeMillis();
2428
2429 private void removeFromTree(IndexNode inode) {
2430 IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(inode.name)));
2431 IndexNode child = parent.child;
2432 if (child.equals(inode)) {
2433 parent.child = child.sibling;
2434 } else {
2435 IndexNode last = child;
2436 while ((child = child.sibling) != null) {
2437 if (child.equals(inode)) {
2438 last.sibling = child.sibling;
2439 break;
2440 } else {
2441 last = child;
2442 }
2443 }
2444 }
|
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.PosixFilePermissions;
55 import java.nio.file.attribute.UserPrincipal;
56 import java.nio.file.attribute.UserPrincipalLookupService;
57 import java.nio.file.spi.FileSystemProvider;
58 import java.security.AccessController;
59 import java.security.PrivilegedAction;
60 import java.security.PrivilegedActionException;
61 import java.security.PrivilegedExceptionAction;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.Collections;
65 import java.util.Formatter;
66 import java.util.HashSet;
67 import java.util.Iterator;
68 import java.util.LinkedHashMap;
69 import java.util.List;
70 import java.util.Map;
71 import java.util.Objects;
72 import java.util.Set;
73 import java.util.concurrent.locks.ReadWriteLock;
74 import java.util.concurrent.locks.ReentrantReadWriteLock;
75 import java.util.regex.Pattern;
76 import java.util.zip.CRC32;
77 import java.util.zip.Deflater;
78 import java.util.zip.DeflaterOutputStream;
79 import java.util.zip.Inflater;
80 import java.util.zip.InflaterInputStream;
81 import java.util.zip.ZipException;
82
83 /**
84 * A FileSystem built on a zip file
85 *
86 * @author Xueming Shen
87 */
88 class ZipFileSystem extends FileSystem {
89 private static final int FILE_ATTRIBUTES_UNIX = 3;
90 private static final int VERSION_BASE_UNIX = FILE_ATTRIBUTES_UNIX << 8;
91 private final ZipFileSystemProvider provider;
92 private final Path zfpath;
93 final ZipCoder zc;
94 private final ZipPath rootdir;
95 private boolean readOnly = false; // readonly file system
96
97 // configurable by env map
98 private final boolean noExtt; // see readExtra()
99 private final boolean useTempFile; // use a temp file for newOS, default
100 // is to use BAOS for better performance
101 private static final boolean isWindows = AccessController.doPrivileged(
102 (PrivilegedAction<Boolean>) () -> System.getProperty("os.name")
103 .startsWith("Windows"));
104 private final boolean forceEnd64;
105 private final int defaultMethod; // METHOD_STORED if "noCompression=true"
106 // METHOD_DEFLATED otherwise
107
108 ZipFileSystem(ZipFileSystemProvider provider,
109 Path zfpath,
110 Map<String, ?> env) throws IOException
438 list.add(zp);
439 child = child.sibling;
440 }
441 return list.iterator();
442 } finally {
443 endWrite();
444 }
445 }
446
447 void createDirectory(byte[] dir, FileAttribute<?>... attrs)
448 throws IOException
449 {
450 checkWritable();
451 // dir = toDirectoryPath(dir);
452 beginWrite();
453 try {
454 ensureOpen();
455 if (dir.length == 0 || exists(dir)) // root dir, or exiting dir
456 throw new FileAlreadyExistsException(getString(dir));
457 checkParents(dir);
458 Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED, attrs);
459 update(e);
460 } finally {
461 endWrite();
462 }
463 }
464
465 void copyFile(boolean deletesrc, byte[]src, byte[] dst, CopyOption... options)
466 throws IOException
467 {
468 checkWritable();
469 if (Arrays.equals(src, dst))
470 return; // do nothing, src and dst are the same
471
472 beginWrite();
473 try {
474 ensureOpen();
475 Entry eSrc = getEntry(src); // ensureOpen checked
476
477 if (eSrc == null)
478 throw new NoSuchFileException(getString(src));
660 SeekableByteChannel sbc =
661 new EntryOutputChannel(new Entry(e, Entry.NEW));
662 if (options.contains(APPEND)) {
663 try (InputStream is = getInputStream(e)) { // copyover
664 byte[] buf = new byte[8192];
665 ByteBuffer bb = ByteBuffer.wrap(buf);
666 int n;
667 while ((n = is.read(buf)) != -1) {
668 bb.position(0);
669 bb.limit(n);
670 sbc.write(bb);
671 }
672 }
673 }
674 return sbc;
675 }
676 if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
677 throw new NoSuchFileException(getString(path));
678 checkParents(path);
679 return new EntryOutputChannel(
680 new Entry(path, Entry.NEW, false, getCompressMethod(attrs), attrs));
681
682 } finally {
683 endRead();
684 }
685 } else {
686 beginRead();
687 try {
688 ensureOpen();
689 Entry e = getEntry(path);
690 if (e == null || e.isDir())
691 throw new NoSuchFileException(getString(path));
692 try (InputStream is = getInputStream(e)) {
693 // TBD: if (e.size < NNNNN);
694 return new ByteArrayChannel(is.readAllBytes(), true);
695 }
696 } finally {
697 endRead();
698 }
699 }
700 }
725 }
726 } else {
727 if (options.contains(StandardOpenOption.CREATE_NEW)) {
728 throw new FileAlreadyExistsException(getString(path));
729 }
730 if (e.isDir())
731 throw new FileAlreadyExistsException("directory <"
732 + getString(path) + "> exists");
733 }
734 options = new HashSet<>(options);
735 options.remove(StandardOpenOption.CREATE_NEW); // for tmpfile
736 } else if (e == null || e.isDir()) {
737 throw new NoSuchFileException(getString(path));
738 }
739
740 final boolean isFCH = (e != null && e.type == Entry.FILECH);
741 final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
742 final FileChannel fch = tmpfile.getFileSystem()
743 .provider()
744 .newFileChannel(tmpfile, options, attrs);
745 final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH, attrs);
746 if (forWrite) {
747 u.flag = FLAG_DATADESCR;
748 u.method = getCompressMethod(attrs);
749 }
750 // is there a better way to hook into the FileChannel's close method?
751 return new FileChannel() {
752 public int write(ByteBuffer src) throws IOException {
753 return fch.write(src);
754 }
755 public long write(ByteBuffer[] srcs, int offset, int length)
756 throws IOException
757 {
758 return fch.write(srcs, offset, length);
759 }
760 public long position() throws IOException {
761 return fch.position();
762 }
763 public FileChannel position(long newPosition)
764 throws IOException
765 {
1471
1472 private InputStream getInputStream(Entry e)
1473 throws IOException
1474 {
1475 InputStream eis = null;
1476
1477 if (e.type == Entry.NEW) {
1478 // now bytes & file is uncompressed.
1479 if (e.bytes != null)
1480 return new ByteArrayInputStream(e.bytes);
1481 else if (e.file != null)
1482 return Files.newInputStream(e.file);
1483 else
1484 throw new ZipException("update entry data is missing");
1485 } else if (e.type == Entry.FILECH) {
1486 // FILECH result is un-compressed.
1487 eis = Files.newInputStream(e.file);
1488 // TBD: wrap to hook close()
1489 // streams.add(eis);
1490 return eis;
1491 } else { // untouched CEN or COPY
1492 eis = new EntryInputStream(e, ch);
1493 }
1494 if (e.method == METHOD_DEFLATED) {
1495 // MORE: Compute good size for inflater stream:
1496 long bufSize = e.size + 2; // Inflater likes a bit of slack
1497 if (bufSize > 65536)
1498 bufSize = 8192;
1499 final long size = e.size;
1500 eis = new InflaterInputStream(eis, getInflater(), (int)bufSize) {
1501 private boolean isClosed = false;
1502 public void close() throws IOException {
1503 if (!isClosed) {
1504 releaseInflater(inf);
1505 this.in.close();
1506 isClosed = true;
1507 streams.remove(this);
1508 }
1509 }
1510 // Override fill() method to provide an extra "dummy" byte
1511 // at the end of the input stream. This is required when
1533 return avail > (long) Integer.MAX_VALUE ?
1534 Integer.MAX_VALUE : (int) avail;
1535 }
1536 };
1537 } else if (e.method == METHOD_STORED) {
1538 // TBD: wrap/ it does not seem necessary
1539 } else {
1540 throw new ZipException("invalid compression method");
1541 }
1542 streams.add(eis);
1543 return eis;
1544 }
1545
1546 // Inner class implementing the input stream used to read
1547 // a (possibly compressed) zip file entry.
1548 private class EntryInputStream extends InputStream {
1549 private final SeekableByteChannel zfch; // local ref to zipfs's "ch". zipfs.ch might
1550 // point to a new channel after sync()
1551 private long pos; // current position within entry data
1552 protected long rem; // number of remaining bytes within entry
1553
1554 EntryInputStream(Entry e, SeekableByteChannel zfch)
1555 throws IOException
1556 {
1557 this.zfch = zfch;
1558 rem = e.csize;
1559 pos = e.locoff;
1560 if (pos == -1) {
1561 Entry e2 = getEntry(e.name);
1562 if (e2 == null) {
1563 throw new ZipException("invalid loc for entry <" + e.name + ">");
1564 }
1565 pos = e2.locoff;
1566 }
1567 pos = -pos; // lazy initialize the real data offset
1568 }
1569
1570 public int read(byte b[], int off, int len) throws IOException {
1571 ensureOpen();
1572 initDataPos();
1573 if (rem == 0) {
1574 return -1;
1575 }
1576 if (len <= 0) {
1577 return 0;
1578 }
1605 return -1;
1606 }
1607 }
1608
1609 public long skip(long n) throws IOException {
1610 ensureOpen();
1611 if (n > rem)
1612 n = rem;
1613 pos += n;
1614 rem -= n;
1615 if (rem == 0) {
1616 close();
1617 }
1618 return n;
1619 }
1620
1621 public int available() {
1622 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
1623 }
1624
1625 public void close() {
1626 rem = 0;
1627 streams.remove(this);
1628 }
1629
1630 private void initDataPos() throws IOException {
1631 if (pos <= 0) {
1632 pos = -pos + locpos;
1633 byte[] buf = new byte[LOCHDR];
1634 if (readFullyAt(buf, 0, buf.length, pos) != LOCHDR) {
1635 throw new ZipException("invalid loc " + pos + " for entry reading");
1636 }
1637 pos += LOCHDR + LOCNAM(buf) + LOCEXT(buf);
1638 }
1639 }
1640 }
1641
1642 static void zerror(String msg) throws ZipException {
1643 throw new ZipException(msg);
1644 }
1660 return new Inflater(true);
1661 }
1662 }
1663 }
1664
1665 // Releases the specified inflater to the list of available inflaters.
1666 private void releaseInflater(Inflater inf) {
1667 synchronized (inflaters) {
1668 if (inflaters.size() < MAX_FLATER) {
1669 inf.reset();
1670 inflaters.add(inf);
1671 } else {
1672 inf.end();
1673 }
1674 }
1675 }
1676
1677 // List of available Deflater objects for compression
1678 private final List<Deflater> deflaters = new ArrayList<>();
1679
1680 // Gets a deflater from the list of available deflaters or allocates
1681 // a new one.
1682 private Deflater getDeflater() {
1683 synchronized (deflaters) {
1684 int size = deflaters.size();
1685 if (size > 0) {
1686 Deflater def = deflaters.remove(size - 1);
1687 return def;
1688 } else {
1689 return new Deflater(Deflater.DEFAULT_COMPRESSION, true);
1690 }
1691 }
1692 }
1693
1694 // End of central directory record
1695 static class END {
1696 // these 2 fields are not used by anyone and write() uses "0"
1697 // int disknum;
1698 // int sdisknum;
1699 int endsub; // endsub
1700 int centot; // 4 bytes
1701 long cenlen; // 4 bytes
1702 long cenoff; // 4 bytes
1703 int comlen; // comment length
1704 byte[] comment;
1705
1706 /* members of Zip64 end of central directory locator */
1707 // int diskNum;
1708 long endpos;
1709 // int disktot;
1710
1711 void write(OutputStream os, long offset, boolean forceEnd64) throws IOException {
1712 boolean hasZip64 = forceEnd64; // false;
1713 long xlen = cenlen;
1841
1842 IndexNode() {}
1843 IndexNode sibling;
1844 IndexNode child; // 1st child
1845 }
1846
1847 static class Entry extends IndexNode implements ZipFileAttributes {
1848
1849 static final int CEN = 1; // entry read from cen
1850 static final int NEW = 2; // updated contents in bytes or file
1851 static final int FILECH = 3; // fch update in "file"
1852 static final int COPY = 4; // copy of a CEN entry
1853
1854 byte[] bytes; // updated content bytes
1855 Path file; // use tmp file to store bytes;
1856 int type = CEN; // default is the entry read from cen
1857
1858 // entry attributes
1859 int version;
1860 int flag;
1861 int posixPerms = -1; // posix permissions
1862 int method = -1; // compression method
1863 long mtime = -1; // last modification time (in DOS time)
1864 long atime = -1; // last access time
1865 long ctime = -1; // create time
1866 long crc = -1; // crc-32 of entry data
1867 long csize = -1; // compressed size of entry data
1868 long size = -1; // uncompressed size of entry data
1869 byte[] extra;
1870
1871 // cen
1872
1873 // these fields are not used
1874 // int versionMade;
1875 // int disk;
1876 // int attrs;
1877 // long attrsEx;
1878 long locoff;
1879 byte[] comment;
1880
1881 Entry() {}
1882
1883 Entry(byte[] name, boolean isdir, int method) {
1884 name(name);
1885 this.isdir = isdir;
1886 this.mtime = this.ctime = this.atime = System.currentTimeMillis();
1887 this.crc = 0;
1888 this.size = 0;
1889 this.csize = 0;
1890 this.method = method;
1891 }
1892
1893 @SuppressWarnings("unchecked")
1894 Entry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) {
1895 this(name, isdir, method);
1896 this.type = type;
1897 for (FileAttribute<?> attr: attrs) {
1898 String attrName = attr.name();
1899 if (attrName.equals("posix:permissions") || attrName.equals("unix:permissions")) {
1900 posixPerms = PosixFilePermissions.toFlags((Set<PosixFilePermission>)attr.value());
1901 }
1902 }
1903 }
1904
1905 Entry(Entry e, int type) {
1906 name(e.name);
1907 this.isdir = e.isdir;
1908 this.version = e.version;
1909 this.ctime = e.ctime;
1910 this.atime = e.atime;
1911 this.mtime = e.mtime;
1912 this.crc = e.crc;
1913 this.size = e.size;
1914 this.csize = e.csize;
1915 this.method = e.method;
1916 this.extra = e.extra;
1917 /*
1918 this.versionMade = e.versionMade;
1919 this.disk = e.disk;
1920 this.attrs = e.attrs;
1921 this.attrsEx = e.attrsEx;
1922 */
1923 this.locoff = e.locoff;
1924 this.comment = e.comment;
1925 this.posixPerms = e.posixPerms;
1926 this.type = type;
1927 }
1928
1929 @SuppressWarnings("unchecked")
1930 Entry(byte[] name, Path file, int type, FileAttribute<?>... attrs) {
1931 this(name, type, false, METHOD_STORED);
1932 this.file = file;
1933 for (FileAttribute<?> attr: attrs) {
1934 String attrName = attr.name();
1935 if (attrName.equals("posix:permissions") || attrName.equals("unix:permissions")) {
1936 posixPerms = PosixFilePermissions.toFlags((Set<PosixFilePermission>)attr.value());
1937 }
1938 }
1939 }
1940
1941 int version(boolean zip64) throws ZipException {
1942 if (zip64) {
1943 return 45;
1944 }
1945 if (method == METHOD_DEFLATED)
1946 return 20;
1947 else if (method == METHOD_STORED)
1948 return 10;
1949 throw new ZipException("unsupported compression method");
1950 }
1951
1952 /**
1953 * Adds information about compatibility of file attribute information
1954 * to a version value.
1955 */
1956 int versionMadeBy(int version) {
1957 return (posixPerms < 0) ? version :
1958 VERSION_BASE_UNIX | (version & 0xff);
1959 }
1960
1961 ///////////////////// CEN //////////////////////
1962 static Entry readCEN(ZipFileSystem zipfs, IndexNode inode)
1963 throws IOException
1964 {
1965 return new Entry().cen(zipfs, inode);
1966 }
1967
1968 private Entry cen(ZipFileSystem zipfs, IndexNode inode)
1969 throws IOException
1970 {
1971 byte[] cen = zipfs.cen;
1972 int pos = inode.pos;
1973 if (!cenSigAt(cen, pos))
1974 zerror("invalid CEN header (bad signature)");
1975 version = CENVER(cen, pos);
1976 flag = CENFLG(cen, pos);
1977 method = CENHOW(cen, pos);
1978 mtime = dosToJavaTime(CENTIM(cen, pos));
1979 crc = CENCRC(cen, pos);
1980 csize = CENSIZ(cen, pos);
1981 size = CENLEN(cen, pos);
1982 int nlen = CENNAM(cen, pos);
1983 int elen = CENEXT(cen, pos);
1984 int clen = CENCOM(cen, pos);
1985 /*
1986 versionMade = CENVEM(cen, pos);
1987 disk = CENDSK(cen, pos);
1988 attrs = CENATT(cen, pos);
1989 attrsEx = CENATX(cen, pos);
1990 */
1991 if (CENVEM_FA(cen, pos) == FILE_ATTRIBUTES_UNIX) {
1992 posixPerms = CENATX_PERMS(cen, pos) & 0xFFF; // 12 bits for setuid, setgid, sticky + perms
1993 }
1994 locoff = CENOFF(cen, pos);
1995 pos += CENHDR;
1996 this.name = inode.name;
1997 this.isdir = inode.isdir;
1998 this.hashcode = inode.hashcode;
1999
2000 pos += nlen;
2001 if (elen > 0) {
2002 extra = Arrays.copyOfRange(cen, pos, pos + elen);
2003 pos += elen;
2004 readExtra(zipfs);
2005 }
2006 if (clen > 0) {
2007 comment = Arrays.copyOfRange(cen, pos, pos + clen);
2008 }
2009 return this;
2010 }
2011
2012 int writeCEN(OutputStream os) throws IOException {
2013 long csize0 = csize;
2014 long size0 = size;
2015 long locoff0 = locoff;
2016 int elen64 = 0; // extra for ZIP64
2017 int elenNTFS = 0; // extra for NTFS (a/c/mtime)
2018 int elenEXTT = 0; // extra for Extended Timestamp
2019 boolean foundExtraTime = false; // if time stamp NTFS, EXTT present
2020
2021 byte[] zname = isdir ? toDirectoryPath(name) : name;
2022
2023 // confirm size/length
2024 int nlen = (zname != null) ? zname.length - 1 : 0; // name has [0] as "slash"
2025 int elen = (extra != null) ? extra.length : 0;
2026 int eoff = 0;
2027 int clen = (comment != null) ? comment.length : 0;
2028 if (csize >= ZIP64_MINVAL) {
2029 csize0 = ZIP64_MINVAL;
2030 elen64 += 8; // csize(8)
2031 }
2032 if (size >= ZIP64_MINVAL) {
2033 size0 = ZIP64_MINVAL; // size(8)
2034 elen64 += 8;
2035 }
2036 if (locoff >= ZIP64_MINVAL) {
2037 locoff0 = ZIP64_MINVAL;
2038 elen64 += 8; // offset(8)
2039 }
2040 if (elen64 != 0) {
2041 elen64 += 4; // header and data sz 4 bytes
2042 }
2043 boolean zip64 = (elen64 != 0);
2044 int version0 = version(zip64);
2045 while (eoff + 4 < elen) {
2046 int tag = SH(extra, eoff);
2047 int sz = SH(extra, eoff + 2);
2048 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2049 foundExtraTime = true;
2050 }
2051 eoff += (4 + sz);
2052 }
2053 if (!foundExtraTime) {
2054 if (isWindows) { // use NTFS
2055 elenNTFS = 36; // total 36 bytes
2056 } else { // Extended Timestamp otherwise
2057 elenEXTT = 9; // only mtime in cen
2058 }
2059 }
2060 writeInt(os, CENSIG); // CEN header signature
2061 writeShort(os, versionMadeBy(version0)); // version made by
2062 writeShort(os, version0); // version needed to extract
2063 writeShort(os, flag); // general purpose bit flag
2064 writeShort(os, method); // compression method
2065 // last modification time
2066 writeInt(os, (int)javaToDosTime(mtime));
2067 writeInt(os, crc); // crc-32
2068 writeInt(os, csize0); // compressed size
2069 writeInt(os, size0); // uncompressed size
2070 writeShort(os, nlen);
2071 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2072
2073 if (comment != null) {
2074 writeShort(os, Math.min(clen, 0xffff));
2075 } else {
2076 writeShort(os, 0);
2077 }
2078 writeShort(os, 0); // starting disk number
2079 writeShort(os, 0); // internal file attributes (unused)
2080 writeInt(os, posixPerms > 0 ? posixPerms << 16 : 0); // external file
2081 // attributes, used for storing posix
2082 // permissions
2083 writeInt(os, locoff0); // relative offset of local header
2084 writeBytes(os, zname, 1, nlen);
2085 if (zip64) {
2086 writeShort(os, EXTID_ZIP64);// Zip64 extra
2087 writeShort(os, elen64 - 4); // size of "this" extra block
2088 if (size0 == ZIP64_MINVAL)
2089 writeLong(os, size);
2090 if (csize0 == ZIP64_MINVAL)
2091 writeLong(os, csize);
2092 if (locoff0 == ZIP64_MINVAL)
2093 writeLong(os, locoff);
2094 }
2095 if (elenNTFS != 0) {
2096 writeShort(os, EXTID_NTFS);
2097 writeShort(os, elenNTFS - 4);
2098 writeInt(os, 0); // reserved
2099 writeShort(os, 0x0001); // NTFS attr tag
2100 writeShort(os, 24);
2101 writeLong(os, javaToWinTime(mtime));
2102 writeLong(os, javaToWinTime(atime));
2103 writeLong(os, javaToWinTime(ctime));
2104 }
2105 if (elenEXTT != 0) {
2106 writeShort(os, EXTID_EXTT);
2107 writeShort(os, elenEXTT - 4);
2108 if (ctime == -1)
2109 os.write(0x3); // mtime and atime
2110 else
2111 os.write(0x7); // mtime, atime and ctime
2112 writeInt(os, javaToUnixTime(mtime));
2113 }
2114 if (extra != null) // whatever not recognized
2115 writeBytes(os, extra);
2116 if (comment != null) //TBD: 0, Math.min(commentBytes.length, 0xffff));
2117 writeBytes(os, comment);
2118 return CENHDR + nlen + elen + clen + elen64 + elenNTFS + elenEXTT;
2119 }
2120
2121 ///////////////////// LOC //////////////////////
2122
2123 int writeLOC(OutputStream os) throws IOException {
2124 writeInt(os, LOCSIG); // LOC header signature
2125 byte[] zname = isdir ? toDirectoryPath(name) : name;
2126 int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
2127 int elen = (extra != null) ? extra.length : 0;
2128 boolean foundExtraTime = false; // if extra timestamp present
2129 int eoff = 0;
2130 int elen64 = 0;
2131 boolean zip64 = false;
2132 int elenEXTT = 0;
2133 int elenNTFS = 0;
2134 if ((flag & FLAG_DATADESCR) != 0) {
2135 writeShort(os, version(zip64)); // version needed to extract
2136 writeShort(os, flag); // general purpose bit flag
2137 writeShort(os, method); // compression method
2138 // last modification time
2139 writeInt(os, (int)javaToDosTime(mtime));
2140 // store size, uncompressed size, and crc-32 in data descriptor
2141 // immediately following compressed entry data
2142 writeInt(os, 0);
2143 writeInt(os, 0);
2144 writeInt(os, 0);
2145 } else {
2146 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2147 elen64 = 20; //headid(2) + size(2) + size(8) + csize(8)
2148 zip64 = true;
2149 }
2150 writeShort(os, version(zip64)); // version needed to extract
2151 writeShort(os, flag); // general purpose bit flag
2152 writeShort(os, method); // compression method
2153 // last modification time
2154 writeInt(os, (int)javaToDosTime(mtime));
2155 writeInt(os, crc); // crc-32
2156 if (zip64) {
2157 writeInt(os, ZIP64_MINVAL);
2158 writeInt(os, ZIP64_MINVAL);
2159 } else {
2160 writeInt(os, csize); // compressed size
2161 writeInt(os, size); // uncompressed size
2162 }
2163 }
2164 while (eoff + 4 < elen) {
2165 int tag = SH(extra, eoff);
2166 int sz = SH(extra, eoff + 2);
2167 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2168 foundExtraTime = true;
2169 }
2170 eoff += (4 + sz);
2171 }
2172 if (!foundExtraTime) {
2173 if (isWindows) {
2174 elenNTFS = 36; // NTFS, total 36 bytes
2175 } else { // on unix use "ext time"
2176 elenEXTT = 9;
2177 if (atime != -1)
2178 elenEXTT += 4;
2179 if (ctime != -1)
2180 elenEXTT += 4;
2181 }
2182 }
2183 writeShort(os, nlen);
2184 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2185 writeBytes(os, zname, 1, nlen);
2186 if (zip64) {
2187 writeShort(os, EXTID_ZIP64);
2188 writeShort(os, 16);
2189 writeLong(os, size);
2190 writeLong(os, csize);
2191 }
2192 if (elenNTFS != 0) {
2193 writeShort(os, EXTID_NTFS);
2194 writeShort(os, elenNTFS - 4);
2195 writeInt(os, 0); // reserved
2196 writeShort(os, 0x0001); // NTFS attr tag
2197 writeShort(os, 24);
2198 writeLong(os, javaToWinTime(mtime));
2199 writeLong(os, javaToWinTime(atime));
2200 writeLong(os, javaToWinTime(ctime));
2201 }
2202 if (elenEXTT != 0) {
2203 writeShort(os, EXTID_EXTT);
2204 writeShort(os, elenEXTT - 4);// size for the folowing data block
2205 int fbyte = 0x1;
2206 if (atime != -1) // mtime and atime
2419 return Arrays.copyOf(comment, comment.length);
2420 return null;
2421 }
2422
2423 public String toString() {
2424 StringBuilder sb = new StringBuilder(1024);
2425 Formatter fm = new Formatter(sb);
2426 fm.format(" name : %s%n", new String(name));
2427 fm.format(" creationTime : %tc%n", creationTime().toMillis());
2428 fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis());
2429 fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
2430 fm.format(" isRegularFile : %b%n", isRegularFile());
2431 fm.format(" isDirectory : %b%n", isDirectory());
2432 fm.format(" isSymbolicLink : %b%n", isSymbolicLink());
2433 fm.format(" isOther : %b%n", isOther());
2434 fm.format(" fileKey : %s%n", fileKey());
2435 fm.format(" size : %d%n", size());
2436 fm.format(" compressedSize : %d%n", compressedSize());
2437 fm.format(" crc : %x%n", crc());
2438 fm.format(" method : %d%n", method());
2439 if (posixPerms != -1) {
2440 fm.format(" permissions : %s%n", permissions());
2441 }
2442 fm.close();
2443 return sb.toString();
2444 }
2445
2446 @Override
2447 public UserPrincipal owner() {
2448 throw new UnsupportedOperationException(
2449 "ZipFileSystem does not support owner.");
2450 }
2451
2452 @Override
2453 public GroupPrincipal group() {
2454 throw new UnsupportedOperationException(
2455 "ZipFileSystem does not support group.");
2456 }
2457
2458 @Override
2459 public Set<PosixFilePermission> permissions() {
2460 if (posixPerms == -1) {
2461 // in case there are no Posix permissions associated with the
2462 // entry, we should not return an empty set of permissions
2463 // because that would be an explicit set of permissions meaning
2464 // no permissions for anyone
2465 throw new UnsupportedOperationException(
2466 "No posix permissions associated with zip entry.");
2467 }
2468 return PosixFilePermissions.fromFlags(posixPerms);
2469 }
2470
2471 @Override
2472 public void setPermissions(Set<PosixFilePermission> perms) {
2473 if (perms == null) {
2474 posixPerms = -1;
2475 return;
2476 }
2477 posixPerms = PosixFilePermissions.toFlags(perms);
2478 }
2479 }
2480
2481 // ZIP directory has two issues:
2482 // (1) ZIP spec does not require the ZIP file to include
2483 // directory entry
2484 // (2) all entries are not stored/organized in a "tree"
2485 // structure.
2486 // A possible solution is to build the node tree ourself as
2487 // implemented below.
2488
2489 // default time stamp for pseudo entries
2490 private long zfsDefaultTimeStamp = System.currentTimeMillis();
2491
2492 private void removeFromTree(IndexNode inode) {
2493 IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(inode.name)));
2494 IndexNode child = parent.child;
2495 if (child.equals(inode)) {
2496 parent.child = child.sibling;
2497 } else {
2498 IndexNode last = child;
2499 while ((child = child.sibling) != null) {
2500 if (child.equals(inode)) {
2501 last.sibling = child.sibling;
2502 break;
2503 } else {
2504 last = child;
2505 }
2506 }
2507 }
|