1 /* 2 * Copyright (c) 1997, 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 java.io; 27 28 import java.net.URI; 29 import java.nio.file.*; 30 import java.security.*; 31 import java.util.Enumeration; 32 import java.util.Objects; 33 import java.util.StringJoiner; 34 import java.util.Vector; 35 import java.util.concurrent.ConcurrentHashMap; 36 37 import jdk.internal.access.JavaIOFilePermissionAccess; 38 import jdk.internal.access.SharedSecrets; 39 import sun.security.action.GetPropertyAction; 40 import sun.security.util.FilePermCompat; 41 import sun.security.util.SecurityConstants; 42 import static sun.nio.fs.BuiltinFileSystemProvider.BUILTIN_FILE_SYSTEM; 43 44 /** 45 * This class represents access to a file or directory. A FilePermission consists 46 * of a pathname and a set of actions valid for that pathname. 47 * <P> 48 * Pathname is the pathname of the file or directory granted the specified 49 * actions. A pathname that ends in "/*" (where "/" is 50 * the file separator character, <code>File.separatorChar</code>) indicates 51 * all the files and directories contained in that directory. A pathname 52 * that ends with "/-" indicates (recursively) all files 53 * and subdirectories contained in that directory. Such a pathname is called 54 * a wildcard pathname. Otherwise, it's a simple pathname. 55 * <P> 56 * A pathname consisting of the special token {@literal "<<ALL FILES>>"} 57 * matches <b>any</b> file. 58 * <P> 59 * Note: A pathname consisting of a single "*" indicates all the files 60 * in the current directory, while a pathname consisting of a single "-" 61 * indicates all the files in the current directory and 62 * (recursively) all files and subdirectories contained in the current 63 * directory. 64 * <P> 65 * The actions to be granted are passed to the constructor in a string containing 66 * a list of one or more comma-separated keywords. The possible keywords are 67 * "read", "write", "execute", "delete", and "readlink". Their meaning is 68 * defined as follows: 69 * 70 * <DL> 71 * <DT> read <DD> read permission 72 * <DT> write <DD> write permission 73 * <DT> execute 74 * <DD> execute permission. Allows <code>Runtime.exec</code> to 75 * be called. Corresponds to <code>SecurityManager.checkExec</code>. 76 * <DT> delete 77 * <DD> delete permission. Allows <code>File.delete</code> to 78 * be called. Corresponds to <code>SecurityManager.checkDelete</code>. 79 * <DT> readlink 80 * <DD> read link permission. Allows the target of a 81 * <a href="../nio/file/package-summary.html#links">symbolic link</a> 82 * to be read by invoking the {@link java.nio.file.Files#readSymbolicLink 83 * readSymbolicLink } method. 84 * </DL> 85 * <P> 86 * The actions string is converted to lowercase before processing. 87 * <P> 88 * Be careful when granting FilePermissions. Think about the implications 89 * of granting read and especially write access to various files and 90 * directories. The {@literal "<<ALL FILES>>"} permission with write action is 91 * especially dangerous. This grants permission to write to the entire 92 * file system. One thing this effectively allows is replacement of the 93 * system binary, including the JVM runtime environment. 94 * <P> 95 * Please note: Code can always read a file from the same 96 * directory it's in (or a subdirectory of that directory); it does not 97 * need explicit permission to do so. 98 * 99 * @see java.security.Permission 100 * @see java.security.Permissions 101 * @see java.security.PermissionCollection 102 * 103 * 104 * @author Marianne Mueller 105 * @author Roland Schemers 106 * @since 1.2 107 * 108 * @serial exclude 109 */ 110 111 public final class FilePermission extends Permission implements Serializable { 112 113 /** 114 * Execute action. 115 */ 116 private static final int EXECUTE = 0x1; 117 /** 118 * Write action. 119 */ 120 private static final int WRITE = 0x2; 121 /** 122 * Read action. 123 */ 124 private static final int READ = 0x4; 125 /** 126 * Delete action. 127 */ 128 private static final int DELETE = 0x8; 129 /** 130 * Read link action. 131 */ 132 private static final int READLINK = 0x10; 133 134 /** 135 * All actions (read,write,execute,delete,readlink) 136 */ 137 private static final int ALL = READ|WRITE|EXECUTE|DELETE|READLINK; 138 /** 139 * No actions. 140 */ 141 private static final int NONE = 0x0; 142 143 // the actions mask 144 private transient int mask; 145 146 // does path indicate a directory? (wildcard or recursive) 147 private transient boolean directory; 148 149 // is it a recursive directory specification? 150 private transient boolean recursive; 151 152 /** 153 * the actions string. 154 * 155 * @serial 156 */ 157 private String actions; // Left null as long as possible, then 158 // created and re-used in the getAction function. 159 160 // canonicalized dir path. used by the "old" behavior (nb == false). 161 // In the case of directories, it is the name "/blah/*" or "/blah/-" 162 // without the last character (the "*" or "-"). 163 164 private transient String cpath; 165 166 // Following fields used by the "new" behavior (nb == true), in which 167 // input path is not canonicalized. For compatibility (so that granting 168 // FilePermission on "x" allows reading "`pwd`/x", an alternative path 169 // can be added so that both can be used in an implies() check. Please note 170 // the alternative path only deals with absolute/relative path, and does 171 // not deal with symlink/target. 172 173 private transient Path npath; // normalized dir path. 174 private transient Path npath2; // alternative normalized dir path. 175 private transient boolean allFiles; // whether this is <<ALL FILES>> 176 private transient boolean invalid; // whether input path is invalid 177 178 // static Strings used by init(int mask) 179 private static final char RECURSIVE_CHAR = '-'; 180 private static final char WILD_CHAR = '*'; 181 182 // public String toString() { 183 // StringBuffer sb = new StringBuffer(); 184 // sb.append("*** FilePermission on " + getName() + " ***"); 185 // for (Field f : FilePermission.class.getDeclaredFields()) { 186 // if (!Modifier.isStatic(f.getModifiers())) { 187 // try { 188 // sb.append(f.getName() + " = " + f.get(this)); 189 // } catch (Exception e) { 190 // sb.append(f.getName() + " = " + e.toString()); 191 // } 192 // sb.append('\n'); 193 // } 194 // } 195 // sb.append("***\n"); 196 // return sb.toString(); 197 // } 198 199 private static final long serialVersionUID = 7930732926638008763L; 200 201 // Always use the internal default file system, in case it was modified 202 // with the java.nio.file.spi.DefaultFileSystemProvider system property. 203 204 private static final Path here = BUILTIN_FILE_SYSTEM.getPath( 205 GetPropertyAction.privilegedGetProperty("user.dir")); 206 207 private static final Path EMPTY_PATH = BUILTIN_FILE_SYSTEM.getPath(""); 208 private static final Path DASH_PATH = BUILTIN_FILE_SYSTEM.getPath("-"); 209 private static final Path DOTDOT_PATH = BUILTIN_FILE_SYSTEM.getPath(".."); 210 211 /** 212 * A private constructor that clones some and updates some, 213 * always with a different name. 214 * @param input 215 */ 216 private FilePermission(String name, 217 FilePermission input, 218 Path npath, 219 Path npath2, 220 int mask, 221 String actions) { 222 super(name); 223 // Customizables 224 this.npath = npath; 225 this.npath2 = npath2; 226 this.actions = actions; 227 this.mask = mask; 228 // Cloneds 229 this.allFiles = input.allFiles; 230 this.invalid = input.invalid; 231 this.recursive = input.recursive; 232 this.directory = input.directory; 233 this.cpath = input.cpath; 234 } 235 236 /** 237 * Returns the alternative path as a Path object, i.e. absolute path 238 * for a relative one, or vice versa. 239 * 240 * @param in a real path w/o "-" or "*" at the end, and not <<ALL FILES>>. 241 * @return the alternative path, or null if cannot find one. 242 */ 243 private static Path altPath(Path in) { 244 try { 245 if (!in.isAbsolute()) { 246 return here.resolve(in).normalize(); 247 } else { 248 return here.relativize(in).normalize(); 249 } 250 } catch (IllegalArgumentException e) { 251 return null; 252 } 253 } 254 255 static { 256 SharedSecrets.setJavaIOFilePermissionAccess( 257 /** 258 * Creates FilePermission objects with special internals. 259 * See {@link FilePermCompat#newPermPlusAltPath(Permission)} and 260 * {@link FilePermCompat#newPermUsingAltPath(Permission)}. 261 */ 262 new JavaIOFilePermissionAccess() { 263 public FilePermission newPermPlusAltPath(FilePermission input) { 264 if (!input.invalid && input.npath2 == null && !input.allFiles) { 265 Path npath2 = altPath(input.npath); 266 if (npath2 != null) { 267 // Please note the name of the new permission is 268 // different than the original so that when one is 269 // added to a FilePermissionCollection it will not 270 // be merged with the original one. 271 return new FilePermission(input.getName() + "#plus", 272 input, 273 input.npath, 274 npath2, 275 input.mask, 276 input.actions); 277 } 278 } 279 return input; 280 } 281 public FilePermission newPermUsingAltPath(FilePermission input) { 282 if (!input.invalid && !input.allFiles) { 283 Path npath2 = altPath(input.npath); 284 if (npath2 != null) { 285 // New name, see above. 286 return new FilePermission(input.getName() + "#using", 287 input, 288 npath2, 289 null, 290 input.mask, 291 input.actions); 292 } 293 } 294 return null; 295 } 296 } 297 ); 298 } 299 300 /** 301 * initialize a FilePermission object. Common to all constructors. 302 * Also called during de-serialization. 303 * 304 * @param mask the actions mask to use. 305 * 306 */ 307 private void init(int mask) { 308 if ((mask & ALL) != mask) 309 throw new IllegalArgumentException("invalid actions mask"); 310 311 if (mask == NONE) 312 throw new IllegalArgumentException("invalid actions mask"); 313 314 if (FilePermCompat.nb) { 315 String name = getName(); 316 317 if (name == null) 318 throw new NullPointerException("name can't be null"); 319 320 this.mask = mask; 321 322 if (name.equals("<<ALL FILES>>")) { 323 allFiles = true; 324 npath = EMPTY_PATH; 325 // other fields remain default 326 return; 327 } 328 329 boolean rememberStar = false; 330 if (name.endsWith("*")) { 331 rememberStar = true; 332 recursive = false; 333 name = name.substring(0, name.length()-1) + "-"; 334 } 335 336 try { 337 // new File() can "normalize" some name, for example, "/C:/X" on 338 // Windows. Some JDK codes generate such illegal names. 339 npath = BUILTIN_FILE_SYSTEM.getPath(new File(name).getPath()) 340 .normalize(); 341 // lastName should always be non-null now 342 Path lastName = npath.getFileName(); 343 if (lastName != null && lastName.equals(DASH_PATH)) { 344 directory = true; 345 recursive = !rememberStar; 346 npath = npath.getParent(); 347 } 348 if (npath == null) { 349 npath = EMPTY_PATH; 350 } 351 invalid = false; 352 } catch (InvalidPathException ipe) { 353 // Still invalid. For compatibility reason, accept it 354 // but make this permission useless. 355 npath = BUILTIN_FILE_SYSTEM.getPath("-u-s-e-l-e-s-s-"); 356 invalid = true; 357 } 358 359 } else { 360 if ((cpath = getName()) == null) 361 throw new NullPointerException("name can't be null"); 362 363 this.mask = mask; 364 365 if (cpath.equals("<<ALL FILES>>")) { 366 directory = true; 367 recursive = true; 368 cpath = ""; 369 return; 370 } 371 372 // store only the canonical cpath if possible 373 cpath = AccessController.doPrivileged(new PrivilegedAction<>() { 374 public String run() { 375 try { 376 String path = cpath; 377 if (cpath.endsWith("*")) { 378 // call getCanonicalPath with a path with wildcard character 379 // replaced to avoid calling it with paths that are 380 // intended to match all entries in a directory 381 path = path.substring(0, path.length() - 1) + "-"; 382 path = new File(path).getCanonicalPath(); 383 return path.substring(0, path.length() - 1) + "*"; 384 } else { 385 return new File(path).getCanonicalPath(); 386 } 387 } catch (IOException ioe) { 388 return cpath; 389 } 390 } 391 }); 392 393 int len = cpath.length(); 394 char last = ((len > 0) ? cpath.charAt(len - 1) : 0); 395 396 if (last == RECURSIVE_CHAR && 397 cpath.charAt(len - 2) == File.separatorChar) { 398 directory = true; 399 recursive = true; 400 cpath = cpath.substring(0, --len); 401 } else if (last == WILD_CHAR && 402 cpath.charAt(len - 2) == File.separatorChar) { 403 directory = true; 404 //recursive = false; 405 cpath = cpath.substring(0, --len); 406 } else { 407 // overkill since they are initialized to false, but 408 // commented out here to remind us... 409 //directory = false; 410 //recursive = false; 411 } 412 413 // XXX: at this point the path should be absolute. die if it isn't? 414 } 415 } 416 417 /** 418 * Creates a new FilePermission object with the specified actions. 419 * <i>path</i> is the pathname of a file or directory, and <i>actions</i> 420 * contains a comma-separated list of the desired actions granted on the 421 * file or directory. Possible actions are 422 * "read", "write", "execute", "delete", and "readlink". 423 * 424 * <p>A pathname that ends in "/*" (where "/" is 425 * the file separator character, <code>File.separatorChar</code>) 426 * indicates all the files and directories contained in that directory. 427 * A pathname that ends with "/-" indicates (recursively) all files and 428 * subdirectories contained in that directory. The special pathname 429 * {@literal "<<ALL FILES>>"} matches any file. 430 * 431 * <p>A pathname consisting of a single "*" indicates all the files 432 * in the current directory, while a pathname consisting of a single "-" 433 * indicates all the files in the current directory and 434 * (recursively) all files and subdirectories contained in the current 435 * directory. 436 * 437 * <p>A pathname containing an empty string represents an empty path. 438 * 439 * @implNote In this implementation, the 440 * {@code jdk.io.permissionsUseCanonicalPath} system property dictates how 441 * the {@code path} argument is processed and stored. 442 * <P> 443 * If the value of the system property is set to {@code true}, {@code path} 444 * is canonicalized and stored as a String object named {@code cpath}. 445 * This means a relative path is converted to an absolute path, a Windows 446 * DOS-style 8.3 path is expanded to a long path, and a symbolic link is 447 * resolved to its target, etc. 448 * <P> 449 * If the value of the system property is set to {@code false}, {@code path} 450 * is converted to a {@link java.nio.file.Path} object named {@code npath} 451 * after {@link Path#normalize() normalization}. No canonicalization is 452 * performed which means the underlying file system is not accessed. 453 * If an {@link InvalidPathException} is thrown during the conversion, 454 * this {@code FilePermission} will be labeled as invalid. 455 * <P> 456 * In either case, the "*" or "-" character at the end of a wildcard 457 * {@code path} is removed before canonicalization or normalization. 458 * It is stored in a separate wildcard flag field. 459 * <P> 460 * The default value of the {@code jdk.io.permissionsUseCanonicalPath} 461 * system property is {@code false} in this implementation. 462 * 463 * @param path the pathname of the file/directory. 464 * @param actions the action string. 465 * 466 * @throws IllegalArgumentException 467 * If actions is <code>null</code>, empty or contains an action 468 * other than the specified possible actions. 469 */ 470 public FilePermission(String path, String actions) { 471 super(path); 472 init(getMask(actions)); 473 } 474 475 /** 476 * Creates a new FilePermission object using an action mask. 477 * More efficient than the FilePermission(String, String) constructor. 478 * Can be used from within 479 * code that needs to create a FilePermission object to pass into the 480 * <code>implies</code> method. 481 * 482 * @param path the pathname of the file/directory. 483 * @param mask the action mask to use. 484 */ 485 // package private for use by the FilePermissionCollection add method 486 FilePermission(String path, int mask) { 487 super(path); 488 init(mask); 489 } 490 491 /** 492 * Checks if this FilePermission object "implies" the specified permission. 493 * <P> 494 * More specifically, this method returns true if: 495 * <ul> 496 * <li> <i>p</i> is an instanceof FilePermission, 497 * <li> <i>p</i>'s actions are a proper subset of this 498 * object's actions, and 499 * <li> <i>p</i>'s pathname is implied by this object's 500 * pathname. For example, "/tmp/*" implies "/tmp/foo", since 501 * "/tmp/*" encompasses all files in the "/tmp" directory, 502 * including the one named "foo". 503 * </ul> 504 * <P> 505 * Precisely, a simple pathname implies another simple pathname 506 * if and only if they are equal. A simple pathname never implies 507 * a wildcard pathname. A wildcard pathname implies another wildcard 508 * pathname if and only if all simple pathnames implied by the latter 509 * are implied by the former. A wildcard pathname implies a simple 510 * pathname if and only if 511 * <ul> 512 * <li>if the wildcard flag is "*", the simple pathname's path 513 * must be right inside the wildcard pathname's path. 514 * <li>if the wildcard flag is "-", the simple pathname's path 515 * must be recursively inside the wildcard pathname's path. 516 * </ul> 517 * <P> 518 * {@literal "<<ALL FILES>>"} implies every other pathname. No pathname, 519 * except for {@literal "<<ALL FILES>>"} itself, implies 520 * {@literal "<<ALL FILES>>"}. 521 * 522 * @implNote 523 * If {@code jdk.io.permissionsUseCanonicalPath} is {@code true}, a 524 * simple {@code cpath} is inside a wildcard {@code cpath} if and only if 525 * after removing the base name (the last name in the pathname's name 526 * sequence) from the former the remaining part equals to the latter, 527 * a simple {@code cpath} is recursively inside a wildcard {@code cpath} 528 * if and only if the former starts with the latter. 529 * <p> 530 * If {@code jdk.io.permissionsUseCanonicalPath} is {@code false}, a 531 * simple {@code npath} is inside a wildcard {@code npath} if and only if 532 * {@code simple_npath.relativize(wildcard_npath)} is exactly "..", 533 * a simple {@code npath} is recursively inside a wildcard {@code npath} 534 * if and only if {@code simple_npath.relativize(wildcard_npath)} is a 535 * series of one or more "..". This means "/-" implies "/foo" but not "foo". 536 * <p> 537 * An invalid {@code FilePermission} does not imply any object except for 538 * itself. An invalid {@code FilePermission} is not implied by any object 539 * except for itself or a {@code FilePermission} on 540 * {@literal "<<ALL FILES>>"} whose actions is a superset of this 541 * invalid {@code FilePermission}. Even if two {@code FilePermission} 542 * are created with the same invalid path, one does not imply the other. 543 * 544 * @param p the permission to check against. 545 * 546 * @return <code>true</code> if the specified permission is not 547 * <code>null</code> and is implied by this object, 548 * <code>false</code> otherwise. 549 */ 550 @Override 551 public boolean implies(Permission p) { 552 if (!(p instanceof FilePermission)) 553 return false; 554 555 FilePermission that = (FilePermission) p; 556 557 // we get the effective mask. i.e., the "and" of this and that. 558 // They must be equal to that.mask for implies to return true. 559 560 return ((this.mask & that.mask) == that.mask) && impliesIgnoreMask(that); 561 } 562 563 /** 564 * Checks if the Permission's actions are a proper subset of the 565 * this object's actions. Returns the effective mask iff the 566 * this FilePermission's path also implies that FilePermission's path. 567 * 568 * @param that the FilePermission to check against. 569 * @return the effective mask 570 */ 571 boolean impliesIgnoreMask(FilePermission that) { 572 if (FilePermCompat.nb) { 573 if (this == that) { 574 return true; 575 } 576 if (allFiles) { 577 return true; 578 } 579 if (this.invalid || that.invalid) { 580 return false; 581 } 582 if (that.allFiles) { 583 return false; 584 } 585 // Left at least same level of wildness as right 586 if ((this.recursive && that.recursive) != that.recursive 587 || (this.directory && that.directory) != that.directory) { 588 return false; 589 } 590 // Same npath is good as long as both or neither are directories 591 if (this.npath.equals(that.npath) 592 && this.directory == that.directory) { 593 return true; 594 } 595 int diff = containsPath(this.npath, that.npath); 596 // Right inside left is good if recursive 597 if (diff >= 1 && recursive) { 598 return true; 599 } 600 // Right right inside left if it is element in set 601 if (diff == 1 && directory && !that.directory) { 602 return true; 603 } 604 605 // Hack: if a npath2 field exists, apply the same checks 606 // on it as a fallback. 607 if (this.npath2 != null) { 608 if (this.npath2.equals(that.npath) 609 && this.directory == that.directory) { 610 return true; 611 } 612 diff = containsPath(this.npath2, that.npath); 613 if (diff >= 1 && recursive) { 614 return true; 615 } 616 if (diff == 1 && directory && !that.directory) { 617 return true; 618 } 619 } 620 621 return false; 622 } else { 623 if (this.directory) { 624 if (this.recursive) { 625 // make sure that.path is longer then path so 626 // something like /foo/- does not imply /foo 627 if (that.directory) { 628 return (that.cpath.length() >= this.cpath.length()) && 629 that.cpath.startsWith(this.cpath); 630 } else { 631 return ((that.cpath.length() > this.cpath.length()) && 632 that.cpath.startsWith(this.cpath)); 633 } 634 } else { 635 if (that.directory) { 636 // if the permission passed in is a directory 637 // specification, make sure that a non-recursive 638 // permission (i.e., this object) can't imply a recursive 639 // permission. 640 if (that.recursive) 641 return false; 642 else 643 return (this.cpath.equals(that.cpath)); 644 } else { 645 int last = that.cpath.lastIndexOf(File.separatorChar); 646 if (last == -1) 647 return false; 648 else { 649 // this.cpath.equals(that.cpath.substring(0, last+1)); 650 // Use regionMatches to avoid creating new string 651 return (this.cpath.length() == (last + 1)) && 652 this.cpath.regionMatches(0, that.cpath, 0, last + 1); 653 } 654 } 655 } 656 } else if (that.directory) { 657 // if this is NOT recursive/wildcarded, 658 // do not let it imply a recursive/wildcarded permission 659 return false; 660 } else { 661 return (this.cpath.equals(that.cpath)); 662 } 663 } 664 } 665 666 /** 667 * Returns the depth between an outer path p1 and an inner path p2. -1 668 * is returned if 669 * 670 * - p1 does not contains p2. 671 * - this is not decidable. For example, p1="../x", p2="y". 672 * - the depth is not decidable. For example, p1="/", p2="x". 673 * 674 * This method can return 2 if the depth is greater than 2. 675 * 676 * @param p1 the expected outer path, normalized 677 * @param p2 the expected inner path, normalized 678 * @return the depth in between 679 */ 680 private static int containsPath(Path p1, Path p2) { 681 682 // Two paths must have the same root. For example, 683 // there is no contains relation between any two of 684 // "/x", "x", "C:/x", "C:x", and "//host/share/x". 685 if (!Objects.equals(p1.getRoot(), p2.getRoot())) { 686 return -1; 687 } 688 689 // Empty path (i.e. "." or "") is a strange beast, 690 // because its getNameCount()==1 but getName(0) is null. 691 // It's better to deal with it separately. 692 if (p1.equals(EMPTY_PATH)) { 693 if (p2.equals(EMPTY_PATH)) { 694 return 0; 695 } else if (p2.getName(0).equals(DOTDOT_PATH)) { 696 // "." contains p2 iff p2 has no "..". Since 697 // a normalized path can only have 0 or more 698 // ".." at the beginning. We only need to look 699 // at the head. 700 return -1; 701 } else { 702 // and the distance is p2's name count. i.e. 703 // 3 between "." and "a/b/c". 704 return p2.getNameCount(); 705 } 706 } else if (p2.equals(EMPTY_PATH)) { 707 int c1 = p1.getNameCount(); 708 if (!p1.getName(c1 - 1).equals(DOTDOT_PATH)) { 709 // "." is inside p1 iff p1 is 1 or more "..". 710 // For the same reason above, we only need to 711 // look at the tail. 712 return -1; 713 } 714 // and the distance is the count of ".." 715 return c1; 716 } 717 718 // Good. No more empty paths. 719 720 // Common heads are removed 721 722 int c1 = p1.getNameCount(); 723 int c2 = p2.getNameCount(); 724 725 int n = Math.min(c1, c2); 726 int i = 0; 727 while (i < n) { 728 if (!p1.getName(i).equals(p2.getName(i))) 729 break; 730 i++; 731 } 732 733 // for p1 containing p2, p1 must be 0-or-more "..", 734 // and p2 cannot have "..". For the same reason, we only 735 // check tail of p1 and head of p2. 736 if (i < c1 && !p1.getName(c1 - 1).equals(DOTDOT_PATH)) { 737 return -1; 738 } 739 740 if (i < c2 && p2.getName(i).equals(DOTDOT_PATH)) { 741 return -1; 742 } 743 744 // and the distance is the name counts added (after removing 745 // the common heads). 746 747 // For example: p1 = "../../..", p2 = "../a". 748 // After removing the common heads, they become "../.." and "a", 749 // and the distance is (3-1)+(2-1) = 3. 750 return c1 - i + c2 - i; 751 } 752 753 /** 754 * Checks two FilePermission objects for equality. Checks that <i>obj</i> is 755 * a FilePermission, and has the same pathname and actions as this object. 756 * 757 * @implNote More specifically, two pathnames are the same if and only if 758 * they have the same wildcard flag and their {@code cpath} 759 * (if {@code jdk.io.permissionsUseCanonicalPath} is {@code true}) or 760 * {@code npath} (if {@code jdk.io.permissionsUseCanonicalPath} 761 * is {@code false}) are equal. Or they are both {@literal "<<ALL FILES>>"}. 762 * <p> 763 * When {@code jdk.io.permissionsUseCanonicalPath} is {@code false}, an 764 * invalid {@code FilePermission} does not equal to any object except 765 * for itself, even if they are created using the same invalid path. 766 * 767 * @param obj the object we are testing for equality with this object. 768 * @return <code>true</code> if obj is a FilePermission, and has the same 769 * pathname and actions as this FilePermission object, 770 * <code>false</code> otherwise. 771 */ 772 @Override 773 public boolean equals(Object obj) { 774 if (obj == this) 775 return true; 776 777 if (! (obj instanceof FilePermission)) 778 return false; 779 780 FilePermission that = (FilePermission) obj; 781 782 if (FilePermCompat.nb) { 783 if (this.invalid || that.invalid) { 784 return false; 785 } 786 return (this.mask == that.mask) && 787 (this.allFiles == that.allFiles) && 788 this.npath.equals(that.npath) && 789 Objects.equals(npath2, that.npath2) && 790 (this.directory == that.directory) && 791 (this.recursive == that.recursive); 792 } else { 793 return (this.mask == that.mask) && 794 this.cpath.equals(that.cpath) && 795 (this.directory == that.directory) && 796 (this.recursive == that.recursive); 797 } 798 } 799 800 /** 801 * Returns the hash code value for this object. 802 * 803 * @return a hash code value for this object. 804 */ 805 @Override 806 public int hashCode() { 807 if (FilePermCompat.nb) { 808 return Objects.hash( 809 mask, allFiles, directory, recursive, npath, npath2, invalid); 810 } else { 811 return 0; 812 } 813 } 814 815 /** 816 * Converts an actions String to an actions mask. 817 * 818 * @param actions the action string. 819 * @return the actions mask. 820 */ 821 private static int getMask(String actions) { 822 int mask = NONE; 823 824 // Null action valid? 825 if (actions == null) { 826 return mask; 827 } 828 829 // Use object identity comparison against known-interned strings for 830 // performance benefit (these values are used heavily within the JDK). 831 if (actions == SecurityConstants.FILE_READ_ACTION) { 832 return READ; 833 } else if (actions == SecurityConstants.FILE_WRITE_ACTION) { 834 return WRITE; 835 } else if (actions == SecurityConstants.FILE_EXECUTE_ACTION) { 836 return EXECUTE; 837 } else if (actions == SecurityConstants.FILE_DELETE_ACTION) { 838 return DELETE; 839 } else if (actions == SecurityConstants.FILE_READLINK_ACTION) { 840 return READLINK; 841 } 842 843 char[] a = actions.toCharArray(); 844 845 int i = a.length - 1; 846 if (i < 0) 847 return mask; 848 849 while (i != -1) { 850 char c; 851 852 // skip whitespace 853 while ((i!=-1) && ((c = a[i]) == ' ' || 854 c == '\r' || 855 c == '\n' || 856 c == '\f' || 857 c == '\t')) 858 i--; 859 860 // check for the known strings 861 int matchlen; 862 863 if (i >= 3 && (a[i-3] == 'r' || a[i-3] == 'R') && 864 (a[i-2] == 'e' || a[i-2] == 'E') && 865 (a[i-1] == 'a' || a[i-1] == 'A') && 866 (a[i] == 'd' || a[i] == 'D')) 867 { 868 matchlen = 4; 869 mask |= READ; 870 871 } else if (i >= 4 && (a[i-4] == 'w' || a[i-4] == 'W') && 872 (a[i-3] == 'r' || a[i-3] == 'R') && 873 (a[i-2] == 'i' || a[i-2] == 'I') && 874 (a[i-1] == 't' || a[i-1] == 'T') && 875 (a[i] == 'e' || a[i] == 'E')) 876 { 877 matchlen = 5; 878 mask |= WRITE; 879 880 } else if (i >= 6 && (a[i-6] == 'e' || a[i-6] == 'E') && 881 (a[i-5] == 'x' || a[i-5] == 'X') && 882 (a[i-4] == 'e' || a[i-4] == 'E') && 883 (a[i-3] == 'c' || a[i-3] == 'C') && 884 (a[i-2] == 'u' || a[i-2] == 'U') && 885 (a[i-1] == 't' || a[i-1] == 'T') && 886 (a[i] == 'e' || a[i] == 'E')) 887 { 888 matchlen = 7; 889 mask |= EXECUTE; 890 891 } else if (i >= 5 && (a[i-5] == 'd' || a[i-5] == 'D') && 892 (a[i-4] == 'e' || a[i-4] == 'E') && 893 (a[i-3] == 'l' || a[i-3] == 'L') && 894 (a[i-2] == 'e' || a[i-2] == 'E') && 895 (a[i-1] == 't' || a[i-1] == 'T') && 896 (a[i] == 'e' || a[i] == 'E')) 897 { 898 matchlen = 6; 899 mask |= DELETE; 900 901 } else if (i >= 7 && (a[i-7] == 'r' || a[i-7] == 'R') && 902 (a[i-6] == 'e' || a[i-6] == 'E') && 903 (a[i-5] == 'a' || a[i-5] == 'A') && 904 (a[i-4] == 'd' || a[i-4] == 'D') && 905 (a[i-3] == 'l' || a[i-3] == 'L') && 906 (a[i-2] == 'i' || a[i-2] == 'I') && 907 (a[i-1] == 'n' || a[i-1] == 'N') && 908 (a[i] == 'k' || a[i] == 'K')) 909 { 910 matchlen = 8; 911 mask |= READLINK; 912 913 } else { 914 // parse error 915 throw new IllegalArgumentException( 916 "invalid permission: " + actions); 917 } 918 919 // make sure we didn't just match the tail of a word 920 // like "ackbarfaccept". Also, skip to the comma. 921 boolean seencomma = false; 922 while (i >= matchlen && !seencomma) { 923 switch(a[i-matchlen]) { 924 case ',': 925 seencomma = true; 926 break; 927 case ' ': case '\r': case '\n': 928 case '\f': case '\t': 929 break; 930 default: 931 throw new IllegalArgumentException( 932 "invalid permission: " + actions); 933 } 934 i--; 935 } 936 937 // point i at the location of the comma minus one (or -1). 938 i -= matchlen; 939 } 940 941 return mask; 942 } 943 944 /** 945 * Return the current action mask. Used by the FilePermissionCollection. 946 * 947 * @return the actions mask. 948 */ 949 int getMask() { 950 return mask; 951 } 952 953 /** 954 * Return the canonical string representation of the actions. 955 * Always returns present actions in the following order: 956 * read, write, execute, delete, readlink. 957 * 958 * @return the canonical string representation of the actions. 959 */ 960 private static String getActions(int mask) { 961 StringJoiner sj = new StringJoiner(","); 962 963 if ((mask & READ) == READ) { 964 sj.add("read"); 965 } 966 if ((mask & WRITE) == WRITE) { 967 sj.add("write"); 968 } 969 if ((mask & EXECUTE) == EXECUTE) { 970 sj.add("execute"); 971 } 972 if ((mask & DELETE) == DELETE) { 973 sj.add("delete"); 974 } 975 if ((mask & READLINK) == READLINK) { 976 sj.add("readlink"); 977 } 978 979 return sj.toString(); 980 } 981 982 /** 983 * Returns the "canonical string representation" of the actions. 984 * That is, this method always returns present actions in the following order: 985 * read, write, execute, delete, readlink. For example, if this FilePermission 986 * object allows both write and read actions, a call to <code>getActions</code> 987 * will return the string "read,write". 988 * 989 * @return the canonical string representation of the actions. 990 */ 991 @Override 992 public String getActions() { 993 if (actions == null) 994 actions = getActions(this.mask); 995 996 return actions; 997 } 998 999 /** 1000 * Returns a new PermissionCollection object for storing FilePermission 1001 * objects. 1002 * <p> 1003 * FilePermission objects must be stored in a manner that allows them 1004 * to be inserted into the collection in any order, but that also enables the 1005 * PermissionCollection <code>implies</code> 1006 * method to be implemented in an efficient (and consistent) manner. 1007 * 1008 * <p>For example, if you have two FilePermissions: 1009 * <OL> 1010 * <LI> <code>"/tmp/-", "read"</code> 1011 * <LI> <code>"/tmp/scratch/foo", "write"</code> 1012 * </OL> 1013 * 1014 * <p>and you are calling the <code>implies</code> method with the FilePermission: 1015 * 1016 * <pre> 1017 * "/tmp/scratch/foo", "read,write", 1018 * </pre> 1019 * 1020 * then the <code>implies</code> function must 1021 * take into account both the "/tmp/-" and "/tmp/scratch/foo" 1022 * permissions, so the effective permission is "read,write", 1023 * and <code>implies</code> returns true. The "implies" semantics for 1024 * FilePermissions are handled properly by the PermissionCollection object 1025 * returned by this <code>newPermissionCollection</code> method. 1026 * 1027 * @return a new PermissionCollection object suitable for storing 1028 * FilePermissions. 1029 */ 1030 @Override 1031 public PermissionCollection newPermissionCollection() { 1032 return new FilePermissionCollection(); 1033 } 1034 1035 /** 1036 * WriteObject is called to save the state of the FilePermission 1037 * to a stream. The actions are serialized, and the superclass 1038 * takes care of the name. 1039 */ 1040 private void writeObject(ObjectOutputStream s) 1041 throws IOException 1042 { 1043 // Write out the actions. The superclass takes care of the name 1044 // call getActions to make sure actions field is initialized 1045 if (actions == null) 1046 getActions(); 1047 s.defaultWriteObject(); 1048 } 1049 1050 /** 1051 * readObject is called to restore the state of the FilePermission from 1052 * a stream. 1053 */ 1054 private void readObject(ObjectInputStream s) 1055 throws IOException, ClassNotFoundException 1056 { 1057 // Read in the actions, then restore everything else by calling init. 1058 s.defaultReadObject(); 1059 init(getMask(actions)); 1060 } 1061 1062 /** 1063 * Create a cloned FilePermission with a different actions. 1064 * @param effective the new actions 1065 * @return a new object 1066 */ 1067 FilePermission withNewActions(int effective) { 1068 return new FilePermission(this.getName(), 1069 this, 1070 this.npath, 1071 this.npath2, 1072 effective, 1073 null); 1074 } 1075 } 1076 1077 /** 1078 * A FilePermissionCollection stores a set of FilePermission permissions. 1079 * FilePermission objects 1080 * must be stored in a manner that allows them to be inserted in any 1081 * order, but enable the implies function to evaluate the implies 1082 * method. 1083 * For example, if you have two FilePermissions: 1084 * <OL> 1085 * <LI> "/tmp/-", "read" 1086 * <LI> "/tmp/scratch/foo", "write" 1087 * </OL> 1088 * And you are calling the implies function with the FilePermission: 1089 * "/tmp/scratch/foo", "read,write", then the implies function must 1090 * take into account both the /tmp/- and /tmp/scratch/foo 1091 * permissions, so the effective permission is "read,write". 1092 * 1093 * @see java.security.Permission 1094 * @see java.security.Permissions 1095 * @see java.security.PermissionCollection 1096 * 1097 * 1098 * @author Marianne Mueller 1099 * @author Roland Schemers 1100 * 1101 * @serial include 1102 * 1103 */ 1104 1105 final class FilePermissionCollection extends PermissionCollection 1106 implements Serializable 1107 { 1108 // Not serialized; see serialization section at end of class 1109 private transient ConcurrentHashMap<String, Permission> perms; 1110 1111 /** 1112 * Create an empty FilePermissionCollection object. 1113 */ 1114 public FilePermissionCollection() { 1115 perms = new ConcurrentHashMap<>(); 1116 } 1117 1118 /** 1119 * Adds a permission to the FilePermissionCollection. The key for the hash is 1120 * permission.path. 1121 * 1122 * @param permission the Permission object to add. 1123 * 1124 * @exception IllegalArgumentException - if the permission is not a 1125 * FilePermission 1126 * 1127 * @exception SecurityException - if this FilePermissionCollection object 1128 * has been marked readonly 1129 */ 1130 @Override 1131 public void add(Permission permission) { 1132 if (! (permission instanceof FilePermission)) 1133 throw new IllegalArgumentException("invalid permission: "+ 1134 permission); 1135 if (isReadOnly()) 1136 throw new SecurityException( 1137 "attempt to add a Permission to a readonly PermissionCollection"); 1138 1139 FilePermission fp = (FilePermission)permission; 1140 1141 // Add permission to map if it is absent, or replace with new 1142 // permission if applicable. 1143 perms.merge(fp.getName(), fp, 1144 new java.util.function.BiFunction<>() { 1145 @Override 1146 public Permission apply(Permission existingVal, 1147 Permission newVal) { 1148 int oldMask = ((FilePermission)existingVal).getMask(); 1149 int newMask = ((FilePermission)newVal).getMask(); 1150 if (oldMask != newMask) { 1151 int effective = oldMask | newMask; 1152 if (effective == newMask) { 1153 return newVal; 1154 } 1155 if (effective != oldMask) { 1156 return ((FilePermission)newVal) 1157 .withNewActions(effective); 1158 } 1159 } 1160 return existingVal; 1161 } 1162 } 1163 ); 1164 } 1165 1166 /** 1167 * Check and see if this set of permissions implies the permissions 1168 * expressed in "permission". 1169 * 1170 * @param permission the Permission object to compare 1171 * 1172 * @return true if "permission" is a proper subset of a permission in 1173 * the set, false if not. 1174 */ 1175 @Override 1176 public boolean implies(Permission permission) { 1177 if (! (permission instanceof FilePermission)) 1178 return false; 1179 1180 FilePermission fperm = (FilePermission) permission; 1181 1182 int desired = fperm.getMask(); 1183 int effective = 0; 1184 int needed = desired; 1185 1186 for (Permission perm : perms.values()) { 1187 FilePermission fp = (FilePermission)perm; 1188 if (((needed & fp.getMask()) != 0) && fp.impliesIgnoreMask(fperm)) { 1189 effective |= fp.getMask(); 1190 if ((effective & desired) == desired) { 1191 return true; 1192 } 1193 needed = (desired ^ effective); 1194 } 1195 } 1196 return false; 1197 } 1198 1199 /** 1200 * Returns an enumeration of all the FilePermission objects in the 1201 * container. 1202 * 1203 * @return an enumeration of all the FilePermission objects. 1204 */ 1205 @Override 1206 public Enumeration<Permission> elements() { 1207 return perms.elements(); 1208 } 1209 1210 private static final long serialVersionUID = 2202956749081564585L; 1211 1212 // Need to maintain serialization interoperability with earlier releases, 1213 // which had the serializable field: 1214 // private Vector permissions; 1215 1216 /** 1217 * @serialField permissions java.util.Vector 1218 * A list of FilePermission objects. 1219 */ 1220 private static final ObjectStreamField[] serialPersistentFields = { 1221 new ObjectStreamField("permissions", Vector.class), 1222 }; 1223 1224 /** 1225 * @serialData "permissions" field (a Vector containing the FilePermissions). 1226 */ 1227 /* 1228 * Writes the contents of the perms field out as a Vector for 1229 * serialization compatibility with earlier releases. 1230 */ 1231 private void writeObject(ObjectOutputStream out) throws IOException { 1232 // Don't call out.defaultWriteObject() 1233 1234 // Write out Vector 1235 Vector<Permission> permissions = new Vector<>(perms.values()); 1236 1237 ObjectOutputStream.PutField pfields = out.putFields(); 1238 pfields.put("permissions", permissions); 1239 out.writeFields(); 1240 } 1241 1242 /* 1243 * Reads in a Vector of FilePermissions and saves them in the perms field. 1244 */ 1245 private void readObject(ObjectInputStream in) 1246 throws IOException, ClassNotFoundException 1247 { 1248 // Don't call defaultReadObject() 1249 1250 // Read in serialized fields 1251 ObjectInputStream.GetField gfields = in.readFields(); 1252 1253 // Get the one we want 1254 @SuppressWarnings("unchecked") 1255 Vector<Permission> permissions = (Vector<Permission>)gfields.get("permissions", null); 1256 perms = new ConcurrentHashMap<>(permissions.size()); 1257 for (Permission perm : permissions) { 1258 perms.put(perm.getName(), perm); 1259 } 1260 } 1261 }