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