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