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 12KB

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