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 for (Map.Entry<String, ?> entry : checksums.entrySet()) {
124 String algo = entry.getKey();
125 Object calculated = actualChecksums.get(algo);
126 if (!(calculated instanceof String)) {
127 continue;
128 }
129 ChecksumAlgorithmFactory checksumAlgorithmFactory = checksumAlgorithmFactories.stream()
130 .filter(a -> a.getName().equals(algo))
131 .findFirst()
132 .orElse(null);
133 if (checksumAlgorithmFactory == null) {
134 continue;
135 }
136
137 String actual = String.valueOf(calculated);
138 String expected = entry.getValue().toString();
139 checksumExpectedValues.put(getChecksumPath(checksumAlgorithmFactory), expected);
140
141 if (!isEqualChecksum(expected, actual)) {
142 checksumPolicy.onChecksumMismatch(
143 checksumAlgorithmFactory.getName(),
144 kind,
145 new ChecksumFailureException(expected, kind.name(), actual));
146 } else if (checksumPolicy.onChecksumMatch(checksumAlgorithmFactory.getName(), kind)) {
147 return true;
148 }
149 }
150 return false;
151 }
152
153 private boolean validateExternalChecksums(Map<String, ?> actualChecksums) throws ChecksumFailureException {
154 for (ChecksumLocation checksumLocation : checksumLocations) {
155 ChecksumAlgorithmFactory factory = checksumLocation.getChecksumAlgorithmFactory();
156 Object calculated = actualChecksums.get(factory.getName());
157 if (calculated instanceof Exception) {
158 checksumPolicy.onChecksumError(
159 factory.getName(), ChecksumKind.REMOTE_EXTERNAL, new ChecksumFailureException((Exception)
160 calculated));
161 continue;
162 }
163 Path checksumFile = getChecksumPath(checksumLocation.getChecksumAlgorithmFactory());
164 try (PathProcessor.TempFile tempFile = pathProcessor.newTempFile()) {
165 Path tmp = tempFile.getPath();
166 try {
167 if (!checksumFetcher.fetchChecksum(checksumLocation.getLocation(), tmp)) {
168 continue;
169 }
170 } catch (Exception e) {
171 checksumPolicy.onChecksumError(
172 factory.getName(), ChecksumKind.REMOTE_EXTERNAL, new ChecksumFailureException(e));
173 continue;
174 }
175
176 String actual = String.valueOf(calculated);
177 String expected = checksumProcessor.readChecksum(tmp);
178 checksumExpectedValues.put(checksumFile, expected);
179
180 if (!isEqualChecksum(expected, actual)) {
181 checksumPolicy.onChecksumMismatch(
182 factory.getName(),
183 ChecksumKind.REMOTE_EXTERNAL,
184 new ChecksumFailureException(expected, ChecksumKind.REMOTE_EXTERNAL.name(), actual));
185 } else if (checksumPolicy.onChecksumMatch(factory.getName(), ChecksumKind.REMOTE_EXTERNAL)) {
186 return true;
187 }
188 } catch (IOException e) {
189 checksumPolicy.onChecksumError(
190 factory.getName(), ChecksumKind.REMOTE_EXTERNAL, new ChecksumFailureException(e));
191 }
192 }
193 return false;
194 }
195
196 private static boolean isEqualChecksum(String expected, String actual) {
197 return expected.equalsIgnoreCase(actual);
198 }
199
200 private Path getChecksumPath(ChecksumAlgorithmFactory factory) {
201 return dataPath.getParent().resolve(dataPath.getFileName() + "." + factory.getFileExtension());
202 }
203
204 public void retry() {
205 checksumPolicy.onTransferRetry();
206 checksumExpectedValues.clear();
207 }
208
209 public boolean handle(ChecksumFailureException exception) {
210 return checksumPolicy.onTransferChecksumFailure(exception);
211 }
212
213 public void commit() {
214 for (Map.Entry<Path, String> entry : checksumExpectedValues.entrySet()) {
215 Path checksumFile = entry.getKey();
216 try {
217 checksumProcessor.writeChecksum(checksumFile, entry.getValue());
218 } catch (IOException e) {
219 LOGGER.debug("Failed to write checksum file {}", checksumFile, e);
220 throw new UncheckedIOException(e);
221 }
222 }
223 checksumExpectedValues.clear();
224 }
225 }