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 * @deprecated Use SPI FileProcessor to read and write checksum files. 054 */ 055 @Deprecated 056 public static String read( File checksumFile ) 057 throws IOException 058 { 059 String checksum = ""; 060 try ( BufferedReader br = new BufferedReader( new InputStreamReader( 061 new FileInputStream( checksumFile ), StandardCharsets.UTF_8 ), 512 ) ) 062 { 063 while ( true ) 064 { 065 String line = br.readLine(); 066 if ( line == null ) 067 { 068 break; 069 } 070 line = line.trim(); 071 if ( line.length() > 0 ) 072 { 073 checksum = line; 074 break; 075 } 076 } 077 } 078 079 if ( checksum.matches( ".+= [0-9A-Fa-f]+" ) ) 080 { 081 int lastSpacePos = checksum.lastIndexOf( ' ' ); 082 checksum = checksum.substring( lastSpacePos + 1 ); 083 } 084 else 085 { 086 int spacePos = checksum.indexOf( ' ' ); 087 088 if ( spacePos != -1 ) 089 { 090 checksum = checksum.substring( 0, spacePos ); 091 } 092 } 093 094 return checksum; 095 } 096 097 /** 098 * Calculates checksums for the specified file. 099 * 100 * @param dataFile The file for which to calculate checksums, must not be {@code null}. 101 * @param algos The names of checksum algorithms (cf. {@link MessageDigest#getInstance(String)} to use, must not be 102 * {@code null}. 103 * @return The calculated checksums, indexed by algorithm name, or the exception that occurred while trying to 104 * calculate it, never {@code null}. 105 * @throws IOException If the data file could not be read. 106 * @deprecated Use SPI checksum selector instead. 107 */ 108 @Deprecated 109 public static Map<String, Object> calc( File dataFile, Collection<String> algos ) 110 throws IOException 111 { 112 return calc( new FileInputStream( dataFile ), algos ); 113 } 114 115 /** 116 * @deprecated Use SPI checksum selector instead. 117 */ 118 @Deprecated 119 public static Map<String, Object> calc( byte[] dataBytes, Collection<String> algos ) 120 throws IOException 121 { 122 return calc( new ByteArrayInputStream( dataBytes ), algos ); 123 } 124 125 private static Map<String, Object> calc( InputStream data, Collection<String> algos ) 126 throws IOException 127 { 128 Map<String, Object> results = new LinkedHashMap<>(); 129 130 Map<String, MessageDigest> digests = new LinkedHashMap<>(); 131 for ( String algo : algos ) 132 { 133 try 134 { 135 digests.put( algo, MessageDigest.getInstance( algo ) ); 136 } 137 catch ( NoSuchAlgorithmException e ) 138 { 139 results.put( algo, e ); 140 } 141 } 142 143 try ( InputStream in = data ) 144 { 145 for ( byte[] buffer = new byte[ 32 * 1024 ];; ) 146 { 147 int read = in.read( buffer ); 148 if ( read < 0 ) 149 { 150 break; 151 } 152 for ( MessageDigest digest : digests.values() ) 153 { 154 digest.update( buffer, 0, read ); 155 } 156 } 157 } 158 159 for ( Map.Entry<String, MessageDigest> entry : digests.entrySet() ) 160 { 161 byte[] bytes = entry.getValue().digest(); 162 163 results.put( entry.getKey(), toHexString( bytes ) ); 164 } 165 166 return results; 167 } 168 169 170 /** 171 * Creates a hexadecimal representation of the specified bytes. Each byte is converted into a two-digit hex number 172 * and appended to the result with no separator between consecutive bytes. 173 * 174 * @param bytes The bytes to represent in hex notation, may be be {@code null}. 175 * @return The hexadecimal representation of the input or {@code null} if the input was {@code null}. 176 */ 177 @SuppressWarnings( "checkstyle:magicnumber" ) 178 public static String toHexString( byte[] bytes ) 179 { 180 if ( bytes == null ) 181 { 182 return null; 183 } 184 185 StringBuilder buffer = new StringBuilder( bytes.length * 2 ); 186 187 for ( byte aByte : bytes ) 188 { 189 int b = aByte & 0xFF; 190 if ( b < 0x10 ) 191 { 192 buffer.append( '0' ); 193 } 194 buffer.append( Integer.toHexString( b ) ); 195 } 196 197 return buffer.toString(); 198 } 199 200 /** 201 * Creates a byte array out of hexadecimal representation of the specified bytes. If input string is {@code null}, 202 * {@code null} is returned. Input value must have even length (due hex encoding = 2 chars one byte). 203 * 204 * @param hexString The hexString to convert to byte array, may be {@code null}. 205 * @return The byte array of the input or {@code null} if the input was {@code null}. 206 * @since 1.8.0 207 */ 208 @SuppressWarnings( "checkstyle:magicnumber" ) 209 public static byte[] fromHexString( String hexString ) 210 { 211 if ( hexString == null ) 212 { 213 return null; 214 } 215 if ( hexString.isEmpty() ) 216 { 217 return new byte[] {}; 218 } 219 int len = hexString.length(); 220 if ( len % 2 != 0 ) 221 { 222 throw new IllegalArgumentException( "hexString length not even" ); 223 } 224 byte[] data = new byte[ len / 2 ]; 225 for ( int i = 0; i < len; i += 2 ) 226 { 227 data[ i / 2 ] = (byte) ( ( Character.digit( hexString.charAt( i ), 16 ) << 4 ) 228 + Character.digit( hexString.charAt( i + 1 ), 16 ) ); 229 } 230 return data; 231 } 232 233}