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