001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.operations; 019 020import java.io.File; 021import java.io.FileInputStream; 022import java.io.FileOutputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.util.Set; 027import java.util.logging.Level; 028import java.util.logging.Logger; 029 030import org.apache.commons.io.IOUtils; 031import org.syncany.config.Config; 032import org.syncany.config.LocalEventBus; 033import org.syncany.database.MultiChunkEntry.MultiChunkId; 034import org.syncany.operations.daemon.messages.DownDownloadFileSyncExternalEvent; 035import org.syncany.plugins.transfer.StorageException; 036import org.syncany.plugins.transfer.TransferManager; 037import org.syncany.plugins.transfer.files.MultichunkRemoteFile; 038 039/** 040 * The downloader uses a {@link TransferManager} to download a given set of multichunks, 041 * decrypt them and store them in the local cache folder. 042 * 043 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 044 */ 045public class Downloader { 046 private static final Logger logger = Logger.getLogger(Downloader.class.getSimpleName()); 047 048 private Config config; 049 private TransferManager transferManager; 050 private LocalEventBus eventBus; 051 052 public Downloader(Config config, TransferManager transferManager) { 053 this.config = config; 054 this.transferManager = transferManager; 055 this.eventBus = LocalEventBus.getInstance(); 056 } 057 058 /** 059 * Downloads the given multichunks from the remote storage and decrypts them 060 * to the local cache folder. 061 */ 062 public void downloadAndDecryptMultiChunks(Set<MultiChunkId> unknownMultiChunkIds) throws StorageException, IOException { 063 logger.log(Level.INFO, "Downloading and extracting multichunks ..."); 064 065 int multiChunkNumber = 0; 066 067 for (MultiChunkId multiChunkId : unknownMultiChunkIds) { 068 File localEncryptedMultiChunkFile = config.getCache().getEncryptedMultiChunkFile(multiChunkId); 069 File localDecryptedMultiChunkFile = config.getCache().getDecryptedMultiChunkFile(multiChunkId); 070 MultichunkRemoteFile remoteMultiChunkFile = new MultichunkRemoteFile(multiChunkId); 071 072 multiChunkNumber++; 073 074 if (localDecryptedMultiChunkFile.exists()) { 075 logger.log(Level.INFO, " + Decrypted multichunk exists locally " + multiChunkId + ". No need to download it!"); 076 } 077 else { 078 eventBus.post(new DownDownloadFileSyncExternalEvent(config.getLocalDir().getAbsolutePath(), "multichunk", multiChunkNumber, 079 unknownMultiChunkIds.size())); 080 081 logger.log(Level.INFO, " + Downloading multichunk " + multiChunkId + " ..."); 082 transferManager.download(remoteMultiChunkFile, localEncryptedMultiChunkFile); 083 084 try { 085 logger.log(Level.INFO, " + Decrypting multichunk " + multiChunkId + " ..."); 086 InputStream multiChunkInputStream = config.getTransformer().createInputStream(new FileInputStream(localEncryptedMultiChunkFile)); 087 OutputStream decryptedMultiChunkOutputStream = new FileOutputStream(localDecryptedMultiChunkFile); 088 089 IOUtils.copy(multiChunkInputStream, decryptedMultiChunkOutputStream); 090 091 decryptedMultiChunkOutputStream.close(); 092 multiChunkInputStream.close(); 093 094 } 095 catch (IOException e) { 096 // Security: Deleting the multichunk if the decryption/extraction failed is important! 097 // If it is not deleted, the partially decrypted multichunk will reside in the 098 // local cache and the next 'down' will try to use it. If this is the only 099 // multichunk that has been tampered with, other changes might be applied to the 100 // file system! See https://github.com/syncany/syncany/issues/59#issuecomment-55154793 101 102 logger.log(Level.FINE, " -> FAILED: Decryption/extraction of multichunk failed, deleting " + multiChunkId + " ..."); 103 localDecryptedMultiChunkFile.delete(); 104 105 throw new IOException("Decryption/extraction of multichunk " + multiChunkId 106 + " failed. The multichunk might have been tampered with!", e); 107 } 108 finally { 109 logger.log(Level.FINE, " + Locally deleting multichunk " + multiChunkId + " ..."); 110 localEncryptedMultiChunkFile.delete(); 111 } 112 } 113 } 114 115 transferManager.disconnect(); 116 } 117}