Path: blob/master/src/java.base/share/classes/java/io/FilePermission.java
41152 views
/*1* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.io;2627import java.nio.file.*;28import java.security.*;29import java.util.Enumeration;30import java.util.Objects;31import java.util.StringJoiner;32import java.util.Vector;33import java.util.concurrent.ConcurrentHashMap;3435import jdk.internal.access.JavaIOFilePermissionAccess;36import jdk.internal.access.SharedSecrets;37import sun.nio.fs.DefaultFileSystemProvider;38import sun.security.action.GetPropertyAction;39import sun.security.util.FilePermCompat;40import sun.security.util.SecurityConstants;4142/**43* This class represents access to a file or directory. A FilePermission consists44* of a pathname and a set of actions valid for that pathname.45* <P>46* Pathname is the pathname of the file or directory granted the specified47* actions. A pathname that ends in "/*" (where "/" is48* the file separator character, {@code File.separatorChar}) indicates49* all the files and directories contained in that directory. A pathname50* that ends with "/-" indicates (recursively) all files51* and subdirectories contained in that directory. Such a pathname is called52* a wildcard pathname. Otherwise, it's a simple pathname.53* <P>54* A pathname consisting of the special token {@literal "<<ALL FILES>>"}55* matches <b>any</b> file.56* <P>57* Note: A pathname consisting of a single "*" indicates all the files58* in the current directory, while a pathname consisting of a single "-"59* indicates all the files in the current directory and60* (recursively) all files and subdirectories contained in the current61* directory.62* <P>63* The actions to be granted are passed to the constructor in a string containing64* a list of one or more comma-separated keywords. The possible keywords are65* "read", "write", "execute", "delete", and "readlink". Their meaning is66* defined as follows:67*68* <DL>69* <DT> read <DD> read permission70* <DT> write <DD> write permission71* <DT> execute72* <DD> execute permission. Allows {@code Runtime.exec} to73* be called. Corresponds to {@code SecurityManager.checkExec}.74* <DT> delete75* <DD> delete permission. Allows {@code File.delete} to76* be called. Corresponds to {@code SecurityManager.checkDelete}.77* <DT> readlink78* <DD> read link permission. Allows the target of a79* <a href="../nio/file/package-summary.html#links">symbolic link</a>80* to be read by invoking the {@link java.nio.file.Files#readSymbolicLink81* readSymbolicLink } method.82* </DL>83* <P>84* The actions string is converted to lowercase before processing.85* <P>86* Be careful when granting FilePermissions. Think about the implications87* of granting read and especially write access to various files and88* directories. The {@literal "<<ALL FILES>>"} permission with write action is89* especially dangerous. This grants permission to write to the entire90* file system. One thing this effectively allows is replacement of the91* system binary, including the JVM runtime environment.92* <P>93* Please note: Code can always read a file from the same94* directory it's in (or a subdirectory of that directory); it does not95* need explicit permission to do so.96*97* @see java.security.Permission98* @see java.security.Permissions99* @see java.security.PermissionCollection100*101*102* @author Marianne Mueller103* @author Roland Schemers104* @since 1.2105*106* @serial exclude107*/108109public final class FilePermission extends Permission implements Serializable {110111/**112* Execute action.113*/114private static final int EXECUTE = 0x1;115/**116* Write action.117*/118private static final int WRITE = 0x2;119/**120* Read action.121*/122private static final int READ = 0x4;123/**124* Delete action.125*/126private static final int DELETE = 0x8;127/**128* Read link action.129*/130private static final int READLINK = 0x10;131132/**133* All actions (read,write,execute,delete,readlink)134*/135private static final int ALL = READ|WRITE|EXECUTE|DELETE|READLINK;136/**137* No actions.138*/139private static final int NONE = 0x0;140141// the actions mask142private transient int mask;143144// does path indicate a directory? (wildcard or recursive)145private transient boolean directory;146147// is it a recursive directory specification?148private transient boolean recursive;149150/**151* the actions string.152*153* @serial154*/155private String actions; // Left null as long as possible, then156// created and re-used in the getAction function.157158// canonicalized dir path. used by the "old" behavior (nb == false).159// In the case of directories, it is the name "/blah/*" or "/blah/-"160// without the last character (the "*" or "-").161162private transient String cpath;163164// Following fields used by the "new" behavior (nb == true), in which165// input path is not canonicalized. For compatibility (so that granting166// FilePermission on "x" allows reading "`pwd`/x", an alternative path167// can be added so that both can be used in an implies() check. Please note168// the alternative path only deals with absolute/relative path, and does169// not deal with symlink/target.170171private transient Path npath; // normalized dir path.172private transient Path npath2; // alternative normalized dir path.173private transient boolean allFiles; // whether this is <<ALL FILES>>174private transient boolean invalid; // whether input path is invalid175176// static Strings used by init(int mask)177private static final char RECURSIVE_CHAR = '-';178private static final char WILD_CHAR = '*';179180// public String toString() {181// StringBuffer sb = new StringBuffer();182// sb.append("*** FilePermission on " + getName() + " ***");183// for (Field f : FilePermission.class.getDeclaredFields()) {184// if (!Modifier.isStatic(f.getModifiers())) {185// try {186// sb.append(f.getName() + " = " + f.get(this));187// } catch (Exception e) {188// sb.append(f.getName() + " = " + e.toString());189// }190// sb.append('\n');191// }192// }193// sb.append("***\n");194// return sb.toString();195// }196197@java.io.Serial198private static final long serialVersionUID = 7930732926638008763L;199200/**201* Use the platform's default file system to avoid recursive initialization202* issues when the VM is configured to use a custom file system provider.203*/204private static final java.nio.file.FileSystem builtInFS =205DefaultFileSystemProvider.theFileSystem();206207private static final Path here = builtInFS.getPath(208GetPropertyAction.privilegedGetProperty("user.dir"));209210private static final Path EMPTY_PATH = builtInFS.getPath("");211private static final Path DASH_PATH = builtInFS.getPath("-");212private static final Path DOTDOT_PATH = builtInFS.getPath("..");213214/**215* A private constructor that clones some and updates some,216* always with a different name.217* @param input218*/219private FilePermission(String name,220FilePermission input,221Path npath,222Path npath2,223int mask,224String actions) {225super(name);226// Customizables227this.npath = npath;228this.npath2 = npath2;229this.actions = actions;230this.mask = mask;231// Cloneds232this.allFiles = input.allFiles;233this.invalid = input.invalid;234this.recursive = input.recursive;235this.directory = input.directory;236this.cpath = input.cpath;237}238239/**240* Returns the alternative path as a Path object, i.e. absolute path241* for a relative one, or vice versa.242*243* @param in a real path w/o "-" or "*" at the end, and not <<ALL FILES>>.244* @return the alternative path, or null if cannot find one.245*/246private static Path altPath(Path in) {247try {248if (!in.isAbsolute()) {249return here.resolve(in).normalize();250} else {251return here.relativize(in).normalize();252}253} catch (IllegalArgumentException e) {254return null;255}256}257258static {259SharedSecrets.setJavaIOFilePermissionAccess(260/**261* Creates FilePermission objects with special internals.262* See {@link FilePermCompat#newPermPlusAltPath(Permission)} and263* {@link FilePermCompat#newPermUsingAltPath(Permission)}.264*/265new JavaIOFilePermissionAccess() {266public FilePermission newPermPlusAltPath(FilePermission input) {267if (!input.invalid && input.npath2 == null && !input.allFiles) {268Path npath2 = altPath(input.npath);269if (npath2 != null) {270// Please note the name of the new permission is271// different than the original so that when one is272// added to a FilePermissionCollection it will not273// be merged with the original one.274return new FilePermission(input.getName() + "#plus",275input,276input.npath,277npath2,278input.mask,279input.actions);280}281}282return input;283}284public FilePermission newPermUsingAltPath(FilePermission input) {285if (!input.invalid && !input.allFiles) {286Path npath2 = altPath(input.npath);287if (npath2 != null) {288// New name, see above.289return new FilePermission(input.getName() + "#using",290input,291npath2,292null,293input.mask,294input.actions);295}296}297return null;298}299}300);301}302303/**304* initialize a FilePermission object. Common to all constructors.305* Also called during de-serialization.306*307* @param mask the actions mask to use.308*309*/310@SuppressWarnings("removal")311private void init(int mask) {312if ((mask & ALL) != mask)313throw new IllegalArgumentException("invalid actions mask");314315if (mask == NONE)316throw new IllegalArgumentException("invalid actions mask");317318if (FilePermCompat.nb) {319String name = getName();320321if (name == null)322throw new NullPointerException("name can't be null");323324this.mask = mask;325326if (name.equals("<<ALL FILES>>")) {327allFiles = true;328npath = EMPTY_PATH;329// other fields remain default330return;331}332333boolean rememberStar = false;334if (name.endsWith("*")) {335rememberStar = true;336recursive = false;337name = name.substring(0, name.length()-1) + "-";338}339340try {341// new File() can "normalize" some name, for example, "/C:/X" on342// Windows. Some JDK codes generate such illegal names.343npath = builtInFS.getPath(new File(name).getPath())344.normalize();345// lastName should always be non-null now346Path lastName = npath.getFileName();347if (lastName != null && lastName.equals(DASH_PATH)) {348directory = true;349recursive = !rememberStar;350npath = npath.getParent();351}352if (npath == null) {353npath = EMPTY_PATH;354}355invalid = false;356} catch (InvalidPathException ipe) {357// Still invalid. For compatibility reason, accept it358// but make this permission useless.359npath = builtInFS.getPath("-u-s-e-l-e-s-s-");360invalid = true;361}362363} else {364if ((cpath = getName()) == null)365throw new NullPointerException("name can't be null");366367this.mask = mask;368369if (cpath.equals("<<ALL FILES>>")) {370allFiles = true;371directory = true;372recursive = true;373cpath = "";374return;375}376377// Validate path by platform's default file system378try {379String name = cpath.endsWith("*") ? cpath.substring(0, cpath.length() - 1) + "-" : cpath;380builtInFS.getPath(new File(name).getPath());381} catch (InvalidPathException ipe) {382invalid = true;383return;384}385386// store only the canonical cpath if possible387cpath = AccessController.doPrivileged(new PrivilegedAction<>() {388public String run() {389try {390String path = cpath;391if (cpath.endsWith("*")) {392// call getCanonicalPath with a path with wildcard character393// replaced to avoid calling it with paths that are394// intended to match all entries in a directory395path = path.substring(0, path.length() - 1) + "-";396path = new File(path).getCanonicalPath();397return path.substring(0, path.length() - 1) + "*";398} else {399return new File(path).getCanonicalPath();400}401} catch (IOException ioe) {402return cpath;403}404}405});406407int len = cpath.length();408char last = ((len > 0) ? cpath.charAt(len - 1) : 0);409410if (last == RECURSIVE_CHAR &&411cpath.charAt(len - 2) == File.separatorChar) {412directory = true;413recursive = true;414cpath = cpath.substring(0, --len);415} else if (last == WILD_CHAR &&416cpath.charAt(len - 2) == File.separatorChar) {417directory = true;418//recursive = false;419cpath = cpath.substring(0, --len);420} else {421// overkill since they are initialized to false, but422// commented out here to remind us...423//directory = false;424//recursive = false;425}426427// XXX: at this point the path should be absolute. die if it isn't?428}429}430431/**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 the435* file or directory. Possible actions are436* "read", "write", "execute", "delete", and "readlink".437*438* <p>A pathname that ends in "/*" (where "/" is439* 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 and442* subdirectories contained in that directory. The special pathname443* {@literal "<<ALL FILES>>"} matches any file.444*445* <p>A pathname consisting of a single "*" indicates all the files446* in the current directory, while a pathname consisting of a single "-"447* indicates all the files in the current directory and448* (recursively) all files and subdirectories contained in the current449* directory.450*451* <p>A pathname containing an empty string represents an empty path.452*453* @implNote In this implementation, the454* {@systemProperty jdk.io.permissionsUseCanonicalPath} system property455* 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 Windows460* DOS-style 8.3 path is expanded to a long path, and a symbolic link is461* 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 is466* 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 wildcard471* {@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 specified485* possible actions486*/487public FilePermission(String path, String actions) {488super(path);489init(getMask(actions));490}491492/**493* Creates a new FilePermission object using an action mask.494* More efficient than the FilePermission(String, String) constructor.495* Can be used from within496* code that needs to create a FilePermission object to pass into the497* {@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 method503FilePermission(String path, int mask) {504super(path);505init(mask);506}507508/**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 this515* object's actions, and516* <li> <i>p</i>'s pathname is implied by this object's517* pathname. For example, "/tmp/*" implies "/tmp/foo", since518* "/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 pathname523* if and only if they are equal. A simple pathname never implies524* a wildcard pathname. A wildcard pathname implies another wildcard525* pathname if and only if all simple pathnames implied by the latter526* are implied by the former. A wildcard pathname implies a simple527* pathname if and only if528* <ul>529* <li>if the wildcard flag is "*", the simple pathname's path530* must be right inside the wildcard pathname's path.531* <li>if the wildcard flag is "-", the simple pathname's path532* 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, implies537* {@literal "<<ALL FILES>>"}.538*539* @implNote540* If {@code jdk.io.permissionsUseCanonicalPath} is {@code true}, a541* simple {@code cpath} is inside a wildcard {@code cpath} if and only if542* after removing the base name (the last name in the pathname's name543* 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}, a548* simple {@code npath} is inside a wildcard {@code npath} if and only if549* {@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 a552* 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 for555* itself. An invalid {@code FilePermission} is not implied by any object556* except for itself or a {@code FilePermission} on557* {@literal "<<ALL FILES>>"} whose actions is a superset of this558* 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 not564* {@code null} and is implied by this object,565* {@code false} otherwise.566*/567@Override568public boolean implies(Permission p) {569if (!(p instanceof FilePermission that))570return false;571572// we get the effective mask. i.e., the "and" of this and that.573// They must be equal to that.mask for implies to return true.574575return ((this.mask & that.mask) == that.mask) && impliesIgnoreMask(that);576}577578/**579* Checks if the Permission's actions are a proper subset of the580* this object's actions. Returns the effective mask iff the581* this FilePermission's path also implies that FilePermission's path.582*583* @param that the FilePermission to check against.584* @return the effective mask585*/586boolean impliesIgnoreMask(FilePermission that) {587if (this == that) {588return true;589}590if (allFiles) {591return true;592}593if (this.invalid || that.invalid) {594return false;595}596if (that.allFiles) {597return false;598}599if (FilePermCompat.nb) {600// Left at least same level of wildness as right601if ((this.recursive && that.recursive) != that.recursive602|| (this.directory && that.directory) != that.directory) {603return false;604}605// Same npath is good as long as both or neither are directories606if (this.npath.equals(that.npath)607&& this.directory == that.directory) {608return true;609}610int diff = containsPath(this.npath, that.npath);611// Right inside left is good if recursive612if (diff >= 1 && recursive) {613return true;614}615// Right right inside left if it is element in set616if (diff == 1 && directory && !that.directory) {617return true;618}619620// Hack: if a npath2 field exists, apply the same checks621// on it as a fallback.622if (this.npath2 != null) {623if (this.npath2.equals(that.npath)624&& this.directory == that.directory) {625return true;626}627diff = containsPath(this.npath2, that.npath);628if (diff >= 1 && recursive) {629return true;630}631if (diff == 1 && directory && !that.directory) {632return true;633}634}635636return false;637} else {638if (this.directory) {639if (this.recursive) {640// make sure that.path is longer then path so641// something like /foo/- does not imply /foo642if (that.directory) {643return (that.cpath.length() >= this.cpath.length()) &&644that.cpath.startsWith(this.cpath);645} else {646return ((that.cpath.length() > this.cpath.length()) &&647that.cpath.startsWith(this.cpath));648}649} else {650if (that.directory) {651// if the permission passed in is a directory652// specification, make sure that a non-recursive653// permission (i.e., this object) can't imply a recursive654// permission.655if (that.recursive)656return false;657else658return (this.cpath.equals(that.cpath));659} else {660int last = that.cpath.lastIndexOf(File.separatorChar);661if (last == -1)662return false;663else {664// this.cpath.equals(that.cpath.substring(0, last+1));665// Use regionMatches to avoid creating new string666return (this.cpath.length() == (last + 1)) &&667this.cpath.regionMatches(0, that.cpath, 0, last + 1);668}669}670}671} else if (that.directory) {672// if this is NOT recursive/wildcarded,673// do not let it imply a recursive/wildcarded permission674return false;675} else {676return (this.cpath.equals(that.cpath));677}678}679}680681/**682* Returns the depth between an outer path p1 and an inner path p2. -1683* is returned if684*685* - p1 does not contains p2.686* - this is not decidable. For example, p1="../x", p2="y".687* - the depth is not decidable. For example, p1="/", p2="x".688*689* This method can return 2 if the depth is greater than 2.690*691* @param p1 the expected outer path, normalized692* @param p2 the expected inner path, normalized693* @return the depth in between694*/695private static int containsPath(Path p1, Path p2) {696697// Two paths must have the same root. For example,698// there is no contains relation between any two of699// "/x", "x", "C:/x", "C:x", and "//host/share/x".700if (!Objects.equals(p1.getRoot(), p2.getRoot())) {701return -1;702}703704// Empty path (i.e. "." or "") is a strange beast,705// because its getNameCount()==1 but getName(0) is null.706// It's better to deal with it separately.707if (p1.equals(EMPTY_PATH)) {708if (p2.equals(EMPTY_PATH)) {709return 0;710} else if (p2.getName(0).equals(DOTDOT_PATH)) {711// "." contains p2 iff p2 has no "..". Since712// a normalized path can only have 0 or more713// ".." at the beginning. We only need to look714// at the head.715return -1;716} else {717// and the distance is p2's name count. i.e.718// 3 between "." and "a/b/c".719return p2.getNameCount();720}721} else if (p2.equals(EMPTY_PATH)) {722int c1 = p1.getNameCount();723if (!p1.getName(c1 - 1).equals(DOTDOT_PATH)) {724// "." is inside p1 iff p1 is 1 or more "..".725// For the same reason above, we only need to726// look at the tail.727return -1;728}729// and the distance is the count of ".."730return c1;731}732733// Good. No more empty paths.734735// Common heads are removed736737int c1 = p1.getNameCount();738int c2 = p2.getNameCount();739740int n = Math.min(c1, c2);741int i = 0;742while (i < n) {743if (!p1.getName(i).equals(p2.getName(i)))744break;745i++;746}747748// for p1 containing p2, p1 must be 0-or-more "..",749// and p2 cannot have "..". For the same reason, we only750// check tail of p1 and head of p2.751if (i < c1 && !p1.getName(c1 - 1).equals(DOTDOT_PATH)) {752return -1;753}754755if (i < c2 && p2.getName(i).equals(DOTDOT_PATH)) {756return -1;757}758759// and the distance is the name counts added (after removing760// the common heads).761762// For example: p1 = "../../..", p2 = "../a".763// After removing the common heads, they become "../.." and "a",764// and the distance is (3-1)+(2-1) = 3.765return c1 - i + c2 - i;766}767768/**769* Checks two FilePermission objects for equality. Checks that <i>obj</i> is770* a FilePermission, and has the same pathname and actions as this object.771*772* @implNote More specifically, two pathnames are the same if and only if773* they have the same wildcard flag and their {@code cpath}774* (if {@code jdk.io.permissionsUseCanonicalPath} is {@code true}) or775* {@code npath} (if {@code jdk.io.permissionsUseCanonicalPath}776* is {@code false}) are equal. Or they are both {@literal "<<ALL FILES>>"}.777* <p>778* When {@code jdk.io.permissionsUseCanonicalPath} is {@code false}, an779* invalid {@code FilePermission} does not equal to any object except780* for itself, even if they are created using the same invalid path.781*782* @param obj the object we are testing for equality with this object.783* @return {@code true} if obj is a FilePermission, and has the same784* pathname and actions as this FilePermission object,785* {@code false} otherwise.786*/787@Override788public boolean equals(Object obj) {789if (obj == this)790return true;791792if (! (obj instanceof FilePermission that))793return false;794795if (this.invalid || that.invalid) {796return false;797}798if (FilePermCompat.nb) {799return (this.mask == that.mask) &&800(this.allFiles == that.allFiles) &&801this.npath.equals(that.npath) &&802Objects.equals(npath2, that.npath2) &&803(this.directory == that.directory) &&804(this.recursive == that.recursive);805} else {806return (this.mask == that.mask) &&807(this.allFiles == that.allFiles) &&808this.cpath.equals(that.cpath) &&809(this.directory == that.directory) &&810(this.recursive == that.recursive);811}812}813814/**815* Returns the hash code value for this object.816*817* @return a hash code value for this object.818*/819@Override820public int hashCode() {821if (FilePermCompat.nb) {822return Objects.hash(823mask, allFiles, directory, recursive, npath, npath2, invalid);824} else {825return 0;826}827}828829/**830* Converts an actions String to an actions mask.831*832* @param actions the action string.833* @return the actions mask.834*/835private static int getMask(String actions) {836int mask = NONE;837838// Null action valid?839if (actions == null) {840return mask;841}842843// Use object identity comparison against known-interned strings for844// performance benefit (these values are used heavily within the JDK).845if (actions == SecurityConstants.FILE_READ_ACTION) {846return READ;847} else if (actions == SecurityConstants.FILE_WRITE_ACTION) {848return WRITE;849} else if (actions == SecurityConstants.FILE_EXECUTE_ACTION) {850return EXECUTE;851} else if (actions == SecurityConstants.FILE_DELETE_ACTION) {852return DELETE;853} else if (actions == SecurityConstants.FILE_READLINK_ACTION) {854return READLINK;855}856857char[] a = actions.toCharArray();858859int i = a.length - 1;860if (i < 0)861return mask;862863while (i != -1) {864char c;865866// skip whitespace867while ((i!=-1) && ((c = a[i]) == ' ' ||868c == '\r' ||869c == '\n' ||870c == '\f' ||871c == '\t'))872i--;873874// check for the known strings875int matchlen;876877if (i >= 3 && (a[i-3] == 'r' || a[i-3] == 'R') &&878(a[i-2] == 'e' || a[i-2] == 'E') &&879(a[i-1] == 'a' || a[i-1] == 'A') &&880(a[i] == 'd' || a[i] == 'D'))881{882matchlen = 4;883mask |= READ;884885} else if (i >= 4 && (a[i-4] == 'w' || a[i-4] == 'W') &&886(a[i-3] == 'r' || a[i-3] == 'R') &&887(a[i-2] == 'i' || a[i-2] == 'I') &&888(a[i-1] == 't' || a[i-1] == 'T') &&889(a[i] == 'e' || a[i] == 'E'))890{891matchlen = 5;892mask |= WRITE;893894} else if (i >= 6 && (a[i-6] == 'e' || a[i-6] == 'E') &&895(a[i-5] == 'x' || a[i-5] == 'X') &&896(a[i-4] == 'e' || a[i-4] == 'E') &&897(a[i-3] == 'c' || a[i-3] == 'C') &&898(a[i-2] == 'u' || a[i-2] == 'U') &&899(a[i-1] == 't' || a[i-1] == 'T') &&900(a[i] == 'e' || a[i] == 'E'))901{902matchlen = 7;903mask |= EXECUTE;904905} else if (i >= 5 && (a[i-5] == 'd' || a[i-5] == 'D') &&906(a[i-4] == 'e' || a[i-4] == 'E') &&907(a[i-3] == 'l' || a[i-3] == 'L') &&908(a[i-2] == 'e' || a[i-2] == 'E') &&909(a[i-1] == 't' || a[i-1] == 'T') &&910(a[i] == 'e' || a[i] == 'E'))911{912matchlen = 6;913mask |= DELETE;914915} else if (i >= 7 && (a[i-7] == 'r' || a[i-7] == 'R') &&916(a[i-6] == 'e' || a[i-6] == 'E') &&917(a[i-5] == 'a' || a[i-5] == 'A') &&918(a[i-4] == 'd' || a[i-4] == 'D') &&919(a[i-3] == 'l' || a[i-3] == 'L') &&920(a[i-2] == 'i' || a[i-2] == 'I') &&921(a[i-1] == 'n' || a[i-1] == 'N') &&922(a[i] == 'k' || a[i] == 'K'))923{924matchlen = 8;925mask |= READLINK;926927} else {928// parse error929throw new IllegalArgumentException(930"invalid permission: " + actions);931}932933// make sure we didn't just match the tail of a word934// like "ackbarfdelete". Also, skip to the comma.935boolean seencomma = false;936while (i >= matchlen && !seencomma) {937switch (c = a[i-matchlen]) {938case ' ': case '\r': case '\n':939case '\f': case '\t':940break;941default:942if (c == ',' && i > matchlen) {943seencomma = true;944break;945}946throw new IllegalArgumentException(947"invalid permission: " + actions);948}949i--;950}951952// point i at the location of the comma minus one (or -1).953i -= matchlen;954}955956return mask;957}958959/**960* Return the current action mask. Used by the FilePermissionCollection.961*962* @return the actions mask.963*/964int getMask() {965return mask;966}967968/**969* Return the canonical string representation of the actions.970* Always returns present actions in the following order:971* read, write, execute, delete, readlink.972*973* @return the canonical string representation of the actions.974*/975private static String getActions(int mask) {976StringJoiner sj = new StringJoiner(",");977978if ((mask & READ) == READ) {979sj.add("read");980}981if ((mask & WRITE) == WRITE) {982sj.add("write");983}984if ((mask & EXECUTE) == EXECUTE) {985sj.add("execute");986}987if ((mask & DELETE) == DELETE) {988sj.add("delete");989}990if ((mask & READLINK) == READLINK) {991sj.add("readlink");992}993994return sj.toString();995}996997/**998* Returns the "canonical string representation" of the actions.999* That is, this method always returns present actions in the following order:1000* read, write, execute, delete, readlink. For example, if this FilePermission1001* object allows both write and read actions, a call to {@code getActions}1002* will return the string "read,write".1003*1004* @return the canonical string representation of the actions.1005*/1006@Override1007public String getActions() {1008if (actions == null)1009actions = getActions(this.mask);10101011return actions;1012}10131014/**1015* Returns a new PermissionCollection object for storing FilePermission1016* objects.1017* <p>1018* FilePermission objects must be stored in a manner that allows them1019* to be inserted into the collection in any order, but that also enables the1020* PermissionCollection {@code implies}1021* method to be implemented in an efficient (and consistent) manner.1022*1023* <p>For example, if you have two FilePermissions:1024* <OL>1025* <LI> {@code "/tmp/-", "read"}1026* <LI> {@code "/tmp/scratch/foo", "write"}1027* </OL>1028*1029* <p>and you are calling the {@code implies} method with the FilePermission:1030*1031* <pre>1032* "/tmp/scratch/foo", "read,write",1033* </pre>1034*1035* then the {@code implies} function must1036* take into account both the "/tmp/-" and "/tmp/scratch/foo"1037* permissions, so the effective permission is "read,write",1038* and {@code implies} returns true. The "implies" semantics for1039* FilePermissions are handled properly by the PermissionCollection object1040* returned by this {@code newPermissionCollection} method.1041*1042* @return a new PermissionCollection object suitable for storing1043* FilePermissions.1044*/1045@Override1046public PermissionCollection newPermissionCollection() {1047return new FilePermissionCollection();1048}10491050/**1051* WriteObject is called to save the state of the FilePermission1052* to a stream. The actions are serialized, and the superclass1053* takes care of the name.1054*/1055@java.io.Serial1056private void writeObject(ObjectOutputStream s)1057throws IOException1058{1059// Write out the actions. The superclass takes care of the name1060// call getActions to make sure actions field is initialized1061if (actions == null)1062getActions();1063s.defaultWriteObject();1064}10651066/**1067* readObject is called to restore the state of the FilePermission from1068* a stream.1069*/1070@java.io.Serial1071private void readObject(ObjectInputStream s)1072throws IOException, ClassNotFoundException1073{1074// Read in the actions, then restore everything else by calling init.1075s.defaultReadObject();1076init(getMask(actions));1077}10781079/**1080* Create a cloned FilePermission with a different actions.1081* @param effective the new actions1082* @return a new object1083*/1084FilePermission withNewActions(int effective) {1085return new FilePermission(this.getName(),1086this,1087this.npath,1088this.npath2,1089effective,1090null);1091}1092}10931094/**1095* A FilePermissionCollection stores a set of FilePermission permissions.1096* FilePermission objects1097* must be stored in a manner that allows them to be inserted in any1098* order, but enable the implies function to evaluate the implies1099* method.1100* For example, if you have two FilePermissions:1101* <OL>1102* <LI> "/tmp/-", "read"1103* <LI> "/tmp/scratch/foo", "write"1104* </OL>1105* And you are calling the implies function with the FilePermission:1106* "/tmp/scratch/foo", "read,write", then the implies function must1107* take into account both the /tmp/- and /tmp/scratch/foo1108* permissions, so the effective permission is "read,write".1109*1110* @see java.security.Permission1111* @see java.security.Permissions1112* @see java.security.PermissionCollection1113*1114*1115* @author Marianne Mueller1116* @author Roland Schemers1117*1118* @serial include1119*1120*/11211122final class FilePermissionCollection extends PermissionCollection1123implements Serializable1124{1125// Not serialized; see serialization section at end of class1126private transient ConcurrentHashMap<String, Permission> perms;11271128/**1129* Create an empty FilePermissionCollection object.1130*/1131public FilePermissionCollection() {1132perms = new ConcurrentHashMap<>();1133}11341135/**1136* Adds a permission to the FilePermissionCollection. The key for the hash is1137* permission.path.1138*1139* @param permission the Permission object to add.1140*1141* @throws IllegalArgumentException if the permission is not a1142* FilePermission1143*1144* @throws SecurityException if this FilePermissionCollection object1145* has been marked readonly1146*/1147@Override1148public void add(Permission permission) {1149if (! (permission instanceof FilePermission fp))1150throw new IllegalArgumentException("invalid permission: "+1151permission);1152if (isReadOnly())1153throw new SecurityException(1154"attempt to add a Permission to a readonly PermissionCollection");11551156// Add permission to map if it is absent, or replace with new1157// permission if applicable.1158perms.merge(fp.getName(), fp,1159new java.util.function.BiFunction<>() {1160@Override1161public Permission apply(Permission existingVal,1162Permission newVal) {1163int oldMask = ((FilePermission)existingVal).getMask();1164int newMask = ((FilePermission)newVal).getMask();1165if (oldMask != newMask) {1166int effective = oldMask | newMask;1167if (effective == newMask) {1168return newVal;1169}1170if (effective != oldMask) {1171return ((FilePermission)newVal)1172.withNewActions(effective);1173}1174}1175return existingVal;1176}1177}1178);1179}11801181/**1182* Check and see if this set of permissions implies the permissions1183* expressed in "permission".1184*1185* @param permission the Permission object to compare1186*1187* @return true if "permission" is a proper subset of a permission in1188* the set, false if not.1189*/1190@Override1191public boolean implies(Permission permission) {1192if (! (permission instanceof FilePermission fperm))1193return false;11941195int desired = fperm.getMask();1196int effective = 0;1197int needed = desired;11981199for (Permission perm : perms.values()) {1200FilePermission fp = (FilePermission)perm;1201if (((needed & fp.getMask()) != 0) && fp.impliesIgnoreMask(fperm)) {1202effective |= fp.getMask();1203if ((effective & desired) == desired) {1204return true;1205}1206needed = (desired & ~effective);1207}1208}1209return false;1210}12111212/**1213* Returns an enumeration of all the FilePermission objects in the1214* container.1215*1216* @return an enumeration of all the FilePermission objects.1217*/1218@Override1219public Enumeration<Permission> elements() {1220return perms.elements();1221}12221223@java.io.Serial1224private static final long serialVersionUID = 2202956749081564585L;12251226// Need to maintain serialization interoperability with earlier releases,1227// which had the serializable field:1228// private Vector permissions;12291230/**1231* @serialField permissions java.util.Vector1232* A list of FilePermission objects.1233*/1234@java.io.Serial1235private static final ObjectStreamField[] serialPersistentFields = {1236new ObjectStreamField("permissions", Vector.class),1237};12381239/**1240* @serialData "permissions" field (a Vector containing the FilePermissions).1241*/1242/**1243* Writes the contents of the perms field out as a Vector for1244* serialization compatibility with earlier releases.1245*1246* @param out the {@code ObjectOutputStream} to which data is written1247* @throws IOException if an I/O error occurs1248*/1249@java.io.Serial1250private void writeObject(ObjectOutputStream out) throws IOException {1251// Don't call out.defaultWriteObject()12521253// Write out Vector1254Vector<Permission> permissions = new Vector<>(perms.values());12551256ObjectOutputStream.PutField pfields = out.putFields();1257pfields.put("permissions", permissions);1258out.writeFields();1259}12601261/**1262* Reads in a Vector of FilePermissions and saves them in the perms field.1263*1264* @param in the {@code ObjectInputStream} from which data is read1265* @throws IOException if an I/O error occurs1266* @throws ClassNotFoundException if a serialized class cannot be loaded1267*/1268@java.io.Serial1269private void readObject(ObjectInputStream in)1270throws IOException, ClassNotFoundException1271{1272// Don't call defaultReadObject()12731274// Read in serialized fields1275ObjectInputStream.GetField gfields = in.readFields();12761277// Get the one we want1278@SuppressWarnings("unchecked")1279Vector<Permission> permissions = (Vector<Permission>)gfields.get("permissions", null);1280perms = new ConcurrentHashMap<>(permissions.size());1281for (Permission perm : permissions) {1282perms.put(perm.getName(), perm);1283}1284}1285}128612871288