1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.io; 19 20import java.security.AccessController; 21import java.security.Permission; 22import java.security.PermissionCollection; 23import java.security.PrivilegedAction; 24 25import org.apache.harmony.luni.util.Msg; 26 27/** 28 * A permission for accessing a file or directory. The FilePermission is made up 29 * of a pathname and a set of actions which are valid for the pathname. 30 * <p> 31 * The {@code File.separatorChar} must be used in all pathnames when 32 * constructing a FilePermission. The following descriptions will assume the 33 * char is {@code /}. A pathname that ends in {@code /*} includes all the files 34 * and directories contained in that directory. If the pathname 35 * ends in {@code /-}, it includes all the files and directories in that 36 * directory <i>recursively</i>. The following pathnames have a special meaning: 37 * <ul> 38 * <li> 39 * "*": all files in the current directory; 40 * </li> 41 * <li> 42 * "-": recursively all files and directories in the current directory; 43 * </li> 44 * <li> 45 * "<<ALL FILES>>": any file and directory in the file system. 46 * </li> 47 * </ul> 48 */ 49public final class FilePermission extends Permission implements Serializable { 50 51 private static final long serialVersionUID = 7930732926638008763L; 52 53 // canonical path of this permission 54 private transient String canonPath; 55 56 // list of actions permitted for socket permission in order 57 private static final String[] actionList = { "read", "write", "execute", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 58 "delete" }; //$NON-NLS-1$ 59 60 // "canonicalized" action list 61 private String actions; 62 63 // the numeric representation of this action list 64 // for implies() to check if one action list is the subset of another. 65 transient int mask = -1; 66 67 // global include all permission? 68 private transient boolean includeAll = false; 69 70 private transient boolean allDir = false; 71 72 private transient boolean allSubdir = false; 73 74 /** 75 * Constructs a new FilePermission with the path and actions specified. 76 * 77 * @param path 78 * the pathname of the file or directory to apply the actions to. 79 * @param actions 80 * the actions for the {@code path}. May be any combination of 81 * "read", "write", "execute" and "delete". 82 * @throws IllegalArgumentException 83 * if {@code actions} is {@code null} or an empty string, or if 84 * it contains a string other than "read", "write", "execute" 85 * and "delete". 86 * @throws NullPointerException 87 * if {@code path} is {@code null}. 88 */ 89 public FilePermission(String path, String actions) { 90 super(path); 91 init(path, actions); 92 } 93 94 private void init(final String path, String pathActions) { 95 if (pathActions == null || pathActions.equals("")) { //$NON-NLS-1$ 96 throw new IllegalArgumentException(Msg.getString("K006d")); //$NON-NLS-1$ 97 } 98 this.actions = toCanonicalActionString(pathActions); 99 100 if (path == null) { 101 throw new NullPointerException(Msg.getString("K006e")); //$NON-NLS-1$ 102 } 103 if (path.equals("<<ALL FILES>>")) { //$NON-NLS-1$ 104 includeAll = true; 105 } else { 106 canonPath = AccessController 107 .doPrivileged(new PrivilegedAction<String>() { 108 public String run() { 109 try { 110 return new File(path).getCanonicalPath(); 111 } catch (IOException e) { 112 return path; 113 } 114 } 115 }); 116 if (path.equals("*") || path.endsWith(File.separator + "*")) { //$NON-NLS-1$ //$NON-NLS-2$ 117 allDir = true; 118 } 119 if (path.equals("-") || path.endsWith(File.separator + "-")) { //$NON-NLS-1$ //$NON-NLS-2$ 120 allSubdir = true; 121 } 122 } 123 } 124 125 /** 126 * Returns the string representing this permission's actions. It must be of 127 * the form "read,write,execute,delete", all lower case and in the correct 128 * order if there is more than one action. 129 * 130 * @param action 131 * the action name 132 * @return the string representing this permission's actions 133 */ 134 private String toCanonicalActionString(String action) { 135 actions = action.trim().toLowerCase(); 136 137 // get the numerical representation of the action list 138 mask = getMask(actions); 139 140 // convert the mask to a canonical action list. 141 int len = actionList.length; 142 // the test mask - shift the 1 to the leftmost position of the 143 // actionList 144 int highestBitMask = 1 << (len - 1); 145 146 // if a bit of mask is set, append the corresponding action to result 147 StringBuilder result = new StringBuilder(); 148 boolean addedItem = false; 149 for (int i = 0; i < len; i++) { 150 if ((highestBitMask & mask) != 0) { 151 if (addedItem) { 152 result.append(","); //$NON-NLS-1$ 153 } 154 result.append(actionList[i]); 155 addedItem = true; 156 } 157 highestBitMask = highestBitMask >> 1; 158 } 159 return result.toString(); 160 } 161 162 /** 163 * Returns the numerical representation of the argument. 164 * 165 * @param actionNames 166 * the action names 167 * @return the action mask 168 */ 169 private int getMask(String actionNames) { 170 int actionInt = 0, head = 0, tail = 0; 171 do { 172 tail = actionNames.indexOf(",", head); //$NON-NLS-1$ 173 String action = tail > 0 ? actionNames.substring(head, tail).trim() 174 : actionNames.substring(head).trim(); 175 if (action.equals("read")) { //$NON-NLS-1$ 176 actionInt |= 8; 177 } else if (action.equals("write")) { //$NON-NLS-1$ 178 actionInt |= 4; 179 } else if (action.equals("execute")) { //$NON-NLS-1$ 180 actionInt |= 2; 181 } else if (action.equals("delete")) { //$NON-NLS-1$ 182 actionInt |= 1; 183 } else { 184 throw new IllegalArgumentException(Msg.getString( 185 "K006f", action)); //$NON-NLS-1$ 186 } 187 head = tail + 1; 188 } while (tail > 0); 189 return actionInt; 190 } 191 192 /** 193 * Returns the actions associated with this file permission. 194 * 195 * @return the actions associated with this file permission. 196 */ 197 @Override 198 public String getActions() { 199 return actions; 200 } 201 202 /** 203 * Indicates if this file permission is equal to another. The two are equal 204 * if {@code obj} is a FilePermission, they have the same path, and they 205 * have the same actions. 206 * 207 * @param obj 208 * the object to check equality with. 209 * @return {@code true} if this file permission is equal to {@code obj}, 210 * {@code false} otherwise. 211 */ 212 @Override 213 public boolean equals(Object obj) { 214 if (obj instanceof FilePermission) { 215 FilePermission fp = (FilePermission) obj; 216 if (fp.actions != actions) { 217 if (fp.actions == null || !fp.actions.equals(actions)) { 218 return false; 219 } 220 } 221 222 /* Matching actions and both are <<ALL FILES>> ? */ 223 if (fp.includeAll || includeAll) { 224 return fp.includeAll == includeAll; 225 } 226 return fp.canonPath.equals(canonPath); 227 } 228 return false; 229 } 230 231 /** 232 * Indicates whether the permission {@code p} is implied by this file 233 * permission. This is the case if {@code p} is an instance of 234 * {@code FilePermission}, if {@code p}'s actions are a subset of this 235 * file permission's actions and if {@code p}'s path is implied by this 236 * file permission's path. 237 * 238 * @param p 239 * the permission to check. 240 * @return {@code true} if the argument permission is implied by the 241 * receiver, and {@code false} if it is not. 242 */ 243 @Override 244 public boolean implies(Permission p) { 245 int match = impliesMask(p); 246 return match != 0 && match == ((FilePermission) p).mask; 247 } 248 249 /** 250 * Returns an int describing what masks are implied by a specific 251 * permission. 252 * 253 * @param p 254 * the permission 255 * @return the mask applied to the given permission 256 */ 257 int impliesMask(Permission p) { 258 if (!(p instanceof FilePermission)) { 259 return 0; 260 } 261 FilePermission fp = (FilePermission) p; 262 int matchedMask = mask & fp.mask; 263 // Can't match any bits? 264 if (matchedMask == 0) { 265 return 0; 266 } 267 268 // Is this permission <<ALL FILES>> 269 if (includeAll) { 270 return matchedMask; 271 } 272 273 // We can't imply all files 274 if (fp.includeAll) { 275 return 0; 276 } 277 278 // Scan the length of p checking all match possibilities 279 // \- implies everything except \ 280 int thisLength = canonPath.length(); 281 if (allSubdir && thisLength == 2 282 && !fp.canonPath.equals(File.separator)) { 283 return matchedMask; 284 } 285 // need /- to imply /- 286 if (fp.allSubdir && !allSubdir) { 287 return 0; 288 } 289 // need /- or /* to imply /* 290 if (fp.allDir && !allSubdir && !allDir) { 291 return 0; 292 } 293 294 boolean includeDir = false; 295 int pLength = fp.canonPath.length(); 296 // do not compare the * or - 297 if (allDir || allSubdir) { 298 thisLength--; 299 } 300 if (fp.allDir || fp.allSubdir) { 301 pLength--; 302 } 303 for (int i = 0; i < pLength; i++) { 304 char pChar = fp.canonPath.charAt(i); 305 // Is p longer than this permissions canonLength? 306 if (i >= thisLength) { 307 if (i == thisLength) { 308 // Is this permission include all? (must have matched up 309 // until this point). 310 if (allSubdir) { 311 return matchedMask; 312 } 313 // Is this permission include a dir? Continue the check 314 // afterwards. 315 if (allDir) { 316 includeDir = true; 317 } 318 } 319 // If not includeDir then is has to be a mismatch. 320 if (!includeDir) { 321 return 0; 322 } 323 /** 324 * If we have * for this and find a separator it is invalid. IE: 325 * this is '/a/*' and p is '/a/b/c' we should fail on the 326 * separator after the b. Except for root, canonical paths do 327 * not end in a separator. 328 */ 329 if (pChar == File.separatorChar) { 330 return 0; 331 } 332 } else { 333 // Are the characters matched? 334 if (canonPath.charAt(i) != pChar) { 335 return 0; 336 } 337 } 338 } 339 // Must have matched up to this point or it's a valid file in an include 340 // all directory 341 if (pLength == thisLength) { 342 if (allSubdir) { 343 // /- implies /- or /* 344 return fp.allSubdir || fp.allDir ? matchedMask : 0; 345 } 346 return allDir == fp.allDir ? matchedMask : 0; 347 } 348 return includeDir ? matchedMask : 0; 349 } 350 351 /** 352 * Returns a new PermissionCollection in which to place FilePermission 353 * objects. 354 * 355 * @return A new PermissionCollection object suitable for storing 356 * FilePermission objects. 357 */ 358 @Override 359 public PermissionCollection newPermissionCollection() { 360 return new FilePermissionCollection(); 361 } 362 363 /** 364 * Calculates the hash code value for this file permission. 365 * 366 * @return the hash code value for this file permission. 367 */ 368 @Override 369 public int hashCode() { 370 return (canonPath == null ? getName().hashCode() : canonPath.hashCode()) 371 + mask; 372 } 373 374 private void writeObject(ObjectOutputStream stream) throws IOException { 375 stream.defaultWriteObject(); 376 } 377 378 private void readObject(ObjectInputStream stream) throws IOException, 379 ClassNotFoundException { 380 stream.defaultReadObject(); 381 init(getName(), actions); 382 } 383} 384