diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 9a4dad2a5af411ae4e916a1349199d2fc5dcdd90..60da01c49a82514d04274e69dd5e25966b246e9c 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -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; } diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java index b1138ba1a1323787cf91ff6d92d48bdf2a0923b0..1af4ba48bd2fe4e17a889e563c9868c86294464d 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java @@ -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); } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index ef51b9af43db25f82df4c30514512a07c1dec9fd..9154c2c422783ed65d10cec2451db1c91220d94c 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -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 WHITE_LISTED_HEADERS = Arrays.asList( - "Authorization", - "Cookie", - "Expires" - ); + static final List 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 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 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 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() { - @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); } -} \ No newline at end of file +} diff --git a/src/main/java/eu/siacs/conversations/http/Method.java b/src/main/java/eu/siacs/conversations/http/Method.java deleted file mode 100644 index 47dae2b30cea010df7ba1f6a5c4f004c21fcf96d..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/http/Method.java +++ /dev/null @@ -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; - } - } -} diff --git a/src/main/java/eu/siacs/conversations/http/SlotRequester.java b/src/main/java/eu/siacs/conversations/http/SlotRequester.java index d76a99fda85eabf39864583d3c2fbc95f105da47..cc8e5ec2b091590bdc9d686987a90f6b64c3ed87 100644 --- a/src/main/java/eu/siacs/conversations/http/SlotRequester.java +++ b/src/main/java/eu/siacs/conversations/http/SlotRequester.java @@ -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 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 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 requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) { - final SettableFuture 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 requestHttpUpload(Account account, Jid host, DownloadableFile file, String mime) { - final SettableFuture future = SettableFuture.create(); + private ListenableFuture 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 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(); + 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 { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 7c627f755d3f0a150b29714aaa1f54b35433b49d..e8fb3ddee314146616b56f84a3a22c388ecf85eb 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -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 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 callback) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 15d88f75aaa6a0800774a945a78b1101754602f6..353898c56b72e48c5b80c6b02869afd4e0ee7872 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -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"; diff --git a/src/main/java/eu/siacs/conversations/xmpp/IqErrorResponseException.java b/src/main/java/eu/siacs/conversations/xmpp/IqErrorResponseException.java new file mode 100644 index 0000000000000000000000000000000000000000..fb8d8e730cd9d4e4b37d3321a084e9d4abc25f93 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/IqErrorResponseException.java @@ -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"; + } +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/IqResponseException.java b/src/main/java/eu/siacs/conversations/xmpp/IqResponseException.java deleted file mode 100644 index 84357eaa3183fde4fac4c513afca6a4e479ad615..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/IqResponseException.java +++ /dev/null @@ -1,8 +0,0 @@ -package eu.siacs.conversations.xmpp; - -public class IqResponseException extends Exception { - - public IqResponseException(final String message) { - super(message); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 11c6df3bbb0995af4311d2c09c9cb98d8444b9f1..e8826c96aa46558a283b2e81adac42076427c1d8 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -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 sendIqPacket(final Iq request) { + final SettableFuture 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 callback) { packet.setFrom(account.getJid()); return this.sendUnmodifiedIqPacket(packet, callback, false); @@ -2720,6 +2738,18 @@ public class XmppConnection implements Runnable { } } + public Entry 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> 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> 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() { diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java b/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java index df90157812be59d01d54ceca65ee47b7d9e764ff..9bac8abcab47f4d76cbdc31c8da6c72c7392706e 100644 --- a/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java @@ -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); + } }