1/*
2 * Copyright (c) 1997, 2017, 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
26package java.io;
27
28import java.net.URI;
29import java.nio.file.*;
30import java.security.*;
31import java.util.Enumeration;
32import java.util.Objects;
33import java.util.StringJoiner;
34import java.util.Vector;
35import java.util.concurrent.ConcurrentHashMap;
36
37import jdk.internal.misc.JavaIOFilePermissionAccess;
38import jdk.internal.misc.SharedSecrets;
39import sun.nio.fs.DefaultFileSystemProvider;
40import sun.security.action.GetPropertyAction;
41import sun.security.util.FilePermCompat;
42import sun.security.util.SecurityConstants;
43
44/**
45 * This class represents access to a file or directory.  A FilePermission consists
46 * of a pathname and a set of actions valid for that pathname.
47 * <P>
48 * Pathname is the pathname of the file or directory granted the specified
49 * actions. A pathname that ends in "/*" (where "/" is
50 * the file separator character, <code>File.separatorChar</code>) indicates
51 * all the files and directories contained in that directory. A pathname
52 * that ends with "/-" indicates (recursively) all files
53 * and subdirectories contained in that directory. Such a pathname is called
54 * a wildcard pathname. Otherwise, it's a simple pathname.
55 * <P>
56 * A pathname consisting of the special token {@literal "<<ALL FILES>>"}
57 * matches <b>any</b> file.
58 * <P>
59 * Note: A pathname consisting of a single "*" indicates all the files
60 * in the current directory, while a pathname consisting of a single "-"
61 * indicates all the files in the current directory and
62 * (recursively) all files and subdirectories contained in the current
63 * directory.
64 * <P>
65 * The actions to be granted are passed to the constructor in a string containing
66 * a list of one or more comma-separated keywords. The possible keywords are
67 * "read", "write", "execute", "delete", and "readlink". Their meaning is
68 * defined as follows:
69 *
70 * <DL>
71 *    <DT> read <DD> read permission
72 *    <DT> write <DD> write permission
73 *    <DT> execute
74 *    <DD> execute permission. Allows <code>Runtime.exec</code> to
75 *         be called. Corresponds to <code>SecurityManager.checkExec</code>.
76 *    <DT> delete
77 *    <DD> delete permission. Allows <code>File.delete</code> to
78 *         be called. Corresponds to <code>SecurityManager.checkDelete</code>.
79 *    <DT> readlink
80 *    <DD> read link permission. Allows the target of a
81 *         <a href="../nio/file/package-summary.html#links">symbolic link</a>
82 *         to be read by invoking the {@link java.nio.file.Files#readSymbolicLink
83 *         readSymbolicLink } method.
84 * </DL>
85 * <P>
86 * The actions string is converted to lowercase before processing.
87 * <P>
88 * Be careful when granting FilePermissions. Think about the implications
89 * of granting read and especially write access to various files and
90 * directories. The {@literal "<<ALL FILES>>"} permission with write action is
91 * especially dangerous. This grants permission to write to the entire
92 * file system. One thing this effectively allows is replacement of the
93 * system binary, including the JVM runtime environment.
94 * <P>
95 * Please note: Code can always read a file from the same
96 * directory it's in (or a subdirectory of that directory); it does not
97 * need explicit permission to do so.
98 *
99 * @see java.security.Permission
100 * @see java.security.Permissions
101 * @see java.security.PermissionCollection
102 *
103 *
104 * @author Marianne Mueller
105 * @author Roland Schemers
106 * @since 1.2
107 *
108 * @serial exclude
109 */
110
111public final class FilePermission extends Permission implements Serializable {
112
113    /**
114     * Execute action.
115     */
116    private static final int EXECUTE = 0x1;
117    /**
118     * Write action.
119     */
120    private static final int WRITE   = 0x2;
121    /**
122     * Read action.
123     */
124    private static final int READ    = 0x4;
125    /**
126     * Delete action.
127     */
128    private static final int DELETE  = 0x8;
129    /**
130     * Read link action.
131     */
132    private static final int READLINK    = 0x10;
133
134    /**
135     * All actions (read,write,execute,delete,readlink)
136     */
137    private static final int ALL     = READ|WRITE|EXECUTE|DELETE|READLINK;
138    /**
139     * No actions.
140     */
141    private static final int NONE    = 0x0;
142
143    // the actions mask
144    private transient int mask;
145
146    // does path indicate a directory? (wildcard or recursive)
147    private transient boolean directory;
148
149    // is it a recursive directory specification?
150    private transient boolean recursive;
151
152    /**
153     * the actions string.
154     *
155     * @serial
156     */
157    private String actions; // Left null as long as possible, then
158                            // created and re-used in the getAction function.
159
160    // canonicalized dir path. used by the "old" behavior (nb == false).
161    // In the case of directories, it is the name "/blah/*" or "/blah/-"
162    // without the last character (the "*" or "-").
163
164    private transient String cpath;
165
166    // Following fields used by the "new" behavior (nb == true), in which
167    // input path is not canonicalized. For compatibility (so that granting
168    // FilePermission on "x" allows reading "`pwd`/x", an alternative path
169    // can be added so that both can be used in an implies() check. Please note
170    // the alternative path only deals with absolute/relative path, and does
171    // not deal with symlink/target.
172
173    private transient Path npath;       // normalized dir path.
174    private transient Path npath2;      // alternative normalized dir path.
175    private transient boolean allFiles; // whether this is <<ALL FILES>>
176    private transient boolean invalid;  // whether input path is invalid
177
178    // static Strings used by init(int mask)
179    private static final char RECURSIVE_CHAR = '-';
180    private static final char WILD_CHAR = '*';
181
182//    public String toString() {
183//        StringBuffer sb = new StringBuffer();
184//        sb.append("*** FilePermission on " + getName() + " ***");
185//        for (Field f : FilePermission.class.getDeclaredFields()) {
186//            if (!Modifier.isStatic(f.getModifiers())) {
187//                try {
188//                    sb.append(f.getName() + " = " + f.get(this));
189//                } catch (Exception e) {
190//                    sb.append(f.getName() + " = " + e.toString());
191//                }
192//                sb.append('\n');
193//            }
194//        }
195//        sb.append("***\n");
196//        return sb.toString();
197//    }
198
199    private static final long serialVersionUID = 7930732926638008763L;
200
201    /**
202     * Always use the internal default file system, in case it was modified
203     * with java.nio.file.spi.DefaultFileSystemProvider.
204     */
205    private static final java.nio.file.FileSystem builtInFS =
206            DefaultFileSystemProvider.create()
207                    .getFileSystem(URI.create("file:///"));
208
209    private static final Path here = builtInFS.getPath(
210            GetPropertyAction.privilegedGetProperty("user.dir"));
211
212    private static final Path EMPTY_PATH = builtInFS.getPath("");
213    private static final Path DASH_PATH = builtInFS.getPath("-");
214    private static final Path DOTDOT_PATH = builtInFS.getPath("..");
215
216    /**
217     * A private constructor that clones some and updates some,
218     * always with a different name.
219     * @param input
220     */
221    private FilePermission(String name,
222                           FilePermission input,
223                           Path npath,
224                           Path npath2,
225                           int mask,
226                           String actions) {
227        super(name);
228        // Customizables
229        this.npath = npath;
230        this.npath2 = npath2;
231        this.actions = actions;
232        this.mask = mask;
233        // Cloneds
234        this.allFiles = input.allFiles;
235        this.invalid = input.invalid;
236        this.recursive = input.recursive;
237        this.directory = input.directory;
238        this.cpath = input.cpath;
239    }
240
241    /**
242     * Returns the alternative path as a Path object, i.e. absolute path
243     * for a relative one, or vice versa.
244     *
245     * @param in a real path w/o "-" or "*" at the end, and not <<ALL FILES>>.
246     * @return the alternative path, or null if cannot find one.
247     */
248    private static Path altPath(Path in) {
249        try {
250            if (!in.isAbsolute()) {
251                return here.resolve(in).normalize();
252            } else {
253                return here.relativize(in).normalize();
254            }
255        } catch (IllegalArgumentException e) {
256            return null;
257        }
258    }
259
260    static {
261        SharedSecrets.setJavaIOFilePermissionAccess(
262            /**
263             * Creates FilePermission objects with special internals.
264             * See {@link FilePermCompat#newPermPlusAltPath(Permission)} and
265             * {@link FilePermCompat#newPermUsingAltPath(Permission)}.
266             */
267            new JavaIOFilePermissionAccess() {
268                public FilePermission newPermPlusAltPath(FilePermission input) {
269                    if (!input.invalid && input.npath2 == null && !input.allFiles) {
270                        Path npath2 = altPath(input.npath);
271                        if (npath2 != null) {
272                            // Please note the name of the new permission is
273                            // different than the original so that when one is
274                            // added to a FilePermissionCollection it will not
275                            // be merged with the original one.
276                            return new FilePermission(input.getName() + "#plus",
277                                    input,
278                                    input.npath,
279                                    npath2,
280                                    input.mask,
281                                    input.actions);
282                        }
283                    }
284                    return input;
285                }
286                public FilePermission newPermUsingAltPath(FilePermission input) {
287                    if (!input.invalid && !input.allFiles) {
288                        Path npath2 = altPath(input.npath);
289                        if (npath2 != null) {
290                            // New name, see above.
291                            return new FilePermission(input.getName() + "#using",
292                                    input,
293                                    npath2,
294                                    null,
295                                    input.mask,
296                                    input.actions);
297                        }
298                    }
299                    return null;
300                }
301            }
302        );
303    }
304
305    /**
306     * initialize a FilePermission object. Common to all constructors.
307     * Also called during de-serialization.
308     *
309     * @param mask the actions mask to use.
310     *
311     */
312    private void init(int mask) {
313        if ((mask & ALL) != mask)
314                throw new IllegalArgumentException("invalid actions mask");
315
316        if (mask == NONE)
317                throw new IllegalArgumentException("invalid actions mask");
318
319        if (FilePermCompat.nb) {
320            String name = getName();
321
322            if (name == null)
323                throw new NullPointerException("name can't be null");
324
325            this.mask = mask;
326
327            if (name.equals("<<ALL FILES>>")) {
328                allFiles = true;
329                npath = builtInFS.getPath("");
330                // other fields remain default
331                return;
332            }
333
334            boolean rememberStar = false;
335            if (name.endsWith("*")) {
336                rememberStar = true;
337                recursive = false;
338                name = name.substring(0, name.length()-1) + "-";
339            }
340
341            try {
342                // new File() can "normalize" some name, for example, "/C:/X" on
343                // Windows. Some JDK codes generate such illegal names.
344                npath = builtInFS.getPath(new File(name).getPath())
345                        .normalize();
346                // lastName should always be non-null now
347                Path lastName = npath.getFileName();
348                if (lastName != null && lastName.equals(DASH_PATH)) {
349                    directory = true;
350                    recursive = !rememberStar;
351                    npath = npath.getParent();
352                }
353                if (npath == null) {
354                    npath = builtInFS.getPath("");
355                }
356                invalid = false;
357            } catch (InvalidPathException ipe) {
358                // Still invalid. For compatibility reason, accept it
359                // but make this permission useless.
360                npath = builtInFS.getPath("-u-s-e-l-e-s-s-");
361                invalid = true;
362            }
363
364        } else {
365            if ((cpath = getName()) == null)
366                throw new NullPointerException("name can't be null");
367
368            this.mask = mask;
369
370            if (cpath.equals("<<ALL FILES>>")) {
371                directory = true;
372                recursive = true;
373                cpath = "";
374                return;
375            }
376
377            // store only the canonical cpath if possible
378            cpath = AccessController.doPrivileged(new PrivilegedAction<>() {
379                public String run() {
380                    try {
381                        String path = cpath;
382                        if (cpath.endsWith("*")) {
383                            // call getCanonicalPath with a path with wildcard character
384                            // replaced to avoid calling it with paths that are
385                            // intended to match all entries in a directory
386                            path = path.substring(0, path.length() - 1) + "-";
387                            path = new File(path).getCanonicalPath();
388                            return path.substring(0, path.length() - 1) + "*";
389                        } else {
390                            return new File(path).getCanonicalPath();
391                        }
392                    } catch (IOException ioe) {
393                        return cpath;
394                    }
395                }
396            });
397
398            int len = cpath.length();
399            char last = ((len > 0) ? cpath.charAt(len - 1) : 0);
400
401            if (last == RECURSIVE_CHAR &&
402                    cpath.charAt(len - 2) == File.separatorChar) {
403                directory = true;
404                recursive = true;
405                cpath = cpath.substring(0, --len);
406            } else if (last == WILD_CHAR &&
407                    cpath.charAt(len - 2) == File.separatorChar) {
408                directory = true;
409                //recursive = false;
410                cpath = cpath.substring(0, --len);
411            } else {
412                // overkill since they are initialized to false, but
413                // commented out here to remind us...
414                //directory = false;
415                //recursive = false;
416            }
417
418            // XXX: at this point the path should be absolute. die if it isn't?
419        }
420    }
421
422    /**
423     * Creates a new FilePermission object with the specified actions.
424     * <i>path</i> is the pathname of a file or directory, and <i>actions</i>
425     * contains a comma-separated list of the desired actions granted on the
426     * file or directory. Possible actions are
427     * "read", "write", "execute", "delete", and "readlink".
428     *
429     * <p>A pathname that ends in "/*" (where "/" is
430     * the file separator character, <code>File.separatorChar</code>)
431     * indicates all the files and directories contained in that directory.
432     * A pathname that ends with "/-" indicates (recursively) all files and
433     * subdirectories contained in that directory. The special pathname
434     * {@literal "<<ALL FILES>>"} matches any file.
435     *
436     * <p>A pathname consisting of a single "*" indicates all the files
437     * in the current directory, while a pathname consisting of a single "-"
438     * indicates all the files in the current directory and
439     * (recursively) all files and subdirectories contained in the current
440     * directory.
441     *
442     * <p>A pathname containing an empty string represents an empty path.
443     *
444     * @implNote In this implementation, the
445     * {@code jdk.io.permissionsUseCanonicalPath} system property dictates how
446     * the {@code path} argument is processed and stored.
447     * <P>
448     * If the value of the system property is set to {@code true}, {@code path}
449     * is canonicalized and stored as a String object named {@code cpath}.
450     * This means a relative path is converted to an absolute path, a Windows
451     * DOS-style 8.3 path is expanded to a long path, and a symbolic link is
452     * resolved to its target, etc.
453     * <P>
454     * If the value of the system property is set to {@code false}, {@code path}
455     * is converted to a {@link java.nio.file.Path} object named {@code npath}
456     * after {@link Path#normalize() normalization}. No canonicalization is
457     * performed which means the underlying file system is not accessed.
458     * If an {@link InvalidPathException} is thrown during the conversion,
459     * this {@code FilePermission} will be labeled as invalid.
460     * <P>
461     * In either case, the "*" or "-" character at the end of a wildcard
462     * {@code path} is removed before canonicalization or normalization.
463     * It is stored in a separate wildcard flag field.
464     * <P>
465     * The default value of the {@code jdk.io.permissionsUseCanonicalPath}
466     * system property is {@code false} in this implementation.
467     *
468     * @param path the pathname of the file/directory.
469     * @param actions the action string.
470     *
471     * @throws IllegalArgumentException
472     *          If actions is <code>null</code>, empty or contains an action
473     *          other than the specified possible actions.
474     */
475    public FilePermission(String path, String actions) {
476        super(path);
477        init(getMask(actions));
478    }
479
480    /**
481     * Creates a new FilePermission object using an action mask.
482     * More efficient than the FilePermission(String, String) constructor.
483     * Can be used from within
484     * code that needs to create a FilePermission object to pass into the
485     * <code>implies</code> method.
486     *
487     * @param path the pathname of the file/directory.
488     * @param mask the action mask to use.
489     */
490    // package private for use by the FilePermissionCollection add method
491    FilePermission(String path, int mask) {
492        super(path);
493        init(mask);
494    }
495
496    /**
497     * Checks if this FilePermission object "implies" the specified permission.
498     * <P>
499     * More specifically, this method returns true if:
500     * <ul>
501     * <li> <i>p</i> is an instanceof FilePermission,
502     * <li> <i>p</i>'s actions are a proper subset of this
503     * object's actions, and
504     * <li> <i>p</i>'s pathname is implied by this object's
505     *      pathname. For example, "/tmp/*" implies "/tmp/foo", since
506     *      "/tmp/*" encompasses all files in the "/tmp" directory,
507     *      including the one named "foo".
508     * </ul>
509     * <P>
510     * Precisely, a simple pathname implies another simple pathname
511     * if and only if they are equal. A simple pathname never implies
512     * a wildcard pathname. A wildcard pathname implies another wildcard
513     * pathname if and only if all simple pathnames implied by the latter
514     * are implied by the former. A wildcard pathname implies a simple
515     * pathname if and only if
516     * <ul>
517     *     <li>if the wildcard flag is "*", the simple pathname's path
518     *     must be right inside the wildcard pathname's path.
519     *     <li>if the wildcard flag is "-", the simple pathname's path
520     *     must be recursively inside the wildcard pathname's path.
521     * </ul>
522     * <P>
523     * {@literal "<<ALL FILES>>"} implies every other pathname. No pathname,
524     * except for {@literal "<<ALL FILES>>"} itself, implies
525     * {@literal "<<ALL FILES>>"}.
526     *
527     * @implNote
528     * If {@code jdk.io.permissionsUseCanonicalPath} is {@code true}, a
529     * simple {@code cpath} is inside a wildcard {@code cpath} if and only if
530     * after removing the base name (the last name in the pathname's name
531     * sequence) from the former the remaining part equals to the latter,
532     * a simple {@code cpath} is recursively inside a wildcard {@code cpath}
533     * if and only if the former starts with the latter.
534     * <p>
535     * If {@code jdk.io.permissionsUseCanonicalPath} is {@code false}, a
536     * simple {@code npath} is inside a wildcard {@code npath} if and only if
537     * {@code  simple_npath.relativize(wildcard_npath)} is exactly "..",
538     * a simple {@code npath} is recursively inside a wildcard {@code npath}
539     * if and only if {@code simple_npath.relativize(wildcard_npath)} is a
540     * series of one or more "..". This means "/-" implies "/foo" but not "foo".
541     * <p>
542     * An invalid {@code FilePermission} does not imply any object except for
543     * itself. An invalid {@code FilePermission} is not implied by any object
544     * except for itself or a {@code FilePermission} on
545     * {@literal "<<ALL FILES>>"} whose actions is a superset of this
546     * invalid {@code FilePermission}. Even if two {@code FilePermission}
547     * are created with the same invalid path, one does not imply the other.
548     *
549     * @param p the permission to check against.
550     *
551     * @return <code>true</code> if the specified permission is not
552     *                  <code>null</code> and is implied by this object,
553     *                  <code>false</code> otherwise.
554     */
555    @Override
556    public boolean implies(Permission p) {
557        if (!(p instanceof FilePermission))
558            return false;
559
560        FilePermission that = (FilePermission) p;
561
562        // we get the effective mask. i.e., the "and" of this and that.
563        // They must be equal to that.mask for implies to return true.
564
565        return ((this.mask & that.mask) == that.mask) && impliesIgnoreMask(that);
566    }
567
568    /**
569     * Checks if the Permission's actions are a proper subset of the
570     * this object's actions. Returns the effective mask iff the
571     * this FilePermission's path also implies that FilePermission's path.
572     *
573     * @param that the FilePermission to check against.
574     * @return the effective mask
575     */
576    boolean impliesIgnoreMask(FilePermission that) {
577        if (FilePermCompat.nb) {
578            if (this == that) {
579                return true;
580            }
581            if (allFiles) {
582                return true;
583            }
584            if (this.invalid || that.invalid) {
585                return false;
586            }
587            if (that.allFiles) {
588                return false;
589            }
590            // Left at least same level of wildness as right
591            if ((this.recursive && that.recursive) != that.recursive
592                    || (this.directory && that.directory) != that.directory) {
593                return false;
594            }
595            // Same npath is good as long as both or neither are directories
596            if (this.npath.equals(that.npath)
597                    && this.directory == that.directory) {
598                return true;
599            }
600            int diff = containsPath(this.npath, that.npath);
601            // Right inside left is good if recursive
602            if (diff >= 1 && recursive) {
603                return true;
604            }
605            // Right right inside left if it is element in set
606            if (diff == 1 && directory && !that.directory) {
607                return true;
608            }
609
610            // Hack: if a npath2 field exists, apply the same checks
611            // on it as a fallback.
612            if (this.npath2 != null) {
613                if (this.npath2.equals(that.npath)
614                        && this.directory == that.directory) {
615                    return true;
616                }
617                diff = containsPath(this.npath2, that.npath);
618                if (diff >= 1 && recursive) {
619                    return true;
620                }
621                if (diff == 1 && directory && !that.directory) {
622                    return true;
623                }
624            }
625
626            return false;
627        } else {
628            if (this.directory) {
629                if (this.recursive) {
630                    // make sure that.path is longer then path so
631                    // something like /foo/- does not imply /foo
632                    if (that.directory) {
633                        return (that.cpath.length() >= this.cpath.length()) &&
634                                that.cpath.startsWith(this.cpath);
635                    } else {
636                        return ((that.cpath.length() > this.cpath.length()) &&
637                                that.cpath.startsWith(this.cpath));
638                    }
639                } else {
640                    if (that.directory) {
641                        // if the permission passed in is a directory
642                        // specification, make sure that a non-recursive
643                        // permission (i.e., this object) can't imply a recursive
644                        // permission.
645                        if (that.recursive)
646                            return false;
647                        else
648                            return (this.cpath.equals(that.cpath));
649                    } else {
650                        int last = that.cpath.lastIndexOf(File.separatorChar);
651                        if (last == -1)
652                            return false;
653                        else {
654                            // this.cpath.equals(that.cpath.substring(0, last+1));
655                            // Use regionMatches to avoid creating new string
656                            return (this.cpath.length() == (last + 1)) &&
657                                    this.cpath.regionMatches(0, that.cpath, 0, last + 1);
658                        }
659                    }
660                }
661            } else if (that.directory) {
662                // if this is NOT recursive/wildcarded,
663                // do not let it imply a recursive/wildcarded permission
664                return false;
665            } else {
666                return (this.cpath.equals(that.cpath));
667            }
668        }
669    }
670
671    /**
672     * Returns the depth between an outer path p1 and an inner path p2. -1
673     * is returned if
674     *
675     * - p1 does not contains p2.
676     * - this is not decidable. For example, p1="../x", p2="y".
677     * - the depth is not decidable. For example, p1="/", p2="x".
678     *
679     * This method can return 2 if the depth is greater than 2.
680     *
681     * @param p1 the expected outer path, normalized
682     * @param p2 the expected inner path, normalized
683     * @return the depth in between
684     */
685    private static int containsPath(Path p1, Path p2) {
686
687        // Two paths must have the same root. For example,
688        // there is no contains relation between any two of
689        // "/x", "x", "C:/x", "C:x", and "//host/share/x".
690        if (!Objects.equals(p1.getRoot(), p2.getRoot())) {
691            return -1;
692        }
693
694        // Empty path (i.e. "." or "") is a strange beast,
695        // because its getNameCount()==1 but getName(0) is null.
696        // It's better to deal with it separately.
697        if (p1.equals(EMPTY_PATH)) {
698            if (p2.equals(EMPTY_PATH)) {
699                return 0;
700            } else if (p2.getName(0).equals(DOTDOT_PATH)) {
701                // "." contains p2 iif p2 has no "..". Since a
702                // a normalized path can only have 0 or more
703                // ".." at the beginning. We only need to look
704                // at the head.
705                return -1;
706            } else {
707                // and the distance is p2's name count. i.e.
708                // 3 between "." and "a/b/c".
709                return p2.getNameCount();
710            }
711        } else if (p2.equals(EMPTY_PATH)) {
712            int c1 = p1.getNameCount();
713            if (!p1.getName(c1 - 1).equals(DOTDOT_PATH)) {
714                // "." is inside p1 iif p1 is 1 or more "..".
715                // For the same reason above, we only need to
716                // look at the tail.
717                return -1;
718            }
719            // and the distance is the count of ".."
720            return c1;
721        }
722
723        // Good. No more empty paths.
724
725        // Common heads are removed
726
727        int c1 = p1.getNameCount();
728        int c2 = p2.getNameCount();
729
730        int n = Math.min(c1, c2);
731        int i = 0;
732        while (i < n) {
733            if (!p1.getName(i).equals(p2.getName(i)))
734                break;
735            i++;
736        }
737
738        // for p1 containing p2, p1 must be 0-or-more "..",
739        // and p2 cannot have "..". For the same reason, we only
740        // check tail of p1 and head of p2.
741        if (i < c1 && !p1.getName(c1 - 1).equals(DOTDOT_PATH)) {
742            return -1;
743        }
744
745        if (i < c2 && p2.getName(i).equals(DOTDOT_PATH)) {
746            return -1;
747        }
748
749        // and the distance is the name counts added (after removing
750        // the common heads).
751
752        // For example: p1 = "../../..", p2 = "../a".
753        // After removing the common heads, they become "../.." and "a",
754        // and the distance is (3-1)+(2-1) = 3.
755        return c1 - i + c2 - i;
756    }
757
758    /**
759     * Checks two FilePermission objects for equality. Checks that <i>obj</i> is
760     * a FilePermission, and has the same pathname and actions as this object.
761     *
762     * @implNote More specifically, two pathnames are the same if and only if
763     * they have the same wildcard flag and their {@code cpath}
764     * (if {@code jdk.io.permissionsUseCanonicalPath} is {@code true}) or
765     * {@code npath} (if {@code jdk.io.permissionsUseCanonicalPath}
766     * is {@code false}) are equal. Or they are both {@literal "<<ALL FILES>>"}.
767     * <p>
768     * When {@code jdk.io.permissionsUseCanonicalPath} is {@code false}, an
769     * invalid {@code FilePermission} does not equal to any object except
770     * for itself, even if they are created using the same invalid path.
771     *
772     * @param obj the object we are testing for equality with this object.
773     * @return <code>true</code> if obj is a FilePermission, and has the same
774     *          pathname and actions as this FilePermission object,
775     *          <code>false</code> otherwise.
776     */
777    @Override
778    public boolean equals(Object obj) {
779        if (obj == this)
780            return true;
781
782        if (! (obj instanceof FilePermission))
783            return false;
784
785        FilePermission that = (FilePermission) obj;
786
787        if (FilePermCompat.nb) {
788            if (this.invalid || that.invalid) {
789                return false;
790            }
791            return (this.mask == that.mask) &&
792                    (this.allFiles == that.allFiles) &&
793                    this.npath.equals(that.npath) &&
794                    Objects.equals(npath2, that.npath2) &&
795                    (this.directory == that.directory) &&
796                    (this.recursive == that.recursive);
797        } else {
798            return (this.mask == that.mask) &&
799                    this.cpath.equals(that.cpath) &&
800                    (this.directory == that.directory) &&
801                    (this.recursive == that.recursive);
802        }
803    }
804
805    /**
806     * Returns the hash code value for this object.
807     *
808     * @return a hash code value for this object.
809     */
810    @Override
811    public int hashCode() {
812        if (FilePermCompat.nb) {
813            return Objects.hash(
814                    mask, allFiles, directory, recursive, npath, npath2, invalid);
815        } else {
816            return 0;
817        }
818    }
819
820    /**
821     * Converts an actions String to an actions mask.
822     *
823     * @param actions the action string.
824     * @return the actions mask.
825     */
826    private static int getMask(String actions) {
827        int mask = NONE;
828
829        // Null action valid?
830        if (actions == null) {
831            return mask;
832        }
833
834        // Use object identity comparison against known-interned strings for
835        // performance benefit (these values are used heavily within the JDK).
836        if (actions == SecurityConstants.FILE_READ_ACTION) {
837            return READ;
838        } else if (actions == SecurityConstants.FILE_WRITE_ACTION) {
839            return WRITE;
840        } else if (actions == SecurityConstants.FILE_EXECUTE_ACTION) {
841            return EXECUTE;
842        } else if (actions == SecurityConstants.FILE_DELETE_ACTION) {
843            return DELETE;
844        } else if (actions == SecurityConstants.FILE_READLINK_ACTION) {
845            return READLINK;
846        }
847
848        char[] a = actions.toCharArray();
849
850        int i = a.length - 1;
851        if (i < 0)
852            return mask;
853
854        while (i != -1) {
855            char c;
856
857            // skip whitespace
858            while ((i!=-1) && ((c = a[i]) == ' ' ||
859                               c == '\r' ||
860                               c == '\n' ||
861                               c == '\f' ||
862                               c == '\t'))
863                i--;
864
865            // check for the known strings
866            int matchlen;
867
868            if (i >= 3 && (a[i-3] == 'r' || a[i-3] == 'R') &&
869                          (a[i-2] == 'e' || a[i-2] == 'E') &&
870                          (a[i-1] == 'a' || a[i-1] == 'A') &&
871                          (a[i] == 'd' || a[i] == 'D'))
872            {
873                matchlen = 4;
874                mask |= READ;
875
876            } else if (i >= 4 && (a[i-4] == 'w' || a[i-4] == 'W') &&
877                                 (a[i-3] == 'r' || a[i-3] == 'R') &&
878                                 (a[i-2] == 'i' || a[i-2] == 'I') &&
879                                 (a[i-1] == 't' || a[i-1] == 'T') &&
880                                 (a[i] == 'e' || a[i] == 'E'))
881            {
882                matchlen = 5;
883                mask |= WRITE;
884
885            } else if (i >= 6 && (a[i-6] == 'e' || a[i-6] == 'E') &&
886                                 (a[i-5] == 'x' || a[i-5] == 'X') &&
887                                 (a[i-4] == 'e' || a[i-4] == 'E') &&
888                                 (a[i-3] == 'c' || a[i-3] == 'C') &&
889                                 (a[i-2] == 'u' || a[i-2] == 'U') &&
890                                 (a[i-1] == 't' || a[i-1] == 'T') &&
891                                 (a[i] == 'e' || a[i] == 'E'))
892            {
893                matchlen = 7;
894                mask |= EXECUTE;
895
896            } else if (i >= 5 && (a[i-5] == 'd' || a[i-5] == 'D') &&
897                                 (a[i-4] == 'e' || a[i-4] == 'E') &&
898                                 (a[i-3] == 'l' || a[i-3] == 'L') &&
899                                 (a[i-2] == 'e' || a[i-2] == 'E') &&
900                                 (a[i-1] == 't' || a[i-1] == 'T') &&
901                                 (a[i] == 'e' || a[i] == 'E'))
902            {
903                matchlen = 6;
904                mask |= DELETE;
905
906            } else if (i >= 7 && (a[i-7] == 'r' || a[i-7] == 'R') &&
907                                 (a[i-6] == 'e' || a[i-6] == 'E') &&
908                                 (a[i-5] == 'a' || a[i-5] == 'A') &&
909                                 (a[i-4] == 'd' || a[i-4] == 'D') &&
910                                 (a[i-3] == 'l' || a[i-3] == 'L') &&
911                                 (a[i-2] == 'i' || a[i-2] == 'I') &&
912                                 (a[i-1] == 'n' || a[i-1] == 'N') &&
913                                 (a[i] == 'k' || a[i] == 'K'))
914            {
915                matchlen = 8;
916                mask |= READLINK;
917
918            } else {
919                // parse error
920                throw new IllegalArgumentException(
921                        "invalid permission: " + actions);
922            }
923
924            // make sure we didn't just match the tail of a word
925            // like "ackbarfaccept".  Also, skip to the comma.
926            boolean seencomma = false;
927            while (i >= matchlen && !seencomma) {
928                switch(a[i-matchlen]) {
929                case ',':
930                    seencomma = true;
931                    break;
932                case ' ': case '\r': case '\n':
933                case '\f': case '\t':
934                    break;
935                default:
936                    throw new IllegalArgumentException(
937                            "invalid permission: " + actions);
938                }
939                i--;
940            }
941
942            // point i at the location of the comma minus one (or -1).
943            i -= matchlen;
944        }
945
946        return mask;
947    }
948
949    /**
950     * Return the current action mask. Used by the FilePermissionCollection.
951     *
952     * @return the actions mask.
953     */
954    int getMask() {
955        return mask;
956    }
957
958    /**
959     * Return the canonical string representation of the actions.
960     * Always returns present actions in the following order:
961     * read, write, execute, delete, readlink.
962     *
963     * @return the canonical string representation of the actions.
964     */
965    private static String getActions(int mask) {
966        StringJoiner sj = new StringJoiner(",");
967
968        if ((mask & READ) == READ) {
969            sj.add("read");
970        }
971        if ((mask & WRITE) == WRITE) {
972            sj.add("write");
973        }
974        if ((mask & EXECUTE) == EXECUTE) {
975            sj.add("execute");
976        }
977        if ((mask & DELETE) == DELETE) {
978            sj.add("delete");
979        }
980        if ((mask & READLINK) == READLINK) {
981            sj.add("readlink");
982        }
983
984        return sj.toString();
985    }
986
987    /**
988     * Returns the "canonical string representation" of the actions.
989     * That is, this method always returns present actions in the following order:
990     * read, write, execute, delete, readlink. For example, if this FilePermission
991     * object allows both write and read actions, a call to <code>getActions</code>
992     * will return the string "read,write".
993     *
994     * @return the canonical string representation of the actions.
995     */
996    @Override
997    public String getActions() {
998        if (actions == null)
999            actions = getActions(this.mask);
1000
1001        return actions;
1002    }
1003
1004    /**
1005     * Returns a new PermissionCollection object for storing FilePermission
1006     * objects.
1007     * <p>
1008     * FilePermission objects must be stored in a manner that allows them
1009     * to be inserted into the collection in any order, but that also enables the
1010     * PermissionCollection <code>implies</code>
1011     * method to be implemented in an efficient (and consistent) manner.
1012     *
1013     * <p>For example, if you have two FilePermissions:
1014     * <OL>
1015     * <LI>  <code>"/tmp/-", "read"</code>
1016     * <LI>  <code>"/tmp/scratch/foo", "write"</code>
1017     * </OL>
1018     *
1019     * <p>and you are calling the <code>implies</code> method with the FilePermission:
1020     *
1021     * <pre>
1022     *   "/tmp/scratch/foo", "read,write",
1023     * </pre>
1024     *
1025     * then the <code>implies</code> function must
1026     * take into account both the "/tmp/-" and "/tmp/scratch/foo"
1027     * permissions, so the effective permission is "read,write",
1028     * and <code>implies</code> returns true. The "implies" semantics for
1029     * FilePermissions are handled properly by the PermissionCollection object
1030     * returned by this <code>newPermissionCollection</code> method.
1031     *
1032     * @return a new PermissionCollection object suitable for storing
1033     * FilePermissions.
1034     */
1035    @Override
1036    public PermissionCollection newPermissionCollection() {
1037        return new FilePermissionCollection();
1038    }
1039
1040    /**
1041     * WriteObject is called to save the state of the FilePermission
1042     * to a stream. The actions are serialized, and the superclass
1043     * takes care of the name.
1044     */
1045    private void writeObject(ObjectOutputStream s)
1046        throws IOException
1047    {
1048        // Write out the actions. The superclass takes care of the name
1049        // call getActions to make sure actions field is initialized
1050        if (actions == null)
1051            getActions();
1052        s.defaultWriteObject();
1053    }
1054
1055    /**
1056     * readObject is called to restore the state of the FilePermission from
1057     * a stream.
1058     */
1059    private void readObject(ObjectInputStream s)
1060         throws IOException, ClassNotFoundException
1061    {
1062        // Read in the actions, then restore everything else by calling init.
1063        s.defaultReadObject();
1064        init(getMask(actions));
1065    }
1066
1067    /**
1068     * Create a cloned FilePermission with a different actions.
1069     * @param effective the new actions
1070     * @return a new object
1071     */
1072    FilePermission withNewActions(int effective) {
1073        return new FilePermission(this.getName(),
1074                this,
1075                this.npath,
1076                this.npath2,
1077                effective,
1078                null);
1079    }
1080}
1081
1082/**
1083 * A FilePermissionCollection stores a set of FilePermission permissions.
1084 * FilePermission objects
1085 * must be stored in a manner that allows them to be inserted in any
1086 * order, but enable the implies function to evaluate the implies
1087 * method.
1088 * For example, if you have two FilePermissions:
1089 * <OL>
1090 * <LI> "/tmp/-", "read"
1091 * <LI> "/tmp/scratch/foo", "write"
1092 * </OL>
1093 * And you are calling the implies function with the FilePermission:
1094 * "/tmp/scratch/foo", "read,write", then the implies function must
1095 * take into account both the /tmp/- and /tmp/scratch/foo
1096 * permissions, so the effective permission is "read,write".
1097 *
1098 * @see java.security.Permission
1099 * @see java.security.Permissions
1100 * @see java.security.PermissionCollection
1101 *
1102 *
1103 * @author Marianne Mueller
1104 * @author Roland Schemers
1105 *
1106 * @serial include
1107 *
1108 */
1109
1110final class FilePermissionCollection extends PermissionCollection
1111    implements Serializable
1112{
1113    // Not serialized; see serialization section at end of class
1114    private transient ConcurrentHashMap<String, Permission> perms;
1115
1116    /**
1117     * Create an empty FilePermissionCollection object.
1118     */
1119    public FilePermissionCollection() {
1120        perms = new ConcurrentHashMap<>();
1121    }
1122
1123    /**
1124     * Adds a permission to the FilePermissionCollection. The key for the hash is
1125     * permission.path.
1126     *
1127     * @param permission the Permission object to add.
1128     *
1129     * @exception IllegalArgumentException - if the permission is not a
1130     *                                       FilePermission
1131     *
1132     * @exception SecurityException - if this FilePermissionCollection object
1133     *                                has been marked readonly
1134     */
1135    @Override
1136    public void add(Permission permission) {
1137        if (! (permission instanceof FilePermission))
1138            throw new IllegalArgumentException("invalid permission: "+
1139                                               permission);
1140        if (isReadOnly())
1141            throw new SecurityException(
1142                "attempt to add a Permission to a readonly PermissionCollection");
1143
1144        FilePermission fp = (FilePermission)permission;
1145
1146        // Add permission to map if it is absent, or replace with new
1147        // permission if applicable.
1148        perms.merge(fp.getName(), fp,
1149            new java.util.function.BiFunction<>() {
1150                @Override
1151                public Permission apply(Permission existingVal,
1152                                        Permission newVal) {
1153                    int oldMask = ((FilePermission)existingVal).getMask();
1154                    int newMask = ((FilePermission)newVal).getMask();
1155                    if (oldMask != newMask) {
1156                        int effective = oldMask | newMask;
1157                        if (effective == newMask) {
1158                            return newVal;
1159                        }
1160                        if (effective != oldMask) {
1161                            return ((FilePermission)newVal)
1162                                    .withNewActions(effective);
1163                        }
1164                    }
1165                    return existingVal;
1166                }
1167            }
1168        );
1169    }
1170
1171    /**
1172     * Check and see if this set of permissions implies the permissions
1173     * expressed in "permission".
1174     *
1175     * @param permission the Permission object to compare
1176     *
1177     * @return true if "permission" is a proper subset of a permission in
1178     * the set, false if not.
1179     */
1180    @Override
1181    public boolean implies(Permission permission) {
1182        if (! (permission instanceof FilePermission))
1183            return false;
1184
1185        FilePermission fperm = (FilePermission) permission;
1186
1187        int desired = fperm.getMask();
1188        int effective = 0;
1189        int needed = desired;
1190
1191        for (Permission perm : perms.values()) {
1192            FilePermission fp = (FilePermission)perm;
1193            if (((needed & fp.getMask()) != 0) && fp.impliesIgnoreMask(fperm)) {
1194                effective |= fp.getMask();
1195                if ((effective & desired) == desired) {
1196                    return true;
1197                }
1198                needed = (desired ^ effective);
1199            }
1200        }
1201        return false;
1202    }
1203
1204    /**
1205     * Returns an enumeration of all the FilePermission objects in the
1206     * container.
1207     *
1208     * @return an enumeration of all the FilePermission objects.
1209     */
1210    @Override
1211    public Enumeration<Permission> elements() {
1212        return perms.elements();
1213    }
1214
1215    private static final long serialVersionUID = 2202956749081564585L;
1216
1217    // Need to maintain serialization interoperability with earlier releases,
1218    // which had the serializable field:
1219    //    private Vector permissions;
1220
1221    /**
1222     * @serialField permissions java.util.Vector
1223     *     A list of FilePermission objects.
1224     */
1225    private static final ObjectStreamField[] serialPersistentFields = {
1226        new ObjectStreamField("permissions", Vector.class),
1227    };
1228
1229    /**
1230     * @serialData "permissions" field (a Vector containing the FilePermissions).
1231     */
1232    /*
1233     * Writes the contents of the perms field out as a Vector for
1234     * serialization compatibility with earlier releases.
1235     */
1236    private void writeObject(ObjectOutputStream out) throws IOException {
1237        // Don't call out.defaultWriteObject()
1238
1239        // Write out Vector
1240        Vector<Permission> permissions = new Vector<>(perms.values());
1241
1242        ObjectOutputStream.PutField pfields = out.putFields();
1243        pfields.put("permissions", permissions);
1244        out.writeFields();
1245    }
1246
1247    /*
1248     * Reads in a Vector of FilePermissions and saves them in the perms field.
1249     */
1250    private void readObject(ObjectInputStream in)
1251        throws IOException, ClassNotFoundException
1252    {
1253        // Don't call defaultReadObject()
1254
1255        // Read in serialized fields
1256        ObjectInputStream.GetField gfields = in.readFields();
1257
1258        // Get the one we want
1259        @SuppressWarnings("unchecked")
1260        Vector<Permission> permissions = (Vector<Permission>)gfields.get("permissions", null);
1261        perms = new ConcurrentHashMap<>(permissions.size());
1262        for (Permission perm : permissions) {
1263            perms.put(perm.getName(), perm);
1264        }
1265    }
1266}
1267