aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de/pixart/messenger
diff options
context:
space:
mode:
authorChristian Schneppe <christian@pix-art.de>2018-05-26 22:56:17 +0200
committerChristian Schneppe <christian@pix-art.de>2018-05-26 22:56:17 +0200
commita554be18a60380d624fc9c65416368775cba9bae (patch)
treebe455ef73e0c047a47bd71651382f2d22dd115ab /src/main/java/de/pixart/messenger
parent369e48c2203a694088156574dd7c7044f68e907b (diff)
add support for S3 file transfers
Diffstat (limited to 'src/main/java/de/pixart/messenger')
-rw-r--r--src/main/java/de/pixart/messenger/entities/Account.java2
-rw-r--r--src/main/java/de/pixart/messenger/generator/IqGenerator.java42
-rw-r--r--src/main/java/de/pixart/messenger/generator/MessageGenerator.java29
-rw-r--r--src/main/java/de/pixart/messenger/http/CustomURLStreamHandlerFactory.java (renamed from src/main/java/de/pixart/messenger/http/AesGcmURLStreamHandlerFactory.java)4
-rw-r--r--src/main/java/de/pixart/messenger/http/HttpConnectionManager.java5
-rw-r--r--src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java93
-rw-r--r--src/main/java/de/pixart/messenger/http/HttpUploadConnection.java126
-rw-r--r--src/main/java/de/pixart/messenger/http/Method.java53
-rw-r--r--src/main/java/de/pixart/messenger/http/P1S3UrlStreamHandler.java62
-rw-r--r--src/main/java/de/pixart/messenger/http/SlotRequester.java189
-rw-r--r--src/main/java/de/pixart/messenger/parser/MessageParser.java12
-rw-r--r--src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java14
-rw-r--r--src/main/java/de/pixart/messenger/services/XmppConnectionService.java4
-rw-r--r--src/main/java/de/pixart/messenger/ui/EditAccountActivity.java6
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java69
-rw-r--r--src/main/java/de/pixart/messenger/utils/Checksum.java60
-rw-r--r--src/main/java/de/pixart/messenger/utils/CryptoHelper.java2
-rw-r--r--src/main/java/de/pixart/messenger/utils/MessageUtils.java4
-rw-r--r--src/main/java/de/pixart/messenger/utils/Namespace.java1
-rw-r--r--src/main/java/de/pixart/messenger/xmpp/XmppConnection.java57
20 files changed, 654 insertions, 180 deletions
diff --git a/src/main/java/de/pixart/messenger/entities/Account.java b/src/main/java/de/pixart/messenger/entities/Account.java
index 9831d091c..a8c72cdf9 100644
--- a/src/main/java/de/pixart/messenger/entities/Account.java
+++ b/src/main/java/de/pixart/messenger/entities/Account.java
@@ -64,7 +64,7 @@ public class Account extends AbstractEntity {
public final HashSet<Pair<String, String>> inProgressDiscoFetches = new HashSet<>();
public boolean httpUploadAvailable(long filesize) {
- return xmppConnection != null && xmppConnection.getFeatures().httpUpload(filesize);
+ return xmppConnection != null && (xmppConnection.getFeatures().httpUpload(filesize) || xmppConnection.getFeatures().p1S3FileTransfer());
}
public boolean httpUploadAvailable() {
diff --git a/src/main/java/de/pixart/messenger/generator/IqGenerator.java b/src/main/java/de/pixart/messenger/generator/IqGenerator.java
index 73f9c7ec8..159e730f3 100644
--- a/src/main/java/de/pixart/messenger/generator/IqGenerator.java
+++ b/src/main/java/de/pixart/messenger/generator/IqGenerator.java
@@ -352,21 +352,37 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime, String http_upload_namespace) {
+ public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
packet.setTo(host);
- Element request = packet.addChild("request", http_upload_namespace);
- if (http_upload_namespace == Namespace.HTTP_UPLOAD) {
- request.setAttribute("filename", convertFilename(file.getName()));
- request.setAttribute("size", file.getExpectedSize());
- request.setAttribute("content-type", mime);
- } else {
- request.addChild("filename").setContent(convertFilename(file.getName()));
- request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
- if (mime != null) {
- request.addChild("content-type").setContent(mime);
- }
- }
+ Element request = packet.addChild("request", Namespace.HTTP_UPLOAD);
+ request.setAttribute("filename", convertFilename(file.getName()));
+ request.setAttribute("size", file.getExpectedSize());
+ request.setAttribute("content-type", mime);
+ return packet;
+ }
+
+ public IqPacket requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) {
+ IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+ packet.setTo(host);
+ Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY);
+ request.addChild("filename").setContent(convertFilename(file.getName()));
+ request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
+ request.addChild("content-type").setContent(mime);
+ return packet;
+ }
+
+ public IqPacket requestP1S3Slot(Jid host, String md5) {
+ IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ packet.setTo(host);
+ packet.query(Namespace.P1_S3_FILE_TRANSFER).setAttribute("md5", md5);
+ return packet;
+ }
+
+ public IqPacket requestP1S3Url(Jid host, String fileId) {
+ IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+ packet.setTo(host);
+ packet.query(Namespace.P1_S3_FILE_TRANSFER).setAttribute("fileid", fileId);
return packet;
}
diff --git a/src/main/java/de/pixart/messenger/generator/MessageGenerator.java b/src/main/java/de/pixart/messenger/generator/MessageGenerator.java
index 57867bc2f..369a1963e 100644
--- a/src/main/java/de/pixart/messenger/generator/MessageGenerator.java
+++ b/src/main/java/de/pixart/messenger/generator/MessageGenerator.java
@@ -3,6 +3,7 @@ package de.pixart.messenger.generator;
import net.java.otr4j.OtrException;
import net.java.otr4j.session.Session;
+import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
@@ -16,6 +17,7 @@ import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Contact;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.Message;
+import de.pixart.messenger.http.P1S3UrlStreamHandler;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.utils.Namespace;
import de.pixart.messenger.xml.Element;
@@ -136,8 +138,17 @@ public class MessageGenerator extends AbstractGenerator {
String content;
if (message.hasFileOnRemoteHost()) {
Message.FileParams fileParams = message.getFileParams();
- content = fileParams.url.toString();
- packet.addChild("x", Namespace.OOB).addChild("url").setContent(content);
+ final URL url = fileParams.url;
+ if (P1S3UrlStreamHandler.PROTOCOL_NAME.equals(url.getProtocol())) {
+ Element x = packet.addChild("x", Namespace.P1_S3_FILE_TRANSFER);
+ final String file = url.getFile();
+ x.setAttribute("name", file.charAt(0) == '/' ? file.substring(1) : file);
+ x.setAttribute("fileid", url.getHost());
+ return packet;
+ } else {
+ content = url.toString();
+ packet.addChild("x", Namespace.OOB).addChild("url").setContent(content);
+ }
} else {
content = message.getBody();
}
@@ -148,9 +159,17 @@ public class MessageGenerator extends AbstractGenerator {
public MessagePacket generatePgpChat(Message message) {
MessagePacket packet = preparePacket(message);
if (message.hasFileOnRemoteHost()) {
- final String url = message.getFileParams().url.toString();
- packet.setBody(url);
- packet.addChild("x", Namespace.OOB).addChild("url").setContent(url);
+ Message.FileParams fileParams = message.getFileParams();
+ final URL url = fileParams.url;
+ if (P1S3UrlStreamHandler.PROTOCOL_NAME.equals(url.getProtocol())) {
+ Element x = packet.addChild("x", Namespace.P1_S3_FILE_TRANSFER);
+ final String file = url.getFile();
+ x.setAttribute("name", file.charAt(0) == '/' ? file.substring(1) : file);
+ x.setAttribute("fileid", url.getHost());
+ } else {
+ packet.setBody(url.toString());
+ packet.addChild("x", Namespace.OOB).addChild("url").setContent(url.toString());
+ }
} else {
if (Config.supportUnencrypted()) {
packet.setBody(PGP_FALLBACK_MESSAGE);
diff --git a/src/main/java/de/pixart/messenger/http/AesGcmURLStreamHandlerFactory.java b/src/main/java/de/pixart/messenger/http/CustomURLStreamHandlerFactory.java
index b5edd2f5a..48bad8f3f 100644
--- a/src/main/java/de/pixart/messenger/http/AesGcmURLStreamHandlerFactory.java
+++ b/src/main/java/de/pixart/messenger/http/CustomURLStreamHandlerFactory.java
@@ -3,11 +3,13 @@ package de.pixart.messenger.http;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
-public class AesGcmURLStreamHandlerFactory implements URLStreamHandlerFactory {
+public class CustomURLStreamHandlerFactory implements URLStreamHandlerFactory {
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
if (AesGcmURLStreamHandler.PROTOCOL_NAME.equals(protocol)) {
return new AesGcmURLStreamHandler();
+ } else if (P1S3UrlStreamHandler.PROTOCOL_NAME.equals(protocol)) {
+ return new P1S3UrlStreamHandler();
} else {
return null;
}
diff --git a/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java b/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java
index 383fe72a5..a16a369d4 100644
--- a/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java
+++ b/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java
@@ -41,11 +41,10 @@ public class HttpConnectionManager extends AbstractConnectionManager {
return connection;
}
- public HttpUploadConnection createNewUploadConnection(Message message, boolean delay) {
- HttpUploadConnection connection = new HttpUploadConnection(this);
+ public void createNewUploadConnection(Message message, boolean delay) {
+ HttpUploadConnection connection = new HttpUploadConnection(Method.determine(message.getConversation().getAccount()), this);
connection.init(message, delay);
this.uploadConnections.add(connection);
- return connection;
}
public void finishConnection(HttpDownloadConnection connection) {
diff --git a/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java b/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java
index 2120ab309..e0829c39c 100644
--- a/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java
+++ b/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java
@@ -1,6 +1,7 @@
package de.pixart.messenger.http;
import android.os.PowerManager;
+import android.support.annotation.Nullable;
import android.util.Log;
import java.io.BufferedInputStream;
@@ -20,6 +21,7 @@ import javax.net.ssl.SSLHandshakeException;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.DownloadableFile;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.entities.Transferable;
@@ -30,6 +32,8 @@ import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.utils.CryptoHelper;
import de.pixart.messenger.utils.FileWriterException;
import de.pixart.messenger.utils.WakeLockHelper;
+import de.pixart.messenger.xmpp.stanzas.IqPacket;
+import rocks.xmpp.addr.Jid;
public class HttpDownloadConnection implements Transferable {
@@ -42,8 +46,9 @@ public class HttpDownloadConnection implements Transferable {
private int mStatus = Transferable.STATUS_UNKNOWN;
private boolean acceptedAutomatically = false;
private int mProgress = 0;
- private boolean mUseTor = false;
+ private final boolean mUseTor;
private boolean canceled = false;
+ private Method method = Method.HTTP_UPLOAD;
private final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
@@ -107,6 +112,7 @@ public class HttpDownloadConnection implements Transferable {
&& this.file.getKey() == null) {
this.message.setEncryption(Message.ENCRYPTION_NONE);
}
+ method = mUrl.getProtocol().equalsIgnoreCase(P1S3UrlStreamHandler.PROTOCOL_NAME) ? Method.P1_S3 : Method.HTTP_UPLOAD;
checkFileSize(interactive);
} catch (MalformedURLException e) {
this.cancel();
@@ -160,7 +166,7 @@ public class HttpDownloadConnection implements Transferable {
}
}
- public void updateProgress(long i) {
+ private void updateProgress(long i) {
this.mProgress = (int) i;
mHttpConnectionManager.updateConversationUi(false);
}
@@ -186,27 +192,63 @@ public class HttpDownloadConnection implements Transferable {
private class FileSizeChecker implements Runnable {
- private boolean interactive = false;
+ private final boolean interactive;
- public FileSizeChecker(boolean interactive) {
+ FileSizeChecker(boolean interactive) {
this.interactive = interactive;
}
@Override
public void run() {
+ if (mUrl.getProtocol().equalsIgnoreCase(P1S3UrlStreamHandler.PROTOCOL_NAME)) {
+ retrieveUrl();
+ } else {
+ check();
+ }
+ }
+
+ private void retrieveUrl() {
+ changeStatus(STATUS_CHECKING);
+ final Account account = message.getConversation().getAccount();
+ IqPacket request = mXmppConnectionService.getIqGenerator().requestP1S3Url(Jid.of(account.getJid().getDomain()), mUrl.getHost());
+ mXmppConnectionService.sendIqPacket(message.getConversation().getAccount(), request, (a, packet) -> {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ String download = packet.query().getAttribute("download");
+ if (download != null) {
+ try {
+ mUrl = new URL(download);
+ check();
+ return;
+ } catch (MalformedURLException e) {
+ //fallthrough
+ }
+ }
+ }
+ Log.d(Config.LOGTAG, "unable to retrieve actual download url");
+ retrieveFailed(null);
+ });
+ }
+
+ private void retrieveFailed(@Nullable Exception e) {
+ changeStatus(STATUS_OFFER_CHECK_FILESIZE);
+ if (interactive) {
+ if (e != null) {
+ showToastForException(e);
+ }
+ } else {
+ HttpDownloadConnection.this.acceptedAutomatically = false;
+ HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
+ }
+ cancel();
+ }
+
+ private void check() {
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();
+ retrieveFailed(e);
return;
}
file.setExpectedSize(size);
@@ -235,10 +277,14 @@ public class HttpDownloadConnection implements Transferable {
} else {
connection = (HttpURLConnection) mUrl.openConnection();
}
- connection.setRequestMethod("HEAD");
+ if (method == Method.P1_S3) {
+ connection.setRequestMethod("GET");
+ connection.addRequestProperty("Range", "bytes=0-0");
+ } else {
+ connection.setRequestMethod("HEAD");
+ }
connection.setUseCaches(false);
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);
@@ -246,7 +292,18 @@ public class HttpDownloadConnection implements Transferable {
connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000);
connection.setReadTimeout(Config.CONNECT_TIMEOUT * 1000);
connection.connect();
- String contentLength = connection.getHeaderField("Content-Length");
+ String contentLength;
+ if (method == Method.P1_S3) {
+ String contentRange = connection.getHeaderField("Content-Range");
+ String[] contentRangeParts = contentRange == null ? new String[0] : contentRange.split("/");
+ if (contentRangeParts.length != 2) {
+ contentLength = null;
+ } else {
+ contentLength = contentRangeParts[1];
+ }
+ } else {
+ contentLength = connection.getHeaderField("Content-Length");
+ }
connection.disconnect();
if (contentLength == null) {
throw new IOException("no content-length found in HEAD response");
@@ -266,7 +323,7 @@ public class HttpDownloadConnection implements Transferable {
private class FileDownloader implements Runnable {
- private boolean interactive = false;
+ private final boolean interactive;
private OutputStream os;
@@ -385,7 +442,9 @@ public class HttpDownloadConnection implements Transferable {
message.setType(Message.TYPE_FILE);
final URL url;
final String ref = mUrl.getRef();
- if (ref != null && AesGcmURLStreamHandler.IV_KEY.matcher(ref).matches()) {
+ if (method == Method.P1_S3) {
+ url = message.getFileParams().url;
+ } else if (ref != null && AesGcmURLStreamHandler.IV_KEY.matcher(ref).matches()) {
url = CryptoHelper.toAesGcmUrl(mUrl);
} else {
url = mUrl;
diff --git a/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java b/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java
index e598db080..9acdfa11a 100644
--- a/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java
+++ b/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java
@@ -9,11 +9,11 @@ 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.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Scanner;
import javax.net.ssl.HttpsURLConnection;
@@ -22,47 +22,43 @@ import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.DownloadableFile;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.entities.Transferable;
-import de.pixart.messenger.parser.IqParser;
import de.pixart.messenger.persistance.FileBackend;
import de.pixart.messenger.services.AbstractConnectionManager;
import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.utils.Checksum;
import de.pixart.messenger.utils.CryptoHelper;
-import de.pixart.messenger.utils.Namespace;
import de.pixart.messenger.utils.WakeLockHelper;
-import de.pixart.messenger.xml.Element;
-import de.pixart.messenger.xmpp.stanzas.IqPacket;
-import rocks.xmpp.addr.Jid;
public class HttpUploadConnection implements Transferable {
- private static final List<String> WHITE_LISTED_HEADERS = Arrays.asList(
+ public static final List<String> WHITE_LISTED_HEADERS = Arrays.asList(
"Authorization",
"Cookie",
"Expires"
);
- private HttpConnectionManager mHttpConnectionManager;
- private XmppConnectionService mXmppConnectionService;
-
+ private final HttpConnectionManager mHttpConnectionManager;
+ private final XmppConnectionService mXmppConnectionService;
+ private final SlotRequester mSlotRequester;
+ private final Method method;
+ private final boolean mUseTor;
private boolean canceled = false;
private boolean delayed = false;
private DownloadableFile file;
private Message message;
private String mime;
- private URL mGetUrl;
- private URL mPutUrl;
- private HashMap<String,String> mPutHeaders;
- private boolean mUseTor = false;
-
+ private SlotRequester.Slot slot;
private byte[] key = null;
private long transmitted = 0;
private InputStream mFileInputStream;
- public HttpUploadConnection(HttpConnectionManager httpConnectionManager) {
+ public HttpUploadConnection(Method method, HttpConnectionManager httpConnectionManager) {
+ this.method = method;
this.mHttpConnectionManager = httpConnectionManager;
this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService();
+ this.mSlotRequester = new SlotRequester(this.mXmppConnectionService);
this.mUseTor = mXmppConnectionService.useTorToConnect();
}
@@ -118,6 +114,21 @@ public class HttpUploadConnection implements Transferable {
mXmppConnectionService.getRNG().nextBytes(this.key);
this.file.setKeyAndIv(this.key);
}
+
+ final String md5;
+
+ if (method == Method.P1_S3) {
+ try {
+ md5 = Checksum.md5(AbstractConnectionManager.createInputStream(file, true).first);
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to calculate md5()", e);
+ fail(e.getMessage());
+ return;
+ }
+ } else {
+ md5 = null;
+ }
+
Pair<InputStream, Integer> pair;
try {
pair = AbstractConnectionManager.createInputStream(file, true);
@@ -129,50 +140,19 @@ public class HttpUploadConnection implements Transferable {
this.file.setExpectedSize(pair.second);
message.resetFileParams();
this.mFileInputStream = pair.first;
- String http_upload_namespace = account.getXmppConnection().getFeatures().http_upload_namespace;
- Jid host = account.getXmppConnection().findDiscoItemByFeature(http_upload_namespace);
- IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host, file, mime, http_upload_namespace);
- mXmppConnectionService.sendIqPacket(account, request, (a, packet) -> {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- Element slot = packet.findChild("slot", http_upload_namespace);
- if (slot != null) {
- try {
- final Element put = slot.findChild("put");
- final Element get = slot.findChild("get");
- final String putUrl;
- final String getUrl;
- if (http_upload_namespace == Namespace.HTTP_UPLOAD) {
- putUrl = put == null ? null : put.getAttribute("url");
- getUrl = get == null ? null : get.getAttribute("url");
- } else {
- putUrl = put == null ? null : put.getContent();
- getUrl = get == null ? null : get.getContent();
- }
- if (getUrl != null && putUrl != null) {
- this.mGetUrl = new URL(getUrl);
- this.mPutUrl = new URL(putUrl);
- this.mPutHeaders = new HashMap<>();
- for (Element child : put.getChildren()) {
- if ("header".equals(child.getName())) {
- final String name = child.getAttribute("name");
- final String value = child.getContent();
- if (WHITE_LISTED_HEADERS.contains(name) && value != null && !value.trim().contains("\n")) {
- this.mPutHeaders.put(name, value.trim());
- }
- }
- }
- if (!canceled) {
- new Thread(this::upload).start();
- }
- return;
- }
- } catch (MalformedURLException e) {
- //fall through
- }
+ this.mSlotRequester.request(method, account, file, mime, md5, new SlotRequester.OnSlotRequested() {
+ @Override
+ public void success(SlotRequester.Slot slot) {
+ if (!canceled) {
+ HttpUploadConnection.this.slot = slot;
+ new Thread(HttpUploadConnection.this::upload).start();
}
}
- Log.d(Config.LOGTAG, account.getJid().toString() + ": invalid response to slot request " + packet);
- fail(IqParser.extractErrorMessage(packet));
+
+ @Override
+ public void failure(String message) {
+ fail(message);
+ }
});
message.setTransferable(this);
mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
@@ -186,11 +166,11 @@ public class HttpUploadConnection implements Transferable {
final int expectedFileSize = (int) file.getExpectedSize();
final int readTimeout = (expectedFileSize / 2048) + Config.SOCKET_TIMEOUT; //assuming a minimum transfer speed of 16kbit/s
wakeLock.acquire(readTimeout);
- Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString() + " w/ read timeout of " + readTimeout + "s");
+ Log.d(Config.LOGTAG, "uploading to " + slot.getPutUrl().toString() + " w/ read timeout of " + readTimeout + "s");
if (mUseTor) {
- connection = (HttpURLConnection) mPutUrl.openConnection(HttpConnectionManager.getProxy());
+ connection = (HttpURLConnection) slot.getPutUrl().openConnection(HttpConnectionManager.getProxy());
} else {
- connection = (HttpURLConnection) mPutUrl.openConnection();
+ connection = (HttpURLConnection) slot.getPutUrl().openConnection();
}
if (connection instanceof HttpsURLConnection) {
mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true);
@@ -198,14 +178,14 @@ public class HttpUploadConnection implements Transferable {
connection.setUseCaches(false);
connection.setRequestMethod("PUT");
connection.setFixedLengthStreamingMode(expectedFileSize);
- connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime);
connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getIdentityName());
- if (mPutHeaders != null) {
- for (HashMap.Entry<String, String> entry : mPutHeaders.entrySet()) {
+ if (slot.getHeaders() != null) {
+ for (HashMap.Entry<String, String> entry : slot.getHeaders().entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
}
connection.setDoOutput(true);
+ connection.setDoInput(true);
connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000);
connection.setReadTimeout(readTimeout * 1000);
connection.connect();
@@ -222,12 +202,26 @@ public class HttpUploadConnection implements Transferable {
os.close();
mFileInputStream.close();
int code = connection.getResponseCode();
+ InputStream is = connection.getErrorStream();
+ if (is != null) {
+ try (Scanner scanner = new Scanner(is)) {
+ scanner.useDelimiter("\\Z");
+ Log.d(Config.LOGTAG, "body: " + scanner.next());
+ }
+ }
if (code == 200 || code == 201) {
Log.d(Config.LOGTAG, "finished uploading file");
+ final URL get;
if (key != null) {
- mGetUrl = CryptoHelper.toAesGcmUrl(new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key)));
+ if (method == Method.P1_S3) {
+ get = new URL(slot.getGetUrl().toString() + "#" + CryptoHelper.bytesToHex(key));
+ } else {
+ get = CryptoHelper.toAesGcmUrl(new URL(slot.getGetUrl().toString() + "#" + CryptoHelper.bytesToHex(key)));
+ }
+ } else {
+ get = slot.getGetUrl();
}
- mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl);
+ mXmppConnectionService.getFileBackend().updateFileParams(message, get);
mXmppConnectionService.getFileBackend().updateMediaScanner(file);
message.setTransferable(null);
message.setCounterpart(message.getConversation().getJid().asBareJid());
diff --git a/src/main/java/de/pixart/messenger/http/Method.java b/src/main/java/de/pixart/messenger/http/Method.java
new file mode 100644
index 000000000..d5a1e07a9
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/http/Method.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018, Daniel Gultsch All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package de.pixart.messenger.http;
+
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.xmpp.XmppConnection;
+
+public enum Method {
+ P1_S3, HTTP_UPLOAD, HTTP_UPLOAD_LEGACY;
+
+ public static Method determine(Account account) {
+ XmppConnection.Features features = account.getXmppConnection() == null ? null : account.getXmppConnection().getFeatures();
+ if (features == null) {
+ return HTTP_UPLOAD;
+ }
+ if (features.useLegacyHttpUpload()) {
+ return HTTP_UPLOAD_LEGACY;
+ } else if (features.httpUpload(0)) {
+ return HTTP_UPLOAD;
+ } else if (features.p1S3FileTransfer()) {
+ return P1_S3;
+ } else {
+ return HTTP_UPLOAD;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/http/P1S3UrlStreamHandler.java b/src/main/java/de/pixart/messenger/http/P1S3UrlStreamHandler.java
new file mode 100644
index 000000000..93b19cd71
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/http/P1S3UrlStreamHandler.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018, Daniel Gultsch All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package de.pixart.messenger.http;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+import de.pixart.messenger.xml.Element;
+
+public class P1S3UrlStreamHandler extends URLStreamHandler {
+
+ public static final String PROTOCOL_NAME = "p1s3";
+
+ public static URL of(String fileId, String filename) throws MalformedURLException {
+ if (fileId == null || filename == null) {
+ throw new MalformedURLException("Paramaters must not be null");
+ }
+ return new URL(PROTOCOL_NAME + "://" + fileId + "/" + filename);
+ }
+
+ public static URL of(Element x) {
+ try {
+ return of(x.getAttribute("fileid"), x.getAttribute("name"));
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ @Override
+ protected URLConnection openConnection(URL url) {
+ throw new IllegalStateException("Unable to open connection with stub protocol");
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/http/SlotRequester.java b/src/main/java/de/pixart/messenger/http/SlotRequester.java
new file mode 100644
index 000000000..0c3a942e5
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/http/SlotRequester.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2018, Daniel Gultsch All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package de.pixart.messenger.http;
+
+import android.util.Log;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.DownloadableFile;
+import de.pixart.messenger.parser.IqParser;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.utils.Namespace;
+import de.pixart.messenger.xml.Element;
+import de.pixart.messenger.xmpp.stanzas.IqPacket;
+import rocks.xmpp.addr.Jid;
+
+public class SlotRequester {
+
+ private XmppConnectionService service;
+
+ public SlotRequester(XmppConnectionService service) {
+ this.service = service;
+ }
+
+ public void request(Method method, Account account, DownloadableFile file, String mime, String md5, OnSlotRequested callback) {
+ if (method == Method.HTTP_UPLOAD) {
+ Jid host = account.getXmppConnection().findDiscoItemByFeature(Namespace.HTTP_UPLOAD);
+ requestHttpUpload(account, host, file, mime, callback);
+ } else if (method == Method.HTTP_UPLOAD_LEGACY) {
+ Jid host = account.getXmppConnection().findDiscoItemByFeature(Namespace.HTTP_UPLOAD_LEGACY);
+ requestHttpUploadLegacy(account, host, file, mime, callback);
+ } else {
+ requestP1S3(account, Jid.of(account.getServer()), file.getName(), md5, callback);
+ }
+ }
+
+ private void requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime, OnSlotRequested callback) {
+ IqPacket request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime);
+ service.sendIqPacket(account, request, (a, packet) -> {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD_LEGACY);
+ if (slotElement != null) {
+ try {
+ final String putUrl = slotElement.findChildContent("put");
+ final String getUrl = slotElement.findChildContent("get");
+ if (getUrl != null && putUrl != null) {
+ Slot slot = new Slot(new URL(putUrl));
+ slot.getUrl = new URL(getUrl);
+ slot.headers = new HashMap<>();
+ slot.headers.put("Content-Type", mime == null ? "application/octet-stream" : mime);
+ callback.success(slot);
+ return;
+ }
+ } catch (MalformedURLException e) {
+ //fall through
+ }
+ }
+ }
+ Log.d(Config.LOGTAG, account.getJid().toString() + ": invalid response to slot request " + packet);
+ callback.failure(IqParser.extractErrorMessage(packet));
+ });
+ }
+
+ private void requestHttpUpload(Account account, Jid host, DownloadableFile file, String mime, OnSlotRequested callback) {
+ IqPacket request = service.getIqGenerator().requestHttpUploadSlot(host, file, mime);
+ service.sendIqPacket(account, request, (a, packet) -> {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD);
+ if (slotElement != null) {
+ try {
+ final Element put = slotElement.findChild("put");
+ final Element get = slotElement.findChild("get");
+ final String putUrl = put == null ? null : put.getAttribute("url");
+ final String getUrl = get == null ? null : get.getAttribute("url");
+ if (getUrl != null && putUrl != null) {
+ Slot slot = new Slot(new URL(putUrl));
+ slot.getUrl = new URL(getUrl);
+ slot.headers = new HashMap<>();
+ for (Element child : put.getChildren()) {
+ if ("header".equals(child.getName())) {
+ final String name = child.getAttribute("name");
+ final String value = child.getContent();
+ if (HttpUploadConnection.WHITE_LISTED_HEADERS.contains(name) && value != null && !value.trim().contains("\n")) {
+ slot.headers.put(name, value.trim());
+ }
+ }
+ }
+ slot.headers.put("Content-Type", mime == null ? "application/octet-stream" : mime);
+ callback.success(slot);
+ return;
+ }
+ } catch (MalformedURLException e) {
+ //fall through
+ }
+ }
+ }
+ Log.d(Config.LOGTAG, account.getJid().toString() + ": invalid response to slot request " + packet);
+ callback.failure(IqParser.extractErrorMessage(packet));
+ });
+
+ }
+
+ private void requestP1S3(final Account account, Jid host, String filename, String md5, OnSlotRequested callback) {
+ IqPacket request = service.getIqGenerator().requestP1S3Slot(host, md5);
+ service.sendIqPacket(account, request, (a, packet) -> {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ String putUrl = packet.query(Namespace.P1_S3_FILE_TRANSFER).getAttribute("upload");
+ String id = packet.query().getAttribute("fileid");
+ try {
+ if (putUrl != null && id != null) {
+ Slot slot = new Slot(new URL(putUrl));
+ slot.getUrl = P1S3UrlStreamHandler.of(id, filename);
+ slot.headers = new HashMap<>();
+ slot.headers.put("Content-MD5", md5);
+ slot.headers.put("Content-Type", " "); //required to force it to empty. otherwise library will set something
+ callback.success(slot);
+ return;
+ }
+ } catch (MalformedURLException e) {
+ //fall through;
+ }
+ }
+ callback.failure("unable to request slot");
+ });
+ Log.d(Config.LOGTAG, "requesting slot with p1. md5=" + md5);
+ }
+
+
+ public interface OnSlotRequested {
+
+ void success(Slot slot);
+
+ void failure(String message);
+
+ }
+
+ public static class Slot {
+ private final URL putUrl;
+ private URL getUrl;
+ private HashMap<String, String> headers;
+
+ private Slot(URL putUrl) {
+ this.putUrl = putUrl;
+ }
+
+ public URL getPutUrl() {
+ return putUrl;
+ }
+
+ public URL getGetUrl() {
+ return getUrl;
+ }
+
+ public HashMap<String, String> getHeaders() {
+ return headers;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/parser/MessageParser.java b/src/main/java/de/pixart/messenger/parser/MessageParser.java
index 701dd860e..7fc2e2d50 100644
--- a/src/main/java/de/pixart/messenger/parser/MessageParser.java
+++ b/src/main/java/de/pixart/messenger/parser/MessageParser.java
@@ -8,6 +8,7 @@ import android.util.Pair;
import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionStatus;
+import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -35,6 +36,7 @@ import de.pixart.messenger.entities.ReadByMarker;
import de.pixart.messenger.entities.ReceiptRequest;
import de.pixart.messenger.entities.ServiceDiscoveryResult;
import de.pixart.messenger.http.HttpConnectionManager;
+import de.pixart.messenger.http.P1S3UrlStreamHandler;
import de.pixart.messenger.services.MessageArchiveService;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.utils.CryptoHelper;
@@ -380,6 +382,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
final Element oob = packet.findChild("x", Namespace.OOB);
+ final Element xP1S3 = packet.findChild("x", Namespace.P1_S3_FILE_TRANSFER);
+ final URL xP1S3url = xP1S3 == null ? null : P1S3UrlStreamHandler.of(xP1S3);
final String oobUrl = oob != null ? oob.findChildContent("url") : null;
final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
@@ -428,7 +432,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
return;
}
- if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null) && !isMucStatusMessage) {
+ if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null || xP1S3 != null) && !isMucStatusMessage) {
final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain());
final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), conversationIsProbablyMuc, false, query, false);
final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI;
@@ -472,6 +476,12 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring OTR message from " + from + " isForwarded=" + Boolean.toString(isForwarded) + ", isProperlyAddressed=" + Boolean.valueOf(isProperlyAddressed));
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
}
+ } else if (xP1S3url != null) {
+ message = new Message(conversation, xP1S3url.toString(), Message.ENCRYPTION_NONE, status);
+ message.setOob(true);
+ if (CryptoHelper.isPgpEncryptedUrl(xP1S3url.toString())) {
+ message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+ }
} else if (pgpEncrypted != null && Config.supportOpenPgp()) {
message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
} else if (axolotlEncrypted != null && Config.supportOmemo()) {
diff --git a/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java b/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java
index 8fd7dc814..1c4c6ca19 100644
--- a/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java
+++ b/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java
@@ -86,7 +86,7 @@ public class AbstractConnectionManager {
is = new FileInputStream(file);
size = (int) file.getSize();
if (file.getKey() == null) {
- return new Pair<InputStream, Integer>(is, size);
+ return new Pair<>(is, size);
}
try {
if (gcm) {
@@ -99,16 +99,10 @@ public class AbstractConnectionManager {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips);
Log.d(Config.LOGTAG, "opening encrypted input stream");
- return new Pair<InputStream, Integer>(new CipherInputStream(is, cipher), (size / 16 + 1) * 16);
+ return new Pair<>(new CipherInputStream(is, cipher), (size / 16 + 1) * 16);
}
- } catch (InvalidKeyException e) {
- return null;
- } catch (NoSuchAlgorithmException e) {
- return null;
- } catch (NoSuchPaddingException e) {
- return null;
- } catch (InvalidAlgorithmParameterException e) {
- return null;
+ } catch (Exception e) {
+ throw new AssertionError(e);
}
}
diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java
index 6ca1c80c4..4aba18387 100644
--- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java
+++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java
@@ -99,7 +99,7 @@ import de.pixart.messenger.generator.AbstractGenerator;
import de.pixart.messenger.generator.IqGenerator;
import de.pixart.messenger.generator.MessageGenerator;
import de.pixart.messenger.generator.PresenceGenerator;
-import de.pixart.messenger.http.AesGcmURLStreamHandlerFactory;
+import de.pixart.messenger.http.CustomURLStreamHandlerFactory;
import de.pixart.messenger.http.HttpConnectionManager;
import de.pixart.messenger.parser.AbstractParser;
import de.pixart.messenger.parser.IqParser;
@@ -165,7 +165,7 @@ public class XmppConnectionService extends Service {
private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
static {
- URL.setURLStreamHandlerFactory(new AesGcmURLStreamHandlerFactory());
+ URL.setURLStreamHandlerFactory(new CustomURLStreamHandlerFactory());
}
public final CountDownLatch restoredFromDatabaseLatch = new CountDownLatch(1);
diff --git a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java
index 464ebde0f..8b79ba09f 100644
--- a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java
@@ -125,7 +125,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
boolean openPaymentUrl = mAccount != null && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED;
final boolean redirectionWorthyStatus = openPaymentUrl || openRegistrationUrl;
URL url = connection != null && redirectionWorthyStatus ? connection.getRedirectionUrl() : null;
- if (url != null && redirectionWorthyStatus && !wasDisabled) {
+ if (url != null && !wasDisabled) {
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url.toString())));
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
@@ -1018,6 +1018,10 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
if (features.httpUpload(0)) {
this.binding.serverInfoHttpUpload.setText(R.string.server_info_available);
+ }
+ if (features.p1S3FileTransfer()) {
+ this.binding.serverInfoHttpUploadDescription.setText(R.string.p1_s3_filetransfer);
+ this.binding.serverInfoHttpUpload.setText(R.string.server_info_available);
} else {
this.binding.serverInfoHttpUpload.setText(R.string.server_info_unavailable);
}
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java
index 92c937053..0c2238cdb 100644
--- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java
+++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java
@@ -67,6 +67,7 @@ import de.pixart.messenger.entities.DownloadableFile;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.entities.Message.FileParams;
import de.pixart.messenger.entities.Transferable;
+import de.pixart.messenger.http.P1S3UrlStreamHandler;
import de.pixart.messenger.persistance.FileBackend;
import de.pixart.messenger.services.AudioPlayer;
import de.pixart.messenger.services.MessageArchiveService;
@@ -99,13 +100,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
private static final int SENT = 0;
private static final int RECEIVED = 1;
private static final int STATUS = 2;
-
private static final int DATE_SEPARATOR = 3;
boolean isResendable = false;
- private List<String> highlightedTerm = null;
-
private static final Linkify.TransformFilter WEBURL_TRANSFORM_FILTER = (matcher, url) -> {
if (url == null) {
return null;
@@ -118,24 +116,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
};
- private static String removeTrailingBracket(final String url) {
- int numOpenBrackets = 0;
- for (char c : url.toCharArray()) {
- if (c == '(') {
- ++numOpenBrackets;
- } else if (c == ')') {
- --numOpenBrackets;
- }
- }
- if (numOpenBrackets != 0 && url.charAt(url.length() - 1) == ')') {
- return url.substring(0, url.length() - 1);
- } else {
- return url;
- }
- }
-
private static final Linkify.MatchFilter WEBURL_MATCH_FILTER = (cs, start, end) -> start < 1 || (cs.charAt(start - 1) != '@' && cs.charAt(start - 1) != '.' && !cs.subSequence(Math.max(0, start - 3), start).equals("://"));
-
private static final Linkify.MatchFilter XMPPURI_MATCH_FILTER = (s, start, end) -> {
XmppUri uri = new XmppUri(s.subSequence(start, end).toString());
return uri.isJidValid();
@@ -143,7 +124,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
private final XmppActivity activity;
private final ListSelectionManager listSelectionManager = new ListSelectionManager();
- public final AudioPlayer audioPlayer;
+ private final AudioPlayer audioPlayer;
+ private List<String> highlightedTerm = null;
private DisplayMetrics metrics;
private OnContactPictureClicked mOnContactPictureClickedListener;
private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
@@ -158,6 +140,22 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
updatePreferences();
}
+ private static String removeTrailingBracket(final String url) {
+ int numOpenBrackets = 0;
+ for (char c : url.toCharArray()) {
+ if (c == '(') {
+ ++numOpenBrackets;
+ } else if (c == ')') {
+ --numOpenBrackets;
+ }
+ }
+ if (numOpenBrackets != 0 && url.charAt(url.length() - 1) == ')') {
+ return url.substring(0, url.length() - 1);
+ } else {
+ return url;
+ }
+ }
+
public static boolean cancelPotentialWork(Message message, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
@@ -183,6 +181,12 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
return null;
}
+ private static void resetClickListener(View... views) {
+ for (View view : views) {
+ view.setOnClickListener(null);
+ }
+ }
+
public void flagScreenOn() {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@@ -957,11 +961,18 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
} else if (message.treatAsDownloadable()) {
try {
URL url = new URL(message.getBody());
- displayDownloadableMessage(viewHolder,
- message,
- activity.getString(R.string.check_x_filesize_on_host,
- UIHelper.getFileDescriptionString(activity, message),
- url.getHost()));
+ if (P1S3UrlStreamHandler.PROTOCOL_NAME.equalsIgnoreCase(url.getProtocol())) {
+ displayDownloadableMessage(viewHolder,
+ message,
+ activity.getString(R.string.check_x_filesize,
+ UIHelper.getFileDescriptionString(activity, message)));
+ } else {
+ displayDownloadableMessage(viewHolder,
+ message,
+ activity.getString(R.string.check_x_filesize_on_host,
+ UIHelper.getFileDescriptionString(activity, message),
+ url.getHost()));
+ }
} catch (Exception e) {
displayDownloadableMessage(viewHolder,
message,
@@ -999,12 +1010,6 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
return view;
}
- private static void resetClickListener(View... views) {
- for (View view : views) {
- view.setOnClickListener(null);
- }
- }
-
private void promptOpenKeychainInstall(View view) {
activity.showInstallPgpDialog();
}
diff --git a/src/main/java/de/pixart/messenger/utils/Checksum.java b/src/main/java/de/pixart/messenger/utils/Checksum.java
new file mode 100644
index 000000000..25f711fcc
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/utils/Checksum.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018, Daniel Gultsch All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package de.pixart.messenger.utils;
+
+import android.util.Base64;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class Checksum {
+
+ public static String md5(InputStream inputStream) throws IOException {
+ byte[] buffer = new byte[4096];
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ }
+
+ int count;
+ do {
+ count = inputStream.read(buffer);
+ if (count > 0) {
+ messageDigest.update(buffer, 0, count);
+ }
+ } while (count != -1);
+ inputStream.close();
+ return Base64.encodeToString(messageDigest.digest(), Base64.NO_WRAP);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/utils/CryptoHelper.java b/src/main/java/de/pixart/messenger/utils/CryptoHelper.java
index 2327f6f96..cd7e72746 100644
--- a/src/main/java/de/pixart/messenger/utils/CryptoHelper.java
+++ b/src/main/java/de/pixart/messenger/utils/CryptoHelper.java
@@ -276,6 +276,6 @@ public final class CryptoHelper {
return false;
}
final String u = url.toLowerCase();
- return !u.contains(" ") && (u.startsWith("https://") || u.startsWith("http://")) && u.endsWith(".pgp");
+ return !u.contains(" ") && (u.startsWith("https://") || u.startsWith("http://") || u.startsWith("p1s3://")) && u.endsWith(".pgp");
}
}
diff --git a/src/main/java/de/pixart/messenger/utils/MessageUtils.java b/src/main/java/de/pixart/messenger/utils/MessageUtils.java
index 78be415f7..4ad0e84a5 100644
--- a/src/main/java/de/pixart/messenger/utils/MessageUtils.java
+++ b/src/main/java/de/pixart/messenger/utils/MessageUtils.java
@@ -35,6 +35,7 @@ import java.util.regex.Pattern;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.http.AesGcmURLStreamHandler;
+import de.pixart.messenger.http.P1S3UrlStreamHandler;
public class MessageUtils {
@@ -77,7 +78,8 @@ public class MessageUtils {
final boolean encrypted = ref != null && AesGcmURLStreamHandler.IV_KEY.matcher(ref).matches();
final boolean followedByDataUri = lines.length == 2 && lines[1].startsWith("data:");
final boolean validAesGcm = AesGcmURLStreamHandler.PROTOCOL_NAME.equalsIgnoreCase(protocol) && encrypted && (lines.length == 1 || followedByDataUri);
- final boolean validOob = ("http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol)) && (oob || encrypted) && lines.length == 1;
+ final boolean validProtocol = "http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol) || P1S3UrlStreamHandler.PROTOCOL_NAME.equalsIgnoreCase(protocol);
+ final boolean validOob = validProtocol && (oob || encrypted) && lines.length == 1;
return validAesGcm || validOob;
} catch (MalformedURLException e) {
return false;
diff --git a/src/main/java/de/pixart/messenger/utils/Namespace.java b/src/main/java/de/pixart/messenger/utils/Namespace.java
index d2ba1a7ff..c3ff29697 100644
--- a/src/main/java/de/pixart/messenger/utils/Namespace.java
+++ b/src/main/java/de/pixart/messenger/utils/Namespace.java
@@ -21,4 +21,5 @@ public final class Namespace {
public static final String NICK = "http://jabber.org/protocol/nick";
public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = "http://jabber.org/protocol/offline";
public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind";
+ public static final String P1_S3_FILE_TRANSFER = "p1:s3filetransfer";
}
diff --git a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java
index 5c9d1108a..1d5587d7c 100644
--- a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java
+++ b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java
@@ -1842,45 +1842,50 @@ public class XmppConnection implements Runnable {
this.blockListRequested = value;
}
+ public boolean p1S3FileTransfer() {
+ return hasDiscoFeature(Jid.of(account.getServer()), Namespace.P1_S3_FILE_TRANSFER);
+ }
+
public boolean httpUpload(long filesize) {
if (Config.DISABLE_HTTP_UPLOAD) {
return false;
} else {
- List<Entry<Jid, ServiceDiscoveryResult>> items = findDiscoItemsByFeature(this.http_upload_namespace);
- if (items.size() == 0) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": this server does not support the latest version of XEP-0363");
- this.http_upload_namespace = Namespace.HTTP_UPLOAD_LEGACY;
- items = findDiscoItemsByFeature(this.http_upload_namespace);
- }
- if (items.size() > 0) {
- try {
- long maxsize = Long.parseLong(items.get(0).getValue().getExtendedDiscoInformation(this.http_upload_namespace, "max-file-size"));
- if (filesize <= maxsize) {
- return true;
- } else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": http upload is not available for files with size " + filesize + " (max is " + maxsize + ")");
- return false;
+ for (String namespace : new String[]{Namespace.HTTP_UPLOAD, Namespace.HTTP_UPLOAD_LEGACY}) {
+ List<Entry<Jid, ServiceDiscoveryResult>> items = findDiscoItemsByFeature(namespace);
+ if (items.size() > 0) {
+ try {
+ long maxsize = Long.parseLong(items.get(0).getValue().getExtendedDiscoInformation(namespace, "max-file-size"));
+ if (filesize <= maxsize) {
+ return true;
+ } else {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": http upload is not available for files with size " + filesize + " (max is " + maxsize + ")");
+ return false;
+ }
+ } catch (Exception e) {
+ //ignored
}
- } catch (Exception e) {
- return true;
}
- } else {
- return false;
}
+ return false;
}
}
+ public boolean useLegacyHttpUpload() {
+ return findDiscoItemByFeature(Namespace.HTTP_UPLOAD) == null && findDiscoItemByFeature(Namespace.HTTP_UPLOAD_LEGACY) != null;
+ }
+
public long getMaxHttpUploadSize() {
- List<Entry<Jid, ServiceDiscoveryResult>> items = findDiscoItemsByFeature(this.http_upload_namespace);
- if (items.size() > 0) {
- try {
- return Long.parseLong(items.get(0).getValue().getExtendedDiscoInformation(this.http_upload_namespace, "max-file-size"));
- } catch (Exception e) {
- return -1;
+ for (String namespace : new String[]{Namespace.HTTP_UPLOAD, Namespace.HTTP_UPLOAD_LEGACY}) {
+ List<Entry<Jid, ServiceDiscoveryResult>> items = findDiscoItemsByFeature(namespace);
+ if (items.size() > 0) {
+ try {
+ return Long.parseLong(items.get(0).getValue().getExtendedDiscoInformation(namespace, "max-file-size"));
+ } catch (Exception e) {
+ //ignored
+ }
}
- } else {
- return -1;
}
+ return -1;
}
public boolean stanzaIds() {