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 "&lt;&lt;ALL FILES&gt;&gt;"
  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 "&lt;&lt;ALL FILES&gt;&gt;" 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      * "&lt;&lt;ALL FILES&gt;&gt;" 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 }