62 /**
63 * A FileSystem built on a zip file
64 *
65 * @author Xueming Shen
66 */
67
68 class ZipFileSystem extends FileSystem {
69
70 private final ZipFileSystemProvider provider;
71 private final Path zfpath;
72 final ZipCoder zc;
73 private final boolean noExtt; // see readExtra()
74 private final ZipPath rootdir;
75 // configurable by env map
76 private final boolean useTempFile; // use a temp file for newOS, default
77 // is to use BAOS for better performance
78 private boolean readOnly = false; // readonly file system
79 private static final boolean isWindows = AccessController.doPrivileged(
80 (PrivilegedAction<Boolean>) () -> System.getProperty("os.name")
81 .startsWith("Windows"));
82
83 ZipFileSystem(ZipFileSystemProvider provider,
84 Path zfpath,
85 Map<String, ?> env) throws IOException
86 {
87 // create a new zip if not exists
88 boolean createNew = "true".equals(env.get("create"));
89 // default encoding for name/comment
90 String nameEncoding = env.containsKey("encoding") ?
91 (String)env.get("encoding") : "UTF-8";
92 this.noExtt = "false".equals(env.get("zipinfo-time"));
93 this.useTempFile = TRUE.equals(env.get("useTempFile"));
94 this.provider = provider;
95 this.zfpath = zfpath;
96 if (Files.notExists(zfpath)) {
97 if (createNew) {
98 try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
99 new END().write(os, 0);
100 }
101 } else {
102 throw new FileSystemNotFoundException(zfpath.toString());
103 }
104 }
105 // sm and existence check
106 zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
107 boolean writeable = AccessController.doPrivileged(
108 (PrivilegedAction<Boolean>) () -> Files.isWritable(zfpath));
109 this.readOnly = !writeable;
110 this.zc = ZipCoder.get(nameEncoding);
111 this.rootdir = new ZipPath(this, new byte[]{'/'});
112 this.ch = Files.newByteChannel(zfpath, READ);
113 try {
114 this.cen = initCEN();
115 } catch (IOException x) {
116 try {
117 this.ch.close();
118 } catch (IOException xx) {
119 x.addSuppressed(xx);
983 int len = buf.length - off;
984 if (readFullyAt(buf, off, len, pos + off) != len)
985 zerror("zip END header not found");
986
987 // Now scan the block backwards for END header signature
988 for (int i = buf.length - ENDHDR; i >= 0; i--) {
989 if (buf[i+0] == (byte)'P' &&
990 buf[i+1] == (byte)'K' &&
991 buf[i+2] == (byte)'\005' &&
992 buf[i+3] == (byte)'\006' &&
993 (pos + i + ENDHDR + ENDCOM(buf, i) == ziplen)) {
994 // Found END header
995 buf = Arrays.copyOfRange(buf, i, i + ENDHDR);
996 END end = new END();
997 end.endsub = ENDSUB(buf);
998 end.centot = ENDTOT(buf);
999 end.cenlen = ENDSIZ(buf);
1000 end.cenoff = ENDOFF(buf);
1001 end.comlen = ENDCOM(buf);
1002 end.endpos = pos + i;
1003 if (end.cenlen == ZIP64_MINVAL ||
1004 end.cenoff == ZIP64_MINVAL ||
1005 end.centot == ZIP64_MINVAL32)
1006 {
1007 // need to find the zip64 end;
1008 byte[] loc64 = new byte[ZIP64_LOCHDR];
1009 if (readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR)
1010 != loc64.length) {
1011 return end;
1012 }
1013 long end64pos = ZIP64_LOCOFF(loc64);
1014 byte[] end64buf = new byte[ZIP64_ENDHDR];
1015 if (readFullyAt(end64buf, 0, end64buf.length, end64pos)
1016 != end64buf.length) {
1017 return end;
1018 }
1019 // end64 found, re-calcualte everything.
1020 end.cenlen = ZIP64_ENDSIZ(end64buf);
1021 end.cenoff = ZIP64_ENDOFF(end64buf);
1022 end.centot = (int)ZIP64_ENDTOT(end64buf); // assume total < 2g
1023 end.endpos = end64pos;
1024 }
1025 return end;
1026 }
1027 }
1028 }
1029 zerror("zip END header not found");
1030 return null; //make compiler happy
1031 }
1032
1033 // Reads zip file central directory. Returns the file position of first
1034 // CEN header, otherwise returns -1 if an error occurred. If zip->msg != NULL
1035 // then the error was a zip format error and zip->msg has the error text.
1036 // Always pass in -1 for knownTotal; it's used for a recursive call.
1037 private byte[] initCEN() throws IOException {
1038 end = findEND();
1039 if (end.endpos == 0) {
1040 inodes = new LinkedHashMap<>(10);
1041 locpos = 0;
1042 buildNodeTree();
1043 return null; // only END header present
1044 }
1175 // use e.csize, LOCSIZ(buf) is zero if FLAG_DATADESCR is on
1176 // size += LOCNAM(buf) + LOCEXT(buf) + LOCSIZ(buf);
1177 size += LOCNAM(buf) + LOCEXT(buf) + e.csize;
1178 written = LOCHDR + size;
1179 }
1180 int n;
1181 while (size > 0 &&
1182 (n = (int)readFullyAt(buf, 0, buf.length, locoff)) != -1)
1183 {
1184 if (size < n)
1185 n = (int)size;
1186 os.write(buf, 0, n);
1187 size -= n;
1188 locoff += n;
1189 }
1190 return written;
1191 }
1192
1193 // sync the zip file system, if there is any udpate
1194 private void sync() throws IOException {
1195 //System.out.printf("->sync(%s) starting....!%n", toString());
1196 // check ex-closer
1197 if (!exChClosers.isEmpty()) {
1198 for (ExChannelCloser ecc : exChClosers) {
1199 if (ecc.streams.isEmpty()) {
1200 ecc.ch.close();
1201 Files.delete(ecc.path);
1202 exChClosers.remove(ecc);
1203 }
1204 }
1205 }
1206 if (!hasUpdate)
1207 return;
1208 Path tmpFile = createTempFileInSameDirectoryAs(zfpath);
1209 try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(tmpFile, WRITE)))
1210 {
1211 ArrayList<Entry> elist = new ArrayList<>(inodes.size());
1212 long written = 0;
1213 byte[] buf = new byte[8192];
1214 Entry e = null;
1215
1265 if (inode.pos == -1) {
1266 continue; // pseudo directory node
1267 }
1268 e = Entry.readCEN(this, inode);
1269 try {
1270 written += copyLOCEntry(e, false, os, written, buf);
1271 elist.add(e);
1272 } catch (IOException x) {
1273 x.printStackTrace(); // skip any wrong entry
1274 }
1275 }
1276 }
1277
1278 // now write back the cen and end table
1279 end.cenoff = written;
1280 for (Entry entry : elist) {
1281 written += entry.writeCEN(os);
1282 }
1283 end.centot = elist.size();
1284 end.cenlen = written - end.cenoff;
1285 end.write(os, written);
1286 }
1287 if (!streams.isEmpty()) {
1288 //
1289 // TBD: ExChannelCloser should not be necessary if we only
1290 // sync when being closed, all streams should have been
1291 // closed already. Keep the logic here for now.
1292 //
1293 // There are outstanding input streams open on existing "ch",
1294 // so, don't close the "cha" and delete the "file for now, let
1295 // the "ex-channel-closer" to handle them
1296 ExChannelCloser ecc = new ExChannelCloser(
1297 createTempFileInSameDirectoryAs(zfpath),
1298 ch,
1299 streams);
1300 Files.move(zfpath, ecc.path, REPLACE_EXISTING);
1301 exChClosers.add(ecc);
1302 streams = Collections.synchronizedSet(new HashSet<InputStream>());
1303 } else {
1304 ch.close();
1305 Files.delete(zfpath);
1695 }
1696 }
1697
1698 // End of central directory record
1699 static class END {
1700 // these 2 fields are not used by anyone and write() uses "0"
1701 // int disknum;
1702 // int sdisknum;
1703 int endsub; // endsub
1704 int centot; // 4 bytes
1705 long cenlen; // 4 bytes
1706 long cenoff; // 4 bytes
1707 int comlen; // comment length
1708 byte[] comment;
1709
1710 /* members of Zip64 end of central directory locator */
1711 // int diskNum;
1712 long endpos;
1713 // int disktot;
1714
1715 void write(OutputStream os, long offset) throws IOException {
1716 boolean hasZip64 = false;
1717 long xlen = cenlen;
1718 long xoff = cenoff;
1719 if (xlen >= ZIP64_MINVAL) {
1720 xlen = ZIP64_MINVAL;
1721 hasZip64 = true;
1722 }
1723 if (xoff >= ZIP64_MINVAL) {
1724 xoff = ZIP64_MINVAL;
1725 hasZip64 = true;
1726 }
1727 int count = centot;
1728 if (count >= ZIP64_MINVAL32) {
1729 count = ZIP64_MINVAL32;
1730 hasZip64 = true;
1731 }
1732 if (hasZip64) {
1733 long off64 = offset;
1734 //zip64 end of central directory record
1735 writeInt(os, ZIP64_ENDSIG); // zip64 END record signature
1736 writeLong(os, ZIP64_ENDHDR - 12); // size of zip64 end
1737 writeShort(os, 45); // version made by
1738 writeShort(os, 45); // version needed to extract
1739 writeInt(os, 0); // number of this disk
1740 writeInt(os, 0); // central directory start disk
1741 writeLong(os, centot); // number of directory entires on disk
1742 writeLong(os, centot); // number of directory entires
1743 writeLong(os, cenlen); // length of central directory
1744 writeLong(os, cenoff); // offset of central directory
1745
1746 //zip64 end of central directory locator
1747 writeInt(os, ZIP64_LOCSIG); // zip64 END locator signature
1748 writeInt(os, 0); // zip64 END start disk
1749 writeLong(os, off64); // offset of zip64 END
1750 writeInt(os, 1); // total number of disks (?)
1751 }
1752 writeInt(os, ENDSIG); // END record signature
1753 writeShort(os, 0); // number of this disk
1754 writeShort(os, 0); // central directory start disk
1755 writeShort(os, count); // number of directory entries on disk
1756 writeShort(os, count); // total number of directory entries
1757 writeInt(os, xlen); // length of central directory
1758 writeInt(os, xoff); // offset of central directory
1759 if (comment != null) { // zip file comment
1760 writeShort(os, comment.length);
1761 writeBytes(os, comment);
1762 } else {
|
62 /**
63 * A FileSystem built on a zip file
64 *
65 * @author Xueming Shen
66 */
67
68 class ZipFileSystem extends FileSystem {
69
70 private final ZipFileSystemProvider provider;
71 private final Path zfpath;
72 final ZipCoder zc;
73 private final boolean noExtt; // see readExtra()
74 private final ZipPath rootdir;
75 // configurable by env map
76 private final boolean useTempFile; // use a temp file for newOS, default
77 // is to use BAOS for better performance
78 private boolean readOnly = false; // readonly file system
79 private static final boolean isWindows = AccessController.doPrivileged(
80 (PrivilegedAction<Boolean>) () -> System.getProperty("os.name")
81 .startsWith("Windows"));
82 private final boolean forceEnd64;
83
84 ZipFileSystem(ZipFileSystemProvider provider,
85 Path zfpath,
86 Map<String, ?> env) throws IOException
87 {
88 // create a new zip if not exists
89 boolean createNew = "true".equals(env.get("create"));
90 // default encoding for name/comment
91 String nameEncoding = env.containsKey("encoding") ?
92 (String)env.get("encoding") : "UTF-8";
93 this.noExtt = "false".equals(env.get("zipinfo-time"));
94 this.useTempFile = TRUE.equals(env.get("useTempFile"));
95 this.forceEnd64 = "true".equals(env.get("forceZIP64End"));
96 this.provider = provider;
97 this.zfpath = zfpath;
98 if (Files.notExists(zfpath)) {
99 if (createNew) {
100 try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
101 new END().write(os, 0, forceEnd64);
102 }
103 } else {
104 throw new FileSystemNotFoundException(zfpath.toString());
105 }
106 }
107 // sm and existence check
108 zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
109 boolean writeable = AccessController.doPrivileged(
110 (PrivilegedAction<Boolean>) () -> Files.isWritable(zfpath));
111 this.readOnly = !writeable;
112 this.zc = ZipCoder.get(nameEncoding);
113 this.rootdir = new ZipPath(this, new byte[]{'/'});
114 this.ch = Files.newByteChannel(zfpath, READ);
115 try {
116 this.cen = initCEN();
117 } catch (IOException x) {
118 try {
119 this.ch.close();
120 } catch (IOException xx) {
121 x.addSuppressed(xx);
985 int len = buf.length - off;
986 if (readFullyAt(buf, off, len, pos + off) != len)
987 zerror("zip END header not found");
988
989 // Now scan the block backwards for END header signature
990 for (int i = buf.length - ENDHDR; i >= 0; i--) {
991 if (buf[i+0] == (byte)'P' &&
992 buf[i+1] == (byte)'K' &&
993 buf[i+2] == (byte)'\005' &&
994 buf[i+3] == (byte)'\006' &&
995 (pos + i + ENDHDR + ENDCOM(buf, i) == ziplen)) {
996 // Found END header
997 buf = Arrays.copyOfRange(buf, i, i + ENDHDR);
998 END end = new END();
999 end.endsub = ENDSUB(buf);
1000 end.centot = ENDTOT(buf);
1001 end.cenlen = ENDSIZ(buf);
1002 end.cenoff = ENDOFF(buf);
1003 end.comlen = ENDCOM(buf);
1004 end.endpos = pos + i;
1005 // try if there is zip64 end;
1006 byte[] loc64 = new byte[ZIP64_LOCHDR];
1007 if (end.endpos < ZIP64_LOCHDR ||
1008 readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR)
1009 != loc64.length ||
1010 !locator64SigAt(loc64, 0)) {
1011 return end;
1012 }
1013 long end64pos = ZIP64_LOCOFF(loc64);
1014 byte[] end64buf = new byte[ZIP64_ENDHDR];
1015 if (readFullyAt(end64buf, 0, end64buf.length, end64pos)
1016 != end64buf.length ||
1017 !end64SigAt(end64buf, 0)) {
1018 return end;
1019 }
1020 // end64 found,
1021 long cenlen64 = ZIP64_ENDSIZ(end64buf);
1022 long cenoff64 = ZIP64_ENDOFF(end64buf);
1023 long centot64 = ZIP64_ENDTOT(end64buf);
1024 // double-check
1025 if (cenlen64 != end.cenlen && end.cenlen != ZIP64_MINVAL ||
1026 cenoff64 != end.cenoff && end.cenoff != ZIP64_MINVAL ||
1027 centot64 != end.centot && end.centot != ZIP64_MINVAL32) {
1028 return end;
1029 }
1030 // to use the end64 values
1031 end.cenlen = cenlen64;
1032 end.cenoff = cenoff64;
1033 end.centot = (int)centot64; // assume total < 2g
1034 end.endpos = end64pos;
1035 return end;
1036 }
1037 }
1038 }
1039 zerror("zip END header not found");
1040 return null; //make compiler happy
1041 }
1042
1043 // Reads zip file central directory. Returns the file position of first
1044 // CEN header, otherwise returns -1 if an error occurred. If zip->msg != NULL
1045 // then the error was a zip format error and zip->msg has the error text.
1046 // Always pass in -1 for knownTotal; it's used for a recursive call.
1047 private byte[] initCEN() throws IOException {
1048 end = findEND();
1049 if (end.endpos == 0) {
1050 inodes = new LinkedHashMap<>(10);
1051 locpos = 0;
1052 buildNodeTree();
1053 return null; // only END header present
1054 }
1185 // use e.csize, LOCSIZ(buf) is zero if FLAG_DATADESCR is on
1186 // size += LOCNAM(buf) + LOCEXT(buf) + LOCSIZ(buf);
1187 size += LOCNAM(buf) + LOCEXT(buf) + e.csize;
1188 written = LOCHDR + size;
1189 }
1190 int n;
1191 while (size > 0 &&
1192 (n = (int)readFullyAt(buf, 0, buf.length, locoff)) != -1)
1193 {
1194 if (size < n)
1195 n = (int)size;
1196 os.write(buf, 0, n);
1197 size -= n;
1198 locoff += n;
1199 }
1200 return written;
1201 }
1202
1203 // sync the zip file system, if there is any udpate
1204 private void sync() throws IOException {
1205 // System.out.printf("->sync(%s) starting....!%n", toString());
1206 // check ex-closer
1207 if (!exChClosers.isEmpty()) {
1208 for (ExChannelCloser ecc : exChClosers) {
1209 if (ecc.streams.isEmpty()) {
1210 ecc.ch.close();
1211 Files.delete(ecc.path);
1212 exChClosers.remove(ecc);
1213 }
1214 }
1215 }
1216 if (!hasUpdate)
1217 return;
1218 Path tmpFile = createTempFileInSameDirectoryAs(zfpath);
1219 try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(tmpFile, WRITE)))
1220 {
1221 ArrayList<Entry> elist = new ArrayList<>(inodes.size());
1222 long written = 0;
1223 byte[] buf = new byte[8192];
1224 Entry e = null;
1225
1275 if (inode.pos == -1) {
1276 continue; // pseudo directory node
1277 }
1278 e = Entry.readCEN(this, inode);
1279 try {
1280 written += copyLOCEntry(e, false, os, written, buf);
1281 elist.add(e);
1282 } catch (IOException x) {
1283 x.printStackTrace(); // skip any wrong entry
1284 }
1285 }
1286 }
1287
1288 // now write back the cen and end table
1289 end.cenoff = written;
1290 for (Entry entry : elist) {
1291 written += entry.writeCEN(os);
1292 }
1293 end.centot = elist.size();
1294 end.cenlen = written - end.cenoff;
1295 end.write(os, written, forceEnd64);
1296 }
1297 if (!streams.isEmpty()) {
1298 //
1299 // TBD: ExChannelCloser should not be necessary if we only
1300 // sync when being closed, all streams should have been
1301 // closed already. Keep the logic here for now.
1302 //
1303 // There are outstanding input streams open on existing "ch",
1304 // so, don't close the "cha" and delete the "file for now, let
1305 // the "ex-channel-closer" to handle them
1306 ExChannelCloser ecc = new ExChannelCloser(
1307 createTempFileInSameDirectoryAs(zfpath),
1308 ch,
1309 streams);
1310 Files.move(zfpath, ecc.path, REPLACE_EXISTING);
1311 exChClosers.add(ecc);
1312 streams = Collections.synchronizedSet(new HashSet<InputStream>());
1313 } else {
1314 ch.close();
1315 Files.delete(zfpath);
1705 }
1706 }
1707
1708 // End of central directory record
1709 static class END {
1710 // these 2 fields are not used by anyone and write() uses "0"
1711 // int disknum;
1712 // int sdisknum;
1713 int endsub; // endsub
1714 int centot; // 4 bytes
1715 long cenlen; // 4 bytes
1716 long cenoff; // 4 bytes
1717 int comlen; // comment length
1718 byte[] comment;
1719
1720 /* members of Zip64 end of central directory locator */
1721 // int diskNum;
1722 long endpos;
1723 // int disktot;
1724
1725 void write(OutputStream os, long offset, boolean forceEnd64) throws IOException {
1726 boolean hasZip64 = forceEnd64; // false;
1727 long xlen = cenlen;
1728 long xoff = cenoff;
1729 if (xlen >= ZIP64_MINVAL) {
1730 xlen = ZIP64_MINVAL;
1731 hasZip64 = true;
1732 }
1733 if (xoff >= ZIP64_MINVAL) {
1734 xoff = ZIP64_MINVAL;
1735 hasZip64 = true;
1736 }
1737 int count = centot;
1738 if (count >= ZIP64_MINVAL32) {
1739 count = ZIP64_MINVAL32;
1740 hasZip64 = true;
1741 }
1742 if (hasZip64) {
1743 long off64 = offset;
1744 //zip64 end of central directory record
1745 writeInt(os, ZIP64_ENDSIG); // zip64 END record signature
1746 writeLong(os, ZIP64_ENDHDR - 12); // size of zip64 end
1747 writeShort(os, 45); // version made by
1748 writeShort(os, 45); // version needed to extract
1749 writeInt(os, 0); // number of this disk
1750 writeInt(os, 0); // central directory start disk
1751 writeLong(os, centot); // number of directory entries on disk
1752 writeLong(os, centot); // number of directory entries
1753 writeLong(os, cenlen); // length of central directory
1754 writeLong(os, cenoff); // offset of central directory
1755
1756 //zip64 end of central directory locator
1757 writeInt(os, ZIP64_LOCSIG); // zip64 END locator signature
1758 writeInt(os, 0); // zip64 END start disk
1759 writeLong(os, off64); // offset of zip64 END
1760 writeInt(os, 1); // total number of disks (?)
1761 }
1762 writeInt(os, ENDSIG); // END record signature
1763 writeShort(os, 0); // number of this disk
1764 writeShort(os, 0); // central directory start disk
1765 writeShort(os, count); // number of directory entries on disk
1766 writeShort(os, count); // total number of directory entries
1767 writeInt(os, xlen); // length of central directory
1768 writeInt(os, xoff); // offset of central directory
1769 if (comment != null) { // zip file comment
1770 writeShort(os, comment.length);
1771 writeBytes(os, comment);
1772 } else {
|