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 }