diff options
Diffstat (limited to 'src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java')
-rw-r--r-- | src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java b/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java new file mode 100644 index 000000000..ae897a8e7 --- /dev/null +++ b/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java @@ -0,0 +1,363 @@ +package de.pixart.messenger.http; + +import android.os.PowerManager; +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.CancellationException; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLHandshakeException; + +import de.pixart.messenger.Config; +import de.pixart.messenger.R; +import de.pixart.messenger.entities.DownloadableFile; +import de.pixart.messenger.entities.Message; +import de.pixart.messenger.entities.Transferable; +import de.pixart.messenger.entities.TransferablePlaceholder; +import de.pixart.messenger.persistance.FileBackend; +import de.pixart.messenger.services.AbstractConnectionManager; +import de.pixart.messenger.services.XmppConnectionService; +import de.pixart.messenger.utils.CryptoHelper; + +public class HttpDownloadConnection implements Transferable { + + private HttpConnectionManager mHttpConnectionManager; + private XmppConnectionService mXmppConnectionService; + + private URL mUrl; + private Message message; + private DownloadableFile file; + private int mStatus = Transferable.STATUS_UNKNOWN; + private boolean acceptedAutomatically = false; + private int mProgress = 0; + private boolean mUseTor = false; + private boolean canceled = false; + + private final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); + + public HttpDownloadConnection(HttpConnectionManager manager) { + this.mHttpConnectionManager = manager; + this.mXmppConnectionService = manager.getXmppConnectionService(); + this.mUseTor = mXmppConnectionService.useTorToConnect(); + } + + @Override + public boolean start() { + if (mXmppConnectionService.hasInternetConnection()) { + if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) { + checkFileSize(true); + } else { + new Thread(new FileDownloader(true)).start(); + } + return true; + } else { + return false; + } + } + + public void init(Message message) { + init(message, false); + } + + public void init(Message message, boolean interactive) { + this.message = message; + this.message.setTransferable(this); + try { + if (message.hasFileOnRemoteHost()) { + mUrl = message.getFileParams().url; + } else { + mUrl = new URL(message.getBody()); + } + String[] parts = mUrl.getPath().toLowerCase().split("\\."); + String lastPart = parts.length >= 1 ? parts[parts.length - 1] : null; + String secondToLast = parts.length >= 2 ? parts[parts.length -2] : null; + if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) { + this.message.setEncryption(Message.ENCRYPTION_PGP); + } else if (message.getEncryption() != Message.ENCRYPTION_OTR + && message.getEncryption() != Message.ENCRYPTION_AXOLOTL) { + this.message.setEncryption(Message.ENCRYPTION_NONE); + } + String extension; + if (VALID_CRYPTO_EXTENSIONS.contains(lastPart)) { + extension = secondToLast; + } else { + extension = lastPart; + } + String filename = fileDateFormat.format(new Date(message.getTimeSent()))+"_"+message.getUuid().substring(0,4); + message.setRelativeFilePath(filename + "." + extension); + this.file = mXmppConnectionService.getFileBackend().getFile(message, false); + String reference = mUrl.getRef(); + if (reference != null && reference.length() == 96) { + this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference)); + } + + if ((this.message.getEncryption() == Message.ENCRYPTION_OTR + || this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL) + && this.file.getKey() == null) { + this.message.setEncryption(Message.ENCRYPTION_NONE); + } + checkFileSize(interactive); + } catch (MalformedURLException e) { + this.cancel(); + } + } + + private void checkFileSize(boolean interactive) { + new Thread(new FileSizeChecker(interactive)).start(); + } + + @Override + public void cancel() { + this.canceled = true; + mHttpConnectionManager.finishConnection(this); + if (message.isFileOrImage()) { + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + } else { + message.setTransferable(null); + } + mXmppConnectionService.updateConversationUi(); + } + + private void finish() { + mXmppConnectionService.getFileBackend().updateMediaScanner(file); + message.setTransferable(null); + mHttpConnectionManager.finishConnection(this); + boolean notify = acceptedAutomatically && !message.isRead(); + if (message.getEncryption() == Message.ENCRYPTION_PGP) { + notify = message.getConversation().getAccount().getPgpDecryptionService().decrypt(message, notify); + } + mXmppConnectionService.updateConversationUi(); + if (notify) { + mXmppConnectionService.getNotificationService().push(message); + } + } + + private void changeStatus(int status) { + this.mStatus = status; + mXmppConnectionService.updateConversationUi(); + } + + private class WriteException extends IOException { + + } + + private void showToastForException(Exception e) { + if (e instanceof java.net.UnknownHostException) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_server_not_found); + } else if (e instanceof java.net.ConnectException) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_connect); + } else if (e instanceof WriteException) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_write_file); + } else if (!(e instanceof CancellationException)) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found); + } + } + + private class FileSizeChecker implements Runnable { + + private boolean interactive = false; + + public FileSizeChecker(boolean interactive) { + this.interactive = interactive; + } + + @Override + public void run() { + long size; + try { + size = retrieveFileSize(); + } catch (Exception e) { + changeStatus(STATUS_OFFER_CHECK_FILESIZE); + Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage()); + if (interactive) { + showToastForException(e); + } else { + HttpDownloadConnection.this.acceptedAutomatically = false; + HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message); + } + cancel(); + return; + } + file.setExpectedSize(size); + if (mHttpConnectionManager.hasStoragePermission() && size <= mHttpConnectionManager.getAutoAcceptFileSize()) { + HttpDownloadConnection.this.acceptedAutomatically = true; + new Thread(new FileDownloader(interactive)).start(); + } else { + changeStatus(STATUS_OFFER); + HttpDownloadConnection.this.acceptedAutomatically = false; + HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message); + } + } + + private long retrieveFileSize() throws IOException { + PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid()); + try { + wakeLock.acquire(); + Log.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive)); + changeStatus(STATUS_CHECKING); + HttpURLConnection connection; + if (mUseTor) { + connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy()); + } else { + connection = (HttpURLConnection) mUrl.openConnection(); + } + connection.setRequestMethod("HEAD"); + Log.d(Config.LOGTAG,"url: "+connection.getURL().toString()); + Log.d(Config.LOGTAG,"connection: "+connection.toString()); + connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getIdentityName()); + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.connect(); + String contentLength = connection.getHeaderField("Content-Length"); + connection.disconnect(); + if (contentLength == null) { + throw new IOException("no content-length found in HEAD response"); + } + wakeLock.release(); + return Long.parseLong(contentLength, 10); + } catch (IOException e) { + throw e; + } catch (NumberFormatException e) { + throw new IOException(); + } + } + + } + + private class FileDownloader implements Runnable { + + private boolean interactive = false; + + private OutputStream os; + + public FileDownloader(boolean interactive) { + this.interactive = interactive; + } + + @Override + public void run() { + try { + changeStatus(STATUS_DOWNLOADING); + download(); + updateImageBounds(); + finish(); + } catch (SSLHandshakeException e) { + changeStatus(STATUS_OFFER); + } catch (Exception e) { + if (interactive) { + showToastForException(e); + } else { + HttpDownloadConnection.this.acceptedAutomatically = false; + HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message); + } + cancel(); + } + } + + private void download() throws Exception { + InputStream is = null; + PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid()); + try { + wakeLock.acquire(); + HttpURLConnection connection; + if (mUseTor) { + connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy()); + } else { + connection = (HttpURLConnection) mUrl.openConnection(); + } + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName()); + final boolean tryResume = file.exists() && file.getKey() == null; + if (tryResume) { + Log.d(Config.LOGTAG,"http download trying resume"); + long size = file.getSize(); + connection.setRequestProperty("Range", "bytes="+size+"-"); + } + connection.connect(); + is = new BufferedInputStream(connection.getInputStream()); + boolean serverResumed = "bytes".equals(connection.getHeaderField("Accept-Ranges")); + long transmitted = 0; + long expected = file.getExpectedSize(); + if (tryResume && serverResumed) { + Log.d(Config.LOGTAG,"server resumed"); + transmitted = file.getSize(); + updateProgress((int) ((((double) transmitted) / expected) * 100)); + os = AbstractConnectionManager.createAppendedOutputStream(file); + } else { + file.getParentFile().mkdirs(); + file.createNewFile(); + os = AbstractConnectionManager.createOutputStream(file, true); + } + int count; + byte[] buffer = new byte[1024]; + while ((count = is.read(buffer)) != -1) { + transmitted += count; + try { + os.write(buffer, 0, count); + } catch (IOException e) { + throw new WriteException(); + } + updateProgress((int) ((((double) transmitted) / expected) * 100)); + if (canceled) { + throw new CancellationException(); + } + } + try { + os.flush(); + } catch (IOException e) { + throw new WriteException(); + } + } catch (CancellationException | IOException e) { + throw e; + } finally { + FileBackend.close(os); + FileBackend.close(is); + wakeLock.release(); + } + } + + private void updateImageBounds() { + message.setType(Message.TYPE_FILE); + mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl); + mXmppConnectionService.updateMessage(message); + } + + } + + public void updateProgress(int i) { + this.mProgress = i; + mXmppConnectionService.updateConversationUi(); + } + + @Override + public int getStatus() { + return this.mStatus; + } + + @Override + public long getFileSize() { + if (this.file != null) { + return this.file.getExpectedSize(); + } else { + return 0; + } + } + + @Override + public int getProgress() { + return this.mProgress; + } +} |