001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.io;
019:
020: import java.security.AccessController;
021: import java.security.Permission;
022: import java.security.PermissionCollection;
023: import java.security.PrivilegedAction;
024:
025: import org.apache.harmony.luni.util.Msg;
026:
027: /**
028: * The class FilePermission is responsible for granting access to files or
029: * directories. The FilePermission is made up of a pathname and a set of actions
030: * which are valid for the pathname.
031: * <P>
032: * The <code>File.separatorChar</code> must be used in all pathnames when
033: * constructing a FilePermission. The following descriptions will assume the
034: * char is </code>/</code>. A pathname which ends in "/*", implies all the
035: * files and directories contained in that directory. If the pathname ends in
036: * "/-", it indicates all the files and directories in that directory
037: * <b>recursively</b>.
038: */
039: public final class FilePermission extends Permission implements
040: Serializable {
041:
042: private static final long serialVersionUID = 7930732926638008763L;
043:
044: // canonical path of this permission
045: private transient String canonPath;
046:
047: // list of actions permitted for socket permission in order
048: private static final String[] actionList = {
049: "read", "write", "execute", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
050: "delete" }; //$NON-NLS-1$
051:
052: // "canonicalized" action list
053: private String actions;
054:
055: // the numeric representation of this action list
056: // for implies() to check if one action list is the subset of another.
057: transient int mask = -1;
058:
059: // global include all permission?
060: private transient boolean includeAll = false;
061:
062: private transient boolean allDir = false;
063:
064: private transient boolean allSubdir = false;
065:
066: /**
067: * Constructs a new FilePermission with the path and actions specified.
068: *
069: * @param path
070: * the path to apply the actions to.
071: * @param actions
072: * the actions for the <code>path<code>. May be any
073: * combination of read, write, execute, or delete.
074: */
075: public FilePermission(String path, String actions) {
076: super (path);
077: init(path, actions);
078: }
079:
080: private void init(final String path, String pathActions) {
081: if (pathActions == null || pathActions.equals("")) { //$NON-NLS-1$
082: throw new IllegalArgumentException(Msg.getString("K006d")); //$NON-NLS-1$
083: }
084: this .actions = toCanonicalActionString(pathActions);
085:
086: if (path == null) {
087: throw new NullPointerException(Msg.getString("K006e")); //$NON-NLS-1$
088: }
089: if (path.equals("<<ALL FILES>>")) { //$NON-NLS-1$
090: includeAll = true;
091: } else {
092: canonPath = AccessController
093: .doPrivileged(new PrivilegedAction<String>() {
094: public String run() {
095: try {
096: return new File(path)
097: .getCanonicalPath();
098: } catch (IOException e) {
099: return path;
100: }
101: }
102: });
103: if (path.equals("*") || path.endsWith(File.separator + "*")) { //$NON-NLS-1$ //$NON-NLS-2$
104: allDir = true;
105: }
106: if (path.equals("-") || path.endsWith(File.separator + "-")) { //$NON-NLS-1$ //$NON-NLS-2$
107: allSubdir = true;
108: }
109: }
110: }
111:
112: /**
113: * Answer the string representing this permissions actions. It must be of
114: * the form "read,write,execute,delete", all lower case and in the correct
115: * order if there is more than one action.
116: *
117: * @param action
118: * the action name
119: * @return the string representing this permission's actions
120: */
121: private String toCanonicalActionString(String action) {
122: actions = action.trim().toLowerCase();
123:
124: // get the numerical representation of the action list
125: mask = getMask(actions);
126:
127: // convert the mask to a canonical action list.
128: int len = actionList.length;
129: // the test mask - shift the 1 to the leftmost position of the
130: // actionList
131: int highestBitMask = 1 << (len - 1);
132:
133: // if a bit of mask is set, append the corresponding action to result
134: StringBuilder result = new StringBuilder();
135: boolean addedItem = false;
136: for (int i = 0; i < len; i++) {
137: if ((highestBitMask & mask) != 0) {
138: if (addedItem) {
139: result.append(","); //$NON-NLS-1$
140: }
141: result.append(actionList[i]);
142: addedItem = true;
143: }
144: highestBitMask = highestBitMask >> 1;
145: }
146: return result.toString();
147: }
148:
149: /**
150: * Answers the numerical representation of the argument.
151: *
152: * @param actionNames
153: * the action names
154: * @return the action mask
155: */
156: private int getMask(String actionNames) {
157: int actionInt = 0, head = 0, tail = 0;
158: do {
159: tail = actionNames.indexOf(",", head); //$NON-NLS-1$
160: String action = tail > 0 ? actionNames
161: .substring(head, tail).trim() : actionNames
162: .substring(head).trim();
163: if (action.equals("read")) { //$NON-NLS-1$
164: actionInt |= 8;
165: } else if (action.equals("write")) { //$NON-NLS-1$
166: actionInt |= 4;
167: } else if (action.equals("execute")) { //$NON-NLS-1$
168: actionInt |= 2;
169: } else if (action.equals("delete")) { //$NON-NLS-1$
170: actionInt |= 1;
171: } else {
172: throw new IllegalArgumentException(Msg.getString(
173: "K006f", action)); //$NON-NLS-1$
174: }
175: head = tail + 1;
176: } while (tail > 0);
177: return actionInt;
178: }
179:
180: /**
181: * Answers the actions associated with the receiver.
182: *
183: * @return the actions associated with the receiver.
184: */
185: @Override
186: public String getActions() {
187: return actions;
188: }
189:
190: /**
191: * Check to see if this permission is equal to another. The two are equal if
192: * <code>obj</code> is a FilePermission, they have the same path, and they
193: * have the same actions.
194: *
195: * @param obj
196: * the object to check equality with.
197: * @return <code>true</code> if the two are equal, <code>false</code>
198: * otherwise.
199: */
200: @Override
201: public boolean equals(Object obj) {
202: if (obj instanceof FilePermission) {
203: FilePermission fp = (FilePermission) obj;
204: if (fp.actions != actions) {
205: if (fp.actions == null || !fp.actions.equals(actions)) {
206: return false;
207: }
208: }
209:
210: /* Matching actions and both are <<ALL FILES>> ? */
211: if (fp.includeAll || includeAll) {
212: return fp.includeAll == includeAll;
213: }
214: return fp.canonPath.equals(canonPath);
215: }
216: return false;
217: }
218:
219: /**
220: * Indicates whether the argument permission is implied by the receiver.
221: *
222: * @param p
223: * java.security.Permission the permission to check.
224: * @return <code>true</code> if the argument permission is implied by the
225: * receiver, and <code>false</code> if it is not.
226: */
227: @Override
228: public boolean implies(Permission p) {
229: int match = impliesMask(p);
230: return match != 0 && match == ((FilePermission) p).mask;
231: }
232:
233: /**
234: * Answers an int describing what masks are implied by a specific
235: * permission.
236: *
237: * @param p
238: * the permission
239: * @return the mask applied to the given permission
240: */
241: int impliesMask(Permission p) {
242: if (!(p instanceof FilePermission)) {
243: return 0;
244: }
245: FilePermission fp = (FilePermission) p;
246: int matchedMask = mask & fp.mask;
247: // Can't match any bits?
248: if (matchedMask == 0) {
249: return 0;
250: }
251:
252: // Is this permission <<ALL FILES>>
253: if (includeAll) {
254: return matchedMask;
255: }
256:
257: // We can't imply all files
258: if (fp.includeAll) {
259: return 0;
260: }
261:
262: // Scan the length of p checking all match possibilities
263: // \- implies everything except \
264: int this Length = canonPath.length();
265: if (allSubdir && this Length == 2
266: && !fp.canonPath.equals(File.separator)) {
267: return matchedMask;
268: }
269: // need /- to imply /-
270: if (fp.allSubdir && !allSubdir) {
271: return 0;
272: }
273: // need /- or /* to imply /*
274: if (fp.allDir && !allSubdir && !allDir) {
275: return 0;
276: }
277:
278: boolean includeDir = false;
279: int pLength = fp.canonPath.length();
280: // do not compare the * or -
281: if (allDir || allSubdir) {
282: this Length--;
283: }
284: if (fp.allDir || fp.allSubdir) {
285: pLength--;
286: }
287: for (int i = 0; i < pLength; i++) {
288: char pChar = fp.canonPath.charAt(i);
289: // Is p longer than this permissions canonLength?
290: if (i >= this Length) {
291: if (i == this Length) {
292: // Is this permission include all? (must have matched up
293: // until this point).
294: if (allSubdir) {
295: return matchedMask;
296: }
297: // Is this permission include a dir? Continue the check
298: // afterwards.
299: if (allDir) {
300: includeDir = true;
301: }
302: }
303: // If not includeDir then is has to be a mismatch.
304: if (!includeDir) {
305: return 0;
306: }
307: /**
308: * If we have * for this and find a separator it is invalid. IE:
309: * this is '/a/*' and p is '/a/b/c' we should fail on the
310: * separator after the b. Except for root, canonical paths do
311: * not end in a separator.
312: */
313: if (pChar == File.separatorChar) {
314: return 0;
315: }
316: } else {
317: // Are the characters matched?
318: if (canonPath.charAt(i) != pChar) {
319: return 0;
320: }
321: }
322: }
323: // Must have matched up to this point or it's a valid file in an include
324: // all directory
325: if (pLength == this Length) {
326: if (allSubdir) {
327: // /- implies /- or /*
328: return fp.allSubdir || fp.allDir ? matchedMask : 0;
329: }
330: return allDir == fp.allDir ? matchedMask : 0;
331: }
332: return includeDir ? matchedMask : 0;
333: }
334:
335: /**
336: * Answers a new PermissionCollection in which to place FilePermission
337: * Objects.
338: *
339: * @return A new PermissionCollection suitable for storing FilePermission
340: * objects.
341: */
342: @Override
343: public PermissionCollection newPermissionCollection() {
344: return new FilePermissionCollection();
345: }
346:
347: /**
348: * Answers an int representing the hash code value for this FilePermission.
349: *
350: * @return int the hash code value for this FilePermission.
351: */
352: @Override
353: public int hashCode() {
354: return (canonPath == null ? getName().hashCode() : canonPath
355: .hashCode())
356: + mask;
357: }
358:
359: private void writeObject(ObjectOutputStream stream)
360: throws IOException {
361: stream.defaultWriteObject();
362: }
363:
364: private void readObject(ObjectInputStream stream)
365: throws IOException, ClassNotFoundException {
366: stream.defaultReadObject();
367: init(getName(), actions);
368: }
369: }
|