diff options
7 files changed, 114 insertions, 84 deletions
diff --git a/src/main/java/de/pixart/messenger/entities/Transferable.java b/src/main/java/de/pixart/messenger/entities/Transferable.java index 054841b6e..968563b32 100644 --- a/src/main/java/de/pixart/messenger/entities/Transferable.java +++ b/src/main/java/de/pixart/messenger/entities/Transferable.java @@ -19,6 +19,7 @@ public interface Transferable { "otr" ); + int STATUS_WAITING = 0x199; int STATUS_UNKNOWN = 0x200; int STATUS_CHECKING = 0x201; int STATUS_FAILED = 0x202; @@ -26,7 +27,7 @@ public interface Transferable { int STATUS_DOWNLOADING = 0x204; int STATUS_OFFER_CHECK_FILESIZE = 0x206; int STATUS_UPLOADING = 0x207; - int STATUS_WAITING = 0x199; + int STATUS_CANCELLED = 0x208; boolean start(); diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java index 6cff1aebd..d11a2af0f 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java @@ -9,10 +9,6 @@ import android.graphics.Rect; import android.graphics.Typeface; import android.net.Uri; import android.preference.PreferenceManager; - -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - import android.text.Editable; import android.text.Spannable; import android.text.SpannableString; @@ -39,6 +35,9 @@ import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + import com.google.common.base.Strings; import com.squareup.picasso.Picasso; @@ -230,7 +229,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie if (message.isFileOrImage() || transferable != null) { FileParams params = message.getFileParams(); filesize = params.size > 0 ? UIHelper.filesizeToString(params.size) : null; - if (transferable != null && transferable.getStatus() == Transferable.STATUS_FAILED) { + if (transferable != null && (transferable.getStatus() == Transferable.STATUS_FAILED || transferable.getStatus() == Transferable.STATUS_CANCELLED)) { error = true; } } diff --git a/src/main/java/de/pixart/messenger/utils/UIHelper.java b/src/main/java/de/pixart/messenger/utils/UIHelper.java index 102848183..e081ae6d6 100644 --- a/src/main/java/de/pixart/messenger/utils/UIHelper.java +++ b/src/main/java/de/pixart/messenger/utils/UIHelper.java @@ -1,12 +1,13 @@ package de.pixart.messenger.utils; import android.content.Context; -import androidx.annotation.ColorInt; import android.text.SpannableStringBuilder; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.Pair; +import androidx.annotation.ColorInt; + import java.math.BigInteger; import java.security.MessageDigest; import java.util.Arrays; @@ -269,6 +270,8 @@ public class UIHelper { getFileDescriptionString(context, message)), true); case Transferable.STATUS_FAILED: return new Pair<>(context.getString(R.string.file_transmission_failed), true); + case Transferable.STATUS_CANCELLED: + return new Pair<>(context.getString(R.string.file_transmission_cancelled), true); case Transferable.STATUS_UPLOADING: if (message.getStatus() == Message.STATUS_OFFERED) { return new Pair<>(context.getString(R.string.offering_x_file, @@ -315,12 +318,12 @@ public class UIHelper { continue; } char first = l.charAt(0); - if ((first != '>' || !isPositionFollowedByQuoteableCharacter(l,0)) && first != '\u00bb') { + if ((first != '>' || !isPositionFollowedByQuoteableCharacter(l, 0)) && first != '\u00bb') { CharSequence line = CharSequenceUtils.trim(l); if (line.length() == 0) { continue; } - char last = line.charAt(line.length()-1); + char last = line.charAt(line.length() - 1); if (builder.length() != 0) { builder.append(' '); } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java index 561404186..bfaeacc31 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java @@ -12,6 +12,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -143,7 +144,7 @@ public class JingleConnection implements Transferable { @Override public void onFileTransferAborted() { - JingleConnection.this.sendCancel(); + JingleConnection.this.sendSessionTerminate("connectivity-error"); JingleConnection.this.fail(); } }; @@ -223,27 +224,27 @@ public class JingleConnection implements Transferable { return this.message.getCounterpart(); } - public void deliverPacket(JinglePacket packet) { - boolean returnResult = true; + void deliverPacket(JinglePacket packet) { if (packet.isAction("session-terminate")) { Reason reason = packet.getReason(); if (reason != null) { if (reason.hasChild("cancel")) { + this.cancelled = true; this.fail(); } else if (reason.hasChild("success")) { this.receiveSuccess(); } else { - this.fail(); + this.fail(reason.getName()); } } else { this.fail(); } } else if (packet.isAction("session-accept")) { - returnResult = receiveAccept(packet); + receiveAccept(packet); } else if (packet.isAction("session-info")) { - Element checksum = packet.getChecksum(); - Element file = checksum == null ? null : checksum.findChild("file"); - Element hash = file == null ? null : file.findChild("hash", "urn:xmpp:hashes:2"); + final Element checksum = packet.getChecksum(); + final Element file = checksum == null ? null : checksum.findChild("file"); + final Element hash = file == null ? null : file.findChild("hash", "urn:xmpp:hashes:2"); if (hash != null && "sha-1".equalsIgnoreCase(hash.getAttribute("algo"))) { try { this.expectedHash = Base64.decode(hash.getContent(), Base64.DEFAULT); @@ -251,27 +252,28 @@ public class JingleConnection implements Transferable { this.expectedHash = new byte[0]; } } + respondToIq(packet, true); } else if (packet.isAction("transport-info")) { - returnResult = receiveTransportInfo(packet); + receiveTransportInfo(packet); } else if (packet.isAction("transport-replace")) { if (packet.getJingleContent().hasIbbTransport()) { - returnResult = this.receiveFallbackToIbb(packet); + receiveFallbackToIbb(packet); } else { - returnResult = false; - Log.d(Config.LOGTAG, "trying to fallback to something unknown" - + packet.toString()); + Log.d(Config.LOGTAG, "trying to fallback to something unknown" + packet.toString()); + respondToIq(packet, false); } } else if (packet.isAction("transport-accept")) { - returnResult = this.receiveTransportAccept(packet); + receiveTransportAccept(packet); } else { - Log.d(Config.LOGTAG, "packet arrived in connection. action was " - + packet.getAction()); - returnResult = false; + Log.d(Config.LOGTAG, "packet arrived in connection. action was " + packet.getAction()); + respondToIq(packet, false); } - IqPacket response; - if (returnResult) { - response = packet.generateResponse(IqPacket.TYPE.RESULT); + } + private void respondToIq(final IqPacket packet, final boolean result) { + final IqPacket response; + if (result) { + response = packet.generateResponse(IqPacket.TYPE.RESULT); } else { response = packet.generateResponse(IqPacket.TYPE.ERROR); final Element error = response.addChild("error").setAttribute("type", "cancel"); @@ -280,6 +282,14 @@ public class JingleConnection implements Transferable { mXmppConnectionService.sendIqPacket(account, response, null); } + private void respondToIqWithOutOfOrder(final IqPacket packet) { + final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); + final Element error = response.addChild("error").setAttribute("type", "wait"); + error.addChild("unexpected-request", "urn:ietf:params:xml:ns:xmpp-stanzas"); + error.addChild("out-of-order", "urn:xmpp:jingle:errors:1"); + mXmppConnectionService.sendIqPacket(account, response, null); + } + public void init(final Message message) { if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { Conversation conversation = (Conversation) message.getConversation(); @@ -323,7 +333,7 @@ public class JingleConnection implements Transferable { @Override public void failed() { - Log.d(Config.LOGTAG, "connection to our own proxy65 candidete failed"); + Log.d(Config.LOGTAG, String.format("connection to our own proxy65 candidate failed (%s:%d)", candidate.getHost(), candidate.getPort())); sendInitRequest(); } @@ -341,6 +351,7 @@ public class JingleConnection implements Transferable { } }); } + } private void gatherAndConnectDirectCandidates() { @@ -402,8 +413,6 @@ public class JingleConnection implements Transferable { this.contentName = content.getAttribute("name"); this.transportId = content.getTransportId(); - mXmppConnectionService.sendIqPacket(account, packet.generateResponse(IqPacket.TYPE.RESULT), null); - if (this.initialTransport == Transport.SOCKS) { this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); } else if (this.initialTransport == Transport.IBB) { @@ -413,20 +422,20 @@ public class JingleConnection implements Transferable { this.ibbBlockSize = Math.min(Integer.parseInt(receivedBlockSize), this.ibbBlockSize); } catch (NumberFormatException e) { Log.d(Config.LOGTAG, "number format exception " + e.getMessage()); - this.sendCancel(); + respondToIq(packet, false); this.fail(); return; } } else { Log.d(Config.LOGTAG, "received block size was null"); - this.sendCancel(); + respondToIq(packet, false); this.fail(); return; } } this.ftVersion = content.getVersion(); if (ftVersion == null) { - this.sendCancel(); + respondToIq(packet, false); this.fail(); return; } @@ -451,14 +460,26 @@ public class JingleConnection implements Transferable { AbstractConnectionManager.Extension extension = AbstractConnectionManager.Extension.of(path); if (VALID_IMAGE_EXTENSIONS.contains(extension.main)) { message.setType(Message.TYPE_IMAGE); - message.setRelativeFilePath(message.getUuid() + "." + extension.main); + if (message.getStatus() == Message.STATUS_RECEIVED) { + message.setRelativeFilePath(fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + "." + extension.main); + } else { + message.setRelativeFilePath("Sent/" + fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + "." + extension.main); + } } else if (VALID_CRYPTO_EXTENSIONS.contains(extension.main)) { if (VALID_IMAGE_EXTENSIONS.contains(extension.secondary)) { message.setType(Message.TYPE_IMAGE); - message.setRelativeFilePath(message.getUuid() + "." + extension.secondary); + if (message.getStatus() == Message.STATUS_RECEIVED) { + message.setRelativeFilePath(fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + "." + extension.secondary); + } else { + message.setRelativeFilePath("Sent/" + fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + "." + extension.secondary); + } } else { message.setType(Message.TYPE_FILE); - message.setRelativeFilePath(message.getUuid() + (extension.secondary != null ? ("." + extension.secondary) : "")); + if (message.getStatus() == Message.STATUS_RECEIVED) { + message.setRelativeFilePath(fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + (extension.secondary != null ? ("." + extension.secondary) : "")); + } else { + message.setRelativeFilePath("Sent/" + fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + (extension.secondary != null ? ("." + extension.secondary) : "")); + } } // only for OTR compatibility if (extension.main.equals("otr")) { @@ -468,7 +489,11 @@ public class JingleConnection implements Transferable { } } else { message.setType(Message.TYPE_FILE); - message.setRelativeFilePath(message.getUuid() + (extension.main != null ? ("." + extension.main) : "")); + if (message.getStatus() == Message.STATUS_RECEIVED) { + message.setRelativeFilePath(fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + (extension.main != null ? ("." + extension.main) : "")); + } else { + message.setRelativeFilePath("Sent/" + fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + (extension.main != null ? ("." + extension.main) : "")); + } } long size = parseLong(fileSize, 0); message.setBody(Long.toString(size)); @@ -491,6 +516,7 @@ public class JingleConnection implements Transferable { //JET reports the plain text size. however lower levels of our receiving code still //expect the cipher text size. so we just + 16 bytes (auth tag size) here this.file.setExpectedSize(size + (remoteIsUsingJet ? 16 : 0)); + respondToIq(packet, true); if (mJingleConnectionManager.hasStoragePermission() && size < this.mJingleConnectionManager.getAutoAcceptFileSize() && mXmppConnectionService.isDataSaverDisabled()) { @@ -508,13 +534,9 @@ public class JingleConnection implements Transferable { this.mXmppConnectionService.getNotificationService().push(message); } Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize()); - } else { - this.sendCancel(); - this.fail(); + return; } - } else { - this.sendCancel(); - this.fail(); + respondToIq(packet, false); } } @@ -562,7 +584,7 @@ public class JingleConnection implements Transferable { try { this.mFileInputStream = new FileInputStream(file); } catch (FileNotFoundException e) { - abort(); + fail(e.getMessage()); return; } content.setTransportId(this.transportId); @@ -642,7 +664,7 @@ public class JingleConnection implements Transferable { @Override public void established() { - Log.d(Config.LOGTAG, "connected to proxy candidate"); + Log.d(Config.LOGTAG, "connected to proxy65 candidate"); mergeCandidate(candidate); content.socks5transport().setChildren(getCandidatesAsElements()); packet.setContent(content); @@ -690,18 +712,19 @@ public class JingleConnection implements Transferable { mXmppConnectionService.sendIqPacket(account, packet, callback); } - private boolean receiveAccept(JinglePacket packet) { + private void receiveAccept(JinglePacket packet) { if (this.mJingleStatus != JINGLE_STATUS_INITIATED) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received out of order session-accept"); - return false; + respondToIqWithOutOfOrder(packet); + return; } this.mJingleStatus = JINGLE_STATUS_ACCEPTED; mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); Content content = packet.getJingleContent(); if (content.hasSocks5Transport()) { + respondToIq(packet, true); mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); this.connectNextCandidate(); - return true; } else if (content.hasIbbTransport()) { String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size"); if (receivedBlockSize != null) { @@ -714,18 +737,19 @@ public class JingleConnection implements Transferable { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to parse block size in session-accept"); } } + respondToIq(packet, true); this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize); this.transport.connect(onIbbTransportConnected); - return true; } else { - return false; + respondToIq(packet, false); } } - private boolean receiveTransportInfo(JinglePacket packet) { - Content content = packet.getJingleContent(); + private void receiveTransportInfo(JinglePacket packet) { + final Content content = packet.getJingleContent(); if (content.hasSocks5Transport()) { if (content.socks5transport().hasChild("activated")) { + respondToIq(packet, true); if ((this.transport != null) && (this.transport instanceof JingleSocks5Transport)) { onProxyActivated.success(); } else { @@ -737,21 +761,20 @@ public class JingleConnection implements Transferable { connection.setActivated(true); } else { Log.d(Config.LOGTAG, "activated connection not found"); - this.sendCancel(); + sendSessionTerminate("failed-transport"); this.fail(); } } - return true; } else if (content.socks5transport().hasChild("proxy-error")) { + respondToIq(packet, true); onProxyActivated.failed(); - return true; } else if (content.socks5transport().hasChild("candidate-error")) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received candidate error"); + respondToIq(packet, true); this.receivedCandidate = true; if (mJingleStatus == JINGLE_STATUS_ACCEPTED && this.sentCandidate) { this.connect(); } - return true; } else if (content.socks5transport().hasChild("candidate-used")) { String cid = content.socks5transport().findChild("candidate-used").getAttribute("cid"); if (cid != null) { @@ -759,8 +782,10 @@ public class JingleConnection implements Transferable { JingleCandidate candidate = getCandidate(cid); if (candidate == null) { Log.d(Config.LOGTAG, "could not find candidate with cid=" + cid); - return false; + respondToIq(packet, false); + return; } + respondToIq(packet, true); candidate.flagAsUsedByCounterpart(); this.receivedCandidate = true; if (mJingleStatus == JINGLE_STATUS_ACCEPTED && this.sentCandidate) { @@ -768,15 +793,14 @@ public class JingleConnection implements Transferable { } else { Log.d(Config.LOGTAG, "ignoring because file is already in transmission or we haven't sent our candidate yet status=" + mJingleStatus + " sentCandidate=" + sentCandidate); } - return true; } else { - return false; + respondToIq(packet, false); } } else { - return false; + respondToIq(packet, false); } } else { - return true; + respondToIq(packet, true); } } @@ -875,11 +899,7 @@ public class JingleConnection implements Transferable { } private void sendSuccess() { - JinglePacket packet = bootstrapPacket("session-terminate"); - Reason reason = new Reason(); - reason.addChild("success"); - packet.setReason(reason); - this.sendJinglePacket(packet); + sendSessionTerminate("success"); this.disconnectSocks5Connections(); this.mJingleStatus = JINGLE_STATUS_FINISHED; this.message.setStatus(Message.STATUS_RECEIVED); @@ -901,7 +921,7 @@ public class JingleConnection implements Transferable { } - private boolean receiveFallbackToIbb(JinglePacket packet) { + private void receiveFallbackToIbb(JinglePacket packet) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": receiving fallback to ibb"); final String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size"); if (receivedBlockSize != null) { @@ -924,6 +944,7 @@ public class JingleConnection implements Transferable { content.ibbTransport().setAttribute("sid", this.transportId); answer.setContent(content); + respondToIq(packet, true); if (initiating()) { this.sendJinglePacket(answer, (account, response) -> { @@ -936,10 +957,9 @@ public class JingleConnection implements Transferable { this.transport.receive(file, onFileTransmissionStatusChanged); this.sendJinglePacket(answer); } - return true; } - private boolean receiveTransportAccept(JinglePacket packet) { + private void receiveTransportAccept(JinglePacket packet) { if (packet.getJingleContent().hasIbbTransport()) { final Element ibbTransport = packet.getJingleContent().ibbTransport(); final String receivedBlockSize = ibbTransport.getAttribute("block-size"); @@ -955,19 +975,20 @@ public class JingleConnection implements Transferable { } } this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize); + if (sid == null || !sid.equals(this.transportId)) { Log.w(Config.LOGTAG, String.format("%s: sid in transport-accept (%s) did not match our sid (%s) ", account.getJid().asBareJid(), sid, transportId)); } + respondToIq(packet, true); //might be receive instead if we are not initiating if (initiating()) { this.transport.connect(onIbbTransportConnected); } else { this.transport.receive(file, onFileTransmissionStatusChanged); } - return true; } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received invalid transport-accept"); - return false; + respondToIq(packet, false); } } @@ -989,18 +1010,18 @@ public class JingleConnection implements Transferable { @Override public void cancel() { this.cancelled = true; - abort(); + abort("cancel"); } - public void abort() { + void abort(final String reason) { this.disconnectSocks5Connections(); if (this.transport instanceof JingleInbandTransport) { this.transport.disconnect(); } - this.sendCancel(); + sendSessionTerminate(reason); this.mJingleConnectionManager.finishConnection(this); if (responding()) { - this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); + this.message.setTransferable(new TransferablePlaceholder(cancelled ? Transferable.STATUS_CANCELLED : Transferable.STATUS_FAILED)); if (this.file != null) { file.delete(); } @@ -1040,11 +1061,11 @@ public class JingleConnection implements Transferable { this.mJingleConnectionManager.finishConnection(this); } - private void sendCancel() { - JinglePacket packet = bootstrapPacket("session-terminate"); - Reason reason = new Reason(); - reason.addChild("cancel"); - packet.setReason(reason); + private void sendSessionTerminate(String reason) { + final JinglePacket packet = bootstrapPacket("session-terminate"); + final Reason r = new Reason(); + r.addChild(reason); + packet.setReason(r); this.sendJinglePacket(packet); } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnectionManager.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnectionManager.java index addd350d2..8faf5fbc1 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnectionManager.java @@ -166,7 +166,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { public void cancelInTransmission() { for (JingleConnection connection : this.connections) { if (connection.getJingleStatus() == JingleConnection.JINGLE_STATUS_TRANSMITTING) { - connection.abort(); + connection.abort("connectivity-error"); } } } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInbandTransport.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInbandTransport.java index 30986ca0c..744a72548 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInbandTransport.java @@ -47,10 +47,15 @@ public class JingleInbandTransport extends JingleTransport { private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (connected && packet.getType() == IqPacket.TYPE.RESULT) { + if (!connected) { + return; + } + if (packet.getType() == IqPacket.TYPE.RESULT) { if (remainingSize > 0) { sendNextBlock(); } + } else if (packet.getType() == IqPacket.TYPE.ERROR) { + onFileTransmissionStatusChanged.onFileTransferAborted(); } } }; diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 24daff1c9..f3a5071a8 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -973,4 +973,5 @@ <string name="intro_privacy">Your privacy\nYour sovereignty</string> <string name="intro_desc_privacy">Pix-Art Messenger will neither sell nor analyze your data and you decide which permissions you grant.</string> <string name="no_results">No results</string> + <string name="file_transmission_cancelled">file transmission cancelled</string> </resources> |