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