You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ChecksummedFile.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. package org.apache.archiva.checksum;
  2. /*
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. */
  20. import org.apache.commons.io.FileUtils;
  21. import org.apache.commons.lang.StringUtils;
  22. import org.slf4j.Logger;
  23. import org.slf4j.LoggerFactory;
  24. import java.io.File;
  25. import java.io.FileInputStream;
  26. import java.io.IOException;
  27. import java.util.ArrayList;
  28. import java.util.List;
  29. import java.util.regex.Matcher;
  30. import java.util.regex.Pattern;
  31. /**
  32. * ChecksummedFile
  33. *
  34. * <p>Terminology:</p>
  35. * <dl>
  36. * <dt>Checksum File</dt>
  37. * <dd>The file that contains the previously calculated checksum value for the reference file.
  38. * This is a text file with the extension ".sha1" or ".md5", and contains a single entry
  39. * consisting of an optional reference filename, and a checksum string.
  40. * </dd>
  41. * <dt>Reference File</dt>
  42. * <dd>The file that is being referenced in the checksum file.</dd>
  43. * </dl>
  44. *
  45. *
  46. */
  47. public class ChecksummedFile
  48. {
  49. private Logger log = LoggerFactory.getLogger( ChecksummedFile.class );
  50. private final File referenceFile;
  51. /**
  52. * Construct a ChecksummedFile object.
  53. *
  54. * @param referenceFile
  55. */
  56. public ChecksummedFile( final File referenceFile )
  57. {
  58. this.referenceFile = referenceFile;
  59. }
  60. /**
  61. * Calculate the checksum based on a given checksum.
  62. *
  63. * @param checksumAlgorithm the algorithm to use.
  64. * @return the checksum string for the file.
  65. * @throws IOException if unable to calculate the checksum.
  66. */
  67. public String calculateChecksum( ChecksumAlgorithm checksumAlgorithm )
  68. throws IOException
  69. {
  70. try (FileInputStream fis = new FileInputStream( referenceFile ))
  71. {
  72. Checksum checksum = new Checksum( checksumAlgorithm );
  73. checksum.update( fis );
  74. return checksum.getChecksum();
  75. }
  76. }
  77. /**
  78. * Creates a checksum file of the provided referenceFile.
  79. *
  80. * @param checksumAlgorithm the hash to use.
  81. * @return the checksum File that was created.
  82. * @throws IOException if there was a problem either reading the referenceFile, or writing the checksum file.
  83. */
  84. public File createChecksum( ChecksumAlgorithm checksumAlgorithm )
  85. throws IOException
  86. {
  87. File checksumFile = new File( referenceFile.getAbsolutePath() + "." + checksumAlgorithm.getExt() );
  88. String checksum = calculateChecksum( checksumAlgorithm );
  89. FileUtils.writeStringToFile( checksumFile, checksum + " " + referenceFile.getName() );
  90. return checksumFile;
  91. }
  92. /**
  93. * Get the checksum file for the reference file and hash.
  94. *
  95. * @param checksumAlgorithm the hash that we are interested in.
  96. * @return the checksum file to return
  97. */
  98. public File getChecksumFile( ChecksumAlgorithm checksumAlgorithm )
  99. {
  100. return new File( referenceFile.getAbsolutePath() + "." + checksumAlgorithm.getExt() );
  101. }
  102. /**
  103. * <p>
  104. * Given a checksum file, check to see if the file it represents is valid according to the checksum.
  105. * </p>
  106. *
  107. * <p>
  108. * NOTE: Only supports single file checksums of type MD5 or SHA1.
  109. * </p>
  110. *
  111. * @param algorithm the algorithms to check for.
  112. * @return true if the checksum is valid for the file it represents. or if the checksum file does not exist.
  113. * @throws IOException if the reading of the checksumFile or the file it refers to fails.
  114. */
  115. public boolean isValidChecksum( ChecksumAlgorithm algorithm )
  116. throws IOException
  117. {
  118. return isValidChecksums( new ChecksumAlgorithm[]{ algorithm } );
  119. }
  120. /**
  121. * Of any checksum files present, validate that the reference file conforms
  122. * the to the checksum.
  123. *
  124. * @param algorithms the algorithms to check for.
  125. * @return true if the checksums report that the the reference file is valid, false if invalid.
  126. */
  127. public boolean isValidChecksums( ChecksumAlgorithm algorithms[] )
  128. {
  129. try (FileInputStream fis = new FileInputStream( referenceFile ))
  130. {
  131. List<Checksum> checksums = new ArrayList<>( algorithms.length );
  132. // Create checksum object for each algorithm.
  133. for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
  134. {
  135. File checksumFile = getChecksumFile( checksumAlgorithm );
  136. // Only add algorithm if checksum file exists.
  137. if ( checksumFile.exists() )
  138. {
  139. checksums.add( new Checksum( checksumAlgorithm ) );
  140. }
  141. }
  142. // Any checksums?
  143. if ( checksums.isEmpty() )
  144. {
  145. // No checksum objects, no checksum files, default to is invalid.
  146. return false;
  147. }
  148. // Parse file once, for all checksums.
  149. try
  150. {
  151. Checksum.update( checksums, fis );
  152. }
  153. catch ( IOException e )
  154. {
  155. log.warn( "Unable to update checksum:{}", e.getMessage() );
  156. return false;
  157. }
  158. boolean valid = true;
  159. // check the checksum files
  160. try
  161. {
  162. for ( Checksum checksum : checksums )
  163. {
  164. ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm();
  165. File checksumFile = getChecksumFile( checksumAlgorithm );
  166. String rawChecksum = FileUtils.readFileToString( checksumFile );
  167. String expectedChecksum = parseChecksum( rawChecksum, checksumAlgorithm, referenceFile.getName() );
  168. if ( !StringUtils.equalsIgnoreCase( expectedChecksum, checksum.getChecksum() ) )
  169. {
  170. valid = false;
  171. }
  172. }
  173. }
  174. catch ( IOException e )
  175. {
  176. log.warn( "Unable to read / parse checksum: {}", e.getMessage() );
  177. return false;
  178. }
  179. return valid;
  180. } catch ( IOException e )
  181. {
  182. log.warn( "Unable to read / parse checksum: {}", e.getMessage() );
  183. return false;
  184. }
  185. }
  186. /**
  187. * Fix or create checksum files for the reference file.
  188. *
  189. * @param algorithms the hashes to check for.
  190. * @return true if checksums were created successfully.
  191. */
  192. public boolean fixChecksums( ChecksumAlgorithm[] algorithms )
  193. {
  194. List<Checksum> checksums = new ArrayList<>( algorithms.length );
  195. // Create checksum object for each algorithm.
  196. for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
  197. {
  198. checksums.add( new Checksum( checksumAlgorithm ) );
  199. }
  200. // Any checksums?
  201. if ( checksums.isEmpty() )
  202. {
  203. // No checksum objects, no checksum files, default to is valid.
  204. return true;
  205. }
  206. try (FileInputStream fis = new FileInputStream( referenceFile ))
  207. {
  208. // Parse file once, for all checksums.
  209. Checksum.update( checksums, fis );
  210. }
  211. catch ( IOException e )
  212. {
  213. log.warn( e.getMessage(), e );
  214. return false;
  215. }
  216. boolean valid = true;
  217. // check the hash files
  218. for ( Checksum checksum : checksums )
  219. {
  220. ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm();
  221. try
  222. {
  223. File checksumFile = getChecksumFile( checksumAlgorithm );
  224. String actualChecksum = checksum.getChecksum();
  225. if ( checksumFile.exists() )
  226. {
  227. String rawChecksum = FileUtils.readFileToString( checksumFile );
  228. String expectedChecksum = parseChecksum( rawChecksum, checksumAlgorithm, referenceFile.getName() );
  229. if ( !StringUtils.equalsIgnoreCase( expectedChecksum, actualChecksum ) )
  230. {
  231. // create checksum (again)
  232. FileUtils.writeStringToFile( checksumFile, actualChecksum + " " + referenceFile.getName() );
  233. }
  234. }
  235. else
  236. {
  237. FileUtils.writeStringToFile( checksumFile, actualChecksum + " " + referenceFile.getName() );
  238. }
  239. }
  240. catch ( IOException e )
  241. {
  242. log.warn( e.getMessage(), e );
  243. valid = false;
  244. }
  245. }
  246. return valid;
  247. }
  248. private boolean isValidChecksumPattern( String filename, String path )
  249. {
  250. // check if it is a remote metadata file
  251. Pattern pattern = Pattern.compile( "maven-metadata-\\S*.xml" );
  252. Matcher m = pattern.matcher( path );
  253. if ( m.matches() )
  254. {
  255. return filename.endsWith( path ) || ( "-".equals( filename ) ) || filename.endsWith( "maven-metadata.xml" );
  256. }
  257. return filename.endsWith( path ) || ( "-".equals( filename ) );
  258. }
  259. /**
  260. * Parse a checksum string.
  261. * <p>
  262. * Validate the expected path, and expected checksum algorithm, then return
  263. * the trimmed checksum hex string.
  264. * </p>
  265. * @param rawChecksumString
  266. * @param expectedHash
  267. * @param expectedPath
  268. * @return
  269. * @throws IOException
  270. */
  271. public String parseChecksum( String rawChecksumString, ChecksumAlgorithm expectedHash, String expectedPath )
  272. throws IOException
  273. {
  274. String trimmedChecksum = rawChecksumString.replace( '\n', ' ' ).trim();
  275. // Free-BSD / openssl
  276. String regex = expectedHash.getType() + "\\s*\\(([^)]*)\\)\\s*=\\s*([a-fA-F0-9]+)";
  277. Matcher m = Pattern.compile( regex ).matcher( trimmedChecksum );
  278. if ( m.matches() )
  279. {
  280. String filename = m.group( 1 );
  281. if ( !isValidChecksumPattern( filename, expectedPath ) )
  282. {
  283. throw new IOException(
  284. "Supplied checksum file '" + filename + "' does not match expected file: '" + expectedPath + "'" );
  285. }
  286. trimmedChecksum = m.group( 2 );
  287. }
  288. else
  289. {
  290. // GNU tools
  291. m = Pattern.compile( "([a-fA-F0-9]+)\\s+\\*?(.+)" ).matcher( trimmedChecksum );
  292. if ( m.matches() )
  293. {
  294. String filename = m.group( 2 );
  295. if ( !isValidChecksumPattern( filename, expectedPath ) )
  296. {
  297. throw new IOException(
  298. "Supplied checksum file '" + filename + "' does not match expected file: '" + expectedPath
  299. + "'" );
  300. }
  301. trimmedChecksum = m.group( 1 );
  302. }
  303. }
  304. return trimmedChecksum;
  305. }
  306. }