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