8203328: Rename EFS in java.util.zip internals to something meaningful
Reviewed-by: sherman
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.IOException;
34 import java.io.InputStream;
35 import java.io.OutputStream;
36 import java.nio.ByteBuffer;
37 import java.nio.MappedByteBuffer;
38 import java.nio.channels.*;
39 import java.nio.file.*;
40 import java.nio.file.attribute.*;
41 import java.nio.file.spi.*;
42 import java.security.AccessController;
43 import java.security.PrivilegedAction;
44 import java.security.PrivilegedActionException;
45 import java.security.PrivilegedExceptionAction;
46 import java.util.*;
47 import java.util.concurrent.locks.ReadWriteLock;
48 import java.util.concurrent.locks.ReentrantReadWriteLock;
49 import java.util.regex.Pattern;
50 import java.util.zip.CRC32;
51 import java.util.zip.Inflater;
52 import java.util.zip.Deflater;
53 import java.util.zip.InflaterInputStream;
54 import java.util.zip.DeflaterOutputStream;
55 import java.util.zip.ZipException;
56 import static java.lang.Boolean.*;
57 import static jdk.nio.zipfs.ZipConstants.*;
58 import static jdk.nio.zipfs.ZipUtils.*;
59 import static java.nio.file.StandardOpenOption.*;
60 import static java.nio.file.StandardCopyOption.*;
61
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);
122 }
123 throw x;
124 }
125 }
126
127 @Override
128 public FileSystemProvider provider() {
129 return provider;
130 }
131
132 @Override
133 public String getSeparator() {
134 return "/";
135 }
136
137 @Override
138 public boolean isOpen() {
139 return isOpen;
140 }
141
142 @Override
143 public boolean isReadOnly() {
144 return readOnly;
145 }
146
147 private void checkWritable() throws IOException {
148 if (readOnly)
149 throw new ReadOnlyFileSystemException();
150 }
151
152 void setReadOnly() {
153 this.readOnly = true;
154 }
155
156 @Override
157 public Iterable<Path> getRootDirectories() {
158 return List.of(rootdir);
159 }
160
161 ZipPath getRootDir() {
162 return rootdir;
163 }
164
165 @Override
166 public ZipPath getPath(String first, String... more) {
167 if (more.length == 0) {
168 return new ZipPath(this, first);
169 }
170 StringBuilder sb = new StringBuilder();
171 sb.append(first);
172 for (String path : more) {
173 if (path.length() > 0) {
174 if (sb.length() > 0) {
175 sb.append('/');
176 }
177 sb.append(path);
178 }
179 }
180 return new ZipPath(this, sb.toString());
181 }
182
183 @Override
184 public UserPrincipalLookupService getUserPrincipalLookupService() {
185 throw new UnsupportedOperationException();
186 }
187
188 @Override
189 public WatchService newWatchService() {
190 throw new UnsupportedOperationException();
191 }
192
193 FileStore getFileStore(ZipPath path) {
194 return new ZipFileStore(path);
195 }
196
197 @Override
198 public Iterable<FileStore> getFileStores() {
199 return List.of(new ZipFileStore(rootdir));
200 }
201
202 private static final Set<String> supportedFileAttributeViews =
203 Set.of("basic", "zip");
204
205 @Override
206 public Set<String> supportedFileAttributeViews() {
207 return supportedFileAttributeViews;
208 }
209
210 @Override
211 public String toString() {
212 return zfpath.toString();
213 }
214
215 Path getZipFile() {
216 return zfpath;
217 }
218
219 private static final String GLOB_SYNTAX = "glob";
220 private static final String REGEX_SYNTAX = "regex";
221
222 @Override
223 public PathMatcher getPathMatcher(String syntaxAndInput) {
224 int pos = syntaxAndInput.indexOf(':');
225 if (pos <= 0 || pos == syntaxAndInput.length()) {
226 throw new IllegalArgumentException();
227 }
228 String syntax = syntaxAndInput.substring(0, pos);
229 String input = syntaxAndInput.substring(pos + 1);
230 String expr;
231 if (syntax.equalsIgnoreCase(GLOB_SYNTAX)) {
232 expr = toRegexPattern(input);
233 } else {
234 if (syntax.equalsIgnoreCase(REGEX_SYNTAX)) {
235 expr = input;
236 } else {
237 throw new UnsupportedOperationException("Syntax '" + syntax +
238 "' not recognized");
239 }
240 }
241 // return matcher
242 final Pattern pattern = Pattern.compile(expr);
243 return new PathMatcher() {
244 @Override
245 public boolean matches(Path path) {
246 return pattern.matcher(path.toString()).matches();
247 }
248 };
249 }
250
251 @Override
252 public void close() throws IOException {
253 beginWrite();
254 try {
255 if (!isOpen)
256 return;
257 isOpen = false; // set closed
258 } finally {
259 endWrite();
260 }
261 if (!streams.isEmpty()) { // unlock and close all remaining streams
262 Set<InputStream> copy = new HashSet<>(streams);
263 for (InputStream is: copy)
264 is.close();
265 }
266 beginWrite(); // lock and sync
267 try {
268 AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
269 sync(); return null;
270 });
271 ch.close(); // close the ch just in case no update
272 } catch (PrivilegedActionException e) { // and sync dose not close the ch
273 throw (IOException)e.getException();
274 } finally {
275 endWrite();
276 }
277
278 synchronized (inflaters) {
279 for (Inflater inf : inflaters)
280 inf.end();
281 }
282 synchronized (deflaters) {
283 for (Deflater def : deflaters)
284 def.end();
285 }
286
287 IOException ioe = null;
288 synchronized (tmppaths) {
289 for (Path p: tmppaths) {
290 try {
291 AccessController.doPrivileged(
292 (PrivilegedExceptionAction<Boolean>)() -> Files.deleteIfExists(p));
293 } catch (PrivilegedActionException e) {
294 IOException x = (IOException)e.getException();
295 if (ioe == null)
296 ioe = x;
297 else
298 ioe.addSuppressed(x);
299 }
300 }
301 }
302 provider.removeFileSystem(zfpath, this);
303 if (ioe != null)
304 throw ioe;
305 }
306
307 ZipFileAttributes getFileAttributes(byte[] path)
308 throws IOException
309 {
310 Entry e;
311 beginRead();
312 try {
313 ensureOpen();
314 e = getEntry(path);
315 if (e == null) {
316 IndexNode inode = getInode(path);
317 if (inode == null)
318 return null;
319 e = new Entry(inode.name, inode.isdir); // pseudo directory
320 e.method = METHOD_STORED; // STORED for dir
321 e.mtime = e.atime = e.ctime = zfsDefaultTimeStamp;
322 }
323 } finally {
324 endRead();
325 }
326 return e;
327 }
328
329 void checkAccess(byte[] path) throws IOException {
330 beginRead();
331 try {
332 ensureOpen();
333 // is it necessary to readCEN as a sanity check?
334 if (getInode(path) == null) {
335 throw new NoSuchFileException(toString());
336 }
337
338 } finally {
339 endRead();
340 }
341 }
342
343 void setTimes(byte[] path, FileTime mtime, FileTime atime, FileTime ctime)
344 throws IOException
345 {
346 checkWritable();
347 beginWrite();
348 try {
349 ensureOpen();
350 Entry e = getEntry(path); // ensureOpen checked
351 if (e == null)
352 throw new NoSuchFileException(getString(path));
353 if (e.type == Entry.CEN)
354 e.type = Entry.COPY; // copy e
355 if (mtime != null)
356 e.mtime = mtime.toMillis();
357 if (atime != null)
358 e.atime = atime.toMillis();
359 if (ctime != null)
360 e.ctime = ctime.toMillis();
361 update(e);
362 } finally {
363 endWrite();
364 }
365 }
366
367 boolean exists(byte[] path)
368 throws IOException
369 {
370 beginRead();
371 try {
372 ensureOpen();
373 return getInode(path) != null;
374 } finally {
375 endRead();
376 }
377 }
378
379 boolean isDirectory(byte[] path)
380 throws IOException
381 {
382 beginRead();
383 try {
384 IndexNode n = getInode(path);
385 return n != null && n.isDir();
386 } finally {
387 endRead();
388 }
389 }
390
391 // returns the list of child paths of "path"
392 Iterator<Path> iteratorOf(byte[] path,
393 DirectoryStream.Filter<? super Path> filter)
394 throws IOException
395 {
396 beginWrite(); // iteration of inodes needs exclusive lock
397 try {
398 ensureOpen();
399 IndexNode inode = getInode(path);
400 if (inode == null)
401 throw new NotDirectoryException(getString(path));
402 List<Path> list = new ArrayList<>();
403 IndexNode child = inode.child;
404 while (child != null) {
405 // assume all path from zip file itself is "normalized"
406 ZipPath zp = new ZipPath(this, child.name, true);
407 if (filter == null || filter.accept(zp))
408 list.add(zp);
409 child = child.sibling;
410 }
411 return list.iterator();
412 } finally {
413 endWrite();
414 }
415 }
416
417 void createDirectory(byte[] dir, FileAttribute<?>... attrs)
418 throws IOException
419 {
420 checkWritable();
421 // dir = toDirectoryPath(dir);
422 beginWrite();
423 try {
424 ensureOpen();
425 if (dir.length == 0 || exists(dir)) // root dir, or exiting dir
426 throw new FileAlreadyExistsException(getString(dir));
427 checkParents(dir);
428 Entry e = new Entry(dir, Entry.NEW, true);
429 e.method = METHOD_STORED; // STORED for dir
430 update(e);
431 } finally {
432 endWrite();
433 }
434 }
435
436 void copyFile(boolean deletesrc, byte[]src, byte[] dst, CopyOption... options)
437 throws IOException
438 {
439 checkWritable();
440 if (Arrays.equals(src, dst))
441 return; // do nothing, src and dst are the same
442
443 beginWrite();
444 try {
445 ensureOpen();
446 Entry eSrc = getEntry(src); // ensureOpen checked
447
448 if (eSrc == null)
449 throw new NoSuchFileException(getString(src));
450 if (eSrc.isDir()) { // spec says to create dst dir
451 createDirectory(dst);
452 return;
453 }
454 boolean hasReplace = false;
455 boolean hasCopyAttrs = false;
456 for (CopyOption opt : options) {
457 if (opt == REPLACE_EXISTING)
458 hasReplace = true;
459 else if (opt == COPY_ATTRIBUTES)
460 hasCopyAttrs = true;
461 }
462 Entry eDst = getEntry(dst);
463 if (eDst != null) {
464 if (!hasReplace)
465 throw new FileAlreadyExistsException(getString(dst));
466 } else {
467 checkParents(dst);
468 }
469 Entry u = new Entry(eSrc, Entry.COPY); // copy eSrc entry
470 u.name(dst); // change name
471 if (eSrc.type == Entry.NEW || eSrc.type == Entry.FILECH)
472 {
473 u.type = eSrc.type; // make it the same type
474 if (deletesrc) { // if it's a "rename", take the data
475 u.bytes = eSrc.bytes;
476 u.file = eSrc.file;
477 } else { // if it's not "rename", copy the data
478 if (eSrc.bytes != null)
479 u.bytes = Arrays.copyOf(eSrc.bytes, eSrc.bytes.length);
480 else if (eSrc.file != null) {
481 u.file = getTempPathForEntry(null);
482 Files.copy(eSrc.file, u.file, REPLACE_EXISTING);
483 }
484 }
485 }
486 if (!hasCopyAttrs)
487 u.mtime = u.atime= u.ctime = System.currentTimeMillis();
488 update(u);
489 if (deletesrc)
490 updateDelete(eSrc);
491 } finally {
492 endWrite();
493 }
494 }
495
496 // Returns an output stream for writing the contents into the specified
497 // entry.
498 OutputStream newOutputStream(byte[] path, OpenOption... options)
499 throws IOException
500 {
501 checkWritable();
502 boolean hasCreateNew = false;
503 boolean hasCreate = false;
504 boolean hasAppend = false;
505 boolean hasTruncate = false;
506 for (OpenOption opt: options) {
507 if (opt == READ)
508 throw new IllegalArgumentException("READ not allowed");
509 if (opt == CREATE_NEW)
510 hasCreateNew = true;
511 if (opt == CREATE)
512 hasCreate = true;
513 if (opt == APPEND)
514 hasAppend = true;
515 if (opt == TRUNCATE_EXISTING)
516 hasTruncate = true;
517 }
518 if (hasAppend && hasTruncate)
519 throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
520 beginRead(); // only need a readlock, the "update()" will
521 try { // try to obtain a writelock when the os is
522 ensureOpen(); // being closed.
523 Entry e = getEntry(path);
524 if (e != null) {
525 if (e.isDir() || hasCreateNew)
526 throw new FileAlreadyExistsException(getString(path));
527 if (hasAppend) {
528 InputStream is = getInputStream(e);
529 OutputStream os = getOutputStream(new Entry(e, Entry.NEW));
530 copyStream(is, os);
531 is.close();
532 return os;
533 }
534 return getOutputStream(new Entry(e, Entry.NEW));
535 } else {
536 if (!hasCreate && !hasCreateNew)
537 throw new NoSuchFileException(getString(path));
538 checkParents(path);
539 return getOutputStream(new Entry(path, Entry.NEW, false));
540 }
541 } finally {
542 endRead();
543 }
544 }
545
546 // Returns an input stream for reading the contents of the specified
547 // file entry.
548 InputStream newInputStream(byte[] path) throws IOException {
549 beginRead();
550 try {
551 ensureOpen();
552 Entry e = getEntry(path);
553 if (e == null)
554 throw new NoSuchFileException(getString(path));
555 if (e.isDir())
556 throw new FileSystemException(getString(path), "is a directory", null);
557 return getInputStream(e);
558 } finally {
559 endRead();
560 }
561 }
562
563 private void checkOptions(Set<? extends OpenOption> options) {
564 // check for options of null type and option is an intance of StandardOpenOption
565 for (OpenOption option : options) {
566 if (option == null)
567 throw new NullPointerException();
568 if (!(option instanceof StandardOpenOption))
569 throw new IllegalArgumentException();
570 }
571 if (options.contains(APPEND) && options.contains(TRUNCATE_EXISTING))
572 throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
573 }
574
575 // Returns a Writable/ReadByteChannel for now. Might consdier to use
576 // newFileChannel() instead, which dump the entry data into a regular
577 // file on the default file system and create a FileChannel on top of
578 // it.
579 SeekableByteChannel newByteChannel(byte[] path,
580 Set<? extends OpenOption> options,
581 FileAttribute<?>... attrs)
582 throws IOException
583 {
584 checkOptions(options);
585 if (options.contains(StandardOpenOption.WRITE) ||
586 options.contains(StandardOpenOption.APPEND)) {
587 checkWritable();
588 beginRead();
589 try {
590 final WritableByteChannel wbc = Channels.newChannel(
591 newOutputStream(path, options.toArray(new OpenOption[0])));
592 long leftover = 0;
593 if (options.contains(StandardOpenOption.APPEND)) {
594 Entry e = getEntry(path);
595 if (e != null && e.size >= 0)
596 leftover = e.size;
597 }
598 final long offset = leftover;
599 return new SeekableByteChannel() {
600 long written = offset;
601 public boolean isOpen() {
602 return wbc.isOpen();
603 }
604
605 public long position() throws IOException {
606 return written;
607 }
608
609 public SeekableByteChannel position(long pos)
610 throws IOException
611 {
612 throw new UnsupportedOperationException();
613 }
614
615 public int read(ByteBuffer dst) throws IOException {
616 throw new UnsupportedOperationException();
617 }
618
619 public SeekableByteChannel truncate(long size)
620 throws IOException
621 {
622 throw new UnsupportedOperationException();
623 }
624
625 public int write(ByteBuffer src) throws IOException {
626 int n = wbc.write(src);
627 written += n;
628 return n;
629 }
630
631 public long size() throws IOException {
632 return written;
633 }
634
635 public void close() throws IOException {
636 wbc.close();
637 }
638 };
639 } finally {
640 endRead();
641 }
642 } else {
643 beginRead();
644 try {
645 ensureOpen();
646 Entry e = getEntry(path);
647 if (e == null || e.isDir())
648 throw new NoSuchFileException(getString(path));
649 final ReadableByteChannel rbc =
650 Channels.newChannel(getInputStream(e));
651 final long size = e.size;
652 return new SeekableByteChannel() {
653 long read = 0;
654 public boolean isOpen() {
655 return rbc.isOpen();
656 }
657
658 public long position() throws IOException {
659 return read;
660 }
661
662 public SeekableByteChannel position(long pos)
663 throws IOException
664 {
665 throw new UnsupportedOperationException();
666 }
667
668 public int read(ByteBuffer dst) throws IOException {
669 int n = rbc.read(dst);
670 if (n > 0) {
671 read += n;
672 }
673 return n;
674 }
675
676 public SeekableByteChannel truncate(long size)
677 throws IOException
678 {
679 throw new NonWritableChannelException();
680 }
681
682 public int write (ByteBuffer src) throws IOException {
683 throw new NonWritableChannelException();
684 }
685
686 public long size() throws IOException {
687 return size;
688 }
689
690 public void close() throws IOException {
691 rbc.close();
692 }
693 };
694 } finally {
695 endRead();
696 }
697 }
698 }
699
700 // Returns a FileChannel of the specified entry.
701 //
702 // This implementation creates a temporary file on the default file system,
703 // copy the entry data into it if the entry exists, and then create a
704 // FileChannel on top of it.
705 FileChannel newFileChannel(byte[] path,
706 Set<? extends OpenOption> options,
707 FileAttribute<?>... attrs)
708 throws IOException
709 {
710 checkOptions(options);
711 final boolean forWrite = (options.contains(StandardOpenOption.WRITE) ||
712 options.contains(StandardOpenOption.APPEND));
713 beginRead();
714 try {
715 ensureOpen();
716 Entry e = getEntry(path);
717 if (forWrite) {
718 checkWritable();
719 if (e == null) {
720 if (!options.contains(StandardOpenOption.CREATE) &&
721 !options.contains(StandardOpenOption.CREATE_NEW)) {
722 throw new NoSuchFileException(getString(path));
723 }
724 } else {
725 if (options.contains(StandardOpenOption.CREATE_NEW)) {
726 throw new FileAlreadyExistsException(getString(path));
727 }
728 if (e.isDir())
729 throw new FileAlreadyExistsException("directory <"
730 + getString(path) + "> exists");
731 }
732 options = new HashSet<>(options);
733 options.remove(StandardOpenOption.CREATE_NEW); // for tmpfile
734 } else if (e == null || e.isDir()) {
735 throw new NoSuchFileException(getString(path));
736 }
737
738 final boolean isFCH = (e != null && e.type == Entry.FILECH);
739 final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
740 final FileChannel fch = tmpfile.getFileSystem()
741 .provider()
742 .newFileChannel(tmpfile, options, attrs);
743 final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH);
744 if (forWrite) {
745 u.flag = FLAG_DATADESCR;
746 u.method = METHOD_DEFLATED;
747 }
748 // is there a better way to hook into the FileChannel's close method?
749 return new FileChannel() {
750 public int write(ByteBuffer src) throws IOException {
751 return fch.write(src);
752 }
753 public long write(ByteBuffer[] srcs, int offset, int length)
754 throws IOException
755 {
756 return fch.write(srcs, offset, length);
757 }
758 public long position() throws IOException {
759 return fch.position();
760 }
761 public FileChannel position(long newPosition)
762 throws IOException
763 {
764 fch.position(newPosition);
765 return this;
766 }
767 public long size() throws IOException {
768 return fch.size();
769 }
770 public FileChannel truncate(long size)
771 throws IOException
772 {
773 fch.truncate(size);
774 return this;
775 }
776 public void force(boolean metaData)
777 throws IOException
778 {
779 fch.force(metaData);
780 }
781 public long transferTo(long position, long count,
782 WritableByteChannel target)
783 throws IOException
784 {
785 return fch.transferTo(position, count, target);
786 }
787 public long transferFrom(ReadableByteChannel src,
788 long position, long count)
789 throws IOException
790 {
791 return fch.transferFrom(src, position, count);
792 }
793 public int read(ByteBuffer dst) throws IOException {
794 return fch.read(dst);
795 }
796 public int read(ByteBuffer dst, long position)
797 throws IOException
798 {
799 return fch.read(dst, position);
800 }
801 public long read(ByteBuffer[] dsts, int offset, int length)
802 throws IOException
803 {
804 return fch.read(dsts, offset, length);
805 }
806 public int write(ByteBuffer src, long position)
807 throws IOException
808 {
809 return fch.write(src, position);
810 }
811 public MappedByteBuffer map(MapMode mode,
812 long position, long size)
813 throws IOException
814 {
815 throw new UnsupportedOperationException();
816 }
817 public FileLock lock(long position, long size, boolean shared)
818 throws IOException
819 {
820 return fch.lock(position, size, shared);
821 }
822 public FileLock tryLock(long position, long size, boolean shared)
823 throws IOException
824 {
825 return fch.tryLock(position, size, shared);
826 }
827 protected void implCloseChannel() throws IOException {
828 fch.close();
829 if (forWrite) {
830 u.mtime = System.currentTimeMillis();
831 u.size = Files.size(u.file);
832
833 update(u);
834 } else {
835 if (!isFCH) // if this is a new fch for reading
836 removeTempPathForEntry(tmpfile);
837 }
838 }
839 };
840 } finally {
841 endRead();
842 }
843 }
844
845 // the outstanding input streams that need to be closed
846 private Set<InputStream> streams =
847 Collections.synchronizedSet(new HashSet<InputStream>());
848
849 // the ex-channel and ex-path that need to close when their outstanding
850 // input streams are all closed by the obtainers.
851 private Set<ExChannelCloser> exChClosers = new HashSet<>();
852
853 private Set<Path> tmppaths = Collections.synchronizedSet(new HashSet<Path>());
854 private Path getTempPathForEntry(byte[] path) throws IOException {
855 Path tmpPath = createTempFileInSameDirectoryAs(zfpath);
856 if (path != null) {
857 Entry e = getEntry(path);
858 if (e != null) {
859 try (InputStream is = newInputStream(path)) {
860 Files.copy(is, tmpPath, REPLACE_EXISTING);
861 }
862 }
863 }
864 return tmpPath;
865 }
866
867 private void removeTempPathForEntry(Path path) throws IOException {
868 Files.delete(path);
869 tmppaths.remove(path);
870 }
871
872 // check if all parents really exit. ZIP spec does not require
873 // the existence of any "parent directory".
874 private void checkParents(byte[] path) throws IOException {
875 beginRead();
876 try {
877 while ((path = getParent(path)) != null &&
878 path != ROOTPATH) {
879 if (!inodes.containsKey(IndexNode.keyOf(path))) {
880 throw new NoSuchFileException(getString(path));
881 }
882 }
883 } finally {
884 endRead();
885 }
886 }
887
888 private static byte[] ROOTPATH = new byte[] { '/' };
889 private static byte[] getParent(byte[] path) {
890 int off = getParentOff(path);
891 if (off <= 1)
892 return ROOTPATH;
893 return Arrays.copyOf(path, off);
894 }
895
896 private static int getParentOff(byte[] path) {
897 int off = path.length - 1;
898 if (off > 0 && path[off] == '/') // isDirectory
899 off--;
900 while (off > 0 && path[off] != '/') { off--; }
901 return off;
902 }
903
904 private final void beginWrite() {
905 rwlock.writeLock().lock();
906 }
907
908 private final void endWrite() {
909 rwlock.writeLock().unlock();
910 }
911
912 private final void beginRead() {
913 rwlock.readLock().lock();
914 }
915
916 private final void endRead() {
917 rwlock.readLock().unlock();
918 }
919
920 ///////////////////////////////////////////////////////////////////
921
922 private volatile boolean isOpen = true;
923 private final SeekableByteChannel ch; // channel to the zipfile
924 final byte[] cen; // CEN & ENDHDR
925 private END end;
926 private long locpos; // position of first LOC header (usually 0)
927
928 private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
929
930 // name -> pos (in cen), IndexNode itself can be used as a "key"
931 private LinkedHashMap<IndexNode, IndexNode> inodes;
932
933 final byte[] getBytes(String name) {
934 return zc.getBytes(name);
935 }
936
937 final String getString(byte[] name) {
938 return zc.toString(name);
939 }
940
941 @SuppressWarnings("deprecation")
942 protected void finalize() throws IOException {
943 close();
944 }
945
946 // Reads len bytes of data from the specified offset into buf.
947 // Returns the total number of bytes read.
948 // Each/every byte read from here (except the cen, which is mapped).
949 final long readFullyAt(byte[] buf, int off, long len, long pos)
950 throws IOException
951 {
952 ByteBuffer bb = ByteBuffer.wrap(buf);
953 bb.position(off);
954 bb.limit((int)(off + len));
955 return readFullyAt(bb, pos);
956 }
957
958 private final long readFullyAt(ByteBuffer bb, long pos)
959 throws IOException
960 {
961 synchronized(ch) {
962 return ch.position(pos).read(bb);
963 }
964 }
965
966 // Searches for end of central directory (END) header. The contents of
967 // the END header will be read and placed in endbuf. Returns the file
968 // position of the END header, otherwise returns -1 if the END header
969 // was not found or an error occurred.
970 private END findEND() throws IOException
971 {
972 byte[] buf = new byte[READBLOCKSZ];
973 long ziplen = ch.size();
974 long minHDR = (ziplen - END_MAXLEN) > 0 ? ziplen - END_MAXLEN : 0;
975 long minPos = minHDR - (buf.length - ENDHDR);
976
977 for (long pos = ziplen - buf.length; pos >= minPos; pos -= (buf.length - ENDHDR))
978 {
979 int off = 0;
980 if (pos < 0) {
981 // Pretend there are some NUL bytes before start of file
982 off = (int)-pos;
983 Arrays.fill(buf, 0, off, (byte)0);
984 }
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 }
1055 if (end.cenlen > end.endpos)
1056 zerror("invalid END header (bad central directory size)");
1057 long cenpos = end.endpos - end.cenlen; // position of CEN table
1058
1059 // Get position of first local file (LOC) header, taking into
1060 // account that there may be a stub prefixed to the zip file.
1061 locpos = cenpos - end.cenoff;
1062 if (locpos < 0)
1063 zerror("invalid END header (bad central directory offset)");
1064
1065 // read in the CEN and END
1066 byte[] cen = new byte[(int)(end.cenlen + ENDHDR)];
1067 if (readFullyAt(cen, 0, cen.length, cenpos) != end.cenlen + ENDHDR) {
1068 zerror("read CEN tables failed");
1069 }
1070 // Iterate through the entries in the central directory
1071 inodes = new LinkedHashMap<>(end.centot + 1);
1072 int pos = 0;
1073 int limit = cen.length - ENDHDR;
1074 while (pos < limit) {
1075 if (!cenSigAt(cen, pos))
1076 zerror("invalid CEN header (bad signature)");
1077 int method = CENHOW(cen, pos);
1078 int nlen = CENNAM(cen, pos);
1079 int elen = CENEXT(cen, pos);
1080 int clen = CENCOM(cen, pos);
1081 if ((CENFLG(cen, pos) & 1) != 0) {
1082 zerror("invalid CEN header (encrypted entry)");
1083 }
1084 if (method != METHOD_STORED && method != METHOD_DEFLATED) {
1085 zerror("invalid CEN header (unsupported compression method: " + method + ")");
1086 }
1087 if (pos + CENHDR + nlen > limit) {
1088 zerror("invalid CEN header (bad header size)");
1089 }
1090 IndexNode inode = new IndexNode(cen, pos + CENHDR, nlen, pos);
1091 inodes.put(inode, inode);
1092
1093 // skip ext and comment
1094 pos += (CENHDR + nlen + elen + clen);
1095 }
1096 if (pos + ENDHDR != cen.length) {
1097 zerror("invalid CEN header (bad header size)");
1098 }
1099 buildNodeTree();
1100 return cen;
1101 }
1102
1103 private void ensureOpen() throws IOException {
1104 if (!isOpen)
1105 throw new ClosedFileSystemException();
1106 }
1107
1108 // Creates a new empty temporary file in the same directory as the
1109 // specified file. A variant of Files.createTempFile.
1110 private Path createTempFileInSameDirectoryAs(Path path)
1111 throws IOException
1112 {
1113 Path parent = path.toAbsolutePath().getParent();
1114 Path dir = (parent == null) ? path.getFileSystem().getPath(".") : parent;
1115 Path tmpPath = Files.createTempFile(dir, "zipfstmp", null);
1116 tmppaths.add(tmpPath);
1117 return tmpPath;
1118 }
1119
1120 ////////////////////update & sync //////////////////////////////////////
1121
1122 private boolean hasUpdate = false;
1123
1124 // shared key. consumer guarantees the "writeLock" before use it.
1125 private final IndexNode LOOKUPKEY = new IndexNode(null, -1);
1126
1127 private void updateDelete(IndexNode inode) {
1128 beginWrite();
1129 try {
1130 removeFromTree(inode);
1131 inodes.remove(inode);
1132 hasUpdate = true;
1133 } finally {
1134 endWrite();
1135 }
1136 }
1137
1138 private void update(Entry e) {
1139 beginWrite();
1140 try {
1141 IndexNode old = inodes.put(e, e);
1142 if (old != null) {
1143 removeFromTree(old);
1144 }
1145 if (e.type == Entry.NEW || e.type == Entry.FILECH || e.type == Entry.COPY) {
1146 IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(e.name)));
1147 e.sibling = parent.child;
1148 parent.child = e;
1149 }
1150 hasUpdate = true;
1151 } finally {
1152 endWrite();
1153 }
1154 }
1155
1156 // copy over the whole LOC entry (header if necessary, data and ext) from
1157 // old zip to the new one.
1158 private long copyLOCEntry(Entry e, boolean updateHeader,
1159 OutputStream os,
1160 long written, byte[] buf)
1161 throws IOException
1162 {
1163 long locoff = e.locoff; // where to read
1164 e.locoff = written; // update the e.locoff with new value
1165
1166 // calculate the size need to write out
1167 long size = 0;
1168 // if there is A ext
1169 if ((e.flag & FLAG_DATADESCR) != 0) {
1170 if (e.size >= ZIP64_MINVAL || e.csize >= ZIP64_MINVAL)
1171 size = 24;
1172 else
1173 size = 16;
1174 }
1175 // read loc, use the original loc.elen/nlen
1176 if (readFullyAt(buf, 0, LOCHDR , locoff) != LOCHDR)
1177 throw new ZipException("loc: reading failed");
1178 if (updateHeader) {
1179 locoff += LOCHDR + LOCNAM(buf) + LOCEXT(buf); // skip header
1180 size += e.csize;
1181 written = e.writeLOC(os) + size;
1182 } else {
1183 os.write(buf, 0, LOCHDR); // write out the loc header
1184 locoff += LOCHDR;
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
1226 // write loc
1227 for (IndexNode inode : inodes.values()) {
1228 if (inode instanceof Entry) { // an updated inode
1229 e = (Entry)inode;
1230 try {
1231 if (e.type == Entry.COPY) {
1232 // entry copy: the only thing changed is the "name"
1233 // and "nlen" in LOC header, so we udpate/rewrite the
1234 // LOC in new file and simply copy the rest (data and
1235 // ext) without enflating/deflating from the old zip
1236 // file LOC entry.
1237 written += copyLOCEntry(e, true, os, written, buf);
1238 } else { // NEW, FILECH or CEN
1239 e.locoff = written;
1240 written += e.writeLOC(os); // write loc header
1241 if (e.bytes != null) { // in-memory, deflated
1242 os.write(e.bytes); // already
1243 written += e.bytes.length;
1244 } else if (e.file != null) { // tmp file
1245 try (InputStream is = Files.newInputStream(e.file)) {
1246 int n;
1247 if (e.type == Entry.NEW) { // deflated already
1248 while ((n = is.read(buf)) != -1) {
1249 os.write(buf, 0, n);
1250 written += n;
1251 }
1252 } else if (e.type == Entry.FILECH) {
1253 // the data are not deflated, use ZEOS
1254 try (OutputStream os2 = new EntryOutputStream(e, os)) {
1255 while ((n = is.read(buf)) != -1) {
1256 os2.write(buf, 0, n);
1257 }
1258 }
1259 written += e.csize;
1260 if ((e.flag & FLAG_DATADESCR) != 0)
1261 written += e.writeEXT(os);
1262 }
1263 }
1264 Files.delete(e.file);
1265 tmppaths.remove(e.file);
1266 } else {
1267 // dir, 0-length data
1268 }
1269 }
1270 elist.add(e);
1271 } catch (IOException x) {
1272 x.printStackTrace(); // skip any in-accurate entry
1273 }
1274 } else { // unchanged inode
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);
1316 }
1317
1318 Files.move(tmpFile, zfpath, REPLACE_EXISTING);
1319 hasUpdate = false; // clear
1320 }
1321
1322 IndexNode getInode(byte[] path) {
1323 if (path == null)
1324 throw new NullPointerException("path");
1325 return inodes.get(IndexNode.keyOf(path));
1326 }
1327
1328 Entry getEntry(byte[] path) throws IOException {
1329 IndexNode inode = getInode(path);
1330 if (inode instanceof Entry)
1331 return (Entry)inode;
1332 if (inode == null || inode.pos == -1)
1333 return null;
1334 return Entry.readCEN(this, inode);
1335 }
1336
1337 public void deleteFile(byte[] path, boolean failIfNotExists)
1338 throws IOException
1339 {
1340 checkWritable();
1341
1342 IndexNode inode = getInode(path);
1343 if (inode == null) {
1344 if (path != null && path.length == 0)
1345 throw new ZipException("root directory </> can't not be delete");
1346 if (failIfNotExists)
1347 throw new NoSuchFileException(getString(path));
1348 } else {
1349 if (inode.isDir() && inode.child != null)
1350 throw new DirectoryNotEmptyException(getString(path));
1351 updateDelete(inode);
1352 }
1353 }
1354
1355 private static void copyStream(InputStream is, OutputStream os)
1356 throws IOException
1357 {
1358 byte[] copyBuf = new byte[8192];
1359 int n;
1360 while ((n = is.read(copyBuf)) != -1) {
1361 os.write(copyBuf, 0, n);
1362 }
1363 }
1364
1365 // Returns an out stream for either
1366 // (1) writing the contents of a new entry, if the entry exits, or
1367 // (2) updating/replacing the contents of the specified existing entry.
1368 private OutputStream getOutputStream(Entry e) throws IOException {
1369
1370 if (e.mtime == -1)
1371 e.mtime = System.currentTimeMillis();
1372 if (e.method == -1)
1373 e.method = METHOD_DEFLATED; // TBD: use default method
1374 // store size, compressed size, and crc-32 in LOC header
1375 e.flag = 0;
1376 if (zc.isUTF8())
1377 e.flag |= FLAG_USE_UTF8;
1378 OutputStream os;
1379 if (useTempFile) {
1380 e.file = getTempPathForEntry(null);
1381 os = Files.newOutputStream(e.file, WRITE);
1382 } else {
1383 os = new ByteArrayOutputStream((e.size > 0)? (int)e.size : 8192);
1384 }
1385 return new EntryOutputStream(e, os);
1386 }
1387
1388 private InputStream getInputStream(Entry e)
1389 throws IOException
1390 {
1391 InputStream eis = null;
1392
1393 if (e.type == Entry.NEW) {
1394 if (e.bytes != null)
1395 eis = new ByteArrayInputStream(e.bytes);
1396 else if (e.file != null)
1397 eis = Files.newInputStream(e.file);
1398 else
1399 throw new ZipException("update entry data is missing");
1400 } else if (e.type == Entry.FILECH) {
1401 // FILECH result is un-compressed.
1402 eis = Files.newInputStream(e.file);
1403 // TBD: wrap to hook close()
1404 // streams.add(eis);
1405 return eis;
1406 } else { // untouced CEN or COPY
1407 eis = new EntryInputStream(e, ch);
1408 }
1409 if (e.method == METHOD_DEFLATED) {
1410 // MORE: Compute good size for inflater stream:
1411 long bufSize = e.size + 2; // Inflater likes a bit of slack
1412 if (bufSize > 65536)
1413 bufSize = 8192;
1414 final long size = e.size;
1415 eis = new InflaterInputStream(eis, getInflater(), (int)bufSize) {
1416 private boolean isClosed = false;
1417 public void close() throws IOException {
1418 if (!isClosed) {
1419 releaseInflater(inf);
1420 this.in.close();
1421 isClosed = true;
1422 streams.remove(this);
1423 }
1424 }
1425 // Override fill() method to provide an extra "dummy" byte
1426 // at the end of the input stream. This is required when
1427 // using the "nowrap" Inflater option. (it appears the new
1428 // zlib in 7 does not need it, but keep it for now)
1429 protected void fill() throws IOException {
1430 if (eof) {
1431 throw new EOFException(
1432 "Unexpected end of ZLIB input stream");
1433 }
1434 len = this.in.read(buf, 0, buf.length);
1435 if (len == -1) {
1436 buf[0] = 0;
1437 len = 1;
1438 eof = true;
1439 }
1440 inf.setInput(buf, 0, len);
1441 }
1442 private boolean eof;
1443
1444 public int available() throws IOException {
1445 if (isClosed)
1446 return 0;
1447 long avail = size - inf.getBytesWritten();
1448 return avail > (long) Integer.MAX_VALUE ?
1449 Integer.MAX_VALUE : (int) avail;
1450 }
1451 };
1452 } else if (e.method == METHOD_STORED) {
1453 // TBD: wrap/ it does not seem necessary
1454 } else {
1455 throw new ZipException("invalid compression method");
1456 }
1457 streams.add(eis);
1458 return eis;
1459 }
1460
1461 // Inner class implementing the input stream used to read
1462 // a (possibly compressed) zip file entry.
1463 private class EntryInputStream extends InputStream {
1464 private final SeekableByteChannel zfch; // local ref to zipfs's "ch". zipfs.ch might
1465 // point to a new channel after sync()
1466 private long pos; // current position within entry data
1467 protected long rem; // number of remaining bytes within entry
1468 protected final long size; // uncompressed size of this entry
1469
1470 EntryInputStream(Entry e, SeekableByteChannel zfch)
1471 throws IOException
1472 {
1473 this.zfch = zfch;
1474 rem = e.csize;
1475 size = e.size;
1476 pos = e.locoff;
1477 if (pos == -1) {
1478 Entry e2 = getEntry(e.name);
1479 if (e2 == null) {
1480 throw new ZipException("invalid loc for entry <" + e.name + ">");
1481 }
1482 pos = e2.locoff;
1483 }
1484 pos = -pos; // lazy initialize the real data offset
1485 }
1486
1487 public int read(byte b[], int off, int len) throws IOException {
1488 ensureOpen();
1489 initDataPos();
1490 if (rem == 0) {
1491 return -1;
1492 }
1493 if (len <= 0) {
1494 return 0;
1495 }
1496 if (len > rem) {
1497 len = (int) rem;
1498 }
1499 // readFullyAt()
1500 long n = 0;
1501 ByteBuffer bb = ByteBuffer.wrap(b);
1502 bb.position(off);
1503 bb.limit(off + len);
1504 synchronized(zfch) {
1505 n = zfch.position(pos).read(bb);
1506 }
1507 if (n > 0) {
1508 pos += n;
1509 rem -= n;
1510 }
1511 if (rem == 0) {
1512 close();
1513 }
1514 return (int)n;
1515 }
1516
1517 public int read() throws IOException {
1518 byte[] b = new byte[1];
1519 if (read(b, 0, 1) == 1) {
1520 return b[0] & 0xff;
1521 } else {
1522 return -1;
1523 }
1524 }
1525
1526 public long skip(long n) throws IOException {
1527 ensureOpen();
1528 if (n > rem)
1529 n = rem;
1530 pos += n;
1531 rem -= n;
1532 if (rem == 0) {
1533 close();
1534 }
1535 return n;
1536 }
1537
1538 public int available() {
1539 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
1540 }
1541
1542 public long size() {
1543 return size;
1544 }
1545
1546 public void close() {
1547 rem = 0;
1548 streams.remove(this);
1549 }
1550
1551 private void initDataPos() throws IOException {
1552 if (pos <= 0) {
1553 pos = -pos + locpos;
1554 byte[] buf = new byte[LOCHDR];
1555 if (readFullyAt(buf, 0, buf.length, pos) != LOCHDR) {
1556 throw new ZipException("invalid loc " + pos + " for entry reading");
1557 }
1558 pos += LOCHDR + LOCNAM(buf) + LOCEXT(buf);
1559 }
1560 }
1561 }
1562
1563 class EntryOutputStream extends DeflaterOutputStream
1564 {
1565 private CRC32 crc;
1566 private Entry e;
1567 private long written;
1568 private boolean isClosed = false;
1569
1570 EntryOutputStream(Entry e, OutputStream os)
1571 throws IOException
1572 {
1573 super(os, getDeflater());
1574 if (e == null)
1575 throw new NullPointerException("Zip entry is null");
1576 this.e = e;
1577 crc = new CRC32();
1578 }
1579
1580 @Override
1581 public synchronized void write(byte b[], int off, int len)
1582 throws IOException
1583 {
1584 if (e.type != Entry.FILECH) // only from sync
1585 ensureOpen();
1586 if (isClosed) {
1587 throw new IOException("Stream closed");
1588 }
1589 if (off < 0 || len < 0 || off > b.length - len) {
1590 throw new IndexOutOfBoundsException();
1591 } else if (len == 0) {
1592 return;
1593 }
1594 switch (e.method) {
1595 case METHOD_DEFLATED:
1596 super.write(b, off, len);
1597 break;
1598 case METHOD_STORED:
1599 written += len;
1600 out.write(b, off, len);
1601 break;
1602 default:
1603 throw new ZipException("invalid compression method");
1604 }
1605 crc.update(b, off, len);
1606 }
1607
1608 @Override
1609 public synchronized void close() throws IOException {
1610 if (isClosed) {
1611 return;
1612 }
1613 isClosed = true;
1614 // TBD ensureOpen();
1615 switch (e.method) {
1616 case METHOD_DEFLATED:
1617 finish();
1618 e.size = def.getBytesRead();
1619 e.csize = def.getBytesWritten();
1620 e.crc = crc.getValue();
1621 break;
1622 case METHOD_STORED:
1623 // we already know that both e.size and e.csize are the same
1624 e.size = e.csize = written;
1625 e.crc = crc.getValue();
1626 break;
1627 default:
1628 throw new ZipException("invalid compression method");
1629 }
1630 //crc.reset();
1631 if (out instanceof ByteArrayOutputStream)
1632 e.bytes = ((ByteArrayOutputStream)out).toByteArray();
1633
1634 if (e.type == Entry.FILECH) {
1635 releaseDeflater(def);
1636 return;
1637 }
1638 super.close();
1639 releaseDeflater(def);
1640 update(e);
1641 }
1642 }
1643
1644 static void zerror(String msg) throws ZipException {
1645 throw new ZipException(msg);
1646 }
1647
1648 // Maxmum number of de/inflater we cache
1649 private final int MAX_FLATER = 20;
1650 // List of available Inflater objects for decompression
1651 private final List<Inflater> inflaters = new ArrayList<>();
1652
1653 // Gets an inflater from the list of available inflaters or allocates
1654 // a new one.
1655 private Inflater getInflater() {
1656 synchronized (inflaters) {
1657 int size = inflaters.size();
1658 if (size > 0) {
1659 Inflater inf = inflaters.remove(size - 1);
1660 return inf;
1661 } else {
1662 return new Inflater(true);
1663 }
1664 }
1665 }
1666
1667 // Releases the specified inflater to the list of available inflaters.
1668 private void releaseInflater(Inflater inf) {
1669 synchronized (inflaters) {
1670 if (inflaters.size() < MAX_FLATER) {
1671 inf.reset();
1672 inflaters.add(inf);
1673 } else {
1674 inf.end();
1675 }
1676 }
1677 }
1678
1679 // List of available Deflater objects for compression
1680 private final List<Deflater> deflaters = new ArrayList<>();
1681
1682 // Gets an deflater from the list of available deflaters or allocates
1683 // a new one.
1684 private Deflater getDeflater() {
1685 synchronized (deflaters) {
1686 int size = deflaters.size();
1687 if (size > 0) {
1688 Deflater def = deflaters.remove(size - 1);
1689 return def;
1690 } else {
1691 return new Deflater(Deflater.DEFAULT_COMPRESSION, true);
1692 }
1693 }
1694 }
1695
1696 // Releases the specified inflater to the list of available inflaters.
1697 private void releaseDeflater(Deflater def) {
1698 synchronized (deflaters) {
1699 if (inflaters.size() < MAX_FLATER) {
1700 def.reset();
1701 deflaters.add(def);
1702 } else {
1703 def.end();
1704 }
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 {
1773 writeShort(os, 0);
1774 }
1775 }
1776 }
1777
1778 // Internal node that links a "name" to its pos in cen table.
1779 // The node itself can be used as a "key" to lookup itself in
1780 // the HashMap inodes.
1781 static class IndexNode {
1782 byte[] name;
1783 int hashcode; // node is hashable/hashed by its name
1784 int pos = -1; // position in cen table, -1 menas the
1785 // entry does not exists in zip file
1786 boolean isdir;
1787
1788 IndexNode(byte[] name, boolean isdir) {
1789 name(name);
1790 this.isdir = isdir;
1791 this.pos = -1;
1792 }
1793
1794 IndexNode(byte[] name, int pos) {
1795 name(name);
1796 this.pos = pos;
1797 }
1798
1799 // constructor for cenInit()
1800 IndexNode(byte[] cen, int noff, int nlen, int pos) {
1801 if (cen[noff + nlen - 1] == '/') {
1802 isdir = true;
1803 nlen--;
1804 }
1805 name = new byte[nlen + 1];
1806 System.arraycopy(cen, pos + CENHDR, name, 1, nlen);
1807 name[0] = '/';
1808 name(name);
1809 this.pos = pos;
1810 }
1811
1812 private static final ThreadLocal<IndexNode> cachedKey = new ThreadLocal<>();
1813
1814 final static IndexNode keyOf(byte[] name) { // get a lookup key;
1815 IndexNode key = cachedKey.get();
1816 if (key == null) {
1817 key = new IndexNode(name, -1);
1818 cachedKey.set(key);
1819 }
1820 return key.as(name);
1821 }
1822
1823 final void name(byte[] name) {
1824 this.name = name;
1825 this.hashcode = Arrays.hashCode(name);
1826 }
1827
1828 final IndexNode as(byte[] name) { // reuse the node, mostly
1829 name(name); // as a lookup "key"
1830 return this;
1831 }
1832
1833 boolean isDir() {
1834 return isdir;
1835 }
1836
1837 public boolean equals(Object other) {
1838 if (!(other instanceof IndexNode)) {
1839 return false;
1840 }
1841 if (other instanceof ParentLookup) {
1842 return ((ParentLookup)other).equals(this);
1843 }
1844 return Arrays.equals(name, ((IndexNode)other).name);
1845 }
1846
1847 public int hashCode() {
1848 return hashcode;
1849 }
1850
1851 IndexNode() {}
1852 IndexNode sibling;
1853 IndexNode child; // 1st child
1854 }
1855
1856 static class Entry extends IndexNode implements ZipFileAttributes {
1857
1858 static final int CEN = 1; // entry read from cen
1859 static final int NEW = 2; // updated contents in bytes or file
1860 static final int FILECH = 3; // fch update in "file"
1861 static final int COPY = 4; // copy of a CEN entry
1862
1863 byte[] bytes; // updated content bytes
1864 Path file; // use tmp file to store bytes;
1865 int type = CEN; // default is the entry read from cen
1866
1867 // entry attributes
1868 int version;
1869 int flag;
1870 int method = -1; // compression method
1871 long mtime = -1; // last modification time (in DOS time)
1872 long atime = -1; // last access time
1873 long ctime = -1; // create time
1874 long crc = -1; // crc-32 of entry data
1875 long csize = -1; // compressed size of entry data
1876 long size = -1; // uncompressed size of entry data
1877 byte[] extra;
1878
1879 // cen
1880
1881 // these fields are not used by anyone and writeCEN uses "0"
1882 // int versionMade;
1883 // int disk;
1884 // int attrs;
1885 // long attrsEx;
1886 long locoff;
1887 byte[] comment;
1888
1889 Entry() {}
1890
1891 Entry(byte[] name, boolean isdir) {
1892 name(name);
1893 this.isdir = isdir;
1894 this.mtime = this.ctime = this.atime = System.currentTimeMillis();
1895 this.crc = 0;
1896 this.size = 0;
1897 this.csize = 0;
1898 this.method = METHOD_DEFLATED;
1899 }
1900
1901 Entry(byte[] name, int type, boolean isdir) {
1902 this(name, isdir);
1903 this.type = type;
1904 }
1905
1906 Entry (Entry e, int type) {
1907 name(e.name);
1908 this.isdir = e.isdir;
1909 this.version = e.version;
1910 this.ctime = e.ctime;
1911 this.atime = e.atime;
1912 this.mtime = e.mtime;
1913 this.crc = e.crc;
1914 this.size = e.size;
1915 this.csize = e.csize;
1916 this.method = e.method;
1917 this.extra = e.extra;
1918 /*
1919 this.versionMade = e.versionMade;
1920 this.disk = e.disk;
1921 this.attrs = e.attrs;
1922 this.attrsEx = e.attrsEx;
1923 */
1924 this.locoff = e.locoff;
1925 this.comment = e.comment;
1926 this.type = type;
1927 }
1928
1929 Entry (byte[] name, Path file, int type) {
1930 this(name, type, false);
1931 this.file = file;
1932 this.method = METHOD_STORED;
1933 }
1934
1935 int version() throws ZipException {
1936 if (method == METHOD_DEFLATED)
1937 return 20;
1938 else if (method == METHOD_STORED)
1939 return 10;
1940 throw new ZipException("unsupported compression method");
1941 }
1942
1943 ///////////////////// CEN //////////////////////
1944 static Entry readCEN(ZipFileSystem zipfs, IndexNode inode)
1945 throws IOException
1946 {
1947 return new Entry().cen(zipfs, inode);
1948 }
1949
1950 private Entry cen(ZipFileSystem zipfs, IndexNode inode)
1951 throws IOException
1952 {
1953 byte[] cen = zipfs.cen;
1954 int pos = inode.pos;
1955 if (!cenSigAt(cen, pos))
1956 zerror("invalid CEN header (bad signature)");
1957 version = CENVER(cen, pos);
1958 flag = CENFLG(cen, pos);
1959 method = CENHOW(cen, pos);
1960 mtime = dosToJavaTime(CENTIM(cen, pos));
1961 crc = CENCRC(cen, pos);
1962 csize = CENSIZ(cen, pos);
1963 size = CENLEN(cen, pos);
1964 int nlen = CENNAM(cen, pos);
1965 int elen = CENEXT(cen, pos);
1966 int clen = CENCOM(cen, pos);
1967 /*
1968 versionMade = CENVEM(cen, pos);
1969 disk = CENDSK(cen, pos);
1970 attrs = CENATT(cen, pos);
1971 attrsEx = CENATX(cen, pos);
1972 */
1973 locoff = CENOFF(cen, pos);
1974 pos += CENHDR;
1975 this.name = inode.name;
1976 this.isdir = inode.isdir;
1977 this.hashcode = inode.hashcode;
1978
1979 pos += nlen;
1980 if (elen > 0) {
1981 extra = Arrays.copyOfRange(cen, pos, pos + elen);
1982 pos += elen;
1983 readExtra(zipfs);
1984 }
1985 if (clen > 0) {
1986 comment = Arrays.copyOfRange(cen, pos, pos + clen);
1987 }
1988 return this;
1989 }
1990
1991 int writeCEN(OutputStream os) throws IOException
1992 {
1993 int written = CENHDR;
1994 int version0 = version();
1995 long csize0 = csize;
1996 long size0 = size;
1997 long locoff0 = locoff;
1998 int elen64 = 0; // extra for ZIP64
1999 int elenNTFS = 0; // extra for NTFS (a/c/mtime)
2000 int elenEXTT = 0; // extra for Extended Timestamp
2001 boolean foundExtraTime = false; // if time stamp NTFS, EXTT present
2002
2003 byte[] zname = isdir ? toDirectoryPath(name) : name;
2004
2005 // confirm size/length
2006 int nlen = (zname != null) ? zname.length - 1 : 0; // name has [0] as "slash"
2007 int elen = (extra != null) ? extra.length : 0;
2008 int eoff = 0;
2009 int clen = (comment != null) ? comment.length : 0;
2010 if (csize >= ZIP64_MINVAL) {
2011 csize0 = ZIP64_MINVAL;
2012 elen64 += 8; // csize(8)
2013 }
2014 if (size >= ZIP64_MINVAL) {
2015 size0 = ZIP64_MINVAL; // size(8)
2016 elen64 += 8;
2017 }
2018 if (locoff >= ZIP64_MINVAL) {
2019 locoff0 = ZIP64_MINVAL;
2020 elen64 += 8; // offset(8)
2021 }
2022 if (elen64 != 0) {
2023 elen64 += 4; // header and data sz 4 bytes
2024 }
2025 while (eoff + 4 < elen) {
2026 int tag = SH(extra, eoff);
2027 int sz = SH(extra, eoff + 2);
2028 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2029 foundExtraTime = true;
2030 }
2031 eoff += (4 + sz);
2032 }
2033 if (!foundExtraTime) {
2034 if (isWindows) { // use NTFS
2035 elenNTFS = 36; // total 36 bytes
2036 } else { // Extended Timestamp otherwise
2037 elenEXTT = 9; // only mtime in cen
2038 }
2039 }
2040 writeInt(os, CENSIG); // CEN header signature
2041 if (elen64 != 0) {
2042 writeShort(os, 45); // ver 4.5 for zip64
2043 writeShort(os, 45);
2044 } else {
2045 writeShort(os, version0); // version made by
2046 writeShort(os, version0); // version needed to extract
2047 }
2048 writeShort(os, flag); // general purpose bit flag
2049 writeShort(os, method); // compression method
2050 // last modification time
2051 writeInt(os, (int)javaToDosTime(mtime));
2052 writeInt(os, crc); // crc-32
2053 writeInt(os, csize0); // compressed size
2054 writeInt(os, size0); // uncompressed size
2055 writeShort(os, nlen);
2056 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2057
2058 if (comment != null) {
2059 writeShort(os, Math.min(clen, 0xffff));
2060 } else {
2061 writeShort(os, 0);
2062 }
2063 writeShort(os, 0); // starting disk number
2064 writeShort(os, 0); // internal file attributes (unused)
2065 writeInt(os, 0); // external file attributes (unused)
2066 writeInt(os, locoff0); // relative offset of local header
2067 writeBytes(os, zname, 1, nlen);
2068 if (elen64 != 0) {
2069 writeShort(os, EXTID_ZIP64);// Zip64 extra
2070 writeShort(os, elen64 - 4); // size of "this" extra block
2071 if (size0 == ZIP64_MINVAL)
2072 writeLong(os, size);
2073 if (csize0 == ZIP64_MINVAL)
2074 writeLong(os, csize);
2075 if (locoff0 == ZIP64_MINVAL)
2076 writeLong(os, locoff);
2077 }
2078 if (elenNTFS != 0) {
2079 writeShort(os, EXTID_NTFS);
2080 writeShort(os, elenNTFS - 4);
2081 writeInt(os, 0); // reserved
2082 writeShort(os, 0x0001); // NTFS attr tag
2083 writeShort(os, 24);
2084 writeLong(os, javaToWinTime(mtime));
2085 writeLong(os, javaToWinTime(atime));
2086 writeLong(os, javaToWinTime(ctime));
2087 }
2088 if (elenEXTT != 0) {
2089 writeShort(os, EXTID_EXTT);
2090 writeShort(os, elenEXTT - 4);
2091 if (ctime == -1)
2092 os.write(0x3); // mtime and atime
2093 else
2094 os.write(0x7); // mtime, atime and ctime
2095 writeInt(os, javaToUnixTime(mtime));
2096 }
2097 if (extra != null) // whatever not recognized
2098 writeBytes(os, extra);
2099 if (comment != null) //TBD: 0, Math.min(commentBytes.length, 0xffff));
2100 writeBytes(os, comment);
2101 return CENHDR + nlen + elen + clen + elen64 + elenNTFS + elenEXTT;
2102 }
2103
2104 ///////////////////// LOC //////////////////////
2105
2106 int writeLOC(OutputStream os) throws IOException {
2107 writeInt(os, LOCSIG); // LOC header signature
2108 int version = version();
2109
2110 byte[] zname = isdir ? toDirectoryPath(name) : name;
2111 int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
2112 int elen = (extra != null) ? extra.length : 0;
2113 boolean foundExtraTime = false; // if extra timestamp present
2114 int eoff = 0;
2115 int elen64 = 0;
2116 int elenEXTT = 0;
2117 int elenNTFS = 0;
2118 if ((flag & FLAG_DATADESCR) != 0) {
2119 writeShort(os, version()); // version needed to extract
2120 writeShort(os, flag); // general purpose bit flag
2121 writeShort(os, method); // compression method
2122 // last modification time
2123 writeInt(os, (int)javaToDosTime(mtime));
2124 // store size, uncompressed size, and crc-32 in data descriptor
2125 // immediately following compressed entry data
2126 writeInt(os, 0);
2127 writeInt(os, 0);
2128 writeInt(os, 0);
2129 } else {
2130 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2131 elen64 = 20; //headid(2) + size(2) + size(8) + csize(8)
2132 writeShort(os, 45); // ver 4.5 for zip64
2133 } else {
2134 writeShort(os, version()); // version needed to extract
2135 }
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 writeInt(os, crc); // crc-32
2141 if (elen64 != 0) {
2142 writeInt(os, ZIP64_MINVAL);
2143 writeInt(os, ZIP64_MINVAL);
2144 } else {
2145 writeInt(os, csize); // compressed size
2146 writeInt(os, size); // uncompressed size
2147 }
2148 }
2149 while (eoff + 4 < elen) {
2150 int tag = SH(extra, eoff);
2151 int sz = SH(extra, eoff + 2);
2152 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2153 foundExtraTime = true;
2154 }
2155 eoff += (4 + sz);
2156 }
2157 if (!foundExtraTime) {
2158 if (isWindows) {
2159 elenNTFS = 36; // NTFS, total 36 bytes
2160 } else { // on unix use "ext time"
2161 elenEXTT = 9;
2162 if (atime != -1)
2163 elenEXTT += 4;
2164 if (ctime != -1)
2165 elenEXTT += 4;
2166 }
2167 }
2168 writeShort(os, nlen);
2169 writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2170 writeBytes(os, zname, 1, nlen);
2171 if (elen64 != 0) {
2172 writeShort(os, EXTID_ZIP64);
2173 writeShort(os, 16);
2174 writeLong(os, size);
2175 writeLong(os, csize);
2176 }
2177 if (elenNTFS != 0) {
2178 writeShort(os, EXTID_NTFS);
2179 writeShort(os, elenNTFS - 4);
2180 writeInt(os, 0); // reserved
2181 writeShort(os, 0x0001); // NTFS attr tag
2182 writeShort(os, 24);
2183 writeLong(os, javaToWinTime(mtime));
2184 writeLong(os, javaToWinTime(atime));
2185 writeLong(os, javaToWinTime(ctime));
2186 }
2187 if (elenEXTT != 0) {
2188 writeShort(os, EXTID_EXTT);
2189 writeShort(os, elenEXTT - 4);// size for the folowing data block
2190 int fbyte = 0x1;
2191 if (atime != -1) // mtime and atime
2192 fbyte |= 0x2;
2193 if (ctime != -1) // mtime, atime and ctime
2194 fbyte |= 0x4;
2195 os.write(fbyte); // flags byte
2196 writeInt(os, javaToUnixTime(mtime));
2197 if (atime != -1)
2198 writeInt(os, javaToUnixTime(atime));
2199 if (ctime != -1)
2200 writeInt(os, javaToUnixTime(ctime));
2201 }
2202 if (extra != null) {
2203 writeBytes(os, extra);
2204 }
2205 return LOCHDR + nlen + elen + elen64 + elenNTFS + elenEXTT;
2206 }
2207
2208 // Data Descriptior
2209 int writeEXT(OutputStream os) throws IOException {
2210 writeInt(os, EXTSIG); // EXT header signature
2211 writeInt(os, crc); // crc-32
2212 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2213 writeLong(os, csize);
2214 writeLong(os, size);
2215 return 24;
2216 } else {
2217 writeInt(os, csize); // compressed size
2218 writeInt(os, size); // uncompressed size
2219 return 16;
2220 }
2221 }
2222
2223 // read NTFS, UNIX and ZIP64 data from cen.extra
2224 void readExtra(ZipFileSystem zipfs) throws IOException {
2225 if (extra == null)
2226 return;
2227 int elen = extra.length;
2228 int off = 0;
2229 int newOff = 0;
2230 while (off + 4 < elen) {
2231 // extra spec: HeaderID+DataSize+Data
2232 int pos = off;
2233 int tag = SH(extra, pos);
2234 int sz = SH(extra, pos + 2);
2235 pos += 4;
2236 if (pos + sz > elen) // invalid data
2237 break;
2238 switch (tag) {
2239 case EXTID_ZIP64 :
2240 if (size == ZIP64_MINVAL) {
2241 if (pos + 8 > elen) // invalid zip64 extra
2242 break; // fields, just skip
2243 size = LL(extra, pos);
2244 pos += 8;
2245 }
2246 if (csize == ZIP64_MINVAL) {
2247 if (pos + 8 > elen)
2248 break;
2249 csize = LL(extra, pos);
2250 pos += 8;
2251 }
2252 if (locoff == ZIP64_MINVAL) {
2253 if (pos + 8 > elen)
2254 break;
2255 locoff = LL(extra, pos);
2256 pos += 8;
2257 }
2258 break;
2259 case EXTID_NTFS:
2260 if (sz < 32)
2261 break;
2262 pos += 4; // reserved 4 bytes
2263 if (SH(extra, pos) != 0x0001)
2264 break;
2265 if (SH(extra, pos + 2) != 24)
2266 break;
2267 // override the loc field, datatime here is
2268 // more "accurate"
2269 mtime = winToJavaTime(LL(extra, pos + 4));
2270 atime = winToJavaTime(LL(extra, pos + 12));
2271 ctime = winToJavaTime(LL(extra, pos + 20));
2272 break;
2273 case EXTID_EXTT:
2274 // spec says the Extened timestamp in cen only has mtime
2275 // need to read the loc to get the extra a/ctime, if flag
2276 // "zipinfo-time" is not specified to false;
2277 // there is performance cost (move up to loc and read) to
2278 // access the loc table foreach entry;
2279 if (zipfs.noExtt) {
2280 if (sz == 5)
2281 mtime = unixToJavaTime(LG(extra, pos + 1));
2282 break;
2283 }
2284 byte[] buf = new byte[LOCHDR];
2285 if (zipfs.readFullyAt(buf, 0, buf.length , locoff)
2286 != buf.length)
2287 throw new ZipException("loc: reading failed");
2288 if (!locSigAt(buf, 0))
2289 throw new ZipException("loc: wrong sig ->"
2290 + Long.toString(getSig(buf, 0), 16));
2291 int locElen = LOCEXT(buf);
2292 if (locElen < 9) // EXTT is at lease 9 bytes
2293 break;
2294 int locNlen = LOCNAM(buf);
2295 buf = new byte[locElen];
2296 if (zipfs.readFullyAt(buf, 0, buf.length , locoff + LOCHDR + locNlen)
2297 != buf.length)
2298 throw new ZipException("loc extra: reading failed");
2299 int locPos = 0;
2300 while (locPos + 4 < buf.length) {
2301 int locTag = SH(buf, locPos);
2302 int locSZ = SH(buf, locPos + 2);
2303 locPos += 4;
2304 if (locTag != EXTID_EXTT) {
2305 locPos += locSZ;
2306 continue;
2307 }
2308 int end = locPos + locSZ - 4;
2309 int flag = CH(buf, locPos++);
2310 if ((flag & 0x1) != 0 && locPos <= end) {
2311 mtime = unixToJavaTime(LG(buf, locPos));
2312 locPos += 4;
2313 }
2314 if ((flag & 0x2) != 0 && locPos <= end) {
2315 atime = unixToJavaTime(LG(buf, locPos));
2316 locPos += 4;
2317 }
2318 if ((flag & 0x4) != 0 && locPos <= end) {
2319 ctime = unixToJavaTime(LG(buf, locPos));
2320 locPos += 4;
2321 }
2322 break;
2323 }
2324 break;
2325 default: // unknown tag
2326 System.arraycopy(extra, off, extra, newOff, sz + 4);
2327 newOff += (sz + 4);
2328 }
2329 off += (sz + 4);
2330 }
2331 if (newOff != 0 && newOff != extra.length)
2332 extra = Arrays.copyOf(extra, newOff);
2333 else
2334 extra = null;
2335 }
2336
2337 ///////// basic file attributes ///////////
2338 @Override
2339 public FileTime creationTime() {
2340 return FileTime.fromMillis(ctime == -1 ? mtime : ctime);
2341 }
2342
2343 @Override
2344 public boolean isDirectory() {
2345 return isDir();
2346 }
2347
2348 @Override
2349 public boolean isOther() {
2350 return false;
2351 }
2352
2353 @Override
2354 public boolean isRegularFile() {
2355 return !isDir();
2356 }
2357
2358 @Override
2359 public FileTime lastAccessTime() {
2360 return FileTime.fromMillis(atime == -1 ? mtime : atime);
2361 }
2362
2363 @Override
2364 public FileTime lastModifiedTime() {
2365 return FileTime.fromMillis(mtime);
2366 }
2367
2368 @Override
2369 public long size() {
2370 return size;
2371 }
2372
2373 @Override
2374 public boolean isSymbolicLink() {
2375 return false;
2376 }
2377
2378 @Override
2379 public Object fileKey() {
2380 return null;
2381 }
2382
2383 ///////// zip entry attributes ///////////
2384 public long compressedSize() {
2385 return csize;
2386 }
2387
2388 public long crc() {
2389 return crc;
2390 }
2391
2392 public int method() {
2393 return method;
2394 }
2395
2396 public byte[] extra() {
2397 if (extra != null)
2398 return Arrays.copyOf(extra, extra.length);
2399 return null;
2400 }
2401
2402 public byte[] comment() {
2403 if (comment != null)
2404 return Arrays.copyOf(comment, comment.length);
2405 return null;
2406 }
2407
2408 public String toString() {
2409 StringBuilder sb = new StringBuilder(1024);
2410 Formatter fm = new Formatter(sb);
2411 fm.format(" creationTime : %tc%n", creationTime().toMillis());
2412 fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis());
2413 fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
2414 fm.format(" isRegularFile : %b%n", isRegularFile());
2415 fm.format(" isDirectory : %b%n", isDirectory());
2416 fm.format(" isSymbolicLink : %b%n", isSymbolicLink());
2417 fm.format(" isOther : %b%n", isOther());
2418 fm.format(" fileKey : %s%n", fileKey());
2419 fm.format(" size : %d%n", size());
2420 fm.format(" compressedSize : %d%n", compressedSize());
2421 fm.format(" crc : %x%n", crc());
2422 fm.format(" method : %d%n", method());
2423 fm.close();
2424 return sb.toString();
2425 }
2426 }
2427
2428 private static class ExChannelCloser {
2429 Path path;
2430 SeekableByteChannel ch;
2431 Set<InputStream> streams;
2432 ExChannelCloser(Path path,
2433 SeekableByteChannel ch,
2434 Set<InputStream> streams)
2435 {
2436 this.path = path;
2437 this.ch = ch;
2438 this.streams = streams;
2439 }
2440 }
2441
2442 // ZIP directory has two issues:
2443 // (1) ZIP spec does not require the ZIP file to include
2444 // directory entry
2445 // (2) all entries are not stored/organized in a "tree"
2446 // structure.
2447 // A possible solution is to build the node tree ourself as
2448 // implemented below.
2449 private IndexNode root;
2450
2451 // default time stamp for pseudo entries
2452 private long zfsDefaultTimeStamp = System.currentTimeMillis();
2453
2454 private void removeFromTree(IndexNode inode) {
2455 IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(inode.name)));
2456 IndexNode child = parent.child;
2457 if (child.equals(inode)) {
2458 parent.child = child.sibling;
2459 } else {
2460 IndexNode last = child;
2461 while ((child = child.sibling) != null) {
2462 if (child.equals(inode)) {
2463 last.sibling = child.sibling;
2464 break;
2465 } else {
2466 last = child;
2467 }
2468 }
2469 }
2470 }
2471
2472 // purely for parent lookup, so we don't have to copy the parent
2473 // name every time
2474 static class ParentLookup extends IndexNode {
2475 int len;
2476 ParentLookup() {}
2477
2478 final ParentLookup as(byte[] name, int len) { // as a lookup "key"
2479 name(name, len);
2480 return this;
2481 }
2482
2483 void name(byte[] name, int len) {
2484 this.name = name;
2485 this.len = len;
2486 // calculate the hashcode the same way as Arrays.hashCode() does
2487 int result = 1;
2488 for (int i = 0; i < len; i++)
2489 result = 31 * result + name[i];
2490 this.hashcode = result;
2491 }
2492
2493 @Override
2494 public boolean equals(Object other) {
2495 if (!(other instanceof IndexNode)) {
2496 return false;
2497 }
2498 byte[] oname = ((IndexNode)other).name;
2499 return Arrays.equals(name, 0, len,
2500 oname, 0, oname.length);
2501 }
2502
2503 }
2504
2505 private void buildNodeTree() throws IOException {
2506 beginWrite();
2507 try {
2508 IndexNode root = new IndexNode(ROOTPATH, true);
2509 IndexNode[] nodes = inodes.keySet().toArray(new IndexNode[0]);
2510 inodes.put(root, root);
2511 ParentLookup lookup = new ParentLookup();
2512 for (IndexNode node : nodes) {
2513 IndexNode parent;
2514 while (true) {
2515 int off = getParentOff(node.name);
2516 if (off <= 1) { // parent is root
2517 node.sibling = root.child;
2518 root.child = node;
2519 break;
2520 }
2521 lookup = lookup.as(node.name, off);
2522 if (inodes.containsKey(lookup)) {
2523 parent = inodes.get(lookup);
2524 node.sibling = parent.child;
2525 parent.child = node;
2526 break;
2527 }
2528 // add new pseudo directory entry
2529 parent = new IndexNode(Arrays.copyOf(node.name, off), true);
2530 inodes.put(parent, parent);
2531 node.sibling = parent.child;
2532 parent.child = node;
2533 node = parent;
2534 }
2535 }
2536 } finally {
2537 endWrite();
2538 }
2539 }
2540 }
--- EOF ---