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.Locale;
29  import java.util.Map;
30  import java.util.UUID;
31  
32  import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
33  import org.eclipse.aether.spi.connector.layout.RepositoryLayout.Checksum;
34  import org.eclipse.aether.spi.io.FileProcessor;
35  import org.eclipse.aether.spi.log.Logger;
36  import org.eclipse.aether.transfer.ChecksumFailureException;
37  import org.eclipse.aether.util.ChecksumUtils;
38  
39  /**
40   * Performs checksum validation for a downloaded file.
41   */
42  final class ChecksumValidator
43  {
44  
45      interface ChecksumFetcher
46      {
47  
48          boolean fetchChecksum( URI remote, File local )
49              throws Exception;
50  
51      }
52  
53      private final Logger logger;
54  
55      private final File dataFile;
56  
57      private final Collection<File> tempFiles;
58  
59      private final FileProcessor fileProcessor;
60  
61      private final ChecksumFetcher checksumFetcher;
62  
63      private final ChecksumPolicy checksumPolicy;
64  
65      private final Collection<Checksum> checksums;
66  
67      private final Map<File, Object> checksumFiles;
68  
69      public ChecksumValidator( Logger logger, File dataFile, FileProcessor fileProcessor,
70                                ChecksumFetcher checksumFetcher, ChecksumPolicy checksumPolicy,
71                                Collection<Checksum> checksums )
72      {
73          this.logger = logger;
74          this.dataFile = dataFile;
75          this.tempFiles = new HashSet<File>();
76          this.fileProcessor = fileProcessor;
77          this.checksumFetcher = checksumFetcher;
78          this.checksumPolicy = checksumPolicy;
79          this.checksums = checksums;
80          checksumFiles = new HashMap<File, Object>();
81      }
82  
83      public ChecksumCalculator newChecksumCalculator( File targetFile )
84      {
85          if ( checksumPolicy != null )
86          {
87              return ChecksumCalculator.newInstance( targetFile, checksums );
88          }
89          return null;
90      }
91  
92      public void validate( Map<String, ?> actualChecksums, Map<String, ?> inlinedChecksums )
93          throws ChecksumFailureException
94      {
95          if ( checksumPolicy == null )
96          {
97              return;
98          }
99          if ( inlinedChecksums != null && validateInlinedChecksums( actualChecksums, inlinedChecksums ) )
100         {
101             return;
102         }
103         if ( validateExternalChecksums( actualChecksums ) )
104         {
105             return;
106         }
107         checksumPolicy.onNoMoreChecksums();
108     }
109 
110     private boolean validateInlinedChecksums( Map<String, ?> actualChecksums, Map<String, ?> inlinedChecksums )
111         throws ChecksumFailureException
112     {
113         for ( Map.Entry<String, ?> entry : inlinedChecksums.entrySet() )
114         {
115             String algo = entry.getKey();
116             Object calculated = actualChecksums.get( algo );
117             if ( !( calculated instanceof String ) )
118             {
119                 continue;
120             }
121 
122             String actual = String.valueOf( calculated );
123             String expected = entry.getValue().toString();
124             checksumFiles.put( getChecksumFile( algo ), expected );
125 
126             if ( !isEqualChecksum( expected, actual ) )
127             {
128                 checksumPolicy.onChecksumMismatch( algo, ChecksumPolicy.KIND_UNOFFICIAL,
129                                                    new ChecksumFailureException( expected, actual ) );
130             }
131             else if ( checksumPolicy.onChecksumMatch( algo, ChecksumPolicy.KIND_UNOFFICIAL ) )
132             {
133                 return true;
134             }
135         }
136         return false;
137     }
138 
139     private boolean validateExternalChecksums( Map<String, ?> actualChecksums )
140         throws ChecksumFailureException
141     {
142         for ( Checksum checksum : checksums )
143         {
144             String algo = checksum.getAlgorithm();
145             Object calculated = actualChecksums.get( algo );
146             if ( calculated instanceof Exception )
147             {
148                 checksumPolicy.onChecksumError( algo, 0, new ChecksumFailureException( (Exception) calculated ) );
149                 continue;
150             }
151             try
152             {
153                 File checksumFile = getChecksumFile( checksum.getAlgorithm() );
154                 File tmp = createTempFile( checksumFile );
155                 try
156                 {
157                     if ( !checksumFetcher.fetchChecksum( checksum.getLocation(), tmp ) )
158                     {
159                         continue;
160                     }
161                 }
162                 catch ( Exception e )
163                 {
164                     checksumPolicy.onChecksumError( algo, 0, new ChecksumFailureException( e ) );
165                     continue;
166                 }
167 
168                 String actual = String.valueOf( calculated );
169                 String expected = ChecksumUtils.read( tmp );
170                 checksumFiles.put( checksumFile, tmp );
171 
172                 if ( !isEqualChecksum( expected, actual ) )
173                 {
174                     checksumPolicy.onChecksumMismatch( algo, 0, new ChecksumFailureException( expected, actual ) );
175                 }
176                 else if ( checksumPolicy.onChecksumMatch( algo, 0 ) )
177                 {
178                     return true;
179                 }
180             }
181             catch ( IOException e )
182             {
183                 checksumPolicy.onChecksumError( algo, 0, new ChecksumFailureException( e ) );
184             }
185         }
186         return false;
187     }
188 
189     private static boolean isEqualChecksum( String expected, String actual )
190     {
191         return expected.equalsIgnoreCase( actual );
192     }
193 
194     private File getChecksumFile( String algorithm )
195     {
196         String ext = algorithm.replace( "-", "" ).toLowerCase( Locale.ENGLISH );
197         return new File( dataFile.getPath() + '.' + ext );
198     }
199 
200     private File createTempFile( File path )
201         throws IOException
202     {
203         File file =
204             File.createTempFile( path.getName() + "-"
205                 + UUID.randomUUID().toString().replace( "-", "" ).substring( 0, 8 ), ".tmp", path.getParentFile() );
206         tempFiles.add( file );
207         return file;
208     }
209 
210     private void clearTempFiles()
211     {
212         for ( File file : tempFiles )
213         {
214             if ( !file.delete() && file.exists() )
215             {
216                 logger.debug( "Could not delete temorary file " + file );
217             }
218         }
219         tempFiles.clear();
220     }
221 
222     public void retry()
223     {
224         checksumPolicy.onTransferRetry();
225         checksumFiles.clear();
226         clearTempFiles();
227     }
228 
229     public boolean handle( ChecksumFailureException exception )
230     {
231         return checksumPolicy.onTransferChecksumFailure( exception );
232     }
233 
234     public void commit()
235     {
236         for ( Map.Entry<File, Object> entry : checksumFiles.entrySet() )
237         {
238             File checksumFile = entry.getKey();
239             Object tmp = entry.getValue();
240             try
241             {
242                 if ( tmp instanceof File )
243                 {
244                     fileProcessor.move( (File) tmp, checksumFile );
245                     tempFiles.remove( tmp );
246                 }
247                 else
248                 {
249                     fileProcessor.write( checksumFile, String.valueOf( tmp ) );
250                 }
251             }
252             catch ( IOException e )
253             {
254                 logger.debug( "Failed to write checksum file " + checksumFile + ": " + e.getMessage(), e );
255             }
256         }
257         checksumFiles.clear();
258     }
259 
260     public void close()
261     {
262         clearTempFiles();
263     }
264 
265 }