aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload')
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java52
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java120
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java84
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java34
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java33
5 files changed, 323 insertions, 0 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java
new file mode 100644
index 00000000..8efd498b
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileTransferEntity.java
@@ -0,0 +1,52 @@
+package de.thedevstack.conversationsplus.services.filetransfer.httpupload;
+
+import de.thedevstack.conversationsplus.Config;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.services.filetransfer.FileTransferEntity;
+import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadSlot;
+
+/**
+ *
+ */
+public class HttpFileTransferEntity extends FileTransferEntity {
+ private HttpUploadSlot slot;
+ private final byte[] key;
+ private final boolean delayed;
+
+ public HttpFileTransferEntity(Message message, boolean delayed) {
+ super(message);
+ this.getMessage().setHttpUploaded(true);
+ this.getMessage().setNoDownloadable();
+ if (Config.ENCRYPT_ON_HTTP_UPLOADED
+ || message.getEncryption() == Message.ENCRYPTION_AXOLOTL
+ || message.getEncryption() == Message.ENCRYPTION_OTR) {
+ this.key = new byte[48];
+ ConversationsPlusApplication.getSecureRandom().nextBytes(this.key);
+ this.getFile().setKeyAndIv(this.key);
+ } else {
+ this.key = null;
+ }
+ this.delayed = delayed;
+ }
+
+ public void setSlot(HttpUploadSlot slot) {
+ this.slot = slot;
+ }
+
+ public String getGetUrl() {
+ return this.slot.getGetUrl();
+ }
+
+ public String getPutUrl() {
+ return this.slot.getPutUrl();
+ }
+
+ public byte[] getKey() {
+ return key;
+ }
+
+ public boolean isDelayed() {
+ return this.delayed;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java
new file mode 100644
index 00000000..6352c7a7
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpFileUploader.java
@@ -0,0 +1,120 @@
+package de.thedevstack.conversationsplus.services.filetransfer.httpupload;
+
+import android.os.PowerManager;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Scanner;
+
+import javax.net.ssl.HttpsURLConnection;
+
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.Config;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.entities.DownloadableFile;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.http.HttpConnectionManager;
+import de.thedevstack.conversationsplus.persistance.FileBackend;
+import de.thedevstack.conversationsplus.utils.CryptoHelper;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
+import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
+import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
+import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUpload;
+
+public class HttpFileUploader implements Runnable {
+ private static final String HTTP_METHOD = "PUT";
+ private static final String MIME_REQUEST_PROPERTY_NAME = "Content-Type";
+ private static final String USER_AGENT_REQUEST_PROPERTY_NAME = "User-Agent";
+ private static final int BUFFER_LENGTH = 4096;
+ private final HttpFileTransferEntity entity;
+
+ public HttpFileUploader(HttpFileTransferEntity entity) {
+ this.entity = entity;
+ }
+
+ @Override
+ public void run() {
+ this.upload();
+ }
+
+ private void upload() {
+ OutputStream os = null;
+ InputStream errorStream = null;
+ HttpURLConnection connection = null;
+ InputStream fileInputStream = null;
+ DownloadableFile file = this.entity.getFile();
+ PowerManager.WakeLock wakeLock = ConversationsPlusApplication.createPartialWakeLock("http_upload_" + entity.getMessage().getUuid());
+ try {
+ wakeLock.acquire();
+ Logging.d(Config.LOGTAG, "uploading to " + this.entity.getPutUrl());
+ URL putUrl = new URL(this.entity.getPutUrl());
+ fileInputStream = this.entity.getFileInputStream();
+ connection = (HttpURLConnection) putUrl.openConnection();
+
+ if (connection instanceof HttpsURLConnection) {
+ HttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true);
+ }
+ connection.setRequestMethod(HTTP_METHOD);
+ connection.setFixedLengthStreamingMode((int) file.getExpectedSize());
+ String mime = this.entity.getFile().getMimeType();
+ connection.setRequestProperty(MIME_REQUEST_PROPERTY_NAME, mime == null ? HttpUpload.DEFAULT_MIME_TYPE : mime);
+ connection.setRequestProperty(USER_AGENT_REQUEST_PROPERTY_NAME, ConversationsPlusApplication.getNameAndVersion());
+ connection.setDoOutput(true);
+ connection.connect();
+ os = connection.getOutputStream();
+ int count = -1;
+ byte[] buffer = new byte[BUFFER_LENGTH];
+ while (((count = fileInputStream.read(buffer)) != -1) && !this.entity.isCanceled()) {
+ this.entity.updateProgress(count);
+ os.write(buffer, 0, count);
+ UiUpdateHelper.updateConversationUi();
+ }
+ os.flush();
+ os.close();
+ fileInputStream.close();
+ int code = connection.getResponseCode();
+ if (code == 200 || code == 201) {
+ Logging.d(Config.LOGTAG, "finished uploading file");
+ this.entity.transferred();
+ URL getUrl = new URL(this.entity.getGetUrl());
+ if (this.entity.getKey() != null) {
+ getUrl = new URL(getUrl.toString() + "#" + CryptoHelper.bytesToHex(this.entity.getKey()));
+ }
+ Message message = this.entity.getMessage();
+ MessageUtil.updateFileParams(message, getUrl);
+ FileBackend.updateMediaScanner(file, XmppConnectionServiceAccessor.xmppConnectionService);
+ message.setTransferable(null);
+ message.setCounterpart(message.getConversation().getJid().toBareJid());
+ if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+ XmppConnectionServiceAccessor.xmppConnectionService.getPgpEngine().encrypt(message, new HttpUploadedFileEncryptionUiCallback(this.entity));
+ } else {
+ XmppConnectionServiceAccessor.xmppConnectionService.resendMessage(message, this.entity.isDelayed());
+ }
+ } else {
+ errorStream = connection.getErrorStream();
+ Logging.e("httpupload", "file upload failed: http code (" + code + ") " + new Scanner(errorStream).useDelimiter("\\A").next());
+ this.entity.fail();
+ }
+ } catch (IOException e) {
+ errorStream = (null != connection) ? connection.getErrorStream() : null;
+ String httpResponseMessage = null;
+ if (null != errorStream) {
+ httpResponseMessage = new Scanner(errorStream).useDelimiter("\\A").next();
+ }
+ Logging.e("httpupload", ((null != httpResponseMessage) ? ("http response: " + httpResponseMessage + ", ") : "") + "exception message: " + e.getMessage());
+ this.entity.fail();
+ } finally {
+ StreamUtil.close(os);
+ StreamUtil.close(errorStream);
+ StreamUtil.close(fileInputStream);
+ if (connection != null) {
+ connection.disconnect();
+ }
+ wakeLock.release();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java
new file mode 100644
index 00000000..59957d1e
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadFileTransferService.java
@@ -0,0 +1,84 @@
+package de.thedevstack.conversationsplus.services.filetransfer.httpupload;
+
+import android.util.Pair;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.entities.Account;
+import de.thedevstack.conversationsplus.entities.DownloadableFile;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.persistance.FileBackend;
+import de.thedevstack.conversationsplus.services.AbstractConnectionManager;
+import de.thedevstack.conversationsplus.services.FileTransferService;
+import de.thedevstack.conversationsplus.services.filetransfer.AbstractFileTransferService;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.XmppSendUtil;
+import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUpload;
+import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadRequestSlotPacketGenerator;
+import de.thedevstack.conversationsplus.xmpp.jid.Jid;
+import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket;
+
+/**
+ *
+ */
+public class HttpUploadFileTransferService extends AbstractFileTransferService implements FileTransferService {
+
+ public HttpUploadFileTransferService() {
+ }
+
+ /**
+ * Transfers a file for the corresponding message.
+ *
+ * @param message the message containing the file to transfer
+ * @return <code>true</code> if the file transfer was started successfully, <code>false</code> otherwise
+ */
+ @Override
+ public boolean transferFile(Message message) {
+ return this.transferFile(message, false);
+ }
+
+ /**
+ * Transfers a file for the corresponding message.
+ *
+ * @param message the message containing the file to transfer
+ * @param delay whether the message is delayed or not
+ * @return <code>true</code> if the file transfer was started successfully, <code>false</code> otherwise
+ */
+ @Override
+ public boolean transferFile(Message message, boolean delay) {
+ boolean started = false;
+ try {
+ final HttpFileTransferEntity entity = new HttpFileTransferEntity(message, delay);
+ Account account = message.getConversation().getAccount();
+ DownloadableFile file = entity.getFile();
+ Pair<InputStream, Integer> inputStreamAndExpectedSize = AbstractConnectionManager.createInputStream(file, true);
+
+ entity.setFileInputStream(inputStreamAndExpectedSize.first);
+ file.setExpectedSize(inputStreamAndExpectedSize.second);
+ Jid host = account.getXmppConnection().findDiscoItemByFeature(HttpUpload.NAMESPACE);
+ IqPacket request = HttpUploadRequestSlotPacketGenerator.generate(host, file.getName(), file.getSize(), file.getMimeType());
+ XmppSendUtil.sendIqPacket(account, request, new HttpUploadSlotRequestReceived(entity));
+ MessageUtil.markMessage(message, Message.STATUS_UNSEND);
+ started = true;
+ } catch (FileNotFoundException e) {
+ Logging.e("httpupload", "Could not find file, exception message: " + e.getMessage());
+ }
+ return started;
+ }
+
+ /**
+ * Checks whether a message can be sent using this service or not.
+ *
+ * @param message the message to be checked
+ * @return <code>true</code> if the message can be processed, <code>false</code> otherwise
+ */
+ @Override
+ public boolean accept(Message message) {
+ return null != message
+ && null != message.getConversation()
+ && null != message.getConversation().getAccount()
+ && message.getConversation().getAccount().httpUploadAvailable(FileBackend.getFile(message, false).getSize());
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java
new file mode 100644
index 00000000..e0b2332a
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadSlotRequestReceived.java
@@ -0,0 +1,34 @@
+package de.thedevstack.conversationsplus.services.filetransfer.httpupload;
+
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.entities.Account;
+import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived;
+import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException;
+import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadSlot;
+import de.thedevstack.conversationsplus.xmpp.httpupload.SlotPacketParser;
+import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket;
+
+/**
+ *
+ */
+public class HttpUploadSlotRequestReceived implements OnIqPacketReceived {
+ private final HttpFileTransferEntity entity;
+
+ public HttpUploadSlotRequestReceived(HttpFileTransferEntity entity) {
+ this.entity = entity;
+ }
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ try {
+ HttpUploadSlot slot = SlotPacketParser.parseGetAndPutUrl(packet);
+ this.entity.setSlot(slot);
+ if (!this.entity.isCanceled()) {
+ new Thread(new HttpFileUploader(this.entity)).start();
+ }
+ } catch (XmppException e) {
+ Logging.e("httpupload", e.getMessage());
+ this.entity.fail();
+ }
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java
new file mode 100644
index 00000000..25a16d78
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/httpupload/HttpUploadedFileEncryptionUiCallback.java
@@ -0,0 +1,33 @@
+package de.thedevstack.conversationsplus.services.filetransfer.httpupload;
+
+import android.app.PendingIntent;
+
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.ui.UiCallback;
+import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
+
+/**
+ *
+ */
+public class HttpUploadedFileEncryptionUiCallback implements UiCallback<Message> {
+ private final HttpFileTransferEntity entity;
+
+ public HttpUploadedFileEncryptionUiCallback(HttpFileTransferEntity entity) {
+ this.entity = entity;
+ }
+
+ @Override
+ public void success(Message message) {
+ XmppConnectionServiceAccessor.xmppConnectionService.resendMessage(message, this.entity.isDelayed());
+ }
+
+ @Override
+ public void error(int errorCode, Message object) {
+ this.entity.fail();
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Message object) {
+ this.entity.fail();
+ }
+}