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