1 package org.eclipse.aether.util;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.BufferedReader;
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
41 private ChecksumUtils()
42 {
43 // hide constructor
44 }
45
46 /**
47 * Extracts the checksum from the specified file.
48 *
49 * @param checksumFile The path to the checksum file, must not be {@code null}.
50 * @return The checksum stored in the file, never {@code null}.
51 * @throws IOException If the checksum does not exist or could not be read for other reasons.
52 */
53 public static String read( File checksumFile )
54 throws IOException
55 {
56 String checksum = "";
57 BufferedReader br = null;
58 try
59 {
60 br = new BufferedReader( new InputStreamReader( new FileInputStream( checksumFile ), StandardCharsets.UTF_8 ), 512 );
61 while ( true )
62 {
63 String line = br.readLine();
64 if ( line == null )
65 {
66 break;
67 }
68 line = line.trim();
69 if ( line.length() > 0 )
70 {
71 checksum = line;
72 break;
73 }
74 }
75 }
76 finally
77 {
78 try
79 {
80 if ( br != null )
81 {
82 br.close();
83 br = null;
84 }
85 }
86 catch ( IOException e )
87 {
88 // Suppressed due to an exception already thrown in the try block.
89 }
90 }
91
92 if ( checksum.matches( ".+= [0-9A-Fa-f]+" ) )
93 {
94 int lastSpacePos = checksum.lastIndexOf( ' ' );
95 checksum = checksum.substring( lastSpacePos + 1 );
96 }
97 else
98 {
99 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 }