1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.internal.impl.filter;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.BufferedReader;
26 import java.io.FileNotFoundException;
27 import java.io.IOException;
28 import java.io.UncheckedIOException;
29 import java.nio.charset.StandardCharsets;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.concurrent.ConcurrentHashMap;
37
38 import org.eclipse.aether.RepositorySystemSession;
39 import org.eclipse.aether.artifact.Artifact;
40 import org.eclipse.aether.metadata.Metadata;
41 import org.eclipse.aether.repository.RemoteRepository;
42 import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
43 import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
44 import org.eclipse.aether.spi.connector.layout.RepositoryLayoutProvider;
45 import org.eclipse.aether.transfer.NoRepositoryLayoutException;
46 import org.eclipse.aether.util.ConfigUtils;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 import static java.util.Objects.requireNonNull;
51 import static java.util.stream.Collectors.toList;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 @Singleton
77 @Named(PrefixesRemoteRepositoryFilterSource.NAME)
78 public final class PrefixesRemoteRepositoryFilterSource extends RemoteRepositoryFilterSourceSupport {
79 public static final String NAME = "prefixes";
80
81 private static final String CONFIG_PROPS_PREFIX =
82 RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".";
83
84
85
86
87
88
89
90
91 public static final String CONFIG_PROP_ENABLED = RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME;
92
93
94
95
96
97
98
99
100 public static final String CONFIG_PROP_BASEDIR = CONFIG_PROPS_PREFIX + "basedir";
101
102 public static final String LOCAL_REPO_PREFIX_DIR = ".remoteRepositoryFilters";
103
104 static final String PREFIXES_FILE_PREFIX = "prefixes-";
105
106 static final String PREFIXES_FILE_SUFFIX = ".txt";
107
108 private static final Logger LOGGER = LoggerFactory.getLogger(PrefixesRemoteRepositoryFilterSource.class);
109
110 private final RepositoryLayoutProvider repositoryLayoutProvider;
111
112 private final ConcurrentHashMap<RemoteRepository, Node> prefixes;
113
114 private final ConcurrentHashMap<RemoteRepository, RepositoryLayout> layouts;
115
116 @Inject
117 public PrefixesRemoteRepositoryFilterSource(RepositoryLayoutProvider repositoryLayoutProvider) {
118 this.repositoryLayoutProvider = requireNonNull(repositoryLayoutProvider);
119 this.prefixes = new ConcurrentHashMap<>();
120 this.layouts = new ConcurrentHashMap<>();
121 }
122
123 @Override
124 protected boolean isEnabled(RepositorySystemSession session) {
125 return ConfigUtils.getBoolean(session, false, CONFIG_PROP_ENABLED);
126 }
127
128 @Override
129 public RemoteRepositoryFilter getRemoteRepositoryFilter(RepositorySystemSession session) {
130 if (isEnabled(session)) {
131 return new PrefixesFilter(session, getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, false));
132 }
133 return null;
134 }
135
136
137
138
139
140
141 private RepositoryLayout cacheLayout(RepositorySystemSession session, RemoteRepository remoteRepository) {
142 return layouts.computeIfAbsent(remoteRepository, r -> {
143 try {
144 return repositoryLayoutProvider.newRepositoryLayout(session, remoteRepository);
145 } catch (NoRepositoryLayoutException e) {
146 return null;
147 }
148 });
149 }
150
151
152
153
154 private Node cacheNode(Path basedir, RemoteRepository remoteRepository) {
155 return prefixes.computeIfAbsent(remoteRepository, r -> loadRepositoryPrefixes(basedir, remoteRepository));
156 }
157
158
159
160
161 private Node loadRepositoryPrefixes(Path baseDir, RemoteRepository remoteRepository) {
162 Path filePath = baseDir.resolve(PREFIXES_FILE_PREFIX + remoteRepository.getId() + PREFIXES_FILE_SUFFIX);
163 if (Files.isReadable(filePath)) {
164 try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
165 LOGGER.debug(
166 "Loading prefixes for remote repository {} from file '{}'", remoteRepository.getId(), filePath);
167 Node root = new Node("");
168 String prefix;
169 int lines = 0;
170 while ((prefix = reader.readLine()) != null) {
171 if (!prefix.startsWith("#") && !prefix.trim().isEmpty()) {
172 lines++;
173 Node currentNode = root;
174 for (String element : elementsOf(prefix)) {
175 currentNode = currentNode.addSibling(element);
176 }
177 }
178 }
179 LOGGER.info("Loaded {} prefixes for remote repository {}", lines, remoteRepository.getId());
180 return root;
181 } catch (FileNotFoundException e) {
182
183 } catch (IOException e) {
184 throw new UncheckedIOException(e);
185 }
186 }
187 LOGGER.debug("Prefix file for remote repository {} not found at '{}'", remoteRepository, filePath);
188 return NOT_PRESENT_NODE;
189 }
190
191 private class PrefixesFilter implements RemoteRepositoryFilter {
192 private final RepositorySystemSession session;
193
194 private final Path basedir;
195
196 private PrefixesFilter(RepositorySystemSession session, Path basedir) {
197 this.session = session;
198 this.basedir = basedir;
199 }
200
201 @Override
202 public Result acceptArtifact(RemoteRepository remoteRepository, Artifact artifact) {
203 RepositoryLayout repositoryLayout = cacheLayout(session, remoteRepository);
204 if (repositoryLayout == null) {
205 return new SimpleResult(true, "Unsupported layout: " + remoteRepository);
206 }
207 return acceptPrefix(
208 remoteRepository,
209 repositoryLayout.getLocation(artifact, false).getPath());
210 }
211
212 @Override
213 public Result acceptMetadata(RemoteRepository remoteRepository, Metadata metadata) {
214 RepositoryLayout repositoryLayout = cacheLayout(session, remoteRepository);
215 if (repositoryLayout == null) {
216 return new SimpleResult(true, "Unsupported layout: " + remoteRepository);
217 }
218 return acceptPrefix(
219 remoteRepository,
220 repositoryLayout.getLocation(metadata, false).getPath());
221 }
222
223 private Result acceptPrefix(RemoteRepository remoteRepository, String path) {
224 Node root = cacheNode(basedir, remoteRepository);
225 if (NOT_PRESENT_NODE == root) {
226 return NOT_PRESENT_RESULT;
227 }
228 List<String> prefix = new ArrayList<>();
229 final List<String> pathElements = elementsOf(path);
230 Node currentNode = root;
231 for (String pathElement : pathElements) {
232 prefix.add(pathElement);
233 currentNode = currentNode.getSibling(pathElement);
234 if (currentNode == null || currentNode.isLeaf()) {
235 break;
236 }
237 }
238 if (currentNode != null && currentNode.isLeaf()) {
239 return new SimpleResult(
240 true, "Prefix " + String.join("/", prefix) + " allowed from " + remoteRepository);
241 } else {
242 return new SimpleResult(
243 false, "Prefix " + String.join("/", prefix) + " NOT allowed from " + remoteRepository);
244 }
245 }
246 }
247
248 private static final Node NOT_PRESENT_NODE = new Node("not-present-node");
249
250 private static final RemoteRepositoryFilter.Result NOT_PRESENT_RESULT =
251 new SimpleResult(true, "Prefix file not present");
252
253 private static class Node {
254 private final String name;
255
256 private final HashMap<String, Node> siblings;
257
258 private Node(String name) {
259 this.name = name;
260 this.siblings = new HashMap<>();
261 }
262
263 public String getName() {
264 return name;
265 }
266
267 public boolean isLeaf() {
268 return siblings.isEmpty();
269 }
270
271 public Node addSibling(String name) {
272 Node sibling = siblings.get(name);
273 if (sibling == null) {
274 sibling = new Node(name);
275 siblings.put(name, sibling);
276 }
277 return sibling;
278 }
279
280 public Node getSibling(String name) {
281 return siblings.get(name);
282 }
283 }
284
285 private static List<String> elementsOf(final String path) {
286 return Arrays.stream(path.split("/"))
287 .filter(e -> e != null && !e.isEmpty())
288 .collect(toList());
289 }
290 }