/* * * * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program 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. * * This program 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 at /legal/license.txt). * * 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 or visit www.sun.com if you need additional * information or have any questions. */ package javax.microedition.io; import java.security.PermissionCollection; import java.security.Permission; import java.util.Enumeration; import java.util.Vector; /** * This class represents access rights to connections via the "file" * protocol. A <code>FileProtocolPermission</code> consists of a * URI string indicating a pathname and a set of actions desired for that * pathname. * <p> * The URI string takes the following form: * <pre> * file://{pathname} * </pre> * A pathname that ends in "/*" 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. * <p> * 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" and "write". The actions string is converted to * lowercase before processing. * * @see Connector#open * @see "javax.microedition.io.file.FileConnection" in <a href="http://www.jcp.org/en/jsr/detail?id=75">FileConnection Optional Package Specification</a> */ public final class FileProtocolPermission extends GCFPermission { /** * Read from a file */ private final static int READ = 0x1; /** * Write to a file */ private final static int WRITE = 0x2; /** * No actions */ private final static int NONE = 0x0; /** * All actions */ private final static int ALL = READ|WRITE; /** * Path normalizer */ private static final PathNormalizer pathNormalizer = new DefaultPathNormalizer(); /** * Action mask */ private int action_mask = NONE; /** * Creates a new <code>FileProtocolPermission</code> with the specified * actions. The specified URI becomes the name of the * <code>FileProtocolPermission</code>. * The URI string must conform to the specification given above. * * @param uri the URI string * @param actions comma-separated list of desired actions * * @throws IllegalArgumentException if <code>uri</code> or * <code>actions</code> is malformed. * @throws NullPointerException if <code>uri</code> or * <code>actions</code> is <code>null</code>. * * @see #getName * @see #getActions */ public FileProtocolPermission(String uri, String actions) { super(uri, false /*require authority*/, null /*port range normalizer*/, pathNormalizer, true /*normalize authority*/); if (!"file".equals(getProtocol())) { throw new IllegalArgumentException("Expected file protocol: " + uri); } checkHostPortPathOnly(); checkNoPortRange(); String host = getHost(); if (host != null && !"".equals(host) && !"localhost".equals(host)) { throw new IllegalArgumentException("Invalid host component: " + uri); } if (!uri.toLowerCase().startsWith("file:")) { throw new IllegalArgumentException( "Expected URI of the form file:{pathname} or file://{pathname}: " + uri); } String path = getPath(); if (path.charAt(0) != '/') { throw new IllegalArgumentException( "Path in the URI must be absolute: " + uri); } if (uri.equalsIgnoreCase("file:") || uri.equalsIgnoreCase("file://") || uri.equalsIgnoreCase("file://localhost")) { throw new IllegalArgumentException("No path specified: " + uri); } action_mask = getMask(actions); } int getActionMask() { return action_mask; } /** * Convert an action string to an integer actions mask. * * @param action the action string * @return the action mask */ private static int getMask(String action) { if (action == null) { throw new NullPointerException("action can't be null"); } if (action.equals("")) { throw new IllegalArgumentException("action can't be empty"); } int mask = NONE; char[] a = action.toCharArray(); int i = a.length - 1; if (i < 0) { return mask; } while (i != -1) { // 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 { // parse error throw new IllegalArgumentException( "invalid actions: " + action); } // make sure we didn't just match the tail of a word // like "ackbarfread". Also, skip to the comma. if (i >= matchlen) { // don't match the comma at the beginning of the string if (i > matchlen && a[i-matchlen] == ',') { i--; } else { // parse error throw new IllegalArgumentException( "invalid actions: " + action); } } // point i at the location of the comma minus one (or -1). i -= matchlen; } return mask; } /** * Checks if this <code>FileProtocolPermission</code> object "implies" * the specified permission. * <p> * More specifically, this method returns <code>true</code> if: * <p> * <ul> * <li> <i>p</i> is an instanceof <code>FileProtocolPermission</code>, * <p> * <li> <i>p</i>'s actions are a proper subset of this * object's actions, and * <p> * <li> <i>p</i>'s pathname is implied by this object's * pathname. For example, "/tmp/*" implies "/tmp/foo", since * "/tmp/*" encompasses the "/tmp" directory and all files in that * directory, including the one named "foo". * </ul> * <p> * @param p the permission to check against * * @return true if the specified permission is implied by this object, * false if not. */ public boolean implies(Permission p) { if (!(p instanceof FileProtocolPermission)) { return false; } FileProtocolPermission that = (FileProtocolPermission)p; if ((this.action_mask & that.action_mask) != that.action_mask) { return false; } return impliesByPath(that); } boolean impliesByPath(FileProtocolPermission that) { String thisPath = this.getPath(); String thatPath = that.getPath(); if (thisPath.equals(thatPath)) { return true; } // A pathname that ends in "/*" indicates all the files and directories // contained in that directory. if (thisPath.endsWith("/*")) { int len = thisPath.length(); String s = thisPath.substring(0, len - 1); return (thatPath.startsWith(s) && !thatPath.endsWith("/-") && thatPath.indexOf('/', len - 1) == -1); } // A pathname that ends with "/-" indicates (recursively) all files // and subdirectories contained in that directory. if (thisPath.endsWith("/-")) { int len = thisPath.length(); String s = thisPath.substring(0, len - 1); return thatPath.startsWith(s); } return false; } /** * Checks two <code>FileProtocolPermission</code> objects for equality. * * @param obj the object we are testing for equality with this object. * * @return <code>true</code> if <code>obj</code> is a * <code>FileProtocolPermission</code>, * and has the same URI string and actions as * this <code>FileProtocolPermission</code> object. */ public boolean equals(Object obj) { if (!(obj instanceof FileProtocolPermission)) { return false; } FileProtocolPermission other = (FileProtocolPermission)obj; return other.getURI().equals(getURI()) && action_mask == other.action_mask; } /** * Returns the hash code value for this object. * * @return a hash code value for this object. */ public int hashCode() { return getURI().hashCode() ^ action_mask; } /** * Returns the canonical string representation of the actions. * If both read and write actions are allowed, this method returns * the string <code>"read,write"</code>. * * @return the canonical string representation of the actions. */ public String getActions() { switch (action_mask) { case ALL: return "read,write"; case READ: return "read"; case WRITE: return "write"; default: return ""; } } /** * Returns a new <code>PermissionCollection</code> for storing * <code>FileProtocolPermission</code> objects. * <p> * <code>FileProtocolPermission</code> objects must be stored in a * manner that allows * them to be inserted into the collection in any order, but that also * enables the <code>PermissionCollection</code> implies method to be * implemented in an efficient (and consistent) manner. * * @return a new <code>PermissionCollection</code> suitable for storing * <code>FileProtocolPermission</code> objects. */ public PermissionCollection newPermissionCollection() { return new FileProtocolPermissionCollection(); } } /** * A FileProtocolPermissionCollection stores a collection * of FileProtocol permissions. FileProtocolPermission 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 in an efficient (and consistent) manner. * * * @see java.security.Permission * @see java.security.Permissions * @see java.security.PermissionsImpl * * * */ final class FileProtocolPermissionCollection extends PermissionCollection { private final Vector permissions = new Vector(6); /** * Create an empty FileProtocolPermissionCollection object. * */ public FileProtocolPermissionCollection() {} /** * Adds a permission to the GCFPermissions. The key for the hash is * permission.uri. * * @param permission the Permission object to add. * * @exception IllegalArgumentException - if the permission is not a * GCFPermission, or if * the permission is not of the * same Class as the other * permissions in this collection. * * @exception SecurityException - if this GCFPermissionCollection object * has been marked readonly */ public void add(Permission permission) { if (! (permission instanceof FileProtocolPermission)) throw new IllegalArgumentException("invalid permission: "+ permission); if (isReadOnly()) { throw new SecurityException( "Cannot add a Permission to a readonly PermissionCollection"); } FileProtocolPermission bp = (FileProtocolPermission) permission; permissions.addElement(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 FileProtocolPermission)) { return false; } FileProtocolPermission fp = (FileProtocolPermission) permission; int desired = fp.getActionMask(); int effective = 0; int needed = desired; Enumeration search = permissions.elements(); while (search.hasMoreElements()) { FileProtocolPermission p = (FileProtocolPermission)search.nextElement(); int actions = p.getActionMask(); if ((actions & needed) != 0 && p.impliesByPath(fp)) { effective |= actions; if ((effective & desired) == desired) { return true; } needed = (desired ^ effective); } } return false; } /** * Returns an enumeration of all the GCFPermission objects in the * container. * * @return an enumeration of all the GCFPermission objects. */ public Enumeration elements() { return permissions.elements(); } }