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.ByteArrayInputStream; 024import java.io.File; 025import java.io.FileInputStream; 026import java.io.IOException; 027import java.io.InputStream; 028import java.io.InputStreamReader; 029import java.nio.charset.StandardCharsets; 030import java.security.MessageDigest; 031import java.security.NoSuchAlgorithmException; 032import java.util.Collection; 033import java.util.LinkedHashMap; 034import java.util.Map; 035 036/** 037 * A utility class to assist in the verification and generation of checksums. 038 */ 039public final class ChecksumUtils 040{ 041 042 private ChecksumUtils() 043 { 044 // hide constructor 045 } 046 047 /** 048 * Extracts the checksum from the specified file. 049 * 050 * @param checksumFile The path to the checksum file, must not be {@code null}. 051 * @return The checksum stored in the file, never {@code null}. 052 * @throws IOException If the checksum does not exist or could not be read for other reasons. 053 */ 054 public static String read( File checksumFile ) 055 throws IOException 056 { 057 String checksum = ""; 058 try ( BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream( checksumFile ), StandardCharsets.UTF_8 ), 512 ) ) 059 { 060 while ( true ) 061 { 062 String line = br.readLine(); 063 if ( line == null ) 064 { 065 break; 066 } 067 line = line.trim(); 068 if ( line.length() > 0 ) 069 { 070 checksum = line; 071 break; 072 } 073 } 074 } 075 076 if ( checksum.matches( ".+= [0-9A-Fa-f]+" ) ) 077 { 078 int lastSpacePos = checksum.lastIndexOf( ' ' ); 079 checksum = checksum.substring( lastSpacePos + 1 ); 080 } 081 else 082 { 083 int spacePos = checksum.indexOf( ' ' ); 084 085 if ( spacePos != -1 ) 086 { 087 checksum = checksum.substring( 0, spacePos ); 088 } 089 } 090 091 return checksum; 092 } 093 094 /** 095 * Calculates checksums for the specified file. 096 * 097 * @param dataFile The file for which to calculate checksums, must not be {@code null}. 098 * @param algos The names of checksum algorithms (cf. {@link MessageDigest#getInstance(String)} to use, must not be 099 * {@code null}. 100 * @return The calculated checksums, indexed by algorithm name, or the exception that occurred while trying to 101 * calculate it, never {@code null}. 102 * @throws IOException If the data file could not be read. 103 */ 104 public static Map<String, Object> calc( File dataFile, Collection<String> algos ) 105 throws IOException 106 { 107 return calc( new FileInputStream( dataFile ), algos ); 108 } 109 110 111 public static Map<String, Object> calc( byte[] dataBytes, Collection<String> algos ) 112 throws IOException 113 { 114 return calc( new ByteArrayInputStream( dataBytes ), algos ); 115 } 116 117 118 private static Map<String, Object> calc( InputStream data, Collection<String> algos ) 119 throws IOException 120 { 121 Map<String, Object> results = new LinkedHashMap<String, Object>(); 122 123 Map<String, MessageDigest> digests = new LinkedHashMap<String, MessageDigest>(); 124 for ( String algo : algos ) 125 { 126 try 127 { 128 digests.put( algo, MessageDigest.getInstance( algo ) ); 129 } 130 catch ( NoSuchAlgorithmException e ) 131 { 132 results.put( algo, e ); 133 } 134 } 135 136 try ( InputStream in = data ) 137 { 138 for ( byte[] buffer = new byte[ 32 * 1024 ];; ) 139 { 140 int read = in.read( buffer ); 141 if ( read < 0 ) 142 { 143 break; 144 } 145 for ( MessageDigest digest : digests.values() ) 146 { 147 digest.update( buffer, 0, read ); 148 } 149 } 150 } 151 152 for ( Map.Entry<String, MessageDigest> entry : digests.entrySet() ) 153 { 154 byte[] bytes = entry.getValue().digest(); 155 156 results.put( entry.getKey(), toHexString( bytes ) ); 157 } 158 159 return results; 160 } 161 162 163 /** 164 * Creates a hexadecimal representation of the specified bytes. Each byte is converted into a two-digit hex number 165 * and appended to the result with no separator between consecutive bytes. 166 * 167 * @param bytes The bytes to represent in hex notation, may be be {@code null}. 168 * @return The hexadecimal representation of the input or {@code null} if the input was {@code null}. 169 */ 170 public static String toHexString( byte[] bytes ) 171 { 172 if ( bytes == null ) 173 { 174 return null; 175 } 176 177 StringBuilder buffer = new StringBuilder( bytes.length * 2 ); 178 179 for ( byte aByte : bytes ) 180 { 181 int b = aByte & 0xFF; 182 if ( b < 0x10 ) 183 { 184 buffer.append( '0' ); 185 } 186 buffer.append( Integer.toHexString( b ) ); 187 } 188 189 return buffer.toString(); 190 } 191 192}