package com.limegroup.gnutella.malware; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.URI; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.AbortableHttpRequest; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.params.HttpClientParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.limewire.core.api.download.DownloadException; import org.limewire.core.api.download.DownloadPiecesInfo; import org.limewire.core.api.download.SaveLocationManager; import org.limewire.core.api.file.CategoryManager; import org.limewire.core.api.malware.AntivirusUpdateType; import org.limewire.core.api.transfer.SourceInfo; import org.limewire.core.settings.DownloadSettings; import org.limewire.core.settings.SharingSettings; import org.limewire.io.Address; import org.limewire.io.GUID; import org.limewire.io.IOUtils; import org.limewire.io.InvalidDataException; import org.limewire.listener.EventListener; import org.limewire.listener.EventListenerList; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.util.FileUtils; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.name.Named; import com.limegroup.gnutella.BandwidthTrackerImpl; import com.limegroup.gnutella.DownloadManager; import com.limegroup.gnutella.InsufficientDataException; import com.limegroup.gnutella.RemoteFileDesc; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.downloader.AbstractCoreDownloader; import com.limegroup.gnutella.downloader.DownloadStateEvent; import com.limegroup.gnutella.downloader.DownloaderType; import com.limegroup.gnutella.downloader.serial.DownloadMemento; import com.limegroup.gnutella.http.HTTPHeaderName; import com.limegroup.gnutella.http.HTTPUtils; import com.limegroup.gnutella.http.HttpClientListener; import com.limegroup.gnutella.http.HttpExecutor; import com.limegroup.gnutella.util.LimeWireUtils; class VirusDefinitionDownloaderImpl extends AbstractCoreDownloader implements VirusDefinitionDownloader { private static final Log LOG = LogFactory.getLog(VirusDefinitionDownloaderImpl.class); private static final int TIMEOUT = 5000; private static final String AVG_LW_USER_AGENT = "AVGINETSDK-LIMEWIRE"; private final DownloadManager downloadManager; private final HttpExecutor httpExecutor; private final EventListenerList<DownloadStateEvent> eventListenerList; private final BandwidthTrackerImpl bandwidthTracker; private final Provider<HttpParams> defaultParams; private volatile URI uri = null; private volatile File incompleteFile = null; private volatile VirusDefinitionHandler handler = null; private volatile DownloadState state = DownloadState.QUEUED; private final AtomicReference<AbortableHttpRequest> activeRequest = new AtomicReference<AbortableHttpRequest>(); private volatile long contentLength = 0; private volatile long amountRead = 0; private volatile long amountWritten = 0; private volatile boolean aborted = false; private File mementoFile; private File mementoBackupFile; @Inject public VirusDefinitionDownloaderImpl(SaveLocationManager saveLocationManager, CategoryManager categoryManager, DownloadManager downloadManager, HttpExecutor httpExecutor, @Named("defaults") Provider<HttpParams> defaultParams) { super(saveLocationManager, categoryManager); this.downloadManager = downloadManager; this.httpExecutor = httpExecutor; this.eventListenerList = new EventListenerList<DownloadStateEvent>(); this.bandwidthTracker = new BandwidthTrackerImpl(); this.defaultParams = defaultParams; } @Override public synchronized void initFromMemento(DownloadMemento memento) throws InvalidDataException { super.initFromMemento(memento); VirusDefinitionDownloadMemento vm = (VirusDefinitionDownloadMemento)memento; this.amountWritten = vm.getAmountWritten(); this.uri = vm.getUri(); this.incompleteFile = vm.getIncompleteFile(); DownloadSettings.NUM_AV_MEMENTOS_RESUMED.set(DownloadSettings.NUM_AV_MEMENTOS_RESUMED.get() + 1); } URI getUri() { return uri; } long getAmountWritten() { return amountWritten; } /** Gets the incomplete file this uses. */ File getIncompleteFile() { return incompleteFile; } void setAmountWritten(long amountWritten) { this.amountWritten = amountWritten; } @Override public void setUriAndName(URI uri, String name) { this.uri = uri; setDefaultFileName(name); incompleteFile = new File(SharingSettings.INCOMPLETE_DIRECTORY.get(), name); } @Override public void fetch(VirusDefinitionHandler handler, File mementoFile, File mementoBackupFile) { assert uri != null; assert incompleteFile != null; assert getDefaultFileName() != null; if(LOG.isDebugEnabled()) LOG.debug("Fetching " + uri); this.handler = handler; this.mementoFile = mementoFile; this.mementoBackupFile = mementoBackupFile; downloadManager.addNewDownloader(this); } private void failDownload(DownloadState failureState) { // If this was aborted, override the state. if(aborted) { failureState = DownloadState.ABORTED; } // if the prior state was active, then proceed to failure state. if(state == DownloadState.QUEUED || state == DownloadState.DOWNLOADING) { state = failureState; if(handler != null) { handler.downloadFailed(incompleteFile); } deleteIncompleteFiles(); eventListenerList.broadcast(new DownloadStateEvent(this, state)); downloadManager.remove(this, true); } } @Override public void discardUnscannedPreview(boolean delete) { } @Override public long getAmountLost() { return 0; } @Override public int getAmountPending() { return 0; } @Override public long getAmountRead() { return amountRead; } @Override public long getAmountVerified() { return 0; } @Override public List<RemoteFileDesc> getRemoteFileDescs() { return Collections.emptyList(); } @Override public int getChunkSize() { return 1; } @Override public long getContentLength() { return contentLength; } @Override public File getDownloadFragment(ScanListener listener) { return null; } @Override public File getFile() { return getSaveFile(); } @Override public int getNumHosts() { return 0; } @Override public List<Address> getSourcesAsAddresses() { return Collections.emptyList(); } @Override public List<SourceInfo> getSourcesDetails() { return Collections.emptyList(); } @Override public int getQueuePosition() { return 0; } @Override public int getQueuedHostCount() { return 0; } @Override public int getRemainingStateTime() { return 0; } @Override public URN getSha1Urn() { return null; } @Override public File getSaveFile() { return incompleteFile; } @Override public void setSaveFile(File saveDirectory, String fileName, boolean overwrite) throws DownloadException { } @Override public DownloadState getState() { return state; } @Override public boolean isCompleted() { return state == DownloadState.COMPLETE; } @Override public boolean isLaunchable() { return false; } @Override public boolean isPaused() { return false; } @Override public boolean isRelocatable() { return false; } @Override public void pause() { } @Override public boolean resume() { return false; } @Override public void stop() { aborted = true; AbortableHttpRequest req = activeRequest.getAndSet(null); if(req != null) { req.abort(); } failDownload(DownloadState.ABORTED); } @Override public float getAverageBandwidth() { return bandwidthTracker.getAverageBandwidth(); } @Override public float getMeasuredBandwidth() throws InsufficientDataException { return bandwidthTracker.getMeasuredBandwidth(); } @Override public void measureBandwidth() { bandwidthTracker.measureBandwidth(amountRead); } @Override public void finish() { } @Override public GUID getQueryGUID() { return null; } @Override public void handleInactivity() { } @Override public void initialize() { } @Override public boolean isAlive() { return state == DownloadState.DOWNLOADING; } @Override public boolean isQueuable() { return false; } @Override public void setInactivePriority(int priority) { } @Override public boolean shouldBeRemoved() { return isCompleted() || state == DownloadState.ABORTED || state == DownloadState.INVALID || state == DownloadState.UNABLE_TO_CONNECT; } @Override public boolean shouldBeRestarted() { return state == DownloadState.QUEUED; } @Override public void startDownload() { HttpGet get = new HttpGet(uri.toString()); get.addHeader("User-Agent", AVG_LW_USER_AGENT + " " + LimeWireUtils.getLimeWireVersion()); get.addHeader(HTTPHeaderName.CONNECTION.httpStringValue(), "close"); if(amountWritten > 0) { LOG.debugf("Adding Range header starting at {0}", amountWritten); get.addHeader(HTTPHeaderName.RANGE.httpStringValue(), "bytes=" + amountWritten + "-"); } // LOG.debugf("Sending GET {0}, {1}", get.getRequestLine(), Arrays.asList(get.getAllHeaders())); HttpParams params = defaultParams.get(); HttpConnectionParams.setConnectionTimeout(params, TIMEOUT); HttpConnectionParams.setSoTimeout(params, TIMEOUT); HttpClientParams.setRedirecting(params, true); state = DownloadState.DOWNLOADING; activeRequest.set(get); eventListenerList.broadcast(new DownloadStateEvent(this, state)); httpExecutor.execute(get, params, new Listener()); } @Override public DownloaderType getDownloadType() { return DownloaderType.ANTIVIRUS; } @Override public void addListener(EventListener<DownloadStateEvent> listener) { eventListenerList.addListener(listener); } @Override public boolean removeListener(EventListener<DownloadStateEvent> listener) { return eventListenerList.removeListener(listener); } @Override public boolean isMementoSupported() { return false; } @Override protected DownloadMemento createMemento() { VirusDefinitionDownloadMemento memento = new VirusDefinitionDownloadMemento(); memento.setUri(uri); memento.setAmountWritten(amountWritten); memento.setIncompleteFile(incompleteFile); memento.setAntivirusUpdateType(AntivirusUpdateType.FULL); return memento; } @Override protected void fillInMemento(DownloadMemento memento) { super.fillInMemento(memento); // override some default values. memento.setSaveFile(getSaveFile()); memento.setDefaultFileName(getDefaultFileName()); } @Override protected File getDefaultSaveFile() { return getSaveFile(); } @Override public boolean conflicts(URN urn, long fileSize, File... files) { return false; } @Override public boolean conflictsWithIncompleteFile(File incomplete) { return false; } @Override public void deleteIncompleteFiles() { if(incompleteFile != null) { FileUtils.forceDelete(incompleteFile); } if(mementoFile != null) { FileUtils.forceDelete(mementoFile); } if(mementoBackupFile != null) { FileUtils.forceDelete(mementoBackupFile); } } private void saveMemento() { if(mementoFile != null) { LOG.debugf("Writing memento to {0}", mementoFile); DownloadMemento memento = toMemento(); FileUtils.writeWithBackupFile(memento, mementoBackupFile, mementoFile, LOG); } } @Override public DownloadPiecesInfo getPieceInfo() { return null; } private class Listener implements HttpClientListener { @Override public boolean requestFailed(HttpUriRequest method, HttpResponse response, IOException e) { activeRequest.set(null); failDownload(DownloadState.UNABLE_TO_CONNECT); return false; } @Override public boolean requestComplete(HttpUriRequest method, HttpResponse response) { try { int status = response.getStatusLine().getStatusCode(); if (status < 200 || status >= 300) { LOG.debugf("Bad status code: {0}", status); failDownload(DownloadState.UNABLE_TO_CONNECT); return false; } HttpEntity entity = response.getEntity(); if (entity == null) { LOG.error("Invalid response: no entity"); failDownload(DownloadState.INVALID); return false; } long length = entity.getContentLength(); contentLength = length > 0 ? length : 0; incompleteFile.getParentFile().mkdirs(); InputStream downloadStream = null; RandomAccessFile outputStream = null; boolean downloadCompleted = false; try { long startPoint = HTTPUtils.getStartPoint(response); if(startPoint > amountWritten) { throw new IOException("must start at or before amount written"); } LOG.debugf("Seeking ahead to {0}, amountWritten was {1}", startPoint, amountWritten); outputStream = new RandomAccessFile(incompleteFile, "rw"); outputStream.seek(startPoint); amountWritten = startPoint; downloadStream = entity.getContent(); int read = 0; byte[] buf = new byte[2048]; long lastRecord = System.currentTimeMillis(); while ((read = downloadStream.read(buf, 0, buf.length)) != -1) { amountRead += read; outputStream.write(buf, 0, read); amountWritten += read; long now = System.currentTimeMillis(); if(TimeUnit.MILLISECONDS.toSeconds(now - lastRecord) > 1) { // If more than a second has passed, since we last // recorded, save a memento of our progress. lastRecord = now; saveMemento(); } } downloadCompleted = true; } catch (IOException e) { LOG.debugf(e, "error downloading virus definitions from {0}", uri); } finally { IOUtils.close(downloadStream); IOUtils.close(outputStream); } if (downloadCompleted) { //handler.downloadSucceeded() applies the update and takes some time. Broadcast the applying state first. state = DownloadState.APPLYING_ANTIVIRUS_DEFINITION_UPDATE; eventListenerList.broadcast(new DownloadStateEvent(VirusDefinitionDownloaderImpl.this, state)); handler.downloadSucceeded(incompleteFile); //we are complete now that the update has been applied state = DownloadState.COMPLETE; deleteIncompleteFiles(); eventListenerList.broadcast(new DownloadStateEvent(VirusDefinitionDownloaderImpl.this, state)); downloadManager.remove(VirusDefinitionDownloaderImpl.this, true); } else { failDownload(DownloadState.INVALID); } return false; } finally { httpExecutor.releaseResources(response); activeRequest.set(null); } } @Override public boolean allowRequest(HttpUriRequest request) { return true; } } }