1 /* 2 * Copyright (c) 2009, 2014, 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.*; 29 import java.net.URI; 30 import java.nio.channels.*; 31 import java.nio.file.*; 32 import java.nio.file.DirectoryStream.Filter; 33 import java.nio.file.attribute.*; 34 import java.util.*; 35 import static java.nio.charset.StandardCharsets.UTF_8; 36 import static java.nio.file.StandardOpenOption.*; 37 import static java.nio.file.StandardCopyOption.*; 38 39 /** 40 * 41 * @author Xueming Shen, Rajendra Gutupalli,Jaya Hangal 42 */ 43 44 final class ZipPath implements Path { 45 46 private final ZipFileSystem zfs; 47 private final byte[] path; 48 private volatile int[] offsets; 49 private int hashcode = 0; // cached hashcode (created lazily) 50 51 ZipPath(ZipFileSystem zfs, byte[] path) { 52 this(zfs, path, false); 53 } 54 55 ZipPath(ZipFileSystem zfs, byte[] path, boolean normalized) 56 { 57 this.zfs = zfs; 58 if (normalized) 59 this.path = path; 60 else 61 this.path = normalize(path); 62 } 63 64 @Override 65 public ZipPath getRoot() { 66 if (this.isAbsolute()) 67 return zfs.getRootDir(); 68 else 69 return null; 70 } 71 72 @Override 73 public Path getFileName() { 74 initOffsets(); 75 int count = offsets.length; 76 if (count == 0) 77 return null; // no elements so no name 78 if (count == 1 && path[0] != '/') 79 return this; 80 int lastOffset = offsets[count-1]; 81 int len = path.length - lastOffset; 82 byte[] result = new byte[len]; 83 System.arraycopy(path, lastOffset, result, 0, len); 84 return new ZipPath(zfs, result); 85 } 86 87 @Override 88 public ZipPath getParent() { 89 initOffsets(); 90 int count = offsets.length; 91 if (count == 0) // no elements so no parent 92 return null; 93 int len = offsets[count-1] - 1; 94 if (len <= 0) // parent is root only (may be null) 95 return getRoot(); 96 byte[] result = new byte[len]; 97 System.arraycopy(path, 0, result, 0, len); 98 return new ZipPath(zfs, result); 99 } 100 101 @Override 102 public int getNameCount() { 103 initOffsets(); 104 return offsets.length; 105 } 106 107 @Override 108 public ZipPath getName(int index) { 109 initOffsets(); 110 if (index < 0 || index >= offsets.length) 111 throw new IllegalArgumentException(); 112 int begin = offsets[index]; 113 int len; 114 if (index == (offsets.length-1)) 115 len = path.length - begin; 116 else 117 len = offsets[index+1] - begin - 1; 118 // construct result 119 byte[] result = new byte[len]; 120 System.arraycopy(path, begin, result, 0, len); 121 return new ZipPath(zfs, result); 122 } 123 124 @Override 125 public ZipPath subpath(int beginIndex, int endIndex) { 126 initOffsets(); 127 if (beginIndex < 0 || 128 beginIndex >= offsets.length || 129 endIndex > offsets.length || 130 beginIndex >= endIndex) 131 throw new IllegalArgumentException(); 132 133 // starting offset and length 134 int begin = offsets[beginIndex]; 135 int len; 136 if (endIndex == offsets.length) 137 len = path.length - begin; 138 else 139 len = offsets[endIndex] - begin - 1; 140 // construct result 141 byte[] result = new byte[len]; 142 System.arraycopy(path, begin, result, 0, len); 143 return new ZipPath(zfs, result); 144 } 145 146 @Override 147 public ZipPath toRealPath(LinkOption... options) throws IOException { 148 ZipPath realPath; 149 byte[] resolved = getResolvedPath(); 150 // resolved is always absolute and normalized 151 if (resolved == path) { 152 realPath = this; 153 } else { 154 realPath = new ZipPath(zfs, resolved, true); 155 realPath.resolved = resolved; 156 } 157 realPath.checkAccess(); 158 return realPath; 159 } 160 161 boolean isHidden() { 162 return false; 163 } 164 165 @Override 166 public ZipPath toAbsolutePath() { 167 if (isAbsolute()) { 168 return this; 169 } else { 170 // add '/' before the existing path 171 byte[] tmp = new byte[path.length + 1]; 172 System.arraycopy(path, 0, tmp, 1, path.length); 173 tmp[0] = '/'; 174 return new ZipPath(zfs, tmp, true); // normalized 175 } 176 } 177 178 @Override 179 public URI toUri() { 180 try { 181 return new URI("jar", 182 decodeUri(zfs.getZipFile().toUri().toString()) + 183 "!" + 184 zfs.getString(toAbsolutePath().path), 185 null); 186 } catch (Exception ex) { 187 throw new AssertionError(ex); 188 } 189 } 190 191 private boolean equalsNameAt(ZipPath other, int index) { 192 int mbegin = offsets[index]; 193 int mlen = 0; 194 if (index == (offsets.length-1)) 195 mlen = path.length - mbegin; 196 else 197 mlen = offsets[index + 1] - mbegin - 1; 198 int obegin = other.offsets[index]; 199 int olen = 0; 200 if (index == (other.offsets.length - 1)) 201 olen = other.path.length - obegin; 202 else 203 olen = other.offsets[index + 1] - obegin - 1; 204 if (mlen != olen) 205 return false; 206 int n = 0; 207 while(n < mlen) { 208 if (path[mbegin + n] != other.path[obegin + n]) 209 return false; 210 n++; 211 } 212 return true; 213 } 214 215 @Override 216 public Path relativize(Path other) { 217 final ZipPath o = checkPath(other); 218 if (o.equals(this)) 219 return new ZipPath(zfs, new byte[0], true); 220 if (this.path.length == 0) 221 return o; 222 if (this.zfs != o.zfs || this.isAbsolute() != o.isAbsolute()) 223 throw new IllegalArgumentException(); 224 if (this.path.length == 1 && this.path[0] == '/') 225 return new ZipPath(zfs, 226 Arrays.copyOfRange(o.path, 1, o.path.length), 227 true); 228 int mc = this.getNameCount(); 229 int oc = o.getNameCount(); 230 int n = Math.min(mc, oc); 231 int i = 0; 232 while (i < n) { 233 if (!equalsNameAt(o, i)) 234 break; 235 i++; 236 } 237 int dotdots = mc - i; 238 int len = dotdots * 3 - 1; 239 if (i < oc) 240 len += (o.path.length - o.offsets[i] + 1); 241 byte[] result = new byte[len]; 242 243 int pos = 0; 244 while (dotdots > 0) { 245 result[pos++] = (byte)'.'; 246 result[pos++] = (byte)'.'; 247 if (pos < len) // no tailing slash at the end 248 result[pos++] = (byte)'/'; 249 dotdots--; 250 } 251 if (i < oc) 252 System.arraycopy(o.path, o.offsets[i], 253 result, pos, 254 o.path.length - o.offsets[i]); 255 return new ZipPath(zfs, result); 256 } 257 258 @Override 259 public ZipFileSystem getFileSystem() { 260 return zfs; 261 } 262 263 @Override 264 public boolean isAbsolute() { 265 return (this.path.length > 0 && path[0] == '/'); 266 } 267 268 @Override 269 public ZipPath resolve(Path other) { 270 final ZipPath o = checkPath(other); 271 int tlen = this.path.length; 272 if (tlen == 0 || o.isAbsolute()) 273 return o; 274 int olen = o.path.length; 275 if (olen == 0) 276 return this; 277 byte[] resolved = null; 278 if (this.path[tlen - 1] == '/') { 279 resolved = new byte[tlen + olen]; 280 System.arraycopy(path, 0, resolved, 0, tlen); 281 System.arraycopy(o.path, 0, resolved, tlen, olen); 282 } else { 283 resolved = new byte[tlen + 1 + olen]; 284 System.arraycopy(path, 0, resolved, 0, tlen); 285 resolved[tlen] = '/'; 286 System.arraycopy(o.path, 0, resolved, tlen + 1, olen); 287 } 288 return new ZipPath(zfs, resolved); 289 } 290 291 @Override 292 public Path resolveSibling(Path other) { 293 Objects.requireNonNull(other, "other"); 294 Path parent = getParent(); 295 return (parent == null) ? other : parent.resolve(other); 296 } 297 298 @Override 299 public boolean startsWith(Path other) { 300 final ZipPath o = checkPath(other); 301 if (o.isAbsolute() != this.isAbsolute() || 302 o.path.length > this.path.length) 303 return false; 304 int olast = o.path.length; 305 for (int i = 0; i < olast; i++) { 306 if (o.path[i] != this.path[i]) 307 return false; 308 } 309 olast--; 310 return o.path.length == this.path.length || 311 o.path[olast] == '/' || 312 this.path[olast + 1] == '/'; 313 } 314 315 @Override 316 public boolean endsWith(Path other) { 317 final ZipPath o = checkPath(other); 318 int olast = o.path.length - 1; 319 if (olast > 0 && o.path[olast] == '/') 320 olast--; 321 int last = this.path.length - 1; 322 if (last > 0 && this.path[last] == '/') 323 last--; 324 if (olast == -1) // o.path.length == 0 325 return last == -1; 326 if ((o.isAbsolute() &&(!this.isAbsolute() || olast != last)) || 327 (last < olast)) 328 return false; 329 for (; olast >= 0; olast--, last--) { 330 if (o.path[olast] != this.path[last]) 331 return false; 332 } 333 return o.path[olast + 1] == '/' || 334 last == -1 || this.path[last] == '/'; 335 } 336 337 @Override 338 public ZipPath resolve(String other) { 339 return resolve(zfs.getPath(other)); 340 } 341 342 @Override 343 public final Path resolveSibling(String other) { 344 return resolveSibling(zfs.getPath(other)); 345 } 346 347 @Override 348 public final boolean startsWith(String other) { 349 return startsWith(zfs.getPath(other)); 350 } 351 352 @Override 353 public final boolean endsWith(String other) { 354 return endsWith(zfs.getPath(other)); 355 } 356 357 @Override 358 public Path normalize() { 359 byte[] resolved = getResolved(); 360 if (resolved == path) // no change 361 return this; 362 return new ZipPath(zfs, resolved, true); 363 } 364 365 private ZipPath checkPath(Path path) { 366 Objects.requireNonNull(path, "path"); 367 if (!(path instanceof ZipPath)) 368 throw new ProviderMismatchException(); 369 return (ZipPath) path; 370 } 371 372 // create offset list if not already created 373 private void initOffsets() { 374 if (offsets == null) { 375 int count, index; 376 // count names 377 count = 0; 378 index = 0; 379 if (path.length == 0) { 380 // empty path has one name 381 count = 1; 382 } else { 383 while (index < path.length) { 384 byte c = path[index++]; 385 if (c != '/') { 386 count++; 387 while (index < path.length && path[index] != '/') 388 index++; 389 } 390 } 391 } 392 // populate offsets 393 int[] result = new int[count]; 394 count = 0; 395 index = 0; 396 while (index < path.length) { 397 byte c = path[index]; 398 if (c == '/') { 399 index++; 400 } else { 401 result[count++] = index++; 402 while (index < path.length && path[index] != '/') 403 index++; 404 } 405 } 406 synchronized (this) { 407 if (offsets == null) 408 offsets = result; 409 } 410 } 411 } 412 413 // resolved path for locating zip entry inside the zip file, 414 // the result path does not contain ./ and .. components 415 private volatile byte[] resolved = null; 416 byte[] getResolvedPath() { 417 byte[] r = resolved; 418 if (r == null) { 419 if (isAbsolute()) 420 r = getResolved(); 421 else 422 r = toAbsolutePath().getResolvedPath(); 423 resolved = r; 424 } 425 return resolved; 426 } 427 428 // removes redundant slashs, replace "\" to zip separator "/" 429 // and check for invalid characters 430 private byte[] normalize(byte[] path) { 431 int len = path.length; 432 if (len == 0) 433 return path; 434 byte prevC = 0; 435 for (int i = 0; i < len; i++) { 436 byte c = path[i]; 437 if (c == '\\' || c == '\u0000') 438 return normalize(path, i); 439 if (c == (byte)'/' && prevC == '/') 440 return normalize(path, i - 1); 441 prevC = c; 442 } 443 if (len > 1 && prevC == '/') 444 return Arrays.copyOf(path, len - 1); 445 return path; 446 } 447 448 private byte[] normalize(byte[] path, int off) { 449 byte[] to = new byte[path.length]; 450 int n = 0; 451 while (n < off) { 452 to[n] = path[n]; 453 n++; 454 } 455 int m = n; 456 byte prevC = 0; 457 while (n < path.length) { 458 byte c = path[n++]; 459 if (c == (byte)'\\') 460 c = (byte)'/'; 461 if (c == (byte)'/' && prevC == (byte)'/') 462 continue; 463 if (c == '\u0000') 464 throw new InvalidPathException(zfs.getString(path), 465 "Path: nul character not allowed"); 466 to[m++] = c; 467 prevC = c; 468 } 469 if (m > 1 && to[m - 1] == '/') 470 m--; 471 return (m == to.length)? to : Arrays.copyOf(to, m); 472 } 473 474 // Remove DotSlash(./) and resolve DotDot (..) components 475 private byte[] getResolved() { 476 for (int i = 0; i < path.length; i++) { 477 if (path[i] == (byte)'.') { 478 return resolve0(); 479 } 480 } 481 return path; 482 } 483 484 // TBD: performance, avoid initOffsets 485 private byte[] resolve0() { 486 byte[] to = new byte[path.length]; 487 int nc = getNameCount(); 488 int[] lastM = new int[nc]; 489 int lastMOff = -1; 490 int m = 0; 491 for (int i = 0; i < nc; i++) { 492 int n = offsets[i]; 493 int len = (i == offsets.length - 1)? 494 (path.length - n):(offsets[i + 1] - n - 1); 495 if (len == 1 && path[n] == (byte)'.') { 496 if (m == 0 && path[0] == '/') // absolute path 497 to[m++] = '/'; 498 continue; 499 } 500 if (len == 2 && path[n] == '.' && path[n + 1] == '.') { 501 if (lastMOff >= 0) { 502 m = lastM[lastMOff--]; // retreat 503 continue; 504 } 505 if (path[0] == '/') { // "/../xyz" skip 506 if (m == 0) 507 to[m++] = '/'; 508 } else { // "../xyz" -> "../xyz" 509 if (m != 0 && to[m-1] != '/') 510 to[m++] = '/'; 511 while (len-- > 0) 512 to[m++] = path[n++]; 513 } 514 continue; 515 } 516 if (m == 0 && path[0] == '/' || // absolute path 517 m != 0 && to[m-1] != '/') { // not the first name 518 to[m++] = '/'; 519 } 520 lastM[++lastMOff] = m; 521 while (len-- > 0) 522 to[m++] = path[n++]; 523 } 524 if (m > 1 && to[m - 1] == '/') 525 m--; 526 return (m == to.length)? to : Arrays.copyOf(to, m); 527 } 528 529 @Override 530 public String toString() { 531 return zfs.getString(path); 532 } 533 534 @Override 535 public int hashCode() { 536 int h = hashcode; 537 if (h == 0) 538 hashcode = h = Arrays.hashCode(path); 539 return h; 540 } 541 542 @Override 543 public boolean equals(Object obj) { 544 return obj != null && 545 obj instanceof ZipPath && 546 this.zfs == ((ZipPath)obj).zfs && 547 compareTo((Path) obj) == 0; 548 } 549 550 @Override 551 public int compareTo(Path other) { 552 final ZipPath o = checkPath(other); 553 int len1 = this.path.length; 554 int len2 = o.path.length; 555 556 int n = Math.min(len1, len2); 557 byte v1[] = this.path; 558 byte v2[] = o.path; 559 560 int k = 0; 561 while (k < n) { 562 int c1 = v1[k] & 0xff; 563 int c2 = v2[k] & 0xff; 564 if (c1 != c2) 565 return c1 - c2; 566 k++; 567 } 568 return len1 - len2; 569 } 570 571 public WatchKey register( 572 WatchService watcher, 573 WatchEvent.Kind<?>[] events, 574 WatchEvent.Modifier... modifiers) { 575 if (watcher == null || events == null || modifiers == null) { 576 throw new NullPointerException(); 577 } 578 // watcher must be associated with a different provider 579 throw new ProviderMismatchException(); 580 } 581 582 @Override 583 public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) { 584 return register(watcher, events, new WatchEvent.Modifier[0]); 585 } 586 587 @Override 588 public final File toFile() { 589 throw new UnsupportedOperationException(); 590 } 591 592 @Override 593 public Iterator<Path> iterator() { 594 return new Iterator<Path>() { 595 private int i = 0; 596 597 @Override 598 public boolean hasNext() { 599 return (i < getNameCount()); 600 } 601 602 @Override 603 public Path next() { 604 if (i < getNameCount()) { 605 Path result = getName(i); 606 i++; 607 return result; 608 } else { 609 throw new NoSuchElementException(); 610 } 611 } 612 613 @Override 614 public void remove() { 615 throw new ReadOnlyFileSystemException(); 616 } 617 }; 618 } 619 620 ///////////////////////////////////////////////////////////////////// 621 622 void createDirectory(FileAttribute<?>... attrs) 623 throws IOException 624 { 625 zfs.createDirectory(getResolvedPath(), attrs); 626 } 627 628 InputStream newInputStream(OpenOption... options) throws IOException 629 { 630 if (options.length > 0) { 631 for (OpenOption opt : options) { 632 if (opt != READ) 633 throw new UnsupportedOperationException("'" + opt + "' not allowed"); 634 } 635 } 636 return zfs.newInputStream(getResolvedPath()); 637 } 638 639 DirectoryStream<Path> newDirectoryStream(Filter<? super Path> filter) 640 throws IOException 641 { 642 return new ZipDirectoryStream(this, filter); 643 } 644 645 void delete() throws IOException { 646 zfs.deleteFile(getResolvedPath(), true); 647 } 648 649 void deleteIfExists() throws IOException { 650 zfs.deleteFile(getResolvedPath(), false); 651 } 652 653 ZipFileAttributes getAttributes() throws IOException 654 { 655 ZipFileAttributes zfas = zfs.getFileAttributes(getResolvedPath()); 656 if (zfas == null) 657 throw new NoSuchFileException(toString()); 658 return zfas; 659 } 660 661 void setAttribute(String attribute, Object value, LinkOption... options) 662 throws IOException 663 { 664 String type = null; 665 String attr = null; 666 int colonPos = attribute.indexOf(':'); 667 if (colonPos == -1) { 668 type = "basic"; 669 attr = attribute; 670 } else { 671 type = attribute.substring(0, colonPos++); 672 attr = attribute.substring(colonPos); 673 } 674 ZipFileAttributeView view = ZipFileAttributeView.get(this, type); 675 if (view == null) 676 throw new UnsupportedOperationException("view <" + view + "> is not supported"); 677 view.setAttribute(attr, value); 678 } 679 680 void setTimes(FileTime mtime, FileTime atime, FileTime ctime) 681 throws IOException 682 { 683 zfs.setTimes(getResolvedPath(), mtime, atime, ctime); 684 } 685 686 Map<String, Object> readAttributes(String attributes, LinkOption... options) 687 throws IOException 688 689 { 690 String view = null; 691 String attrs = null; 692 int colonPos = attributes.indexOf(':'); 693 if (colonPos == -1) { 694 view = "basic"; 695 attrs = attributes; 696 } else { 697 view = attributes.substring(0, colonPos++); 698 attrs = attributes.substring(colonPos); 699 } 700 ZipFileAttributeView zfv = ZipFileAttributeView.get(this, view); 701 if (zfv == null) { 702 throw new UnsupportedOperationException("view not supported"); 703 } 704 return zfv.readAttributes(attrs); 705 } 706 707 FileStore getFileStore() throws IOException { 708 // each ZipFileSystem only has one root (as requested for now) 709 if (exists()) 710 return zfs.getFileStore(this); 711 throw new NoSuchFileException(zfs.getString(path)); 712 } 713 714 boolean isSameFile(Path other) throws IOException { 715 if (this.equals(other)) 716 return true; 717 if (other == null || 718 this.getFileSystem() != other.getFileSystem()) 719 return false; 720 this.checkAccess(); 721 ((ZipPath)other).checkAccess(); 722 return Arrays.equals(this.getResolvedPath(), 723 ((ZipPath)other).getResolvedPath()); 724 } 725 726 SeekableByteChannel newByteChannel(Set<? extends OpenOption> options, 727 FileAttribute<?>... attrs) 728 throws IOException 729 { 730 return zfs.newByteChannel(getResolvedPath(), options, attrs); 731 } 732 733 734 FileChannel newFileChannel(Set<? extends OpenOption> options, 735 FileAttribute<?>... attrs) 736 throws IOException 737 { 738 return zfs.newFileChannel(getResolvedPath(), options, attrs); 739 } 740 741 void checkAccess(AccessMode... modes) throws IOException { 742 boolean w = false; 743 boolean x = false; 744 for (AccessMode mode : modes) { 745 switch (mode) { 746 case READ: 747 break; 748 case WRITE: 749 w = true; 750 break; 751 case EXECUTE: 752 x = true; 753 break; 754 default: 755 throw new UnsupportedOperationException(); 756 } 757 } 758 zfs.checkAccess(getResolvedPath()); 759 if ((w && zfs.isReadOnly()) || x) { 760 throw new AccessDeniedException(toString()); 761 } 762 } 763 764 boolean exists() { 765 try { 766 return zfs.exists(getResolvedPath()); 767 } catch (IOException x) {} 768 return false; 769 } 770 771 OutputStream newOutputStream(OpenOption... options) throws IOException 772 { 773 if (options.length == 0) 774 return zfs.newOutputStream(getResolvedPath(), 775 CREATE, TRUNCATE_EXISTING, WRITE); 776 return zfs.newOutputStream(getResolvedPath(), options); 777 } 778 779 void move(ZipPath target, CopyOption... options) 780 throws IOException 781 { 782 if (Files.isSameFile(this.zfs.getZipFile(), target.zfs.getZipFile())) 783 { 784 zfs.copyFile(true, 785 getResolvedPath(), target.getResolvedPath(), 786 options); 787 } else { 788 copyToTarget(target, options); 789 delete(); 790 } 791 } 792 793 void copy(ZipPath target, CopyOption... options) 794 throws IOException 795 { 796 if (Files.isSameFile(this.zfs.getZipFile(), target.zfs.getZipFile())) 797 zfs.copyFile(false, 798 getResolvedPath(), target.getResolvedPath(), 799 options); 800 else 801 copyToTarget(target, options); 802 } 803 804 private void copyToTarget(ZipPath target, CopyOption... options) 805 throws IOException 806 { 807 boolean replaceExisting = false; 808 boolean copyAttrs = false; 809 for (CopyOption opt : options) { 810 if (opt == REPLACE_EXISTING) 811 replaceExisting = true; 812 else if (opt == COPY_ATTRIBUTES) 813 copyAttrs = true; 814 } 815 // attributes of source file 816 ZipFileAttributes zfas = getAttributes(); 817 // check if target exists 818 boolean exists; 819 if (replaceExisting) { 820 try { 821 target.deleteIfExists(); 822 exists = false; 823 } catch (DirectoryNotEmptyException x) { 824 exists = true; 825 } 826 } else { 827 exists = target.exists(); 828 } 829 if (exists) 830 throw new FileAlreadyExistsException(target.toString()); 831 832 if (zfas.isDirectory()) { 833 // create directory or file 834 target.createDirectory(); 835 } else { 836 InputStream is = zfs.newInputStream(getResolvedPath()); 837 try { 838 OutputStream os = target.newOutputStream(); 839 try { 840 byte[] buf = new byte[8192]; 841 int n = 0; 842 while ((n = is.read(buf)) != -1) { 843 os.write(buf, 0, n); 844 } 845 } finally { 846 os.close(); 847 } 848 } finally { 849 is.close(); 850 } 851 } 852 if (copyAttrs) { 853 BasicFileAttributeView view = 854 ZipFileAttributeView.get(target, BasicFileAttributeView.class); 855 try { 856 view.setTimes(zfas.lastModifiedTime(), 857 zfas.lastAccessTime(), 858 zfas.creationTime()); 859 } catch (IOException x) { 860 // rollback? 861 try { 862 target.delete(); 863 } catch (IOException ignore) { } 864 throw x; 865 } 866 } 867 } 868 869 private static int decode(char c) { 870 if ((c >= '0') && (c <= '9')) 871 return c - '0'; 872 if ((c >= 'a') && (c <= 'f')) 873 return c - 'a' + 10; 874 if ((c >= 'A') && (c <= 'F')) 875 return c - 'A' + 10; 876 assert false; 877 return -1; 878 } 879 880 // to avoid double escape 881 static String decodeUri(String s) { 882 if (s == null) 883 return s; 884 int n = s.length(); 885 if (n == 0) 886 return s; 887 if (s.indexOf('%') < 0) 888 return s; 889 890 StringBuilder sb = new StringBuilder(n); 891 byte[] bb = new byte[n]; 892 boolean betweenBrackets = false; 893 894 for (int i = 0; i < n;) { 895 char c = s.charAt(i); 896 if (c == '[') { 897 betweenBrackets = true; 898 } else if (betweenBrackets && c == ']') { 899 betweenBrackets = false; 900 } 901 if (c != '%' || betweenBrackets ) { 902 sb.append(c); 903 i++; 904 continue; 905 } 906 int nb = 0; 907 while (c == '%') { 908 assert (n - i >= 2); 909 bb[nb++] = (byte)(((decode(s.charAt(++i)) & 0xf) << 4) | 910 (decode(s.charAt(++i)) & 0xf)); 911 if (++i >= n) { 912 break; 913 } 914 c = s.charAt(i); 915 } 916 sb.append(new String(bb, 0, nb, UTF_8)); 917 } 918 return sb.toString(); 919 } 920 921 }