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