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 }