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