1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.connector.basic;
20
21 import java.io.IOException;
22 import java.io.UncheckedIOException;
23 import java.net.URI;
24 import java.nio.file.Path;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.Map;
28
29 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
30 import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
31 import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind;
32 import org.eclipse.aether.spi.connector.layout.RepositoryLayout.ChecksumLocation;
33 import org.eclipse.aether.spi.io.ChecksumProcessor;
34 import org.eclipse.aether.spi.io.PathProcessor;
35 import org.eclipse.aether.transfer.ChecksumFailureException;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39
40
41
42 final class ChecksumValidator {
43
44 interface ChecksumFetcher {
45
46
47
48
49
50 boolean fetchChecksum(URI remote, Path local) throws Exception;
51 }
52
53 private static final Logger LOGGER = LoggerFactory.getLogger(ChecksumValidator.class);
54
55 private final Path dataPath;
56
57 private final Collection<ChecksumAlgorithmFactory> checksumAlgorithmFactories;
58
59 private final PathProcessor pathProcessor;
60
61 private final ChecksumProcessor checksumProcessor;
62
63 private final ChecksumFetcher checksumFetcher;
64
65 private final ChecksumPolicy checksumPolicy;
66
67 private final Map<String, String> providedChecksums;
68
69 private final Collection<ChecksumLocation> checksumLocations;
70
71 private final Map<Path, String> checksumExpectedValues;
72
73 @SuppressWarnings("checkstyle:parameternumber")
74 ChecksumValidator(
75 Path dataPath,
76 Collection<ChecksumAlgorithmFactory> checksumAlgorithmFactories,
77 PathProcessor pathProcessor,
78 ChecksumProcessor checksumProcessor,
79 ChecksumFetcher checksumFetcher,
80 ChecksumPolicy checksumPolicy,
81 Map<String, String> providedChecksums,
82 Collection<ChecksumLocation> checksumLocations) {
83 this.dataPath = dataPath;
84 this.checksumAlgorithmFactories = checksumAlgorithmFactories;
85 this.pathProcessor = pathProcessor;
86 this.checksumProcessor = checksumProcessor;
87 this.checksumFetcher = checksumFetcher;
88 this.checksumPolicy = checksumPolicy;
89 this.providedChecksums = providedChecksums;
90 this.checksumLocations = checksumLocations;
91 this.checksumExpectedValues = new HashMap<>();
92 }
93
94 public ChecksumCalculator newChecksumCalculator(Path targetFile) {
95 if (checksumPolicy != null) {
96 return ChecksumCalculator.newInstance(targetFile, checksumAlgorithmFactories);
97 }
98 return null;
99 }
100
101 public void validate(Map<String, ?> actualChecksums, Map<String, ?> includedChecksums)
102 throws ChecksumFailureException {
103 if (checksumPolicy == null) {
104 return;
105 }
106 if (providedChecksums != null && validateChecksums(actualChecksums, ChecksumKind.PROVIDED, providedChecksums)) {
107 return;
108 }
109 if (includedChecksums != null
110 && validateChecksums(actualChecksums, ChecksumKind.REMOTE_INCLUDED, includedChecksums)) {
111 return;
112 }
113 if (!checksumLocations.isEmpty()) {
114 if (validateExternalChecksums(actualChecksums)) {
115 return;
116 }
117 checksumPolicy.onNoMoreChecksums();
118 }
119 }
120
121 private boolean validateChecksums(Map<String, ?> actualChecksums, ChecksumKind kind, Map<String, ?> checksums)
122 throws ChecksumFailureException {
123 boolean accepted = false;
124 boolean rejected = false;
125 for (Map.Entry<String, ?> entry : checksums.entrySet()) {
126 String algo = entry.getKey();
127 Object calculated = actualChecksums.get(algo);
128 if (!(calculated instanceof String)) {
129 continue;
130 }
131 ChecksumAlgorithmFactory checksumAlgorithmFactory = checksumAlgorithmFactories.stream()
132 .filter(a -> a.getName().equals(algo))
133 .findFirst()
134 .orElse(null);
135 if (checksumAlgorithmFactory == null) {
136 continue;
137 }
138
139 String actual = String.valueOf(calculated);
140 String expected = entry.getValue().toString();
141 checksumExpectedValues.put(getChecksumPath(checksumAlgorithmFactory), expected);
142
143 if (!isEqualChecksum(expected, actual)) {
144
145
146
147
148 rejected = true;
149 checksumPolicy.onChecksumMismatch(
150 checksumAlgorithmFactory.getName(),
151 kind,
152 ChecksumFailureException.mismatch(expected, kind.name(), actual));
153 } else if (checksumPolicy.onChecksumMatch(checksumAlgorithmFactory.getName(), kind)) {
154 accepted = true;
155 }
156 }
157 return accepted && !rejected;
158 }
159
160 private boolean validateExternalChecksums(Map<String, ?> actualChecksums) throws ChecksumFailureException {
161 boolean rejected = false;
162 for (ChecksumLocation checksumLocation : checksumLocations) {
163 ChecksumAlgorithmFactory factory = checksumLocation.getChecksumAlgorithmFactory();
164 Object calculated = actualChecksums.get(factory.getName());
165 if (calculated instanceof Exception) {
166 checksumPolicy.onChecksumError(
167 factory.getName(),
168 ChecksumKind.REMOTE_EXTERNAL,
169 ChecksumFailureException.processingFailure(
170 "Checksum calculation problem", (Exception) calculated));
171 continue;
172 }
173 Path checksumFile = getChecksumPath(checksumLocation.getChecksumAlgorithmFactory());
174 try (PathProcessor.TempFile tempFile = pathProcessor.newTempFile()) {
175 Path tmp = tempFile.getPath();
176 try {
177 if (!checksumFetcher.fetchChecksum(checksumLocation.getLocation(), tmp)) {
178 continue;
179 }
180 } catch (Exception e) {
181 checksumPolicy.onChecksumError(
182 factory.getName(),
183 ChecksumKind.REMOTE_EXTERNAL,
184 ChecksumFailureException.processingFailure("Checksum fetching problem", e));
185 continue;
186 }
187
188 String actual = String.valueOf(calculated);
189 String expected = checksumProcessor.readChecksum(tmp);
190 checksumExpectedValues.put(checksumFile, expected);
191
192 if (!isEqualChecksum(expected, actual)) {
193 rejected = true;
194 checksumPolicy.onChecksumMismatch(
195 factory.getName(),
196 ChecksumKind.REMOTE_EXTERNAL,
197 ChecksumFailureException.mismatch(expected, ChecksumKind.REMOTE_EXTERNAL.name(), actual));
198 } else if (checksumPolicy.onChecksumMatch(factory.getName(), ChecksumKind.REMOTE_EXTERNAL)
199 && !rejected) {
200
201
202 return true;
203 }
204 } catch (IOException e) {
205 checksumPolicy.onChecksumError(
206 factory.getName(),
207 ChecksumKind.REMOTE_EXTERNAL,
208 ChecksumFailureException.processingFailure("Checksum related IO problem", e));
209 }
210 }
211 return false;
212 }
213
214 private static boolean isEqualChecksum(String expected, String actual) {
215 return expected.equalsIgnoreCase(actual);
216 }
217
218 private Path getChecksumPath(ChecksumAlgorithmFactory factory) {
219 return dataPath.getParent().resolve(dataPath.getFileName() + "." + factory.getFileExtension());
220 }
221
222 public void retry() {
223 checksumPolicy.onTransferRetry();
224 checksumExpectedValues.clear();
225 }
226
227 public boolean handle(ChecksumFailureException exception) {
228 return checksumPolicy.onTransferChecksumFailure(exception);
229 }
230
231 public void commit() {
232 for (Map.Entry<Path, String> entry : checksumExpectedValues.entrySet()) {
233 Path checksumFile = entry.getKey();
234 try {
235 checksumProcessor.writeChecksum(checksumFile, entry.getValue());
236 } catch (IOException e) {
237 LOGGER.debug("Failed to write checksum file {}", checksumFile, e);
238 throw new UncheckedIOException(e);
239 }
240 }
241 checksumExpectedValues.clear();
242 }
243 }