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