1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.eclipse.aether.util;
20
21 import java.io.BufferedReader;
22 import java.io.ByteArrayInputStream;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.nio.charset.StandardCharsets;
29 import java.security.MessageDigest;
30 import java.security.NoSuchAlgorithmException;
31 import java.util.Collection;
32 import java.util.LinkedHashMap;
33 import java.util.Map;
34
35 /**
36 * A utility class to assist in the verification and generation of checksums.
37 */
38 public final class ChecksumUtils {
39
40 private ChecksumUtils() {
41 // hide constructor
42 }
43
44 /**
45 * Extracts the checksum from the specified file.
46 *
47 * @param checksumFile The path to the checksum file, must not be {@code null}.
48 * @return The checksum stored in the file, never {@code null}.
49 * @throws IOException If the checksum does not exist or could not be read for other reasons.
50 * @deprecated Use SPI FileProcessor to read and write checksum files.
51 */
52 @Deprecated
53 public static String read(File checksumFile) throws IOException {
54 String checksum = "";
55 try (BufferedReader br = new BufferedReader(
56 new InputStreamReader(new FileInputStream(checksumFile), StandardCharsets.UTF_8), 512)) {
57 while (true) {
58 String line = br.readLine();
59 if (line == null) {
60 break;
61 }
62 line = line.trim();
63 if (line.length() > 0) {
64 checksum = line;
65 break;
66 }
67 }
68 }
69
70 if (checksum.matches(".+= [0-9A-Fa-f]+")) {
71 int lastSpacePos = checksum.lastIndexOf(' ');
72 checksum = checksum.substring(lastSpacePos + 1);
73 } else {
74 int spacePos = checksum.indexOf(' ');
75
76 if (spacePos != -1) {
77 checksum = checksum.substring(0, spacePos);
78 }
79 }
80
81 return checksum;
82 }
83
84 /**
85 * Calculates checksums for the specified file.
86 *
87 * @param dataFile The file for which to calculate checksums, must not be {@code null}.
88 * @param algos The names of checksum algorithms (cf. {@link MessageDigest#getInstance(String)} to use, must not be
89 * {@code null}.
90 * @return The calculated checksums, indexed by algorithm name, or the exception that occurred while trying to
91 * calculate it, never {@code null}.
92 * @throws IOException If the data file could not be read.
93 * @deprecated Use SPI checksum selector instead.
94 */
95 @Deprecated
96 public static Map<String, Object> calc(File dataFile, Collection<String> algos) throws IOException {
97 return calc(new FileInputStream(dataFile), algos);
98 }
99
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 public static String toHexString(byte[] bytes) {
149 if (bytes == null) {
150 return null;
151 }
152
153 StringBuilder buffer = new StringBuilder(bytes.length * 2);
154
155 for (byte aByte : bytes) {
156 int b = aByte & 0xFF;
157 if (b < 0x10) {
158 buffer.append('0');
159 }
160 buffer.append(Integer.toHexString(b));
161 }
162
163 return buffer.toString();
164 }
165
166 /**
167 * Creates a byte array out of hexadecimal representation of the specified bytes. If input string is {@code null},
168 * {@code null} is returned. Input value must have even length (due hex encoding = 2 chars one byte).
169 *
170 * @param hexString The hexString to convert to byte array, may be {@code null}.
171 * @return The byte array of the input or {@code null} if the input was {@code null}.
172 * @since 1.8.0
173 */
174 public static byte[] fromHexString(String hexString) {
175 if (hexString == null) {
176 return null;
177 }
178 if (hexString.isEmpty()) {
179 return new byte[] {};
180 }
181 int len = hexString.length();
182 if (len % 2 != 0) {
183 throw new IllegalArgumentException("hexString length not even");
184 }
185 byte[] data = new byte[len / 2];
186 for (int i = 0; i < len; i += 2) {
187 data[i / 2] = (byte)
188 ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
189 }
190 return data;
191 }
192 }