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