View Javadoc
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   * @deprecated the use of class should be avoided, see {@link StringDigestUtil} and file processor in SPI module
39   */
40  @Deprecated
41  public final class ChecksumUtils {
42      /**
43       * Upper bound (in characters) for data read from a checksum file, see {@link #read(File)}. Every sane
44       * checksum file format fits well within this limit; longer input is rejected as malformed instead of being
45       * buffered into memory.
46       */
47      private static final int MAX_CHECKSUM_FILE_CHARS = 8192;
48  
49      private ChecksumUtils() {
50          // hide constructor
51      }
52  
53      /**
54       * Extracts the checksum from the specified file.
55       *
56       * @param checksumFile the path to the checksum file, must not be {@code null}
57       * @return the checksum stored in the file, never {@code null}
58       * @throws IOException if the checksum does not exist or could not be read for other reasons
59       * @deprecated use SPI FileProcessor to read and write checksum files
60       */
61      @Deprecated
62      public static String read(File checksumFile) throws IOException {
63          String checksum;
64          try (BufferedReader br = new BufferedReader(
65                  new InputStreamReader(new FileInputStream(checksumFile), StandardCharsets.UTF_8), 512)) {
66              checksum = readFirstNonEmptyLine(br, checksumFile.toString());
67          }
68  
69          if (isAlgorithmHeaderFormat(checksum)) {
70              int lastSpacePos = checksum.lastIndexOf(' ');
71              checksum = checksum.substring(lastSpacePos + 1);
72          } else {
73              int spacePos = checksum.indexOf(' ');
74  
75              if (spacePos != -1) {
76                  checksum = checksum.substring(0, spacePos);
77              }
78          }
79  
80          return checksum;
81      }
82  
83      /**
84       * Reads the first non-empty line, enforcing {@link #MAX_CHECKSUM_FILE_CHARS} on the total amount of data
85       * consumed. Returns the trimmed line, or an empty string if the stream holds no non-empty line.
86       */
87      private static String readFirstNonEmptyLine(BufferedReader reader, String source) throws IOException {
88          StringBuilder buffer = new StringBuilder(64);
89          int read = 0;
90          int c;
91          while ((c = reader.read()) != -1) {
92              if (++read > MAX_CHECKSUM_FILE_CHARS) {
93                  throw new IOException("Checksum file " + source + " is malformed: longer than "
94                          + MAX_CHECKSUM_FILE_CHARS + " characters");
95              }
96              if (c == '\n' || c == '\r') {
97                  String line = buffer.toString().trim();
98                  if (!line.isEmpty()) {
99                      return line;
100                 }
101                 buffer.setLength(0);
102             } else {
103                 buffer.append((char) c);
104             }
105         }
106         return buffer.toString().trim();
107     }
108 
109     /**
110      * Non-backtracking equivalent of {@code line.matches(".+= [0-9A-Fa-f]+")}: at least one character, followed
111      * by "= ", followed by one or more hex digits reaching the end of the line ("<algorithm> (<file>)
112      * = <hex>" style checksum lines).
113      */
114     private static boolean isAlgorithmHeaderFormat(String line) {
115         int lastSpacePos = line.lastIndexOf(' ');
116         if (lastSpacePos < 2 || lastSpacePos == line.length() - 1 || line.charAt(lastSpacePos - 1) != '=') {
117             return false;
118         }
119         for (int i = lastSpacePos + 1; i < line.length(); i++) {
120             char ch = line.charAt(i);
121             boolean hex = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
122             if (!hex) {
123                 return false;
124             }
125         }
126         return true;
127     }
128 
129     /**
130      * Calculates checksums for the specified file.
131      *
132      * @param dataFile the file for which to calculate checksums, must not be {@code null}
133      * @param algos the names of checksum algorithms (cf. {@link MessageDigest#getInstance(String)} to use, must not be
134      *            {@code null}.
135      * @return the calculated checksums, indexed by algorithm name, or the exception that occurred while trying to
136      *         calculate it, never {@code null}
137      * @throws IOException if the data file could not be read
138      * @deprecated use SPI checksum selector instead
139      */
140     @Deprecated
141     public static Map<String, Object> calc(File dataFile, Collection<String> algos) throws IOException {
142         return calc(new FileInputStream(dataFile), algos);
143     }
144 
145     /**
146      * @deprecated use SPI checksum selector instead
147      */
148     @Deprecated
149     public static Map<String, Object> calc(byte[] dataBytes, Collection<String> algos) throws IOException {
150         return calc(new ByteArrayInputStream(dataBytes), algos);
151     }
152 
153     private static Map<String, Object> calc(InputStream data, Collection<String> algos) throws IOException {
154         Map<String, Object> results = new LinkedHashMap<>();
155 
156         Map<String, MessageDigest> digests = new LinkedHashMap<>();
157         for (String algo : algos) {
158             try {
159                 digests.put(algo, MessageDigest.getInstance(algo));
160             } catch (NoSuchAlgorithmException e) {
161                 results.put(algo, e);
162             }
163         }
164 
165         try (InputStream in = data) {
166             for (byte[] buffer = new byte[32 * 1024]; ; ) {
167                 int read = in.read(buffer);
168                 if (read < 0) {
169                     break;
170                 }
171                 for (MessageDigest digest : digests.values()) {
172                     digest.update(buffer, 0, read);
173                 }
174             }
175         }
176 
177         for (Map.Entry<String, MessageDigest> entry : digests.entrySet()) {
178             byte[] bytes = entry.getValue().digest();
179 
180             results.put(entry.getKey(), toHexString(bytes));
181         }
182 
183         return results;
184     }
185 
186     /**
187      * Creates a hexadecimal representation of the specified bytes. Each byte is converted into a two-digit hex number
188      * and appended to the result with no separator between consecutive bytes.
189      *
190      * @param bytes the bytes to represent in hex notation, may be be {@code null}
191      * @return the hexadecimal representation of the input or {@code null} if the input was {@code null}
192      */
193     public static String toHexString(byte[] bytes) {
194         return StringDigestUtil.toHexString(bytes);
195     }
196 
197     /**
198      * Creates a byte array out of hexadecimal representation of the specified bytes. If input string is {@code null},
199      * {@code null} is returned. Input value must have even length (due hex encoding = 2 chars one byte).
200      *
201      * @param hexString the hexString to convert to byte array, may be {@code null}
202      * @return the byte array of the input or {@code null} if the input was {@code null}
203      * @since 1.8.0
204      */
205     public static byte[] fromHexString(String hexString) {
206         return StringDigestUtil.fromHexString(hexString);
207     }
208 }