View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  
18  package org.apache.commons.text.lookup;
19  
20  import java.nio.file.Path;
21  import java.nio.file.Paths;
22  import java.util.Arrays;
23  import java.util.List;
24  import java.util.Optional;
25  import java.util.function.Supplier;
26  import java.util.stream.Collectors;
27  
28  /**
29   * A Path fence guards against using paths outside of a "fence" of made of root paths.
30   *
31   * Keep package-private.
32   */
33  final class PathFence {
34  
35      /**
36       * Builds {@link PathFence} instances.
37       */
38      static final class Builder implements Supplier<PathFence> {
39  
40          /** The empty Path array. */
41          private static final Path[] EMPTY = {};
42  
43          /**
44           * A fence is made of root Paths.
45           */
46          private Path[] roots = EMPTY;
47  
48          @Override
49          public PathFence get() {
50              return new PathFence(this);
51          }
52  
53          /**
54           * Sets the paths that delineate this fence.
55           *
56           * @param paths the paths that delineate this fence.
57           * @return {@code this} instance.
58           */
59          Builder setRoots(final Path... paths) {
60              this.roots = paths != null ? paths.clone() : EMPTY;
61              return this;
62          }
63      }
64  
65      /**
66       * Creates a new builder.
67       *
68       * @return a new builder.
69       */
70      static Builder builder() {
71          return new Builder();
72      }
73  
74      /**
75       * A fence is made of Paths guarding Path resolution.
76       */
77      private final List<Path> roots;
78  
79      /**
80       * Constructs a new instance.
81       *
82       * @param builder A builder.
83       */
84      private PathFence(final Builder builder) {
85          this.roots = Arrays.stream(builder.roots).map(Path::toAbsolutePath).collect(Collectors.toList());
86      }
87  
88      /**
89       * Gets a Path for the given file name checking that it resolves within our fence.
90       *
91       * @param fileName the file name to resolve.
92       * @return a fenced Path.
93       * @throws IllegalArgumentException if the file name is not without our fence.
94       */
95      Path apply(final String fileName) {
96          final Path path = Paths.get(fileName);
97          if (roots.isEmpty()) {
98              return path;
99          }
100         final Path pathAbs = path.normalize().toAbsolutePath();
101         final Optional<Path> first = roots.stream().filter(pathAbs::startsWith).findFirst();
102         if (first.isPresent()) {
103             return path;
104         }
105         throw new IllegalArgumentException(String.format("[%s] -> [%s] not in the fence %s", fileName, pathAbs, roots));
106     }
107 
108 }