1 /*
   2  * Copyright (c) 1997, 2009, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.io;
  27 
  28 import java.security.*;
  29 import java.util.Enumeration;
  30 import java.util.List;
  31 import java.util.ArrayList;
  32 import java.util.Vector;
  33 import java.util.Collections;
  34 import java.io.ObjectStreamField;
  35 import java.io.ObjectOutputStream;
  36 import java.io.ObjectInputStream;
  37 import java.io.IOException;
  38 import sun.security.util.SecurityConstants;
  39 
  40 /**
  41  * This class represents access to a file or directory.  A FilePermission consists
  42  * of a pathname and a set of actions valid for that pathname.
  43  * <P>
  44  * Pathname is the pathname of the file or directory granted the specified
  45  * actions. A pathname that ends in "/*" (where "/" is
  46  * the file separator character, <code>File.separatorChar</code>) indicates
  47  * all the files and directories contained in that directory. A pathname
  48  * that ends with "/-" indicates (recursively) all files
  49  * and subdirectories contained in that directory. A pathname consisting of
  50  * the special token "&lt;&lt;ALL FILES&gt;&gt;" matches <b>any</b> file.
  51  * <P>
  52  * Note: A pathname consisting of a single "*" indicates all the files
  53  * in the current directory, while a pathname consisting of a single "-"
  54  * indicates all the files in the current directory and
  55  * (recursively) all files and subdirectories contained in the current
  56  * directory.
  57  * <P>
  58  * The actions to be granted are passed to the constructor in a string containing
  59  * a list of one or more comma-separated keywords. The possible keywords are
  60  * "read", "write", "execute", "delete", and "readlink". Their meaning is
  61  * defined as follows:
  62  * <P>
  63  * <DL>
  64  *    <DT> read <DD> read permission
  65  *    <DT> write <DD> write permission
  66  *    <DT> execute
  67  *    <DD> execute permission. Allows <code>Runtime.exec</code> to
  68  *         be called. Corresponds to <code>SecurityManager.checkExec</code>.
  69  *    <DT> delete
  70  *    <DD> delete permission. Allows <code>File.delete</code> to
  71  *         be called. Corresponds to <code>SecurityManager.checkDelete</code>.
  72  *    <DT> readlink
  73  *    <DD> read link permission. Allows the target of a
  74  *         <a href="../nio/file/package-summary.html#links">symbolic link</a>
  75  *         to be read by invoking the {@link java.nio.file.Path#readSymbolicLink
  76  *         readSymbolicLink } method.
  77  * </DL>
  78  * <P>
  79  * The actions string is converted to lowercase before processing.
  80  * <P>
  81  * Be careful when granting FilePermissions. Think about the implications
  82  * of granting read and especially write access to various files and
  83  * directories. The "&lt;&lt;ALL FILES>>" permission with write action is
  84  * especially dangerous. This grants permission to write to the entire
  85  * file system. One thing this effectively allows is replacement of the
  86  * system binary, including the JVM runtime environment.
  87  *
  88  * <p>Please note: Code can always read a file from the same
  89  * directory it's in (or a subdirectory of that directory); it does not
  90  * need explicit permission to do so.
  91  *
  92  * @see java.security.Permission
  93  * @see java.security.Permissions
  94  * @see java.security.PermissionCollection
  95  *
  96  *
  97  * @author Marianne Mueller
  98  * @author Roland Schemers
  99  * @since 1.2
 100  *
 101  * @serial exclude
 102  */
 103 
 104 public final class FilePermission extends Permission implements Serializable {
 105 
 106     /**
 107      * Execute action.
 108      */
 109     private final static int EXECUTE = 0x1;
 110     /**
 111      * Write action.
 112      */
 113     private final static int WRITE   = 0x2;
 114     /**
 115      * Read action.
 116      */
 117     private final static int READ    = 0x4;
 118     /**
 119      * Delete action.
 120      */
 121     private final static int DELETE  = 0x8;
 122     /**
 123      * Read link action.
 124      */
 125     private final static int READLINK    = 0x10;
 126 
 127     /**
 128      * All actions (read,write,execute,delete,readlink)
 129      */
 130     private final static int ALL     = READ|WRITE|EXECUTE|DELETE|READLINK;
 131     /**
 132      * No actions.
 133      */
 134     private final static int NONE    = 0x0;
 135 
 136     // the actions mask
 137     private transient int mask;
 138 
 139     // does path indicate a directory? (wildcard or recursive)
 140     private transient boolean directory;
 141 
 142     // is it a recursive directory specification?
 143     private transient boolean recursive;
 144 
 145     /**
 146      * the actions string.
 147      *
 148      * @serial
 149      */
 150     private String actions; // Left null as long as possible, then
 151                             // created and re-used in the getAction function.
 152 
 153     // canonicalized dir path. In the case of
 154     // directories, it is the name "/blah/*" or "/blah/-" without
 155     // the last character (the "*" or "-").
 156 
 157     private transient String cpath;
 158 
 159     // static Strings used by init(int mask)
 160     private static final char RECURSIVE_CHAR = '-';
 161     private static final char WILD_CHAR = '*';
 162 
 163 /*
 164     public String toString()
 165     {
 166         StringBuffer sb = new StringBuffer();
 167         sb.append("***\n");
 168         sb.append("cpath = "+cpath+"\n");
 169         sb.append("mask = "+mask+"\n");
 170         sb.append("actions = "+getActions()+"\n");
 171         sb.append("directory = "+directory+"\n");
 172         sb.append("recursive = "+recursive+"\n");
 173         sb.append("***\n");
 174         return sb.toString();
 175     }
 176 */
 177 
 178     private static final long serialVersionUID = 7930732926638008763L;
 179 
 180     /**
 181      * initialize a FilePermission object. Common to all constructors.
 182      * Also called during de-serialization.
 183      *
 184      * @param mask the actions mask to use.
 185      *
 186      */
 187     private void init(int mask)
 188     {
 189 
 190         if ((mask & ALL) != mask)
 191                 throw new IllegalArgumentException("invalid actions mask");
 192 
 193         if (mask == NONE)
 194                 throw new IllegalArgumentException("invalid actions mask");
 195 
 196         if ((cpath = getName()) == null)
 197                 throw new NullPointerException("name can't be null");
 198 
 199         this.mask = mask;
 200 
 201         if (cpath.equals("<<ALL FILES>>")) {
 202             directory = true;
 203             recursive = true;
 204             cpath = "";
 205             return;
 206         }
 207 
 208         // store only the canonical cpath if possible
 209         cpath = AccessController.doPrivileged(new PrivilegedAction<String>() {
 210             public String run() {
 211                 try {
 212                     String path = cpath;
 213                     if (cpath.endsWith("*")) {
 214                         // call getCanonicalPath with a path with wildcard character
 215                         // replaced to avoid calling it with paths that are
 216                         // intended to match all entries in a directory
 217                         path = path.substring(0, path.length()-1) + "-";
 218                         path = new File(path).getCanonicalPath();
 219                         return path.substring(0, path.length()-1) + "*";
 220                     } else {
 221                         return new File(path).getCanonicalPath();
 222                     }
 223                 } catch (IOException ioe) {
 224                     return cpath;
 225                 }
 226             }
 227         });
 228 
 229         int len = cpath.length();
 230         char last = ((len > 0) ? cpath.charAt(len - 1) : 0);
 231 
 232         if (last == RECURSIVE_CHAR &&
 233             cpath.charAt(len - 2) == File.separatorChar) {
 234             directory = true;
 235             recursive = true;
 236             cpath = cpath.substring(0, --len);
 237         } else if (last == WILD_CHAR &&
 238             cpath.charAt(len - 2) == File.separatorChar) {
 239             directory = true;
 240             //recursive = false;
 241             cpath = cpath.substring(0, --len);
 242         } else {
 243             // overkill since they are initialized to false, but
 244             // commented out here to remind us...
 245             //directory = false;
 246             //recursive = false;
 247         }
 248 
 249         // XXX: at this point the path should be absolute. die if it isn't?
 250     }
 251 
 252     /**
 253      * Creates a new FilePermission object with the specified actions.
 254      * <i>path</i> is the pathname of a file or directory, and <i>actions</i>
 255      * contains a comma-separated list of the desired actions granted on the
 256      * file or directory. Possible actions are
 257      * "read", "write", "execute", "delete", and "readlink".
 258      *
 259      * <p>A pathname that ends in "/*" (where "/" is
 260      * the file separator character, <code>File.separatorChar</code>)
 261      * indicates all the files and directories contained in that directory.
 262      * A pathname that ends with "/-" indicates (recursively) all files and
 263      * subdirectories contained in that directory. The special pathname
 264      * "&lt;&lt;ALL FILES&gt;&gt;" matches any file.
 265      *
 266      * <p>A pathname consisting of a single "*" indicates all the files
 267      * in the current directory, while a pathname consisting of a single "-"
 268      * indicates all the files in the current directory and
 269      * (recursively) all files and subdirectories contained in the current
 270      * directory.
 271      *
 272      * <p>A pathname containing an empty string represents an empty path.
 273      *
 274      * @param path the pathname of the file/directory.
 275      * @param actions the action string.
 276      *
 277      * @throws IllegalArgumentException
 278      *          If actions is <code>null</code>, empty or contains an action
 279      *          other than the specified possible actions.
 280      */
 281 
 282     public FilePermission(String path, String actions)
 283     {
 284         super(path);
 285         init(getMask(actions));
 286     }
 287 
 288     /**
 289      * Creates a new FilePermission object using an action mask.
 290      * More efficient than the FilePermission(String, String) constructor.
 291      * Can be used from within
 292      * code that needs to create a FilePermission object to pass into the
 293      * <code>implies</code> method.
 294      *
 295      * @param path the pathname of the file/directory.
 296      * @param mask the action mask to use.
 297      */
 298 
 299     // package private for use by the FilePermissionCollection add method
 300     FilePermission(String path, int mask)
 301     {
 302         super(path);
 303         init(mask);
 304     }
 305 
 306     /**
 307      * Checks if this FilePermission object "implies" the specified permission.
 308      * <P>
 309      * More specifically, this method returns true if:<p>
 310      * <ul>
 311      * <li> <i>p</i> is an instanceof FilePermission,<p>
 312      * <li> <i>p</i>'s actions are a proper subset of this
 313      * object's actions, and <p>
 314      * <li> <i>p</i>'s pathname is implied by this object's
 315      *      pathname. For example, "/tmp/*" implies "/tmp/foo", since
 316      *      "/tmp/*" encompasses all files in the "/tmp" directory,
 317      *      including the one named "foo".
 318      * </ul>
 319      *
 320      * @param p the permission to check against.
 321      *
 322      * @return <code>true</code> if the specified permission is not
 323      *                  <code>null</code> and is implied by this object,
 324      *                  <code>false</code> otherwise.
 325      */
 326     public boolean implies(Permission p) {
 327         if (!(p instanceof FilePermission))
 328             return false;
 329 
 330         FilePermission that = (FilePermission) p;
 331 
 332         // we get the effective mask. i.e., the "and" of this and that.
 333         // They must be equal to that.mask for implies to return true.
 334 
 335         return ((this.mask & that.mask) == that.mask) && impliesIgnoreMask(that);
 336     }
 337 
 338     /**
 339      * Checks if the Permission's actions are a proper subset of the
 340      * this object's actions. Returns the effective mask iff the
 341      * this FilePermission's path also implies that FilePermission's path.
 342      *
 343      * @param that the FilePermission to check against.
 344      * @param exact return immediately if the masks are not equal
 345      * @return the effective mask
 346      */
 347     boolean impliesIgnoreMask(FilePermission that) {
 348         if (this.directory) {
 349             if (this.recursive) {
 350                 // make sure that.path is longer then path so
 351                 // something like /foo/- does not imply /foo
 352                 if (that.directory) {
 353                     return (that.cpath.length() >= this.cpath.length()) &&
 354                             that.cpath.startsWith(this.cpath);
 355                 }  else {
 356                     return ((that.cpath.length() > this.cpath.length()) &&
 357                         that.cpath.startsWith(this.cpath));
 358                 }
 359             } else {
 360                 if (that.directory) {
 361                     // if the permission passed in is a directory
 362                     // specification, make sure that a non-recursive
 363                     // permission (i.e., this object) can't imply a recursive
 364                     // permission.
 365                     if (that.recursive)
 366                         return false;
 367                     else
 368                         return (this.cpath.equals(that.cpath));
 369                 } else {
 370                     int last = that.cpath.lastIndexOf(File.separatorChar);
 371                     if (last == -1)
 372                         return false;
 373                     else {
 374                         // this.cpath.equals(that.cpath.substring(0, last+1));
 375                         // Use regionMatches to avoid creating new string
 376                         return (this.cpath.length() == (last + 1)) &&
 377                             this.cpath.regionMatches(0, that.cpath, 0, last+1);
 378                     }
 379                 }
 380             }
 381         } else if (that.directory) {
 382             // if this is NOT recursive/wildcarded,
 383             // do not let it imply a recursive/wildcarded permission
 384             return false;
 385         } else {
 386             return (this.cpath.equals(that.cpath));
 387         }
 388     }
 389 
 390     /**
 391      * Checks two FilePermission objects for equality. Checks that <i>obj</i> is
 392      * a FilePermission, and has the same pathname and actions as this object.
 393      * <P>
 394      * @param obj the object we are testing for equality with this object.
 395      * @return <code>true</code> if obj is a FilePermission, and has the same
 396      *          pathname and actions as this FilePermission object,
 397      *          <code>false</code> otherwise.
 398      */
 399     public boolean equals(Object obj) {
 400         if (obj == this)
 401             return true;
 402 
 403         if (! (obj instanceof FilePermission))
 404             return false;
 405 
 406         FilePermission that = (FilePermission) obj;
 407 
 408         return (this.mask == that.mask) &&
 409             this.cpath.equals(that.cpath) &&
 410             (this.directory == that.directory) &&
 411             (this.recursive == that.recursive);
 412     }
 413 
 414     /**
 415      * Returns the hash code value for this object.
 416      *
 417      * @return a hash code value for this object.
 418      */
 419 
 420     public int hashCode() {
 421         return this.cpath.hashCode();
 422     }
 423 
 424     /**
 425      * Converts an actions String to an actions mask.
 426      *
 427      * @param action the action string.
 428      * @return the actions mask.
 429      */
 430     private static int getMask(String actions) {
 431 
 432         int mask = NONE;
 433 
 434         // Null action valid?
 435         if (actions == null) {
 436             return mask;
 437         }
 438         // Check against use of constants (used heavily within the JDK)
 439         if (actions == SecurityConstants.FILE_READ_ACTION) {
 440             return READ;
 441         } else if (actions == SecurityConstants.FILE_WRITE_ACTION) {
 442             return WRITE;
 443         } else if (actions == SecurityConstants.FILE_EXECUTE_ACTION) {
 444             return EXECUTE;
 445         } else if (actions == SecurityConstants.FILE_DELETE_ACTION) {
 446             return DELETE;
 447         } else if (actions == SecurityConstants.FILE_READLINK_ACTION) {
 448             return READLINK;
 449         }
 450 
 451         char[] a = actions.toCharArray();
 452 
 453         int i = a.length - 1;
 454         if (i < 0)
 455             return mask;
 456 
 457         while (i != -1) {
 458             char c;
 459 
 460             // skip whitespace
 461             while ((i!=-1) && ((c = a[i]) == ' ' ||
 462                                c == '\r' ||
 463                                c == '\n' ||
 464                                c == '\f' ||
 465                                c == '\t'))
 466                 i--;
 467 
 468             // check for the known strings
 469             int matchlen;
 470 
 471             if (i >= 3 && (a[i-3] == 'r' || a[i-3] == 'R') &&
 472                           (a[i-2] == 'e' || a[i-2] == 'E') &&
 473                           (a[i-1] == 'a' || a[i-1] == 'A') &&
 474                           (a[i] == 'd' || a[i] == 'D'))
 475             {
 476                 matchlen = 4;
 477                 mask |= READ;
 478 
 479             } else if (i >= 4 && (a[i-4] == 'w' || a[i-4] == 'W') &&
 480                                  (a[i-3] == 'r' || a[i-3] == 'R') &&
 481                                  (a[i-2] == 'i' || a[i-2] == 'I') &&
 482                                  (a[i-1] == 't' || a[i-1] == 'T') &&
 483                                  (a[i] == 'e' || a[i] == 'E'))
 484             {
 485                 matchlen = 5;
 486                 mask |= WRITE;
 487 
 488             } else if (i >= 6 && (a[i-6] == 'e' || a[i-6] == 'E') &&
 489                                  (a[i-5] == 'x' || a[i-5] == 'X') &&
 490                                  (a[i-4] == 'e' || a[i-4] == 'E') &&
 491                                  (a[i-3] == 'c' || a[i-3] == 'C') &&
 492                                  (a[i-2] == 'u' || a[i-2] == 'U') &&
 493                                  (a[i-1] == 't' || a[i-1] == 'T') &&
 494                                  (a[i] == 'e' || a[i] == 'E'))
 495             {
 496                 matchlen = 7;
 497                 mask |= EXECUTE;
 498 
 499             } else if (i >= 5 && (a[i-5] == 'd' || a[i-5] == 'D') &&
 500                                  (a[i-4] == 'e' || a[i-4] == 'E') &&
 501                                  (a[i-3] == 'l' || a[i-3] == 'L') &&
 502                                  (a[i-2] == 'e' || a[i-2] == 'E') &&
 503                                  (a[i-1] == 't' || a[i-1] == 'T') &&
 504                                  (a[i] == 'e' || a[i] == 'E'))
 505             {
 506                 matchlen = 6;
 507                 mask |= DELETE;
 508 
 509             } else if (i >= 7 && (a[i-7] == 'r' || a[i-7] == 'R') &&
 510                                  (a[i-6] == 'e' || a[i-6] == 'E') &&
 511                                  (a[i-5] == 'a' || a[i-5] == 'A') &&
 512                                  (a[i-4] == 'd' || a[i-4] == 'D') &&
 513                                  (a[i-3] == 'l' || a[i-3] == 'L') &&
 514                                  (a[i-2] == 'i' || a[i-2] == 'I') &&
 515                                  (a[i-1] == 'n' || a[i-1] == 'N') &&
 516                                  (a[i] == 'k' || a[i] == 'K'))
 517             {
 518                 matchlen = 8;
 519                 mask |= READLINK;
 520 
 521             } else {
 522                 // parse error
 523                 throw new IllegalArgumentException(
 524                         "invalid permission: " + actions);
 525             }
 526 
 527             // make sure we didn't just match the tail of a word
 528             // like "ackbarfaccept".  Also, skip to the comma.
 529             boolean seencomma = false;
 530             while (i >= matchlen && !seencomma) {
 531                 switch(a[i-matchlen]) {
 532                 case ',':
 533                     seencomma = true;
 534                     /*FALLTHROUGH*/
 535                 case ' ': case '\r': case '\n':
 536                 case '\f': case '\t':
 537                     break;
 538                 default:
 539                     throw new IllegalArgumentException(
 540                             "invalid permission: " + actions);
 541                 }
 542                 i--;
 543             }
 544 
 545             // point i at the location of the comma minus one (or -1).
 546             i -= matchlen;
 547         }
 548 
 549         return mask;
 550     }
 551 
 552     /**
 553      * Return the current action mask. Used by the FilePermissionCollection.
 554      *
 555      * @return the actions mask.
 556      */
 557 
 558     int getMask() {
 559         return mask;
 560     }
 561 
 562     /**
 563      * Return the canonical string representation of the actions.
 564      * Always returns present actions in the following order:
 565      * read, write, execute, delete, readlink.
 566      *
 567      * @return the canonical string representation of the actions.
 568      */
 569     private static String getActions(int mask)
 570     {
 571         StringBuilder sb = new StringBuilder();
 572         boolean comma = false;
 573 
 574         if ((mask & READ) == READ) {
 575             comma = true;
 576             sb.append("read");
 577         }
 578 
 579         if ((mask & WRITE) == WRITE) {
 580             if (comma) sb.append(',');
 581             else comma = true;
 582             sb.append("write");
 583         }
 584 
 585         if ((mask & EXECUTE) == EXECUTE) {
 586             if (comma) sb.append(',');
 587             else comma = true;
 588             sb.append("execute");
 589         }
 590 
 591         if ((mask & DELETE) == DELETE) {
 592             if (comma) sb.append(',');
 593             else comma = true;
 594             sb.append("delete");
 595         }
 596 
 597         if ((mask & READLINK) == READLINK) {
 598             if (comma) sb.append(',');
 599             else comma = true;
 600             sb.append("readlink");
 601         }
 602 
 603         return sb.toString();
 604     }
 605 
 606     /**
 607      * Returns the "canonical string representation" of the actions.
 608      * That is, this method always returns present actions in the following order:
 609      * read, write, execute, delete, readlink. For example, if this FilePermission
 610      * object allows both write and read actions, a call to <code>getActions</code>
 611      * will return the string "read,write".
 612      *
 613      * @return the canonical string representation of the actions.
 614      */
 615     public String getActions()
 616     {
 617         if (actions == null)
 618             actions = getActions(this.mask);
 619 
 620         return actions;
 621     }
 622 
 623 
 624     /**
 625      * Returns a new PermissionCollection object for storing FilePermission
 626      * objects.
 627      * <p>
 628      * FilePermission objects must be stored in a manner that allows them
 629      * to be inserted into the collection in any order, but that also enables the
 630      * PermissionCollection <code>implies</code>
 631      * method to be implemented in an efficient (and consistent) manner.
 632      *
 633      * <p>For example, if you have two FilePermissions:
 634      * <OL>
 635      * <LI>  <code>"/tmp/-", "read"</code>
 636      * <LI>  <code>"/tmp/scratch/foo", "write"</code>
 637      * </OL>
 638      *
 639      * <p>and you are calling the <code>implies</code> method with the FilePermission:
 640      *
 641      * <pre>
 642      *   "/tmp/scratch/foo", "read,write",
 643      * </pre>
 644      *
 645      * then the <code>implies</code> function must
 646      * take into account both the "/tmp/-" and "/tmp/scratch/foo"
 647      * permissions, so the effective permission is "read,write",
 648      * and <code>implies</code> returns true. The "implies" semantics for
 649      * FilePermissions are handled properly by the PermissionCollection object
 650      * returned by this <code>newPermissionCollection</code> method.
 651      *
 652      * @return a new PermissionCollection object suitable for storing
 653      * FilePermissions.
 654      */
 655 
 656     public PermissionCollection newPermissionCollection() {
 657         return new FilePermissionCollection();
 658     }
 659 
 660     /**
 661      * WriteObject is called to save the state of the FilePermission
 662      * to a stream. The actions are serialized, and the superclass
 663      * takes care of the name.
 664      */
 665     private void writeObject(ObjectOutputStream s)
 666         throws IOException
 667     {
 668         // Write out the actions. The superclass takes care of the name
 669         // call getActions to make sure actions field is initialized
 670         if (actions == null)
 671             getActions();
 672         s.defaultWriteObject();
 673     }
 674 
 675     /**
 676      * readObject is called to restore the state of the FilePermission from
 677      * a stream.
 678      */
 679     private void readObject(ObjectInputStream s)
 680          throws IOException, ClassNotFoundException
 681     {
 682         // Read in the actions, then restore everything else by calling init.
 683         s.defaultReadObject();
 684         init(getMask(actions));
 685     }
 686 }
 687 
 688 /**
 689  * A FilePermissionCollection stores a set of FilePermission permissions.
 690  * FilePermission objects
 691  * must be stored in a manner that allows them to be inserted in any
 692  * order, but enable the implies function to evaluate the implies
 693  * method.
 694  * For example, if you have two FilePermissions:
 695  * <OL>
 696  * <LI> "/tmp/-", "read"
 697  * <LI> "/tmp/scratch/foo", "write"
 698  * </OL>
 699  * And you are calling the implies function with the FilePermission:
 700  * "/tmp/scratch/foo", "read,write", then the implies function must
 701  * take into account both the /tmp/- and /tmp/scratch/foo
 702  * permissions, so the effective permission is "read,write".
 703  *
 704  * @see java.security.Permission
 705  * @see java.security.Permissions
 706  * @see java.security.PermissionCollection
 707  *
 708  *
 709  * @author Marianne Mueller
 710  * @author Roland Schemers
 711  *
 712  * @serial include
 713  *
 714  */
 715 
 716 final class FilePermissionCollection extends PermissionCollection
 717 implements Serializable {
 718 
 719     // Not serialized; see serialization section at end of class
 720     private transient List<Permission> perms;
 721 
 722     /**
 723      * Create an empty FilePermissions object.
 724      *
 725      */
 726 
 727     public FilePermissionCollection() {
 728         perms = new ArrayList<Permission>();
 729     }
 730 
 731     /**
 732      * Adds a permission to the FilePermissions. The key for the hash is
 733      * permission.path.
 734      *
 735      * @param permission the Permission object to add.
 736      *
 737      * @exception IllegalArgumentException - if the permission is not a
 738      *                                       FilePermission
 739      *
 740      * @exception SecurityException - if this FilePermissionCollection object
 741      *                                has been marked readonly
 742      */
 743 
 744     public void add(Permission permission)
 745     {
 746         if (! (permission instanceof FilePermission))
 747             throw new IllegalArgumentException("invalid permission: "+
 748                                                permission);
 749         if (isReadOnly())
 750             throw new SecurityException(
 751                 "attempt to add a Permission to a readonly PermissionCollection");
 752 
 753         synchronized (this) {
 754             perms.add(permission);
 755         }
 756     }
 757 
 758     /**
 759      * Check and see if this set of permissions implies the permissions
 760      * expressed in "permission".
 761      *
 762      * @param p the Permission object to compare
 763      *
 764      * @return true if "permission" is a proper subset of a permission in
 765      * the set, false if not.
 766      */
 767 
 768     public boolean implies(Permission permission)
 769     {
 770         if (! (permission instanceof FilePermission))
 771                 return false;
 772 
 773         FilePermission fp = (FilePermission) permission;
 774 
 775         int desired = fp.getMask();
 776         int effective = 0;
 777         int needed = desired;
 778 
 779         synchronized (this) {
 780             int len = perms.size();
 781             for (int i = 0; i < len; i++) {
 782                 FilePermission x = (FilePermission) perms.get(i);
 783                 if (((needed & x.getMask()) != 0) && x.impliesIgnoreMask(fp)) {
 784                     effective |=  x.getMask();
 785                     if ((effective & desired) == desired)
 786                         return true;
 787                     needed = (desired ^ effective);
 788                 }
 789             }
 790         }
 791         return false;
 792     }
 793 
 794     /**
 795      * Returns an enumeration of all the FilePermission objects in the
 796      * container.
 797      *
 798      * @return an enumeration of all the FilePermission objects.
 799      */
 800 
 801     public Enumeration elements() {
 802         // Convert Iterator into Enumeration
 803         synchronized (this) {
 804             return Collections.enumeration(perms);
 805         }
 806     }
 807 
 808     private static final long serialVersionUID = 2202956749081564585L;
 809 
 810     // Need to maintain serialization interoperability with earlier releases,
 811     // which had the serializable field:
 812     //    private Vector permissions;
 813 
 814     /**
 815      * @serialField permissions java.util.Vector
 816      *     A list of FilePermission objects.
 817      */
 818     private static final ObjectStreamField[] serialPersistentFields = {
 819         new ObjectStreamField("permissions", Vector.class),
 820     };
 821 
 822     /**
 823      * @serialData "permissions" field (a Vector containing the FilePermissions).
 824      */
 825     /*
 826      * Writes the contents of the perms field out as a Vector for
 827      * serialization compatibility with earlier releases.
 828      */
 829     private void writeObject(ObjectOutputStream out) throws IOException {
 830         // Don't call out.defaultWriteObject()
 831 
 832         // Write out Vector
 833         Vector<Permission> permissions = new Vector<Permission>(perms.size());
 834         synchronized (this) {
 835             permissions.addAll(perms);
 836         }
 837 
 838         ObjectOutputStream.PutField pfields = out.putFields();
 839         pfields.put("permissions", permissions);
 840         out.writeFields();
 841     }
 842 
 843     /*
 844      * Reads in a Vector of FilePermissions and saves them in the perms field.
 845      */
 846     @SuppressWarnings("unchecked")
 847     private void readObject(ObjectInputStream in) throws IOException,
 848     ClassNotFoundException {
 849         // Don't call defaultReadObject()
 850 
 851         // Read in serialized fields
 852         ObjectInputStream.GetField gfields = in.readFields();
 853 
 854         // Get the one we want
 855         Vector<Permission> permissions = (Vector<Permission>)gfields.get("permissions", null);
 856         perms = new ArrayList<Permission>(permissions.size());
 857         perms.addAll(permissions);
 858     }
 859 }