diff -r fd16c54261b3 -r 90ce3da70b43 jdk/src/share/classes/java/io/FilePermission.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/java/io/FilePermission.java Sat Dec 01 00:00:00 2007 +0000 @@ -0,0 +1,819 @@ +/* + * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package java.io; + +import java.security.*; +import java.util.Enumeration; +import java.util.List; +import java.util.ArrayList; +import java.util.StringTokenizer; +import java.util.Vector; +import java.util.Collections; +import java.io.ObjectStreamField; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.IOException; +import sun.security.util.SecurityConstants; + +/** + * This class represents access to a file or directory. A FilePermission consists + * of a pathname and a set of actions valid for that pathname. + *

+ * Pathname is the pathname of the file or directory granted the specified + * actions. A pathname that ends in "/*" (where "/" is + * the file separator character, File.separatorChar) indicates + * all the files and directories contained in that directory. A pathname + * that ends with "/-" indicates (recursively) all files + * and subdirectories contained in that directory. A pathname consisting of + * the special token "<<ALL FILES>>" matches any file. + *

+ * Note: A pathname consisting of a single "*" indicates all the files + * in the current directory, while a pathname consisting of a single "-" + * indicates all the files in the current directory and + * (recursively) all files and subdirectories contained in the current + * directory. + *

+ * The actions to be granted are passed to the constructor in a string containing + * a list of one or more comma-separated keywords. The possible keywords are + * "read", "write", "execute", and "delete". Their meaning is defined as follows: + *

+ *

+ *
read
read permission + *
write
write permission + *
execute + *
execute permission. Allows Runtime.exec to + * be called. Corresponds to SecurityManager.checkExec. + *
delete + *
delete permission. Allows File.delete to + * be called. Corresponds to SecurityManager.checkDelete. + *
+ *

+ * The actions string is converted to lowercase before processing. + *

+ * Be careful when granting FilePermissions. Think about the implications + * of granting read and especially write access to various files and + * directories. The "<<ALL FILES>>" permission with write action is + * especially dangerous. This grants permission to write to the entire + * file system. One thing this effectively allows is replacement of the + * system binary, including the JVM runtime environment. + * + *

Please note: Code can always read a file from the same + * directory it's in (or a subdirectory of that directory); it does not + * need explicit permission to do so. + * + * @see java.security.Permission + * @see java.security.Permissions + * @see java.security.PermissionCollection + * + * + * @author Marianne Mueller + * @author Roland Schemers + * @since 1.2 + * + * @serial exclude + */ + +public final class FilePermission extends Permission implements Serializable { + + /** + * Execute action. + */ + private final static int EXECUTE = 0x1; + /** + * Write action. + */ + private final static int WRITE = 0x2; + /** + * Read action. + */ + private final static int READ = 0x4; + /** + * Delete action. + */ + private final static int DELETE = 0x8; + + /** + * All actions (read,write,execute,delete) + */ + private final static int ALL = READ|WRITE|EXECUTE|DELETE; + /** + * No actions. + */ + private final static int NONE = 0x0; + + // the actions mask + private transient int mask; + + // does path indicate a directory? (wildcard or recursive) + private transient boolean directory; + + // is it a recursive directory specification? + private transient boolean recursive; + + /** + * the actions string. + * + * @serial + */ + private String actions; // Left null as long as possible, then + // created and re-used in the getAction function. + + // canonicalized dir path. In the case of + // directories, it is the name "/blah/*" or "/blah/-" without + // the last character (the "*" or "-"). + + private transient String cpath; + + // static Strings used by init(int mask) + private static final char RECURSIVE_CHAR = '-'; + private static final char WILD_CHAR = '*'; + +/* + public String toString() + { + StringBuffer sb = new StringBuffer(); + sb.append("***\n"); + sb.append("cpath = "+cpath+"\n"); + sb.append("mask = "+mask+"\n"); + sb.append("actions = "+getActions()+"\n"); + sb.append("directory = "+directory+"\n"); + sb.append("recursive = "+recursive+"\n"); + sb.append("***\n"); + return sb.toString(); + } +*/ + + private static final long serialVersionUID = 7930732926638008763L; + + /** + * initialize a FilePermission object. Common to all constructors. + * Also called during de-serialization. + * + * @param mask the actions mask to use. + * + */ + private void init(int mask) + { + + if ((mask & ALL) != mask) + throw new IllegalArgumentException("invalid actions mask"); + + if (mask == NONE) + throw new IllegalArgumentException("invalid actions mask"); + + if ((cpath = getName()) == null) + throw new NullPointerException("name can't be null"); + + this.mask = mask; + + if (cpath.equals("<>")) { + directory = true; + recursive = true; + cpath = ""; + return; + } + + // store only the canonical cpath if possible + cpath = AccessController.doPrivileged(new PrivilegedAction() { + public String run() { + try { + return sun.security.provider.PolicyFile.canonPath(cpath); + } catch (IOException ioe) { + return cpath; + } + } + }); + + int len = cpath.length(); + char last = ((len > 0) ? cpath.charAt(len - 1) : 0); + + if (last == RECURSIVE_CHAR && + cpath.charAt(len - 2) == File.separatorChar) { + directory = true; + recursive = true; + cpath = cpath.substring(0, --len); + } else if (last == WILD_CHAR && + cpath.charAt(len - 2) == File.separatorChar) { + directory = true; + //recursive = false; + cpath = cpath.substring(0, --len); + } else { + // overkill since they are initialized to false, but + // commented out here to remind us... + //directory = false; + //recursive = false; + } + + // XXX: at this point the path should be absolute. die if it isn't? + } + + /** + * Creates a new FilePermission object with the specified actions. + * path is the pathname of a file or directory, and actions + * contains a comma-separated list of the desired actions granted on the + * file or directory. Possible actions are + * "read", "write", "execute", and "delete". + * + *

A pathname that ends in "/*" (where "/" is + * the file separator character, File.separatorChar) + * indicates all the files and directories contained in that directory. + * A pathname that ends with "/-" indicates (recursively) all files and + * subdirectories contained in that directory. The special pathname + * "<<ALL FILES>>" matches any file. + * + *

A pathname consisting of a single "*" indicates all the files + * in the current directory, while a pathname consisting of a single "-" + * indicates all the files in the current directory and + * (recursively) all files and subdirectories contained in the current + * directory. + * + *

A pathname containing an empty string represents an empty path. + * + * @param path the pathname of the file/directory. + * @param actions the action string. + * + * @throws IllegalArgumentException + * If actions is null, empty or contains an action + * other than the specified possible actions. + */ + + public FilePermission(String path, String actions) + { + super(path); + init(getMask(actions)); + } + + /** + * Creates a new FilePermission object using an action mask. + * More efficient than the FilePermission(String, String) constructor. + * Can be used from within + * code that needs to create a FilePermission object to pass into the + * implies method. + * + * @param path the pathname of the file/directory. + * @param mask the action mask to use. + */ + + // package private for use by the FilePermissionCollection add method + FilePermission(String path, int mask) + { + super(path); + init(mask); + } + + /** + * Checks if this FilePermission object "implies" the specified permission. + *

+ * More specifically, this method returns true if:

+ *

+ * + * @param p the permission to check against. + * + * @return true if the specified permission is not + * null and is implied by this object, + * false otherwise. + */ + public boolean implies(Permission p) { + if (!(p instanceof FilePermission)) + return false; + + FilePermission that = (FilePermission) p; + + // we get the effective mask. i.e., the "and" of this and that. + // They must be equal to that.mask for implies to return true. + + return ((this.mask & that.mask) == that.mask) && impliesIgnoreMask(that); + } + + /** + * Checks if the Permission's actions are a proper subset of the + * this object's actions. Returns the effective mask iff the + * this FilePermission's path also implies that FilePermission's path. + * + * @param that the FilePermission to check against. + * @param exact return immediately if the masks are not equal + * @return the effective mask + */ + boolean impliesIgnoreMask(FilePermission that) { + if (this.directory) { + if (this.recursive) { + // make sure that.path is longer then path so + // something like /foo/- does not imply /foo + if (that.directory) { + return (that.cpath.length() >= this.cpath.length()) && + that.cpath.startsWith(this.cpath); + } else { + return ((that.cpath.length() > this.cpath.length()) && + that.cpath.startsWith(this.cpath)); + } + } else { + if (that.directory) { + // if the permission passed in is a directory + // specification, make sure that a non-recursive + // permission (i.e., this object) can't imply a recursive + // permission. + if (that.recursive) + return false; + else + return (this.cpath.equals(that.cpath)); + } else { + int last = that.cpath.lastIndexOf(File.separatorChar); + if (last == -1) + return false; + else { + // this.cpath.equals(that.cpath.substring(0, last+1)); + // Use regionMatches to avoid creating new string + return (this.cpath.length() == (last + 1)) && + this.cpath.regionMatches(0, that.cpath, 0, last+1); + } + } + } + } else if (that.directory) { + // if this is NOT recursive/wildcarded, + // do not let it imply a recursive/wildcarded permission + return false; + } else { + return (this.cpath.equals(that.cpath)); + } + } + + /** + * Checks two FilePermission objects for equality. Checks that obj is + * a FilePermission, and has the same pathname and actions as this object. + *

+ * @param obj the object we are testing for equality with this object. + * @return true if obj is a FilePermission, and has the same + * pathname and actions as this FilePermission object, + * false otherwise. + */ + public boolean equals(Object obj) { + if (obj == this) + return true; + + if (! (obj instanceof FilePermission)) + return false; + + FilePermission that = (FilePermission) obj; + + return (this.mask == that.mask) && + this.cpath.equals(that.cpath) && + (this.directory == that.directory) && + (this.recursive == that.recursive); + } + + /** + * Returns the hash code value for this object. + * + * @return a hash code value for this object. + */ + + public int hashCode() { + return this.cpath.hashCode(); + } + + /** + * Converts an actions String to an actions mask. + * + * @param action the action string. + * @return the actions mask. + */ + private static int getMask(String actions) { + + int mask = NONE; + + // Null action valid? + if (actions == null) { + return mask; + } + // Check against use of constants (used heavily within the JDK) + if (actions == SecurityConstants.FILE_READ_ACTION) { + return READ; + } else if (actions == SecurityConstants.FILE_WRITE_ACTION) { + return WRITE; + } else if (actions == SecurityConstants.FILE_EXECUTE_ACTION) { + return EXECUTE; + } else if (actions == SecurityConstants.FILE_DELETE_ACTION) { + return DELETE; + } + + char[] a = actions.toCharArray(); + + int i = a.length - 1; + if (i < 0) + return mask; + + while (i != -1) { + char c; + + // skip whitespace + while ((i!=-1) && ((c = a[i]) == ' ' || + c == '\r' || + c == '\n' || + c == '\f' || + c == '\t')) + i--; + + // check for the known strings + int matchlen; + + if (i >= 3 && (a[i-3] == 'r' || a[i-3] == 'R') && + (a[i-2] == 'e' || a[i-2] == 'E') && + (a[i-1] == 'a' || a[i-1] == 'A') && + (a[i] == 'd' || a[i] == 'D')) + { + matchlen = 4; + mask |= READ; + + } else if (i >= 4 && (a[i-4] == 'w' || a[i-4] == 'W') && + (a[i-3] == 'r' || a[i-3] == 'R') && + (a[i-2] == 'i' || a[i-2] == 'I') && + (a[i-1] == 't' || a[i-1] == 'T') && + (a[i] == 'e' || a[i] == 'E')) + { + matchlen = 5; + mask |= WRITE; + + } else if (i >= 6 && (a[i-6] == 'e' || a[i-6] == 'E') && + (a[i-5] == 'x' || a[i-5] == 'X') && + (a[i-4] == 'e' || a[i-4] == 'E') && + (a[i-3] == 'c' || a[i-3] == 'C') && + (a[i-2] == 'u' || a[i-2] == 'U') && + (a[i-1] == 't' || a[i-1] == 'T') && + (a[i] == 'e' || a[i] == 'E')) + { + matchlen = 7; + mask |= EXECUTE; + + } else if (i >= 5 && (a[i-5] == 'd' || a[i-5] == 'D') && + (a[i-4] == 'e' || a[i-4] == 'E') && + (a[i-3] == 'l' || a[i-3] == 'L') && + (a[i-2] == 'e' || a[i-2] == 'E') && + (a[i-1] == 't' || a[i-1] == 'T') && + (a[i] == 'e' || a[i] == 'E')) + { + matchlen = 6; + mask |= DELETE; + + } else { + // parse error + throw new IllegalArgumentException( + "invalid permission: " + actions); + } + + // make sure we didn't just match the tail of a word + // like "ackbarfaccept". Also, skip to the comma. + boolean seencomma = false; + while (i >= matchlen && !seencomma) { + switch(a[i-matchlen]) { + case ',': + seencomma = true; + /*FALLTHROUGH*/ + case ' ': case '\r': case '\n': + case '\f': case '\t': + break; + default: + throw new IllegalArgumentException( + "invalid permission: " + actions); + } + i--; + } + + // point i at the location of the comma minus one (or -1). + i -= matchlen; + } + + return mask; + } + + /** + * Return the current action mask. Used by the FilePermissionCollection. + * + * @return the actions mask. + */ + + int getMask() { + return mask; + } + + /** + * Return the canonical string representation of the actions. + * Always returns present actions in the following order: + * read, write, execute, delete. + * + * @return the canonical string representation of the actions. + */ + private static String getActions(int mask) + { + StringBuilder sb = new StringBuilder(); + boolean comma = false; + + if ((mask & READ) == READ) { + comma = true; + sb.append("read"); + } + + if ((mask & WRITE) == WRITE) { + if (comma) sb.append(','); + else comma = true; + sb.append("write"); + } + + if ((mask & EXECUTE) == EXECUTE) { + if (comma) sb.append(','); + else comma = true; + sb.append("execute"); + } + + if ((mask & DELETE) == DELETE) { + if (comma) sb.append(','); + else comma = true; + sb.append("delete"); + } + + return sb.toString(); + } + + /** + * Returns the "canonical string representation" of the actions. + * That is, this method always returns present actions in the following order: + * read, write, execute, delete. For example, if this FilePermission object + * allows both write and read actions, a call to getActions + * will return the string "read,write". + * + * @return the canonical string representation of the actions. + */ + public String getActions() + { + if (actions == null) + actions = getActions(this.mask); + + return actions; + } + + + /** + * Returns a new PermissionCollection object for storing FilePermission + * objects. + *

+ * FilePermission objects must be stored in a manner that allows them + * to be inserted into the collection in any order, but that also enables the + * PermissionCollection implies + * method to be implemented in an efficient (and consistent) manner. + * + *

For example, if you have two FilePermissions: + *

    + *
  1. "/tmp/-", "read" + *
  2. "/tmp/scratch/foo", "write" + *
+ * + *

and you are calling the implies method with the FilePermission: + * + *

+     *   "/tmp/scratch/foo", "read,write",
+     * 
+ * + * then the implies function must + * take into account both the "/tmp/-" and "/tmp/scratch/foo" + * permissions, so the effective permission is "read,write", + * and implies returns true. The "implies" semantics for + * FilePermissions are handled properly by the PermissionCollection object + * returned by this newPermissionCollection method. + * + * @return a new PermissionCollection object suitable for storing + * FilePermissions. + */ + + public PermissionCollection newPermissionCollection() { + return new FilePermissionCollection(); + } + + /** + * WriteObject is called to save the state of the FilePermission + * to a stream. The actions are serialized, and the superclass + * takes care of the name. + */ + private void writeObject(ObjectOutputStream s) + throws IOException + { + // Write out the actions. The superclass takes care of the name + // call getActions to make sure actions field is initialized + if (actions == null) + getActions(); + s.defaultWriteObject(); + } + + /** + * readObject is called to restore the state of the FilePermission from + * a stream. + */ + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException + { + // Read in the actions, then restore everything else by calling init. + s.defaultReadObject(); + init(getMask(actions)); + } +} + +/** + * A FilePermissionCollection stores a set of FilePermission permissions. + * FilePermission objects + * must be stored in a manner that allows them to be inserted in any + * order, but enable the implies function to evaluate the implies + * method. + * For example, if you have two FilePermissions: + *
    + *
  1. "/tmp/-", "read" + *
  2. "/tmp/scratch/foo", "write" + *
+ * And you are calling the implies function with the FilePermission: + * "/tmp/scratch/foo", "read,write", then the implies function must + * take into account both the /tmp/- and /tmp/scratch/foo + * permissions, so the effective permission is "read,write". + * + * @see java.security.Permission + * @see java.security.Permissions + * @see java.security.PermissionCollection + * + * + * @author Marianne Mueller + * @author Roland Schemers + * + * @serial include + * + */ + +final class FilePermissionCollection extends PermissionCollection +implements Serializable { + + // Not serialized; see serialization section at end of class + private transient List perms; + + /** + * Create an empty FilePermissions object. + * + */ + + public FilePermissionCollection() { + perms = new ArrayList(); + } + + /** + * Adds a permission to the FilePermissions. The key for the hash is + * permission.path. + * + * @param permission the Permission object to add. + * + * @exception IllegalArgumentException - if the permission is not a + * FilePermission + * + * @exception SecurityException - if this FilePermissionCollection object + * has been marked readonly + */ + + public void add(Permission permission) + { + if (! (permission instanceof FilePermission)) + throw new IllegalArgumentException("invalid permission: "+ + permission); + if (isReadOnly()) + throw new SecurityException( + "attempt to add a Permission to a readonly PermissionCollection"); + + synchronized (this) { + perms.add(permission); + } + } + + /** + * Check and see if this set of permissions implies the permissions + * expressed in "permission". + * + * @param p the Permission object to compare + * + * @return true if "permission" is a proper subset of a permission in + * the set, false if not. + */ + + public boolean implies(Permission permission) + { + if (! (permission instanceof FilePermission)) + return false; + + FilePermission fp = (FilePermission) permission; + + int desired = fp.getMask(); + int effective = 0; + int needed = desired; + + synchronized (this) { + int len = perms.size(); + for (int i = 0; i < len; i++) { + FilePermission x = (FilePermission) perms.get(i); + if (((needed & x.getMask()) != 0) && x.impliesIgnoreMask(fp)) { + effective |= x.getMask(); + if ((effective & desired) == desired) + return true; + needed = (desired ^ effective); + } + } + } + return false; + } + + /** + * Returns an enumeration of all the FilePermission objects in the + * container. + * + * @return an enumeration of all the FilePermission objects. + */ + + public Enumeration elements() { + // Convert Iterator into Enumeration + synchronized (this) { + return Collections.enumeration(perms); + } + } + + private static final long serialVersionUID = 2202956749081564585L; + + // Need to maintain serialization interoperability with earlier releases, + // which had the serializable field: + // private Vector permissions; + + /** + * @serialField permissions java.util.Vector + * A list of FilePermission objects. + */ + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("permissions", Vector.class), + }; + + /** + * @serialData "permissions" field (a Vector containing the FilePermissions). + */ + /* + * Writes the contents of the perms field out as a Vector for + * serialization compatibility with earlier releases. + */ + private void writeObject(ObjectOutputStream out) throws IOException { + // Don't call out.defaultWriteObject() + + // Write out Vector + Vector permissions = new Vector(perms.size()); + synchronized (this) { + permissions.addAll(perms); + } + + ObjectOutputStream.PutField pfields = out.putFields(); + pfields.put("permissions", permissions); + out.writeFields(); + } + + /* + * Reads in a Vector of FilePermissions and saves them in the perms field. + */ + private void readObject(ObjectInputStream in) throws IOException, + ClassNotFoundException { + // Don't call defaultReadObject() + + // Read in serialized fields + ObjectInputStream.GetField gfields = in.readFields(); + + // Get the one we want + Vector permissions = (Vector)gfields.get("permissions", null); + perms = new ArrayList(permissions.size()); + perms.addAll(permissions); + } +}