Detailed changes
@@ -18,6 +18,7 @@ import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.pep.Avatar;
import im.conversations.android.xmpp.model.stanza.Iq;
+import im.conversations.android.xmpp.model.upload.Request;
import java.nio.ByteBuffer;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
@@ -438,23 +439,13 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public Iq requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) {
+ public Iq requestHttpUploadSlot(
+ final Jid host, final DownloadableFile file, final String mime) {
final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(host);
- 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 Iq requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) {
- final Iq packet = new Iq(Iq.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);
+ final var request = packet.addExtension(new Request());
+ request.setFilename(convertFilename(file.getName()));
+ request.setSize(file.getExpectedSize());
return packet;
}
@@ -114,11 +114,7 @@ public class HttpConnectionManager extends AbstractConnectionManager {
return;
}
}
- HttpUploadConnection connection =
- new HttpUploadConnection(
- message,
- Method.determine(message.getConversation().getAccount()),
- this);
+ HttpUploadConnection connection = new HttpUploadConnection(message, this);
connection.init(delay);
this.uploadConnections.add(connection);
}
@@ -3,20 +3,12 @@ package eu.siacs.conversations.http;
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.util.Log;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.Future;
-
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile;
@@ -25,6 +17,10 @@ import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Future;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
@@ -32,17 +28,14 @@ import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
-public class HttpUploadConnection implements Transferable, AbstractConnectionManager.ProgressListener {
+public class HttpUploadConnection
+ implements Transferable, AbstractConnectionManager.ProgressListener {
- static final List<String> WHITE_LISTED_HEADERS = Arrays.asList(
- "Authorization",
- "Cookie",
- "Expires"
- );
+ static final List<String> WHITE_LISTED_HEADERS =
+ Arrays.asList("Authorization", "Cookie", "Expires");
private final HttpConnectionManager mHttpConnectionManager;
private final XmppConnectionService mXmppConnectionService;
- private final Method method;
private boolean delayed = false;
private DownloadableFile file;
private final Message message;
@@ -53,9 +46,9 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan
private Call mostRecentCall;
private ListenableFuture<SlotRequester.Slot> slotFuture;
- public HttpUploadConnection(Message message, Method method, HttpConnectionManager httpConnectionManager) {
+ public HttpUploadConnection(
+ final Message message, final HttpConnectionManager httpConnectionManager) {
this.message = message;
- this.method = method;
this.mHttpConnectionManager = httpConnectionManager;
this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService();
}
@@ -88,13 +81,13 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan
final ListenableFuture<SlotRequester.Slot> slotFuture = this.slotFuture;
if (slotFuture != null && !slotFuture.isDone()) {
if (slotFuture.cancel(true)) {
- Log.d(Config.LOGTAG,"cancelled slot requester");
+ Log.d(Config.LOGTAG, "cancelled slot requester");
}
}
final Call call = this.mostRecentCall;
if (call != null && !call.isCanceled()) {
call.cancel();
- Log.d(Config.LOGTAG,"cancelled HTTP request");
+ Log.d(Config.LOGTAG, "cancelled HTTP request");
}
}
@@ -102,8 +95,13 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan
finish();
final Call call = this.mostRecentCall;
final Future<SlotRequester.Slot> slotFuture = this.slotFuture;
- final boolean cancelled = (call != null && call.isCanceled()) || (slotFuture != null && slotFuture.isCancelled());
- mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, cancelled ? Message.ERROR_MESSAGE_CANCELLED : errorMessage);
+ final boolean cancelled =
+ (call != null && call.isCanceled())
+ || (slotFuture != null && slotFuture.isCancelled());
+ mXmppConnectionService.markMessage(
+ message,
+ Message.STATUS_SEND_FAILED,
+ cancelled ? Message.ERROR_MESSAGE_CANCELLED : errorMessage);
}
private void finish() {
@@ -115,7 +113,8 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan
final Account account = message.getConversation().getAccount();
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
final String mime;
- if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+ if (message.getEncryption() == Message.ENCRYPTION_PGP
+ || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
mime = "application/pgp-encrypted";
} else {
mime = this.file.getMimeType();
@@ -130,75 +129,85 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan
}
this.file.setExpectedSize(originalFileSize + (file.getKey() != null ? 16 : 0));
message.resetFileParams();
- this.slotFuture = new SlotRequester(mXmppConnectionService).request(method, account, file, mime);
- Futures.addCallback(this.slotFuture, new FutureCallback<SlotRequester.Slot>() {
- @Override
- public void onSuccess(@Nullable SlotRequester.Slot result) {
- HttpUploadConnection.this.slot = result;
- try {
- HttpUploadConnection.this.upload();
- } catch (final Exception e) {
- fail(e.getMessage());
- }
- }
+ this.slotFuture = new SlotRequester(mXmppConnectionService).request(account, file, mime);
+ Futures.addCallback(
+ this.slotFuture,
+ new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable SlotRequester.Slot result) {
+ HttpUploadConnection.this.slot = result;
+ try {
+ HttpUploadConnection.this.upload();
+ } catch (final Exception e) {
+ fail(e.getMessage());
+ }
+ }
- @Override
- public void onFailure(@NonNull final Throwable throwable) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to request slot", throwable);
- // TODO consider fall back to jingle in 1-on-1 chats with exactly one online presence
- fail(throwable.getMessage());
- }
- }, MoreExecutors.directExecutor());
+ @Override
+ public void onFailure(@NonNull final Throwable throwable) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid() + ": unable to request slot",
+ throwable);
+ // TODO consider fall back to jingle in 1-on-1 chats with exactly one online
+ // presence
+ fail(throwable.getMessage());
+ }
+ },
+ MoreExecutors.directExecutor());
message.setTransferable(this);
mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
}
private void upload() {
- final OkHttpClient client = mHttpConnectionManager.buildHttpClient(
- slot.put,
- message.getConversation().getAccount(),
- 0,
- true
- );
+ final OkHttpClient client =
+ mHttpConnectionManager.buildHttpClient(
+ slot.put, message.getConversation().getAccount(), 0, true);
final RequestBody requestBody = AbstractConnectionManager.requestBody(file, this);
- final Request request = new Request.Builder()
- .url(slot.put)
- .put(requestBody)
- .headers(slot.headers)
- .build();
+ final Request request =
+ new Request.Builder().url(slot.put).put(requestBody).headers(slot.headers).build();
Log.d(Config.LOGTAG, "uploading file to " + slot.put);
this.mostRecentCall = client.newCall(request);
- this.mostRecentCall.enqueue(new Callback() {
- @Override
- public void onFailure(@NonNull Call call, IOException e) {
- Log.d(Config.LOGTAG, "http upload failed", e);
- fail(e.getMessage());
- }
-
- @Override
- public void onResponse(@NonNull Call call, @NonNull Response response) {
- final int code = response.code();
- if (code == 200 || code == 201) {
- Log.d(Config.LOGTAG, "finished uploading file");
- final String get;
- if (key != null) {
- get = AesGcmURL.toAesGcmUrl(slot.get.newBuilder().fragment(CryptoHelper.bytesToHex(key)).build());
- } else {
- get = slot.get.toString();
+ this.mostRecentCall.enqueue(
+ new Callback() {
+ @Override
+ public void onFailure(@NonNull Call call, IOException e) {
+ Log.d(Config.LOGTAG, "http upload failed", e);
+ fail(e.getMessage());
}
- mXmppConnectionService.getFileBackend().updateFileParams(message, get);
- mXmppConnectionService.getFileBackend().updateMediaScanner(file);
- finish();
- if (!message.isPrivateMessage()) {
- message.setCounterpart(message.getConversation().getJid().asBareJid());
+
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ final int code = response.code();
+ if (code == 200 || code == 201) {
+ Log.d(Config.LOGTAG, "finished uploading file");
+ final String get;
+ if (key != null) {
+ get =
+ AesGcmURL.toAesGcmUrl(
+ slot.get
+ .newBuilder()
+ .fragment(CryptoHelper.bytesToHex(key))
+ .build());
+ } else {
+ get = slot.get.toString();
+ }
+ mXmppConnectionService.getFileBackend().updateFileParams(message, get);
+ mXmppConnectionService.getFileBackend().updateMediaScanner(file);
+ finish();
+ if (!message.isPrivateMessage()) {
+ message.setCounterpart(
+ message.getConversation().getJid().asBareJid());
+ }
+ mXmppConnectionService.resendMessage(message, delayed);
+ } else {
+ Log.d(
+ Config.LOGTAG,
+ "http upload failed because response code was " + code);
+ fail("http upload failed because response code was " + code);
+ }
}
- mXmppConnectionService.resendMessage(message, delayed);
- } else {
- Log.d(Config.LOGTAG, "http upload failed because response code was " + code);
- fail("http upload failed because response code was " + code);
- }
- }
- });
+ });
}
public Message getMessage() {
@@ -210,4 +219,4 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan
this.transmitted = progress;
mHttpConnectionManager.updateConversationUi(false);
}
-}
+}
@@ -1,51 +0,0 @@
-/*
- * 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 eu.siacs.conversations.http;
-
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.xmpp.XmppConnection;
-
-public enum Method {
- 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 {
- return HTTP_UPLOAD;
- }
- }
-}
@@ -29,21 +29,22 @@
package eu.siacs.conversations.http;
+import android.util.Log;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.SettableFuture;
-
-import java.util.Map;
-
+import com.google.common.util.concurrent.MoreExecutors;
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile;
-import eu.siacs.conversations.parser.IqParser;
import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.IqResponseException;
import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.xmpp.model.stanza.Iq;
+import im.conversations.android.xmpp.model.upload.Header;
+import im.conversations.android.xmpp.model.upload.Slot;
+import java.util.Map;
import okhttp3.Headers;
import okhttp3.HttpUrl;
@@ -55,83 +56,54 @@ public class SlotRequester {
this.service = service;
}
- public ListenableFuture<Slot> request(Method method, Account account, DownloadableFile file, String mime) {
- if (method == Method.HTTP_UPLOAD_LEGACY) {
- final Jid host = account.getXmppConnection().findDiscoItemByFeature(Namespace.HTTP_UPLOAD_LEGACY);
- return requestHttpUploadLegacy(account, host, file, mime);
- } else {
- final Jid host = account.getXmppConnection().findDiscoItemByFeature(Namespace.HTTP_UPLOAD);
- return requestHttpUpload(account, host, file, mime);
+ public ListenableFuture<Slot> request(
+ final Account account, final DownloadableFile file, final String mime) {
+ final var result =
+ account.getXmppConnection()
+ .getServiceDiscoveryResultByFeature(Namespace.HTTP_UPLOAD);
+ if (result == null) {
+ return Futures.immediateFailedFuture(
+ new IllegalStateException("No HTTP upload host found"));
}
+ return requestHttpUpload(account, result.getKey(), file, mime);
}
- private ListenableFuture<Slot> requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) {
- final SettableFuture<Slot> future = SettableFuture.create();
- final Iq request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime);
- service.sendIqPacket(account, request, (packet) -> {
- if (packet.getType() == Iq.Type.RESULT) {
- final 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) {
- final Slot slot = new Slot(
- HttpUrl.get(putUrl),
- HttpUrl.get(getUrl),
- Headers.of("Content-Type", mime == null ? "application/octet-stream" : mime)
- );
- future.set(slot);
- return;
- }
- } catch (final IllegalArgumentException e) {
- future.setException(e);
- return;
- }
- }
- }
- future.setException(new IqResponseException(IqParser.extractErrorMessage(packet)));
- });
- return future;
- }
-
- private ListenableFuture<Slot> requestHttpUpload(Account account, Jid host, DownloadableFile file, String mime) {
- final SettableFuture<Slot> future = SettableFuture.create();
+ private ListenableFuture<Slot> requestHttpUpload(
+ final Account account, final Jid host, final DownloadableFile file, final String mime) {
final Iq request = service.getIqGenerator().requestHttpUploadSlot(host, file, mime);
- service.sendIqPacket(account, request, (packet) -> {
- if (packet.getType() == Iq.Type.RESULT) {
- final 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) {
- final ImmutableMap.Builder<String, String> headers = new ImmutableMap.Builder<>();
- for (final 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")) {
- headers.put(name, value.trim());
- }
- }
- }
- headers.put("Content-Type", mime == null ? "application/octet-stream" : mime);
- final Slot slot = new Slot(HttpUrl.get(putUrl), HttpUrl.get(getUrl), headers.build());
- future.set(slot);
- return;
+ final var iqFuture = service.sendIqPacket(account, request);
+ return Futures.transform(
+ iqFuture,
+ response -> {
+ final var slot =
+ response.getExtension(
+ im.conversations.android.xmpp.model.upload.Slot.class);
+ if (slot == null) {
+ Log.d(Config.LOGTAG, "-->" + response.toString());
+ throw new IllegalStateException("Slot not found in IQ response");
+ }
+ final var getUrl = slot.getGetUrl();
+ final var put = slot.getPut();
+ if (getUrl == null || put == null) {
+ throw new IllegalStateException("Missing get or put in slot response");
+ }
+ final var putUrl = put.getUrl();
+ if (putUrl == null) {
+ throw new IllegalStateException("Missing put url");
+ }
+ final var headers = new ImmutableMap.Builder<String, String>();
+ for (final Header header : put.getHeaders()) {
+ final String name = header.getHeaderName();
+ final String value = header.getContent();
+ if (Strings.isNullOrEmpty(value) || value.contains("\n")) {
+ continue;
}
- } catch (final IllegalArgumentException e) {
- future.setException(e);
- return;
+ headers.put(name, value.trim());
}
- }
- }
- future.setException(new IqResponseException(IqParser.extractErrorMessage(packet)));
- });
- return future;
+ headers.put("Content-Type", mime == null ? "application/octet-stream" : mime);
+ return new Slot(putUrl, getUrl, headers.buildKeepingLast());
+ },
+ MoreExecutors.directExecutor());
}
public static class Slot {
@@ -60,6 +60,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.Config;
@@ -169,6 +170,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@@ -5958,6 +5960,14 @@ public class XmppConnectionService extends Service {
connection.sendCreateAccountWithCaptchaPacket(id, data);
}
+ public ListenableFuture<Iq> sendIqPacket(final Account account, final Iq request) {
+ final XmppConnection connection = account.getXmppConnection();
+ if (connection == null) {
+ return Futures.immediateFailedFuture(new TimeoutException());
+ }
+ return connection.sendIqPacket(request);
+ }
+
public void sendIqPacket(final Account account, final Iq packet, final Consumer<Iq> callback) {
final XmppConnection connection = account.getXmppConnection();
if (connection != null) {
@@ -33,7 +33,6 @@ public final class Namespace {
public static final String REGISTER_STREAM_FEATURE = "http://jabber.org/features/iq-register";
public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams";
public static final String HTTP_UPLOAD = "urn:xmpp:http:upload:0";
- public static final String HTTP_UPLOAD_LEGACY = "urn:xmpp:http:upload";
public static final String STANZA_IDS = "urn:xmpp:sid:0";
public static final String IDLE = "urn:xmpp:idle:1";
public static final String DATA = "jabber:x:data";
@@ -0,0 +1,33 @@
+package eu.siacs.conversations.xmpp;
+
+import im.conversations.android.xmpp.model.stanza.Iq;
+
+public class IqErrorResponseException extends Exception {
+
+ private final Iq response;
+
+ public IqErrorResponseException(final Iq response) {
+ super(message(response));
+ this.response = response;
+ }
+
+ public Iq getResponse() {
+ return this.response;
+ }
+
+ public static String message(final Iq iq) {
+ final var error = iq.getError();
+ if (error == null) {
+ return "missing error element in response";
+ }
+ final var text = error.getTextAsString();
+ if (text != null) {
+ return text;
+ }
+ final var condition = error.getCondition();
+ if (condition != null) {
+ return condition.getName();
+ }
+ return "no condition attached to error";
+ }
+}
@@ -1,8 +0,0 @@
-package eu.siacs.conversations.xmpp;
-
-public class IqResponseException extends Exception {
-
- public IqResponseException(final String message) {
- super(message);
- }
-}
@@ -21,6 +21,8 @@ import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Ints;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
import de.gultsch.common.Patterns;
import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.BuildConfig;
@@ -128,6 +130,7 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -2533,6 +2536,21 @@ public class XmppConnection implements Runnable {
return String.format("%s.%s", BuildConfig.APP_NAME, CryptoHelper.random(3));
}
+ public ListenableFuture<Iq> sendIqPacket(final Iq request) {
+ final SettableFuture<Iq> settable = SettableFuture.create();
+ this.sendIqPacket(
+ request,
+ response -> {
+ final var type = response.getType();
+ switch (type) {
+ case RESULT -> settable.set(response);
+ case TIMEOUT -> settable.setException(new TimeoutException());
+ default -> settable.setException(new IqErrorResponseException(response));
+ }
+ });
+ return settable;
+ }
+
public String sendIqPacket(final Iq packet, final Consumer<Iq> callback) {
packet.setFrom(account.getJid());
return this.sendUnmodifiedIqPacket(packet, callback, false);
@@ -2720,6 +2738,18 @@ public class XmppConnection implements Runnable {
}
}
+ public Entry<Jid, ServiceDiscoveryResult> getServiceDiscoveryResultByFeature(
+ final String feature) {
+ synchronized (this.disco) {
+ for (final var cursor : this.disco.entrySet()) {
+ if (cursor.getValue().getFeatures().contains(feature)) {
+ return cursor;
+ }
+ }
+ return null;
+ }
+ }
+
public Jid findDiscoItemByFeature(final String feature) {
final var items = findDiscoItemsByFeature(feature);
if (items.isEmpty()) {
@@ -3152,66 +3182,54 @@ public class XmppConnection implements Runnable {
return HttpUrl.parse(address);
}
- public boolean httpUpload(long filesize) {
+ public boolean httpUpload(long fileSize) {
if (Config.DISABLE_HTTP_UPLOAD) {
return false;
+ }
+ final var result = getServiceDiscoveryResultByFeature(Namespace.HTTP_UPLOAD);
+ if (result == null) {
+ return false;
+ }
+ final long maxSize;
+ try {
+ maxSize =
+ Long.parseLong(
+ result.getValue()
+ .getExtendedDiscoInformation(
+ Namespace.HTTP_UPLOAD, "max-file-size"));
+ } catch (final Exception e) {
+ return true;
+ }
+ if (fileSize <= maxSize) {
+ return true;
} else {
- for (String namespace :
- new String[] {Namespace.HTTP_UPLOAD, Namespace.HTTP_UPLOAD_LEGACY}) {
- List<Entry<Jid, ServiceDiscoveryResult>> items =
- findDiscoItemsByFeature(namespace);
- if (!items.isEmpty()) {
- 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) {
- return true;
- }
- }
- }
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": http upload is not available for files with"
+ + " size "
+ + fileSize
+ + " (max is "
+ + maxSize
+ + ")");
return false;
}
}
- public boolean useLegacyHttpUpload() {
- return findDiscoItemByFeature(Namespace.HTTP_UPLOAD) == null
- && findDiscoItemByFeature(Namespace.HTTP_UPLOAD_LEGACY) != null;
- }
-
public long getMaxHttpUploadSize() {
- for (String namespace :
- new String[] {Namespace.HTTP_UPLOAD, Namespace.HTTP_UPLOAD_LEGACY}) {
- List<Entry<Jid, ServiceDiscoveryResult>> items = findDiscoItemsByFeature(namespace);
- if (!items.isEmpty()) {
- try {
- return Long.parseLong(
- items.get(0)
- .getValue()
- .getExtendedDiscoInformation(namespace, "max-file-size"));
- } catch (Exception e) {
- // ignored
- }
- }
+ final var result = getServiceDiscoveryResultByFeature(Namespace.HTTP_UPLOAD);
+ if (result == null) {
+ return -1;
+ }
+ try {
+ return Long.parseLong(
+ result.getValue()
+ .getExtendedDiscoInformation(
+ Namespace.HTTP_UPLOAD, "max-file-size"));
+ } catch (final Exception e) {
+ return -1;
+ // ignored
}
- return -1;
}
public boolean stanzaIds() {
@@ -2,6 +2,7 @@ package im.conversations.android.xmpp.model.upload;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
+import okhttp3.HttpUrl;
@XmlElement
public class Slot extends Extension {
@@ -9,4 +10,13 @@ public class Slot extends Extension {
public Slot() {
super(Slot.class);
}
+
+ public HttpUrl getGetUrl() {
+ final var get = getExtension(Get.class);
+ return get == null ? null : get.getUrl();
+ }
+
+ public Put getPut() {
+ return getExtension(Put.class);
+ }
}