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