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