/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.io; import java.security.AccessController; import java.security.Permission; import java.security.PermissionCollection; import java.security.PrivilegedAction; import org.apache.harmony.luni.util.Msg; /** * A permission for accessing a file or directory. The FilePermission is made up * of a pathname and a set of actions which are valid for the pathname. * <p> * The {@code File.separatorChar} must be used in all pathnames when * constructing a FilePermission. The following descriptions will assume the * char is {@code /}. A pathname that ends in {@code /*} includes all the files * and directories contained in that directory. If the pathname * ends in {@code /-}, it includes all the files and directories in that * directory <i>recursively</i>. The following pathnames have a special meaning: * <ul> * <li> * "*": all files in the current directory; * </li> * <li> * "-": recursively all files and directories in the current directory; * </li> * <li> * "<<ALL FILES>>": any file and directory in the file system. * </li> * </ul> */ public final class FilePermission extends Permission implements Serializable { private static final long serialVersionUID = 7930732926638008763L; // canonical path of this permission private transient String canonPath; // list of actions permitted for socket permission in order private static final String[] actionList = { "read", "write", "execute", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ "delete" }; //$NON-NLS-1$ // "canonicalized" action list private String actions; // the numeric representation of this action list // for implies() to check if one action list is the subset of another. transient int mask = -1; // global include all permission? private transient boolean includeAll = false; private transient boolean allDir = false; private transient boolean allSubdir = false; /** * Constructs a new FilePermission with the path and actions specified. * * @param path * the pathname of the file or directory to apply the actions to. * @param actions * the actions for the {@code path}. May be any combination of * "read", "write", "execute" and "delete". * @throws IllegalArgumentException * if {@code actions} is {@code null} or an empty string, or if * it contains a string other than "read", "write", "execute" * and "delete". * @throws NullPointerException * if {@code path} is {@code null}. */ public FilePermission(String path, String actions) { super(path); init(path, actions); } private void init(final String path, String pathActions) { if (pathActions == null || pathActions.equals("")) { //$NON-NLS-1$ throw new IllegalArgumentException(Msg.getString("K006d")); //$NON-NLS-1$ } this.actions = toCanonicalActionString(pathActions); if (path == null) { throw new NullPointerException(Msg.getString("K006e")); //$NON-NLS-1$ } if (path.equals("<<ALL FILES>>")) { //$NON-NLS-1$ includeAll = true; } else { canonPath = AccessController .doPrivileged(new PrivilegedAction<String>() { public String run() { try { return new File(path).getCanonicalPath(); } catch (IOException e) { return path; } } }); if (path.equals("*") || path.endsWith(File.separator + "*")) { //$NON-NLS-1$ //$NON-NLS-2$ allDir = true; } if (path.equals("-") || path.endsWith(File.separator + "-")) { //$NON-NLS-1$ //$NON-NLS-2$ allSubdir = true; } } } /** * Returns the string representing this permission's actions. It must be of * the form "read,write,execute,delete", all lower case and in the correct * order if there is more than one action. * * @param action * the action name * @return the string representing this permission's actions */ private String toCanonicalActionString(String action) { actions = action.trim().toLowerCase(); // get the numerical representation of the action list mask = getMask(actions); // convert the mask to a canonical action list. int len = actionList.length; // the test mask - shift the 1 to the leftmost position of the // actionList int highestBitMask = 1 << (len - 1); // if a bit of mask is set, append the corresponding action to result StringBuilder result = new StringBuilder(); boolean addedItem = false; for (int i = 0; i < len; i++) { if ((highestBitMask & mask) != 0) { if (addedItem) { result.append(","); //$NON-NLS-1$ } result.append(actionList[i]); addedItem = true; } highestBitMask = highestBitMask >> 1; } return result.toString(); } /** * Returns the numerical representation of the argument. * * @param actionNames * the action names * @return the action mask */ private int getMask(String actionNames) { int actionInt = 0, head = 0, tail = 0; do { tail = actionNames.indexOf(",", head); //$NON-NLS-1$ String action = tail > 0 ? actionNames.substring(head, tail).trim() : actionNames.substring(head).trim(); if (action.equals("read")) { //$NON-NLS-1$ actionInt |= 8; } else if (action.equals("write")) { //$NON-NLS-1$ actionInt |= 4; } else if (action.equals("execute")) { //$NON-NLS-1$ actionInt |= 2; } else if (action.equals("delete")) { //$NON-NLS-1$ actionInt |= 1; } else { throw new IllegalArgumentException(Msg.getString( "K006f", action)); //$NON-NLS-1$ } head = tail + 1; } while (tail > 0); return actionInt; } /** * Returns the actions associated with this file permission. * * @return the actions associated with this file permission. */ @Override public String getActions() { return actions; } /** * Indicates if this file permission is equal to another. The two are equal * if {@code obj} is a FilePermission, they have the same path, and they * have the same actions. * * @param obj * the object to check equality with. * @return {@code true} if this file permission is equal to {@code obj}, * {@code false} otherwise. */ @Override public boolean equals(Object obj) { if (obj instanceof FilePermission) { FilePermission fp = (FilePermission) obj; if (fp.actions != actions) { if (fp.actions == null || !fp.actions.equals(actions)) { return false; } } /* Matching actions and both are <<ALL FILES>> ? */ if (fp.includeAll || includeAll) { return fp.includeAll == includeAll; } return fp.canonPath.equals(canonPath); } return false; } /** * Indicates whether the permission {@code p} is implied by this file * permission. This is the case if {@code p} is an instance of * {@code FilePermission}, if {@code p}'s actions are a subset of this * file permission's actions and if {@code p}'s path is implied by this * file permission's path. * * @param p * the permission to check. * @return {@code true} if the argument permission is implied by the * receiver, and {@code false} if it is not. */ @Override public boolean implies(Permission p) { int match = impliesMask(p); return match != 0 && match == ((FilePermission) p).mask; } /** * Returns an int describing what masks are implied by a specific * permission. * * @param p * the permission * @return the mask applied to the given permission */ int impliesMask(Permission p) { if (!(p instanceof FilePermission)) { return 0; } FilePermission fp = (FilePermission) p; int matchedMask = mask & fp.mask; // Can't match any bits? if (matchedMask == 0) { return 0; } // Is this permission <<ALL FILES>> if (includeAll) { return matchedMask; } // We can't imply all files if (fp.includeAll) { return 0; } // Scan the length of p checking all match possibilities // \- implies everything except \ int thisLength = canonPath.length(); if (allSubdir && thisLength == 2 && !fp.canonPath.equals(File.separator)) { return matchedMask; } // need /- to imply /- if (fp.allSubdir && !allSubdir) { return 0; } // need /- or /* to imply /* if (fp.allDir && !allSubdir && !allDir) { return 0; } boolean includeDir = false; int pLength = fp.canonPath.length(); // do not compare the * or - if (allDir || allSubdir) { thisLength--; } if (fp.allDir || fp.allSubdir) { pLength--; } for (int i = 0; i < pLength; i++) { char pChar = fp.canonPath.charAt(i); // Is p longer than this permissions canonLength? if (i >= thisLength) { if (i == thisLength) { // Is this permission include all? (must have matched up // until this point). if (allSubdir) { return matchedMask; } // Is this permission include a dir? Continue the check // afterwards. if (allDir) { includeDir = true; } } // If not includeDir then is has to be a mismatch. if (!includeDir) { return 0; } /** * If we have * for this and find a separator it is invalid. IE: * this is '/a/*' and p is '/a/b/c' we should fail on the * separator after the b. Except for root, canonical paths do * not end in a separator. */ if (pChar == File.separatorChar) { return 0; } } else { // Are the characters matched? if (canonPath.charAt(i) != pChar) { return 0; } } } // Must have matched up to this point or it's a valid file in an include // all directory if (pLength == thisLength) { if (allSubdir) { // /- implies /- or /* return fp.allSubdir || fp.allDir ? matchedMask : 0; } return allDir == fp.allDir ? matchedMask : 0; } return includeDir ? matchedMask : 0; } /** * Returns a new PermissionCollection in which to place FilePermission * objects. * * @return A new PermissionCollection object suitable for storing * FilePermission objects. */ @Override public PermissionCollection newPermissionCollection() { return new FilePermissionCollection(); } /** * Calculates the hash code value for this file permission. * * @return the hash code value for this file permission. */ @Override public int hashCode() { return (canonPath == null ? getName().hashCode() : canonPath.hashCode()) + mask; } private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); } private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); init(getName(), actions); } }