/******************************************************************************* * Copyright (c) 2006-2013, Cloudsmith Inc. * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the copyright holder * listed above, as the Initial Contributor under such license. The text of * such license is available at www.eclipse.org. ******************************************************************************/ package org.eclipse.buckminster.download.policy; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ConnectException; import java.net.URL; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Locale; import org.eclipse.buckminster.download.ICache; import org.eclipse.buckminster.download.Messages; import org.eclipse.buckminster.download.internal.FileReader; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.buckminster.runtime.IFileInfo; import org.eclipse.buckminster.runtime.IOUtils; import org.eclipse.buckminster.runtime.MonitorUtils; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.ecf.core.security.IConnectContext; import org.eclipse.osgi.util.NLS; /** * @author Thomas Hallgren * */ public class DigestPolicy extends AbstractFetchPolicy { static class BytesFromHexBuilder extends ByteArrayOutputStream { private final int byteCount; public BytesFromHexBuilder(int digestLength) { byteCount = digestLength * 2; } public synchronized byte[] getBytes() throws CoreException { if (byteCount > count) throw BuckminsterException.fromMessage(NLS.bind(Messages.digest_not_fully_read_expected_0_got_1, String.valueOf(byteCount), String.valueOf(count))); return Hex.decode(buf, byteCount); } } public static final int DEFAULT_MAX_DIGEST_AGE = 30000; public static final int MAX_RETRIES = 3; private final String algorithm; private final URL remoteDigest; private final IConnectContext connectContext; private final int digestLength; private final int maxDigestAge; public DigestPolicy(ICache cache, URL remoteDigest, IConnectContext cctx, String algorithm, int maxDigestAge) throws CoreException { super(cache); this.remoteDigest = remoteDigest; this.algorithm = algorithm; this.maxDigestAge = maxDigestAge; this.connectContext = cctx; try { this.digestLength = MessageDigest.getInstance(algorithm).getDigestLength(); } catch (NoSuchAlgorithmException e) { throw BuckminsterException.wrap(e); } } @Override public boolean update(URL remoteFile, File localFile, boolean checkOnly, IFileInfo[] fiHandle, IProgressMonitor monitor) throws CoreException, FileNotFoundException { byte[] localDigest; byte[] remoteDgst = null; MonitorUtils.begin(monitor, checkOnly ? 100 : 1000); try { File localDigestFile = getLocalDigest(localFile); if (localFile.exists()) { boolean localDigestCalculated = false; long digestTS = localDigestFile.lastModified(); if (digestTS != 0L) { long digestAge = System.currentTimeMillis() - digestTS; if (digestAge < maxDigestAge) return false; localDigest = readLocalDigest(localDigestFile); } else { // No local digest file exists. Calculate digest from the // local file // localDigest = calculateDigest(localFile); localDigestCalculated = true; } // The local digest is either calculated or too old to be // "trusted". Let's read it // from the remote source and verify that it hasn't changed // remoteDgst = readRemoteDigest(); if (Arrays.equals(remoteDgst, localDigest)) { if (localDigestCalculated) writeLocalDigest(localDigestFile, localDigest); else localDigestFile.setLastModified(System.currentTimeMillis()); return false; } if (checkOnly) return true; } else { if (checkOnly) return true; remoteDgst = readRemoteDigest(); } MonitorUtils.worked(monitor, 100); File tempFile = new File(localFile.getPath() + ".tmp"); //$NON-NLS-1$ IProgressMonitor subMon = MonitorUtils.subMonitor(monitor, 800); for (int idx = 0;; ++idx) { try { localDigest = readRemoteFile(remoteFile, tempFile, fiHandle, subMon); } catch (CoreException e) { tempFile.delete(); if (idx < MAX_RETRIES) { Throwable cause = e.getStatus().getException(); if (cause instanceof ConnectException) { try { Thread.sleep(3000); } catch (InterruptedException e1) { } subMon = MonitorUtils.subMonitor(monitor, 100 / (MAX_RETRIES - 1)); continue; } } throw e; } if (Arrays.equals(remoteDgst, localDigest)) { // File transfer was successful // safeRename(tempFile, localFile); writeLocalDigest(localDigestFile, localDigest); return true; } tempFile.delete(); if (idx < MAX_RETRIES) continue; throw BuckminsterException.fromMessage(NLS.bind(Messages.digest_mismatch_reading_0, remoteFile)); } } finally { MonitorUtils.done(monitor); } } protected byte[] calculateDigest(File content) throws CoreException { InputStream input = null; MessageDigest md = getDigest(); try { byte[] buf = new byte[8192]; int count; input = new FileInputStream(content); while ((count = input.read(buf)) >= 0) md.update(buf, 0, count); return md.digest(); } catch (IOException e) { throw BuckminsterException.wrap(e); } finally { IOUtils.close(input); } } protected MessageDigest getDigest() throws CoreException { try { return MessageDigest.getInstance(algorithm); } catch (NoSuchAlgorithmException e) { throw BuckminsterException.wrap(e); } } protected File getLocalDigest(File localFile) { return new File(localFile.getPath() + '.' + algorithm.toLowerCase(Locale.ENGLISH)); } protected byte[] readLocalDigest(File localDigestFile) throws CoreException { InputStream input = null; try { input = new FileInputStream(localDigestFile); return Hex.readHex(localDigestFile.getPath(), input, digestLength); } catch (IOException e) { throw BuckminsterException.wrap(e); } finally { IOUtils.close(input); } } protected byte[] readRemoteDigest() throws CoreException, FileNotFoundException { FileReader reader = new FileReader(connectContext); BytesFromHexBuilder digestByteBuilder = new BytesFromHexBuilder(digestLength); reader.readInto(remoteDigest, digestByteBuilder, null); return digestByteBuilder.getBytes(); } protected byte[] readRemoteFile(URL url, File localFile, IFileInfo[] fiHandle, IProgressMonitor monitor) throws CoreException, FileNotFoundException { // Set up the file transfer // MessageDigest md = getDigest(); DigestOutputStream output = null; try { File parentFolder = localFile.getParentFile(); if (parentFolder != null) parentFolder.mkdirs(); output = new DigestOutputStream(new FileOutputStream(localFile), md); FileReader retriever = new FileReader(connectContext); retriever.readInto(url, output, monitor); IFileInfo fi = retriever.getLastFileInfo(); saveLocalFileInfo(url, fi); if (fiHandle != null) fiHandle[0] = fi; } finally { IOUtils.close(output); } return md.digest(); } protected void writeLocalDigest(File localDigestFile, byte[] localDigest) throws CoreException { OutputStream output = null; try { File parentFolder = localDigestFile.getParentFile(); if (parentFolder != null) parentFolder.mkdirs(); output = new FileOutputStream(localDigestFile); Hex.writeHex(localDigest, output); } catch (IOException e) { throw BuckminsterException.wrap(e); } finally { IOUtils.close(output); } } }