001package org.eclipse.aether.util; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.BufferedReader; 023import java.io.File; 024import java.io.FileInputStream; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.InputStreamReader; 028import java.nio.charset.StandardCharsets; 029import java.security.MessageDigest; 030import java.security.NoSuchAlgorithmException; 031import java.util.Collection; 032import java.util.LinkedHashMap; 033import java.util.Map; 034 035/** 036 * A utility class to assist in the verification and generation of checksums. 037 */ 038public final class ChecksumUtils 039{ 040 041 private ChecksumUtils() 042 { 043 // hide constructor 044 } 045 046 /** 047 * Extracts the checksum from the specified file. 048 * 049 * @param checksumFile The path to the checksum file, must not be {@code null}. 050 * @return The checksum stored in the file, never {@code null}. 051 * @throws IOException If the checksum does not exist or could not be read for other reasons. 052 */ 053 public static String read( File checksumFile ) 054 throws IOException 055 { 056 String checksum = ""; 057 BufferedReader br = null; 058 try 059 { 060 br = new BufferedReader( new InputStreamReader( new FileInputStream( checksumFile ), StandardCharsets.UTF_8 ), 512 ); 061 while ( true ) 062 { 063 String line = br.readLine(); 064 if ( line == null ) 065 { 066 break; 067 } 068 line = line.trim(); 069 if ( line.length() > 0 ) 070 { 071 checksum = line; 072 break; 073 } 074 } 075 } 076 finally 077 { 078 try 079 { 080 if ( br != null ) 081 { 082 br.close(); 083 br = null; 084 } 085 } 086 catch ( IOException e ) 087 { 088 // Suppressed due to an exception already thrown in the try block. 089 } 090 } 091 092 if ( checksum.matches( ".+= [0-9A-Fa-f]+" ) ) 093 { 094 int lastSpacePos = checksum.lastIndexOf( ' ' ); 095 checksum = checksum.substring( lastSpacePos + 1 ); 096 } 097 else 098 { 099 int spacePos = checksum.indexOf( ' ' ); 100 101 if ( spacePos != -1 ) 102 { 103 checksum = checksum.substring( 0, spacePos ); 104 } 105 } 106 107 return checksum; 108 } 109 110 /** 111 * Calculates checksums for the specified file. 112 * 113 * @param dataFile The file for which to calculate checksums, must not be {@code null}. 114 * @param algos The names of checksum algorithms (cf. {@link MessageDigest#getInstance(String)} to use, must not be 115 * {@code null}. 116 * @return The calculated checksums, indexed by algorithm name, or the exception that occurred while trying to 117 * calculate it, never {@code null}. 118 * @throws IOException If the data file could not be read. 119 */ 120 public static Map<String, Object> calc( File dataFile, Collection<String> algos ) 121 throws IOException 122 { 123 Map<String, Object> results = new LinkedHashMap<String, Object>(); 124 125 Map<String, MessageDigest> digests = new LinkedHashMap<String, MessageDigest>(); 126 for ( String algo : algos ) 127 { 128 try 129 { 130 digests.put( algo, MessageDigest.getInstance( algo ) ); 131 } 132 catch ( NoSuchAlgorithmException e ) 133 { 134 results.put( algo, e ); 135 } 136 } 137 138 InputStream in = null; 139 try 140 { 141 in = new FileInputStream( dataFile ); 142 for ( byte[] buffer = new byte[ 32 * 1024 ];; ) 143 { 144 int read = in.read( buffer ); 145 if ( read < 0 ) 146 { 147 break; 148 } 149 for ( MessageDigest digest : digests.values() ) 150 { 151 digest.update( buffer, 0, read ); 152 } 153 } 154 in.close(); 155 in = null; 156 } 157 finally 158 { 159 try 160 { 161 if ( in != null ) 162 { 163 in.close(); 164 } 165 } 166 catch ( IOException e ) 167 { 168 // Suppressed due to an exception already thrown in the try block. 169 } 170 } 171 172 for ( Map.Entry<String, MessageDigest> entry : digests.entrySet() ) 173 { 174 byte[] bytes = entry.getValue().digest(); 175 176 results.put( entry.getKey(), toHexString( bytes ) ); 177 } 178 179 return results; 180 } 181 182 /** 183 * Creates a hexadecimal representation of the specified bytes. Each byte is converted into a two-digit hex number 184 * and appended to the result with no separator between consecutive bytes. 185 * 186 * @param bytes The bytes to represent in hex notation, may be be {@code null}. 187 * @return The hexadecimal representation of the input or {@code null} if the input was {@code null}. 188 */ 189 public static String toHexString( byte[] bytes ) 190 { 191 if ( bytes == null ) 192 { 193 return null; 194 } 195 196 StringBuilder buffer = new StringBuilder( bytes.length * 2 ); 197 198 for ( byte aByte : bytes ) 199 { 200 int b = aByte & 0xFF; 201 if ( b < 0x10 ) 202 { 203 buffer.append( '0' ); 204 } 205 buffer.append( Integer.toHexString( b ) ); 206 } 207 208 return buffer.toString(); 209 } 210 211}