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