       1 /*
       2  * Copyright (c) 1997, 2020, 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      * @param  out the {@code ObjectOutputStream} to which data is written
    1255      * @throws IOException if an I/O error occurs
    1256      */
    1257     @java.io.Serial
    1258     private void writeObject(ObjectOutputStream out) throws IOException {
    1259         // Don't call out.defaultWriteObject()
    1260
    1261         // Write out Vector
    1262         Vector<Permission> permissions = new Vector<>(perms.values());
    1263
    1264         ObjectOutputStream.PutField pfields = out.putFields();
    1265         pfields.put("permissions", permissions);
    1266         out.writeFields();
    1267     }
    1268
    1269     /**
    1270      * Reads in a Vector of FilePermissions and saves them in the perms field.
    1271      *
    1272      * @param  in the {@code ObjectInputStream} from which data is read
    1273      * @throws IOException if an I/O error occurs
    1274      * @throws ClassNotFoundException if a serialized class cannot be loaded
    1275      */
    1276     @java.io.Serial
    1277     private void readObject(ObjectInputStream in)
    1278         throws IOException, ClassNotFoundException
    1279     {
    1280         // Don't call defaultReadObject()
    1281
    1282         // Read in serialized fields
    1283         ObjectInputStream.GetField gfields = in.readFields();
    1284
    1285         // Get the one we want
    1286         @SuppressWarnings("unchecked")
    1287         Vector<Permission> permissions = (Vector<Permission>)gfields.get("permissions", null);
    1288         perms = new ConcurrentHashMap<>(permissions.size());
    1289         for (Permission perm : permissions) {
    1290             perms.put(perm.getName(), perm);
    1291         }
    1292     }
    1293 }
    --- EOF ---