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.io.UncheckedIOException;
25  import java.net.URI;
26  import java.util.Collection;
27  import java.util.HashMap;
28  import java.util.Map;
29  
30  import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
31  import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
32  import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind;
33  import org.eclipse.aether.spi.connector.layout.RepositoryLayout.ChecksumLocation;
34  import org.eclipse.aether.spi.io.FileProcessor;
35  import org.eclipse.aether.transfer.ChecksumFailureException;
36  import org.eclipse.aether.util.FileUtils;
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 FileProcessor fileProcessor;
65  
66      private final ChecksumFetcher checksumFetcher;
67  
68      private final ChecksumPolicy checksumPolicy;
69  
70      private final Map<String, String> providedChecksums;
71  
72      private final Collection<ChecksumLocation> checksumLocations;
73  
74      private final Map<File, String> checksumExpectedValues;
75  
76      ChecksumValidator( File dataFile,
77                         Collection<ChecksumAlgorithmFactory> checksumAlgorithmFactories,
78                         FileProcessor fileProcessor,
79                         ChecksumFetcher checksumFetcher,
80                         ChecksumPolicy checksumPolicy,
81                         Map<String, String> providedChecksums,
82                         Collection<ChecksumLocation> checksumLocations )
83      {
84          this.dataFile = dataFile;
85          this.checksumAlgorithmFactories = checksumAlgorithmFactories;
86          this.fileProcessor = fileProcessor;
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( File targetFile )
95      {
96          if ( checksumPolicy != null )
97          {
98              return ChecksumCalculator.newInstance( targetFile, checksumAlgorithmFactories );
99          }
100         return null;
101     }
102 
103     public void validate( Map<String, ?> actualChecksums, Map<String, ?> includedChecksums )
104         throws ChecksumFailureException
105     {
106         if ( checksumPolicy == null )
107         {
108             return;
109         }
110         if ( providedChecksums != null
111                && validateChecksums( actualChecksums, ChecksumKind.PROVIDED, providedChecksums ) )
112         {
113             return;
114         }
115         if ( includedChecksums != null
116                && validateChecksums( actualChecksums, ChecksumKind.REMOTE_INCLUDED, includedChecksums ) )
117         {
118             return;
119         }
120         if ( !checksumLocations.isEmpty() )
121         {
122             if ( validateExternalChecksums( actualChecksums ) )
123             {
124                 return;
125             }
126             checksumPolicy.onNoMoreChecksums();
127         }
128     }
129 
130     private boolean validateChecksums( Map<String, ?> actualChecksums, ChecksumKind kind, Map<String, ?> checksums )
131         throws ChecksumFailureException
132     {
133         for ( Map.Entry<String, ?> entry : checksums.entrySet() )
134         {
135             String algo = entry.getKey();
136             Object calculated = actualChecksums.get( algo );
137             if ( !( calculated instanceof String ) )
138             {
139                 continue;
140             }
141             ChecksumAlgorithmFactory checksumAlgorithmFactory = checksumAlgorithmFactories.stream()
142                     .filter( a -> a.getName().equals( algo ) )
143                     .findFirst()
144                     .orElse( null );
145             if ( checksumAlgorithmFactory == null )
146             {
147                 continue;
148             }
149 
150             String actual = String.valueOf( calculated );
151             String expected = entry.getValue().toString();
152             checksumExpectedValues.put( getChecksumFile( checksumAlgorithmFactory ), expected );
153 
154             if ( !isEqualChecksum( expected, actual ) )
155             {
156                 checksumPolicy.onChecksumMismatch( checksumAlgorithmFactory.getName(), kind,
157                     new ChecksumFailureException( expected, kind.name(), actual )
158                 );
159             }
160             else if ( checksumPolicy.onChecksumMatch( checksumAlgorithmFactory.getName(), kind ) )
161             {
162                 return true;
163             }
164         }
165         return false;
166     }
167 
168     private boolean validateExternalChecksums( Map<String, ?> actualChecksums )
169         throws ChecksumFailureException
170     {
171         for ( ChecksumLocation checksumLocation : checksumLocations )
172         {
173             ChecksumAlgorithmFactory factory = checksumLocation.getChecksumAlgorithmFactory();
174             Object calculated = actualChecksums.get( factory.getName() );
175             if ( calculated instanceof Exception )
176             {
177                 checksumPolicy.onChecksumError(
178                         factory.getName(), ChecksumKind.REMOTE_EXTERNAL,
179                         new ChecksumFailureException( (Exception) calculated )
180                 );
181                 continue;
182             }
183             File checksumFile = getChecksumFile( checksumLocation.getChecksumAlgorithmFactory() );
184             try ( FileUtils.TempFile tempFile = FileUtils.newTempFile( checksumFile.toPath() ) )
185             {
186                 File tmp = tempFile.getPath().toFile();
187                 try
188                 {
189                     if ( !checksumFetcher.fetchChecksum(
190                         checksumLocation.getLocation(), tmp
191                     ) )
192                     {
193                         continue;
194                     }
195                 }
196                 catch ( Exception e )
197                 {
198                     checksumPolicy.onChecksumError(
199                         factory.getName(), ChecksumKind.REMOTE_EXTERNAL, new ChecksumFailureException( e )
200                     );
201                     continue;
202                 }
203 
204                 String actual = String.valueOf( calculated );
205                 String expected = fileProcessor.readChecksum( tmp );
206                 checksumExpectedValues.put( checksumFile, expected );
207 
208                 if ( !isEqualChecksum( expected, actual ) )
209                 {
210                     checksumPolicy.onChecksumMismatch(
211                         factory.getName(), ChecksumKind.REMOTE_EXTERNAL,
212                           new ChecksumFailureException( expected, ChecksumKind.REMOTE_EXTERNAL.name(), actual )
213                     );
214                 }
215                 else if ( checksumPolicy.onChecksumMatch( factory.getName(), ChecksumKind.REMOTE_EXTERNAL ) )
216                 {
217                     return true;
218                 }
219             }
220             catch ( IOException e )
221             {
222                 checksumPolicy.onChecksumError(
223                     factory.getName(), ChecksumKind.REMOTE_EXTERNAL, new ChecksumFailureException( e )
224                 );
225             }
226         }
227         return false;
228     }
229 
230     private static boolean isEqualChecksum( String expected, String actual )
231     {
232         return expected.equalsIgnoreCase( actual );
233     }
234 
235     private File getChecksumFile( ChecksumAlgorithmFactory factory )
236     {
237         return new File( dataFile.getPath() + '.' + factory.getFileExtension() );
238     }
239 
240     public void retry()
241     {
242         checksumPolicy.onTransferRetry();
243         checksumExpectedValues.clear();
244     }
245 
246     public boolean handle( ChecksumFailureException exception )
247     {
248         return checksumPolicy.onTransferChecksumFailure( exception );
249     }
250 
251     public void commit()
252     {
253         for ( Map.Entry<File, String> entry : checksumExpectedValues.entrySet() )
254         {
255             File checksumFile = entry.getKey();
256             try
257             {
258                 fileProcessor.writeChecksum( checksumFile, entry.getValue() );
259             }
260             catch ( IOException e )
261             {
262                 LOGGER.debug( "Failed to write checksum file {}", checksumFile, e );
263                 throw new UncheckedIOException( e );
264             }
265         }
266         checksumExpectedValues.clear();
267     }
268 }