View Javadoc
1   package org.eclipse.aether.connector.basic;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.net.URI;
25  import java.util.Collection;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Map;
29  import java.util.UUID;
30  
31  import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
32  import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
33  import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind;
34  import org.eclipse.aether.spi.connector.layout.RepositoryLayout.ChecksumLocation;
35  import org.eclipse.aether.spi.io.FileProcessor;
36  import org.eclipse.aether.transfer.ChecksumFailureException;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  /**
41   * Performs checksum validation for a downloaded file.
42   */
43  final class ChecksumValidator
44  {
45  
46      interface ChecksumFetcher
47      {
48  
49          /**
50           * Fetches the checksums from remote location into provided local file. The checksums fetched in this way
51           * are of kind {@link ChecksumKind#REMOTE_EXTERNAL}.
52           */
53          boolean fetchChecksum( URI remote, File local )
54              throws Exception;
55  
56      }
57  
58      private static final Logger LOGGER = LoggerFactory.getLogger( ChecksumValidator.class );
59  
60      private final File dataFile;
61  
62      private final Collection<ChecksumAlgorithmFactory> checksumAlgorithmFactories;
63  
64      private final Collection<File> tempFiles;
65  
66      private final FileProcessor fileProcessor;
67  
68      private final ChecksumFetcher checksumFetcher;
69  
70      private final ChecksumPolicy checksumPolicy;
71  
72      private final Map<String, String> providedChecksums;
73  
74      private final Collection<ChecksumLocation> checksumLocations;
75  
76      private final Map<File, Object> checksumFiles;
77  
78      ChecksumValidator( File dataFile,
79                         Collection<ChecksumAlgorithmFactory> checksumAlgorithmFactories,
80                         FileProcessor fileProcessor,
81                         ChecksumFetcher checksumFetcher,
82                         ChecksumPolicy checksumPolicy,
83                         Map<String, String> providedChecksums,
84                         Collection<ChecksumLocation> checksumLocations )
85      {
86          this.dataFile = dataFile;
87          this.checksumAlgorithmFactories = checksumAlgorithmFactories;
88          this.tempFiles = new HashSet<>();
89          this.fileProcessor = fileProcessor;
90          this.checksumFetcher = checksumFetcher;
91          this.checksumPolicy = checksumPolicy;
92          this.providedChecksums = providedChecksums;
93          this.checksumLocations = checksumLocations;
94          this.checksumFiles = new HashMap<>();
95      }
96  
97      public ChecksumCalculator newChecksumCalculator( File targetFile )
98      {
99          if ( checksumPolicy != null )
100         {
101             return ChecksumCalculator.newInstance( targetFile, checksumAlgorithmFactories );
102         }
103         return null;
104     }
105 
106     public void validate( Map<String, ?> actualChecksums, Map<String, ?> includedChecksums )
107         throws ChecksumFailureException
108     {
109         if ( checksumPolicy == null )
110         {
111             return;
112         }
113         if ( providedChecksums != null
114                && validateChecksums( actualChecksums, ChecksumKind.PROVIDED, providedChecksums ) )
115         {
116             return;
117         }
118         if ( includedChecksums != null
119                && validateChecksums( actualChecksums, ChecksumKind.REMOTE_INCLUDED, includedChecksums ) )
120         {
121             return;
122         }
123         if ( !checksumLocations.isEmpty() )
124         {
125             if ( validateExternalChecksums( actualChecksums ) )
126             {
127                 return;
128             }
129             checksumPolicy.onNoMoreChecksums();
130         }
131     }
132 
133     private boolean validateChecksums( Map<String, ?> actualChecksums, ChecksumKind kind, Map<String, ?> checksums )
134         throws ChecksumFailureException
135     {
136         for ( Map.Entry<String, ?> entry : checksums.entrySet() )
137         {
138             String algo = entry.getKey();
139             Object calculated = actualChecksums.get( algo );
140             if ( !( calculated instanceof String ) )
141             {
142                 continue;
143             }
144             ChecksumAlgorithmFactory checksumAlgorithmFactory = checksumAlgorithmFactories.stream()
145                     .filter( a -> a.getName().equals( algo ) )
146                     .findFirst()
147                     .orElse( null );
148             if ( checksumAlgorithmFactory == null )
149             {
150                 continue;
151             }
152 
153             String actual = String.valueOf( calculated );
154             String expected = entry.getValue().toString();
155             checksumFiles.put( getChecksumFile( checksumAlgorithmFactory ), expected );
156 
157             if ( !isEqualChecksum( expected, actual ) )
158             {
159                 checksumPolicy.onChecksumMismatch( checksumAlgorithmFactory.getName(), kind,
160                     new ChecksumFailureException( expected, kind.name(), actual )
161                 );
162             }
163             else if ( checksumPolicy.onChecksumMatch( checksumAlgorithmFactory.getName(), kind ) )
164             {
165                 return true;
166             }
167         }
168         return false;
169     }
170 
171     private boolean validateExternalChecksums( Map<String, ?> actualChecksums )
172         throws ChecksumFailureException
173     {
174         for ( ChecksumLocation checksumLocation : checksumLocations )
175         {
176             ChecksumAlgorithmFactory factory = checksumLocation.getChecksumAlgorithmFactory();
177             Object calculated = actualChecksums.get( factory.getName() );
178             if ( calculated instanceof Exception )
179             {
180                 checksumPolicy.onChecksumError(
181                         factory.getName(), ChecksumKind.REMOTE_EXTERNAL,
182                         new ChecksumFailureException( (Exception) calculated )
183                 );
184                 continue;
185             }
186             try
187             {
188                 File checksumFile = getChecksumFile( checksumLocation.getChecksumAlgorithmFactory() );
189                 File tmp = createTempFile( checksumFile );
190                 try
191                 {
192                     if ( !checksumFetcher.fetchChecksum(
193                         checksumLocation.getLocation(), tmp
194                     ) )
195                     {
196                         continue;
197                     }
198                 }
199                 catch ( Exception e )
200                 {
201                     checksumPolicy.onChecksumError(
202                         factory.getName(), ChecksumKind.REMOTE_EXTERNAL, new ChecksumFailureException( e )
203                     );
204                     continue;
205                 }
206 
207                 String actual = String.valueOf( calculated );
208                 String expected = fileProcessor.readChecksum( tmp );
209                 checksumFiles.put( checksumFile, tmp );
210 
211                 if ( !isEqualChecksum( expected, actual ) )
212                 {
213                     checksumPolicy.onChecksumMismatch(
214                         factory.getName(), ChecksumKind.REMOTE_EXTERNAL,
215                           new ChecksumFailureException( expected, ChecksumKind.REMOTE_EXTERNAL.name(), actual )
216                     );
217                 }
218                 else if ( checksumPolicy.onChecksumMatch( factory.getName(), ChecksumKind.REMOTE_EXTERNAL ) )
219                 {
220                     return true;
221                 }
222             }
223             catch ( IOException e )
224             {
225                 checksumPolicy.onChecksumError(
226                     factory.getName(), ChecksumKind.REMOTE_EXTERNAL, new ChecksumFailureException( e )
227                 );
228             }
229         }
230         return false;
231     }
232 
233     private static boolean isEqualChecksum( String expected, String actual )
234     {
235         return expected.equalsIgnoreCase( actual );
236     }
237 
238     private File getChecksumFile( ChecksumAlgorithmFactory factory )
239     {
240         return new File( dataFile.getPath() + '.' + factory.getFileExtension() );
241     }
242 
243     private File createTempFile( File path )
244         throws IOException
245     {
246         File file =
247             File.createTempFile( path.getName() + "-"
248                 + UUID.randomUUID().toString().replace( "-", "" ).substring( 0, 8 ), ".tmp", path.getParentFile() );
249         tempFiles.add( file );
250         return file;
251     }
252 
253     private void clearTempFiles()
254     {
255         for ( File file : tempFiles )
256         {
257             if ( !file.delete() && file.exists() )
258             {
259                 LOGGER.debug( "Could not delete temporary file {}", file );
260             }
261         }
262         tempFiles.clear();
263     }
264 
265     public void retry()
266     {
267         checksumPolicy.onTransferRetry();
268         checksumFiles.clear();
269         clearTempFiles();
270     }
271 
272     public boolean handle( ChecksumFailureException exception )
273     {
274         return checksumPolicy.onTransferChecksumFailure( exception );
275     }
276 
277     public void commit()
278     {
279         for ( Map.Entry<File, Object> entry : checksumFiles.entrySet() )
280         {
281             File checksumFile = entry.getKey();
282             Object tmp = entry.getValue();
283             try
284             {
285                 if ( tmp instanceof File )
286                 {
287                     fileProcessor.move( (File) tmp, checksumFile );
288                     tempFiles.remove( tmp );
289                 }
290                 else
291                 {
292                     fileProcessor.writeChecksum( checksumFile, String.valueOf( tmp ) );
293                 }
294             }
295             catch ( IOException e )
296             {
297                 LOGGER.debug( "Failed to write checksum file {}", checksumFile, e );
298             }
299         }
300         checksumFiles.clear();
301     }
302 
303     public void close()
304     {
305         clearTempFiles();
306     }
307 
308 }