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