add support for S3 file transfers
This commit is contained in:
parent
369e48c220
commit
a554be18a6
22 changed files with 662 additions and 186 deletions
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
53
src/main/java/de/pixart/messenger/http/Method.java
Normal file
53
src/main/java/de/pixart/messenger/http/Method.java
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
189
src/main/java/de/pixart/messenger/http/SlotRequester.java
Normal file
189
src/main/java/de/pixart/messenger/http/SlotRequester.java
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,6 +116,30 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
}
|
||||
};
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
private final XmppActivity activity;
|
||||
private final ListSelectionManager listSelectionManager = new ListSelectionManager();
|
||||
private final AudioPlayer audioPlayer;
|
||||
private List<String> highlightedTerm = null;
|
||||
private DisplayMetrics metrics;
|
||||
private OnContactPictureClicked mOnContactPictureClickedListener;
|
||||
private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
|
||||
private boolean mIndicateReceived = false;
|
||||
private OnQuoteListener onQuoteListener;
|
||||
|
||||
public MessageAdapter(XmppActivity activity, List<Message> messages) {
|
||||
super(activity, 0, messages);
|
||||
this.audioPlayer = new AudioPlayer(this);
|
||||
this.activity = activity;
|
||||
metrics = getContext().getResources().getDisplayMetrics();
|
||||
updatePreferences();
|
||||
}
|
||||
|
||||
private static String removeTrailingBracket(final String url) {
|
||||
int numOpenBrackets = 0;
|
||||
for (char c : url.toCharArray()) {
|
||||
|
@ -134,30 +156,6 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
|||
}
|
||||
}
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
private final XmppActivity activity;
|
||||
private final ListSelectionManager listSelectionManager = new ListSelectionManager();
|
||||
public final AudioPlayer audioPlayer;
|
||||
private DisplayMetrics metrics;
|
||||
private OnContactPictureClicked mOnContactPictureClickedListener;
|
||||
private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
|
||||
private boolean mIndicateReceived = false;
|
||||
private OnQuoteListener onQuoteListener;
|
||||
|
||||
public MessageAdapter(XmppActivity activity, List<Message> messages) {
|
||||
super(activity, 0, messages);
|
||||
this.audioPlayer = new AudioPlayer(this);
|
||||
this.activity = activity;
|
||||
metrics = getContext().getResources().getDisplayMetrics();
|
||||
updatePreferences();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
60
src/main/java/de/pixart/messenger/utils/Checksum.java
Normal file
60
src/main/java/de/pixart/messenger/utils/Checksum.java
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -454,6 +454,7 @@
|
|||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/server_info_http_upload_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
|
|
|
@ -792,4 +792,5 @@
|
|||
<string name="choose_your_server">Choose your jabber provider</string>
|
||||
<string name="show_privacy">Privacy Policy</string>
|
||||
<string name="show_termsofuse">Terms of Use</string>
|
||||
<string name="p1_s3_filetransfer">HTTP File Sharing for S3</string>
|
||||
</resources>
|
||||
|
|
Reference in a new issue