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