39 import java.nio.channels.FileLock; |
39 import java.nio.channels.FileLock; |
40 import java.nio.channels.ReadableByteChannel; |
40 import java.nio.channels.ReadableByteChannel; |
41 import java.nio.channels.SeekableByteChannel; |
41 import java.nio.channels.SeekableByteChannel; |
42 import java.nio.channels.WritableByteChannel; |
42 import java.nio.channels.WritableByteChannel; |
43 import java.nio.file.*; |
43 import java.nio.file.*; |
44 import java.nio.file.attribute.FileAttribute; |
44 import java.nio.file.attribute.*; |
45 import java.nio.file.attribute.FileTime; |
|
46 import java.nio.file.attribute.UserPrincipalLookupService; |
|
47 import java.nio.file.spi.FileSystemProvider; |
45 import java.nio.file.spi.FileSystemProvider; |
48 import java.security.AccessController; |
46 import java.security.AccessController; |
49 import java.security.PrivilegedAction; |
47 import java.security.PrivilegedAction; |
50 import java.security.PrivilegedActionException; |
48 import java.security.PrivilegedActionException; |
51 import java.security.PrivilegedExceptionAction; |
49 import java.security.PrivilegedExceptionAction; |
80 class ZipFileSystem extends FileSystem { |
78 class ZipFileSystem extends FileSystem { |
81 // statics |
79 // statics |
82 private static final boolean isWindows = AccessController.doPrivileged( |
80 private static final boolean isWindows = AccessController.doPrivileged( |
83 (PrivilegedAction<Boolean>)()->System.getProperty("os.name") |
81 (PrivilegedAction<Boolean>)()->System.getProperty("os.name") |
84 .startsWith("Windows")); |
82 .startsWith("Windows")); |
85 private static final Set<String> supportedFileAttributeViews = |
|
86 Set.of("basic", "zip"); |
|
87 private static final byte[] ROOTPATH = new byte[] { '/' }; |
83 private static final byte[] ROOTPATH = new byte[] { '/' }; |
|
84 private static final String OPT_POSIX = "enablePosixFileAttributes"; |
|
85 private static final String OPT_DEFAULT_OWNER = "defaultOwner"; |
|
86 private static final String OPT_DEFAULT_GROUP = "defaultGroup"; |
|
87 private static final String OPT_DEFAULT_PERMISSIONS = "defaultPermissions"; |
|
88 |
|
89 private static final Set<PosixFilePermission> DEFAULT_PERMISSIONS = |
|
90 PosixFilePermissions.fromString("rwxrwxrwx"); |
88 |
91 |
89 private final ZipFileSystemProvider provider; |
92 private final ZipFileSystemProvider provider; |
90 private final Path zfpath; |
93 private final Path zfpath; |
91 final ZipCoder zc; |
94 final ZipCoder zc; |
92 private final ZipPath rootdir; |
95 private final ZipPath rootdir; |
100 private final boolean useTempFile; // use a temp file for newOS, default |
103 private final boolean useTempFile; // use a temp file for newOS, default |
101 // is to use BAOS for better performance |
104 // is to use BAOS for better performance |
102 private final boolean forceEnd64; |
105 private final boolean forceEnd64; |
103 private final int defaultCompressionMethod; // METHOD_STORED if "noCompression=true" |
106 private final int defaultCompressionMethod; // METHOD_STORED if "noCompression=true" |
104 // METHOD_DEFLATED otherwise |
107 // METHOD_DEFLATED otherwise |
|
108 |
|
109 // POSIX support |
|
110 final boolean supportPosix; |
|
111 private final UserPrincipal defaultOwner; |
|
112 private final GroupPrincipal defaultGroup; |
|
113 private final Set<PosixFilePermission> defaultPermissions; |
|
114 |
|
115 private final Set<String> supportedFileAttributeViews; |
105 |
116 |
106 ZipFileSystem(ZipFileSystemProvider provider, |
117 ZipFileSystem(ZipFileSystemProvider provider, |
107 Path zfpath, |
118 Path zfpath, |
108 Map<String, ?> env) throws IOException |
119 Map<String, ?> env) throws IOException |
109 { |
120 { |
112 (String)env.get("encoding") : "UTF-8"; |
123 (String)env.get("encoding") : "UTF-8"; |
113 this.noExtt = "false".equals(env.get("zipinfo-time")); |
124 this.noExtt = "false".equals(env.get("zipinfo-time")); |
114 this.useTempFile = isTrue(env, "useTempFile"); |
125 this.useTempFile = isTrue(env, "useTempFile"); |
115 this.forceEnd64 = isTrue(env, "forceZIP64End"); |
126 this.forceEnd64 = isTrue(env, "forceZIP64End"); |
116 this.defaultCompressionMethod = isTrue(env, "noCompression") ? METHOD_STORED : METHOD_DEFLATED; |
127 this.defaultCompressionMethod = isTrue(env, "noCompression") ? METHOD_STORED : METHOD_DEFLATED; |
|
128 this.supportPosix = isTrue(env, OPT_POSIX); |
|
129 this.defaultOwner = initOwner(zfpath, env); |
|
130 this.defaultGroup = initGroup(zfpath, env); |
|
131 this.defaultPermissions = initPermissions(env); |
|
132 this.supportedFileAttributeViews = supportPosix ? |
|
133 Set.of("basic", "posix", "zip") : Set.of("basic", "zip"); |
117 if (Files.notExists(zfpath)) { |
134 if (Files.notExists(zfpath)) { |
118 // create a new zip if it doesn't exist |
135 // create a new zip if it doesn't exist |
119 if (isTrue(env, "create")) { |
136 if (isTrue(env, "create")) { |
120 try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) { |
137 try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) { |
121 new END().write(os, 0, forceEnd64); |
138 new END().write(os, 0, forceEnd64); |
149 // returns true if there is a name=true/"true" setting in env |
166 // returns true if there is a name=true/"true" setting in env |
150 private static boolean isTrue(Map<String, ?> env, String name) { |
167 private static boolean isTrue(Map<String, ?> env, String name) { |
151 return "true".equals(env.get(name)) || TRUE.equals(env.get(name)); |
168 return "true".equals(env.get(name)) || TRUE.equals(env.get(name)); |
152 } |
169 } |
153 |
170 |
|
171 // Initialize the default owner for files inside the zip archive. |
|
172 // If not specified in env, it is the owner of the archive. If no owner can |
|
173 // be determined, we try to go with system property "user.name". If that's not |
|
174 // accessible, we return "<zipfs_default>". |
|
175 private UserPrincipal initOwner(Path zfpath, Map<String, ?> env) throws IOException { |
|
176 Object o = env.get(OPT_DEFAULT_OWNER); |
|
177 if (o == null) { |
|
178 try { |
|
179 PrivilegedExceptionAction<UserPrincipal> pa = ()->Files.getOwner(zfpath); |
|
180 return AccessController.doPrivileged(pa); |
|
181 } catch (UnsupportedOperationException | PrivilegedActionException e) { |
|
182 if (e instanceof UnsupportedOperationException || |
|
183 e.getCause() instanceof NoSuchFileException) |
|
184 { |
|
185 PrivilegedAction<String> pa = ()->System.getProperty("user.name"); |
|
186 String userName = AccessController.doPrivileged(pa); |
|
187 return ()->userName; |
|
188 } else { |
|
189 throw new IOException(e); |
|
190 } |
|
191 } |
|
192 } |
|
193 if (o instanceof String) { |
|
194 if (((String)o).isEmpty()) { |
|
195 throw new IllegalArgumentException("Value for property " + |
|
196 OPT_DEFAULT_OWNER + " must not be empty."); |
|
197 } |
|
198 return ()->(String)o; |
|
199 } |
|
200 if (o instanceof UserPrincipal) { |
|
201 return (UserPrincipal)o; |
|
202 } |
|
203 throw new IllegalArgumentException("Value for property " + |
|
204 OPT_DEFAULT_OWNER + " must be of type " + String.class + |
|
205 " or " + UserPrincipal.class); |
|
206 } |
|
207 |
|
208 // Initialize the default group for files inside the zip archive. |
|
209 // If not specified in env, we try to determine the group of the zip archive itself. |
|
210 // If this is not possible/unsupported, we will return a group principal going by |
|
211 // the same name as the default owner. |
|
212 private GroupPrincipal initGroup(Path zfpath, Map<String, ?> env) throws IOException { |
|
213 Object o = env.get(OPT_DEFAULT_GROUP); |
|
214 if (o == null) { |
|
215 try { |
|
216 PosixFileAttributeView zfpv = Files.getFileAttributeView(zfpath, PosixFileAttributeView.class); |
|
217 if (zfpv == null) { |
|
218 return defaultOwner::getName; |
|
219 } |
|
220 PrivilegedExceptionAction<GroupPrincipal> pa = ()->zfpv.readAttributes().group(); |
|
221 return AccessController.doPrivileged(pa); |
|
222 } catch (UnsupportedOperationException | PrivilegedActionException e) { |
|
223 if (e instanceof UnsupportedOperationException || |
|
224 e.getCause() instanceof NoSuchFileException) |
|
225 { |
|
226 return defaultOwner::getName; |
|
227 } else { |
|
228 throw new IOException(e); |
|
229 } |
|
230 } |
|
231 } |
|
232 if (o instanceof String) { |
|
233 if (((String)o).isEmpty()) { |
|
234 throw new IllegalArgumentException("Value for property " + |
|
235 OPT_DEFAULT_GROUP + " must not be empty."); |
|
236 } |
|
237 return ()->(String)o; |
|
238 } |
|
239 if (o instanceof GroupPrincipal) { |
|
240 return (GroupPrincipal)o; |
|
241 } |
|
242 throw new IllegalArgumentException("Value for property " + |
|
243 OPT_DEFAULT_GROUP + " must be of type " + String.class + |
|
244 " or " + GroupPrincipal.class); |
|
245 } |
|
246 |
|
247 // Initialize the default permissions for files inside the zip archive. |
|
248 // If not specified in env, it will return 777. |
|
249 private Set<PosixFilePermission> initPermissions(Map<String, ?> env) { |
|
250 Object o = env.get(OPT_DEFAULT_PERMISSIONS); |
|
251 if (o == null) { |
|
252 return DEFAULT_PERMISSIONS; |
|
253 } |
|
254 if (o instanceof String) { |
|
255 return PosixFilePermissions.fromString((String)o); |
|
256 } |
|
257 if (!(o instanceof Set)) { |
|
258 throw new IllegalArgumentException("Value for property " + |
|
259 OPT_DEFAULT_PERMISSIONS + " must be of type " + String.class + |
|
260 " or " + Set.class); |
|
261 } |
|
262 Set<PosixFilePermission> perms = new HashSet<>(); |
|
263 for (Object o2 : (Set<?>)o) { |
|
264 if (o2 instanceof PosixFilePermission) { |
|
265 perms.add((PosixFilePermission)o2); |
|
266 } else { |
|
267 throw new IllegalArgumentException(OPT_DEFAULT_PERMISSIONS + |
|
268 " must only contain objects of type " + PosixFilePermission.class); |
|
269 } |
|
270 } |
|
271 return perms; |
|
272 } |
|
273 |
154 @Override |
274 @Override |
155 public FileSystemProvider provider() { |
275 public FileSystemProvider provider() { |
156 return provider; |
276 return provider; |
157 } |
277 } |
158 |
278 |
336 return null; |
456 return null; |
337 } else if (inode instanceof Entry) { |
457 } else if (inode instanceof Entry) { |
338 return (Entry)inode; |
458 return (Entry)inode; |
339 } else if (inode.pos == -1) { |
459 } else if (inode.pos == -1) { |
340 // pseudo directory, uses METHOD_STORED |
460 // pseudo directory, uses METHOD_STORED |
341 Entry e = new Entry(inode.name, inode.isdir, METHOD_STORED); |
461 Entry e = supportPosix ? |
|
462 new PosixEntry(inode.name, inode.isdir, METHOD_STORED) : |
|
463 new Entry(inode.name, inode.isdir, METHOD_STORED); |
342 e.mtime = e.atime = e.ctime = zfsDefaultTimeStamp; |
464 e.mtime = e.atime = e.ctime = zfsDefaultTimeStamp; |
343 return e; |
465 return e; |
344 } else { |
466 } else { |
345 return new Entry(this, inode); |
467 return supportPosix ? new PosixEntry(this, inode) : new Entry(this, inode); |
346 } |
468 } |
347 } finally { |
469 } finally { |
348 endRead(); |
470 endRead(); |
349 } |
471 } |
350 } |
472 } |
379 e.mtime = mtime.toMillis(); |
501 e.mtime = mtime.toMillis(); |
380 if (atime != null) |
502 if (atime != null) |
381 e.atime = atime.toMillis(); |
503 e.atime = atime.toMillis(); |
382 if (ctime != null) |
504 if (ctime != null) |
383 e.ctime = ctime.toMillis(); |
505 e.ctime = ctime.toMillis(); |
|
506 update(e); |
|
507 } finally { |
|
508 endWrite(); |
|
509 } |
|
510 } |
|
511 |
|
512 void setOwner(byte[] path, UserPrincipal owner) throws IOException { |
|
513 checkWritable(); |
|
514 beginWrite(); |
|
515 try { |
|
516 ensureOpen(); |
|
517 Entry e = getEntry(path); // ensureOpen checked |
|
518 if (e == null) { |
|
519 throw new NoSuchFileException(getString(path)); |
|
520 } |
|
521 // as the owner information is not persistent, we don't need to |
|
522 // change e.type to Entry.COPY |
|
523 if (e instanceof PosixEntry) { |
|
524 ((PosixEntry)e).owner = owner; |
|
525 update(e); |
|
526 } |
|
527 } finally { |
|
528 endWrite(); |
|
529 } |
|
530 } |
|
531 |
|
532 void setGroup(byte[] path, GroupPrincipal group) throws IOException { |
|
533 checkWritable(); |
|
534 beginWrite(); |
|
535 try { |
|
536 ensureOpen(); |
|
537 Entry e = getEntry(path); // ensureOpen checked |
|
538 if (e == null) { |
|
539 throw new NoSuchFileException(getString(path)); |
|
540 } |
|
541 // as the group information is not persistent, we don't need to |
|
542 // change e.type to Entry.COPY |
|
543 if (e instanceof PosixEntry) { |
|
544 ((PosixEntry)e).group = group; |
|
545 update(e); |
|
546 } |
|
547 } finally { |
|
548 endWrite(); |
|
549 } |
|
550 } |
|
551 |
|
552 void setPermissions(byte[] path, Set<PosixFilePermission> perms) throws IOException { |
|
553 checkWritable(); |
|
554 beginWrite(); |
|
555 try { |
|
556 ensureOpen(); |
|
557 Entry e = getEntry(path); // ensureOpen checked |
|
558 if (e == null) { |
|
559 throw new NoSuchFileException(getString(path)); |
|
560 } |
|
561 if (e.type == Entry.CEN) { |
|
562 e.type = Entry.COPY; // copy e |
|
563 } |
|
564 e.posixPerms = perms == null ? -1 : ZipUtils.permsToFlags(perms); |
384 update(e); |
565 update(e); |
385 } finally { |
566 } finally { |
386 endWrite(); |
567 endWrite(); |
387 } |
568 } |
388 } |
569 } |
487 throw new FileAlreadyExistsException(getString(dst)); |
670 throw new FileAlreadyExistsException(getString(dst)); |
488 } else { |
671 } else { |
489 checkParents(dst); |
672 checkParents(dst); |
490 } |
673 } |
491 // copy eSrc entry and change name |
674 // copy eSrc entry and change name |
492 Entry u = new Entry(eSrc, Entry.COPY); |
675 Entry u = supportPosix ? |
|
676 new PosixEntry((PosixEntry)eSrc, Entry.COPY) : |
|
677 new Entry(eSrc, Entry.COPY); |
493 u.name(dst); |
678 u.name(dst); |
494 if (eSrc.type == Entry.NEW || eSrc.type == Entry.FILECH) { |
679 if (eSrc.type == Entry.NEW || eSrc.type == Entry.FILECH) { |
495 u.type = eSrc.type; // make it the same type |
680 u.type = eSrc.type; // make it the same type |
496 if (deletesrc) { // if it's a "rename", take the data |
681 if (deletesrc) { // if it's a "rename", take the data |
497 u.bytes = eSrc.bytes; |
682 u.bytes = eSrc.bytes; |
551 try (InputStream is = getInputStream(e)) { |
736 try (InputStream is = getInputStream(e)) { |
552 is.transferTo(os); |
737 is.transferTo(os); |
553 } |
738 } |
554 return os; |
739 return os; |
555 } |
740 } |
556 return getOutputStream(new Entry(e, Entry.NEW)); |
741 return getOutputStream(supportPosix ? |
|
742 new PosixEntry((PosixEntry)e, Entry.NEW) : new Entry(e, Entry.NEW)); |
557 } else { |
743 } else { |
558 if (!hasCreate && !hasCreateNew) |
744 if (!hasCreate && !hasCreateNew) |
559 throw new NoSuchFileException(getString(path)); |
745 throw new NoSuchFileException(getString(path)); |
560 checkParents(path); |
746 checkParents(path); |
561 return getOutputStream(new Entry(path, Entry.NEW, false, defaultCompressionMethod)); |
747 return getOutputStream(supportPosix ? |
|
748 new PosixEntry(path, Entry.NEW, false, defaultCompressionMethod) : |
|
749 new Entry(path, Entry.NEW, false, defaultCompressionMethod)); |
562 } |
750 } |
563 } finally { |
751 } finally { |
564 endRead(); |
752 endRead(); |
565 } |
753 } |
566 } |
754 } |
643 Entry e = getEntry(path); |
831 Entry e = getEntry(path); |
644 if (e != null) { |
832 if (e != null) { |
645 if (e.isDir() || options.contains(CREATE_NEW)) |
833 if (e.isDir() || options.contains(CREATE_NEW)) |
646 throw new FileAlreadyExistsException(getString(path)); |
834 throw new FileAlreadyExistsException(getString(path)); |
647 SeekableByteChannel sbc = |
835 SeekableByteChannel sbc = |
648 new EntryOutputChannel(new Entry(e, Entry.NEW)); |
836 new EntryOutputChannel(supportPosix ? |
|
837 new PosixEntry((PosixEntry)e, Entry.NEW) : |
|
838 new Entry(e, Entry.NEW)); |
649 if (options.contains(APPEND)) { |
839 if (options.contains(APPEND)) { |
650 try (InputStream is = getInputStream(e)) { // copyover |
840 try (InputStream is = getInputStream(e)) { // copyover |
651 byte[] buf = new byte[8192]; |
841 byte[] buf = new byte[8192]; |
652 ByteBuffer bb = ByteBuffer.wrap(buf); |
842 ByteBuffer bb = ByteBuffer.wrap(buf); |
653 int n; |
843 int n; |
726 final boolean isFCH = (e != null && e.type == Entry.FILECH); |
918 final boolean isFCH = (e != null && e.type == Entry.FILECH); |
727 final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path); |
919 final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path); |
728 final FileChannel fch = tmpfile.getFileSystem() |
920 final FileChannel fch = tmpfile.getFileSystem() |
729 .provider() |
921 .provider() |
730 .newFileChannel(tmpfile, options, attrs); |
922 .newFileChannel(tmpfile, options, attrs); |
731 final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH); |
923 final Entry u = isFCH ? e : ( |
|
924 supportPosix ? |
|
925 new PosixEntry(path, tmpfile, Entry.FILECH, attrs) : |
|
926 new Entry(path, tmpfile, Entry.FILECH, attrs)); |
732 if (forWrite) { |
927 if (forWrite) { |
733 u.flag = FLAG_DATADESCR; |
928 u.flag = FLAG_DATADESCR; |
734 u.method = defaultCompressionMethod; |
929 u.method = defaultCompressionMethod; |
735 } |
930 } |
736 // is there a better way to hook into the FileChannel's close method? |
931 // is there a better way to hook into the FileChannel's close method? |
2079 this.size = 0; |
2275 this.size = 0; |
2080 this.csize = 0; |
2276 this.csize = 0; |
2081 this.method = method; |
2277 this.method = method; |
2082 } |
2278 } |
2083 |
2279 |
2084 Entry(byte[] name, int type, boolean isdir, int method) { |
2280 @SuppressWarnings("unchecked") |
|
2281 Entry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) { |
2085 this(name, isdir, method); |
2282 this(name, isdir, method); |
2086 this.type = type; |
2283 this.type = type; |
2087 } |
2284 for (FileAttribute<?> attr : attrs) { |
2088 |
2285 String attrName = attr.name(); |
2089 Entry(byte[] name, Path file, int type) { |
2286 if (attrName.equals("posix:permissions")) { |
2090 this(name, type, false, METHOD_STORED); |
2287 posixPerms = ZipUtils.permsToFlags((Set<PosixFilePermission>)attr.value()); |
|
2288 } |
|
2289 } |
|
2290 } |
|
2291 |
|
2292 Entry(byte[] name, Path file, int type, FileAttribute<?>... attrs) { |
|
2293 this(name, type, false, METHOD_STORED, attrs); |
2091 this.file = file; |
2294 this.file = file; |
2092 } |
2295 } |
2093 |
2296 |
2094 Entry(Entry e, int type) { |
2297 Entry(Entry e, int type) { |
2095 name(e.name); |
2298 name(e.name); |
2131 if (method == METHOD_DEFLATED) |
2335 if (method == METHOD_DEFLATED) |
2132 return 20; |
2336 return 20; |
2133 else if (method == METHOD_STORED) |
2337 else if (method == METHOD_STORED) |
2134 return 10; |
2338 return 10; |
2135 throw new ZipException("unsupported compression method"); |
2339 throw new ZipException("unsupported compression method"); |
|
2340 } |
|
2341 |
|
2342 /** |
|
2343 * Adds information about compatibility of file attribute information |
|
2344 * to a version value. |
|
2345 */ |
|
2346 private int versionMadeBy(int version) { |
|
2347 return (posixPerms < 0) ? version : |
|
2348 VERSION_MADE_BY_BASE_UNIX | (version & 0xff); |
2136 } |
2349 } |
2137 |
2350 |
2138 ///////////////////// CEN ////////////////////// |
2351 ///////////////////// CEN ////////////////////// |
2139 private void readCEN(ZipFileSystem zipfs, IndexNode inode) throws IOException { |
2352 private void readCEN(ZipFileSystem zipfs, IndexNode inode) throws IOException { |
2140 byte[] cen = zipfs.cen; |
2353 byte[] cen = zipfs.cen; |
2155 versionMade = CENVEM(cen, pos); |
2368 versionMade = CENVEM(cen, pos); |
2156 disk = CENDSK(cen, pos); |
2369 disk = CENDSK(cen, pos); |
2157 attrs = CENATT(cen, pos); |
2370 attrs = CENATT(cen, pos); |
2158 attrsEx = CENATX(cen, pos); |
2371 attrsEx = CENATX(cen, pos); |
2159 */ |
2372 */ |
|
2373 if (CENVEM_FA(cen, pos) == FILE_ATTRIBUTES_UNIX) { |
|
2374 posixPerms = CENATX_PERMS(cen, pos) & 0xFFF; // 12 bits for setuid, setgid, sticky + perms |
|
2375 } |
2160 locoff = CENOFF(cen, pos); |
2376 locoff = CENOFF(cen, pos); |
2161 pos += CENHDR; |
2377 pos += CENHDR; |
2162 this.name = inode.name; |
2378 this.name = inode.name; |
2163 this.isdir = inode.isdir; |
2379 this.isdir = inode.isdir; |
2164 this.hashcode = inode.hashcode; |
2380 this.hashcode = inode.hashcode; |
2221 } else { // Extended Timestamp otherwise |
2437 } else { // Extended Timestamp otherwise |
2222 elenEXTT = 9; // only mtime in cen |
2438 elenEXTT = 9; // only mtime in cen |
2223 } |
2439 } |
2224 } |
2440 } |
2225 writeInt(os, CENSIG); // CEN header signature |
2441 writeInt(os, CENSIG); // CEN header signature |
2226 writeShort(os, version0); // version made by |
2442 writeShort(os, versionMadeBy(version0)); // version made by |
2227 writeShort(os, version0); // version needed to extract |
2443 writeShort(os, version0); // version needed to extract |
2228 writeShort(os, flag); // general purpose bit flag |
2444 writeShort(os, flag); // general purpose bit flag |
2229 writeShort(os, method); // compression method |
2445 writeShort(os, method); // compression method |
2230 // last modification time |
2446 // last modification time |
2231 writeInt(os, (int)javaToDosTime(mtime)); |
2447 writeInt(os, (int)javaToDosTime(mtime)); |
2240 } else { |
2456 } else { |
2241 writeShort(os, 0); |
2457 writeShort(os, 0); |
2242 } |
2458 } |
2243 writeShort(os, 0); // starting disk number |
2459 writeShort(os, 0); // starting disk number |
2244 writeShort(os, 0); // internal file attributes (unused) |
2460 writeShort(os, 0); // internal file attributes (unused) |
2245 writeInt(os, 0); // external file attributes (unused) |
2461 writeInt(os, posixPerms > 0 ? posixPerms << 16 : 0); // external file |
|
2462 // attributes, used for storing posix |
|
2463 // permissions |
2246 writeInt(os, locoff0); // relative offset of local header |
2464 writeInt(os, locoff0); // relative offset of local header |
2247 writeBytes(os, zname, 1, nlen); |
2465 writeBytes(os, zname, 1, nlen); |
2248 if (zip64) { |
2466 if (zip64) { |
2249 writeShort(os, EXTID_ZIP64);// Zip64 extra |
2467 writeShort(os, EXTID_ZIP64);// Zip64 extra |
2250 writeShort(os, elen64 - 4); // size of "this" extra block |
2468 writeShort(os, elen64 - 4); // size of "this" extra block |
2525 fm.format(" fileKey : %s%n", fileKey()); |
2743 fm.format(" fileKey : %s%n", fileKey()); |
2526 fm.format(" size : %d%n", size()); |
2744 fm.format(" size : %d%n", size()); |
2527 fm.format(" compressedSize : %d%n", compressedSize()); |
2745 fm.format(" compressedSize : %d%n", compressedSize()); |
2528 fm.format(" crc : %x%n", crc()); |
2746 fm.format(" crc : %x%n", crc()); |
2529 fm.format(" method : %d%n", method()); |
2747 fm.format(" method : %d%n", method()); |
|
2748 Set<PosixFilePermission> permissions = storedPermissions().orElse(null); |
|
2749 if (permissions != null) { |
|
2750 fm.format(" permissions : %s%n", permissions); |
|
2751 } |
2530 fm.close(); |
2752 fm.close(); |
2531 return sb.toString(); |
2753 return sb.toString(); |
2532 } |
2754 } |
2533 |
2755 |
2534 ///////// basic file attributes /////////// |
2756 ///////// basic file attributes /////////// |
2604 @Override |
2826 @Override |
2605 public byte[] comment() { |
2827 public byte[] comment() { |
2606 if (comment != null) |
2828 if (comment != null) |
2607 return Arrays.copyOf(comment, comment.length); |
2829 return Arrays.copyOf(comment, comment.length); |
2608 return null; |
2830 return null; |
|
2831 } |
|
2832 |
|
2833 @Override |
|
2834 public Optional<Set<PosixFilePermission>> storedPermissions() { |
|
2835 Set<PosixFilePermission> perms = null; |
|
2836 if (posixPerms != -1) { |
|
2837 perms = new HashSet<>(PosixFilePermission.values().length); |
|
2838 for (PosixFilePermission perm : PosixFilePermission.values()) { |
|
2839 if ((posixPerms & ZipUtils.permToFlag(perm)) != 0) { |
|
2840 perms.add(perm); |
|
2841 } |
|
2842 } |
|
2843 } |
|
2844 return Optional.ofNullable(perms); |
|
2845 } |
|
2846 } |
|
2847 |
|
2848 final class PosixEntry extends Entry implements PosixFileAttributes { |
|
2849 private UserPrincipal owner = defaultOwner; |
|
2850 private GroupPrincipal group = defaultGroup; |
|
2851 |
|
2852 PosixEntry(byte[] name, boolean isdir, int method) { |
|
2853 super(name, isdir, method); |
|
2854 } |
|
2855 |
|
2856 PosixEntry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) { |
|
2857 super(name, type, isdir, method, attrs); |
|
2858 } |
|
2859 |
|
2860 PosixEntry(byte[] name, Path file, int type, FileAttribute<?>... attrs) { |
|
2861 super(name, file, type, attrs); |
|
2862 } |
|
2863 |
|
2864 PosixEntry(PosixEntry e, int type) { |
|
2865 super(e, type); |
|
2866 this.owner = e.owner; |
|
2867 this.group = e.group; |
|
2868 } |
|
2869 |
|
2870 PosixEntry(ZipFileSystem zipfs, IndexNode inode) throws IOException { |
|
2871 super(zipfs, inode); |
|
2872 } |
|
2873 |
|
2874 @Override |
|
2875 public UserPrincipal owner() { |
|
2876 return owner; |
|
2877 } |
|
2878 |
|
2879 @Override |
|
2880 public GroupPrincipal group() { |
|
2881 return group; |
|
2882 } |
|
2883 |
|
2884 @Override |
|
2885 public Set<PosixFilePermission> permissions() { |
|
2886 return storedPermissions().orElse(Set.copyOf(defaultPermissions)); |
2609 } |
2887 } |
2610 } |
2888 } |
2611 |
2889 |
2612 private static class ExistingChannelCloser { |
2890 private static class ExistingChannelCloser { |
2613 private final Path path; |
2891 private final Path path; |