Merge tag '2.5.8' into develop

This commit is contained in:
Martin/Geno 2019-09-12 08:23:37 +00:00
commit fa0452761f
No known key found for this signature in database
GPG Key ID: E7907CD4A3C7A6C1
50 changed files with 565 additions and 318 deletions

View File

@ -1,5 +1,10 @@
# Changelog
## Version 2.5.8
* fixed connection issues over Tor
* P2P file transfer (Jingle) now offers direct candidates
* Support XEP-0396: Jingle Encrypted Transports - OMEMO
### Version 2.5.7
* fixed crash when scanning QR codes on Android 6 and lower
* when sharing a message from and to Conversations insert it as quote

View File

@ -83,8 +83,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 25
versionCode 337
versionName "2.5.7"
versionCode 338
versionName "2.5.8"
archivesBaseName += "-$versionName"
applicationId "eu.sum7.conversations"
resValue "string", "applicationId", applicationId

View File

@ -101,6 +101,7 @@ public final class Config {
public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb
public static final boolean USE_DIRECT_JINGLE_CANDIDATES = true;
public static final boolean DISABLE_HTTP_UPLOAD = false;
public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts
public static final boolean BACKGROUND_STANZA_LOGGING = false; //log all stanzas that were received while the app is in background

View File

@ -29,6 +29,8 @@ public abstract class AbstractGenerator {
Content.Version.FT_5.getNamespace(),
Namespace.JINGLE_TRANSPORTS_S5B,
Namespace.JINGLE_TRANSPORTS_IBB,
Namespace.JINGLE_ENCRYPTED_TRANSPORT,
Namespace.JINGLE_ENCRYPTED_TRANSPORT_OMEMO,
"http://jabber.org/protocol/muc",
"jabber:x:conference",
Namespace.OOB,

View File

@ -39,6 +39,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.security.DigestOutputStream;
@ -359,6 +360,15 @@ public class FileBackend {
}
}
public static void close(final ServerSocket socket) {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
}
public static boolean weOwnFile(Context context, Uri uri) {
if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return false;

View File

@ -26,6 +26,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.CryptoHelper;
public class AbstractConnectionManager {

View File

@ -11,7 +11,6 @@ import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
import android.media.RingtoneManager;
import android.net.Uri;
@ -23,7 +22,6 @@ import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.BigPictureStyle;
import android.support.v4.app.NotificationCompat.Builder;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation;
import android.support.v4.app.Person;
import android.support.v4.app.RemoteInput;
import android.support.v4.content.ContextCompat;
@ -32,7 +30,6 @@ import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import java.io.File;
import java.io.IOException;
@ -64,7 +61,6 @@ import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.XmppConnection;
import rocks.xmpp.addr.Jid;
public class NotificationService {
@ -469,10 +465,7 @@ public class NotificationService {
private Builder buildMultipleConversation(final boolean notify, final boolean quietHours) {
final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
style.setBigContentTitle(notifications.size()
+ " "
+ mXmppConnectionService
.getString(R.string.unread_conversations));
style.setBigContentTitle(mXmppConnectionService.getString(R.string.x_unread_conversations,notifications.size()));
final StringBuilder names = new StringBuilder();
Conversation conversation = null;
for (final ArrayList<Message> messages : notifications.values()) {
@ -497,10 +490,8 @@ public class NotificationService {
if (names.length() >= 2) {
names.delete(names.length() - 2, names.length());
}
mBuilder.setContentTitle(notifications.size()
+ " "
+ mXmppConnectionService
.getString(R.string.unread_conversations));
mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.x_unread_conversations, notifications.size()));
mBuilder.setTicker(mXmppConnectionService.getString(R.string.x_unread_conversations, notifications.size()));
mBuilder.setContentText(names.toString());
mBuilder.setStyle(style);
if (conversation != null) {
@ -627,8 +618,11 @@ public class NotificationService {
CharSequence text = getMergedBodies(tmp);
bigPictureStyle.setSummaryText(text);
builder.setContentText(text);
builder.setTicker(text);
} else {
builder.setContentText(UIHelper.getFileDescriptionString(mXmppConnectionService, message));
final String description = UIHelper.getFileDescriptionString(mXmppConnectionService, message);
builder.setContentText(description);
builder.setTicker(description);
}
builder.setStyle(bigPictureStyle);
} catch (final IOException e) {
@ -685,7 +679,9 @@ public class NotificationService {
} else {
if (messages.get(0).getConversation().getMode() == Conversation.MODE_SINGLE) {
builder.setStyle(new NotificationCompat.BigTextStyle().bigText(getMergedBodies(messages)));
builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService, messages.get(messages.size()-1)).first);
final CharSequence preview = UIHelper.getMessagePreview(mXmppConnectionService, messages.get(messages.size()-1)).first;
builder.setContentText(preview);
builder.setTicker(preview);
builder.setNumber(messages.size());
} else {
final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
@ -703,8 +699,11 @@ public class NotificationService {
styledString = new SpannableString(name + ": " + messages.get(0).getBody());
styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
builder.setContentText(styledString);
builder.setTicker(styledString);
} else {
builder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count));
final String text = mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count);
builder.setContentText(text);
builder.setTicker(text);
}
}
}

View File

@ -66,12 +66,16 @@ public class Resolver {
Result result = new Result();
result.hostname = DNSName.from(hostname);
result.port = port;
result.directTls = port == 443 || port == 5223;
result.directTls = useDirectTls(port);
result.authenticated = true;
return Collections.singletonList(result);
}
public static boolean useDirectTls(final int port) {
return port == 443 || port == 5223;
}
public static List<Result> resolve(String domain) {
final List<Result> ipResults = fromIpAddress(domain);
if (ipResults.size() > 0) {

View File

@ -20,6 +20,9 @@ public class SocksSocketFactory {
proxyOs.write(new byte[]{0x05, 0x01, 0x00});
byte[] response = new byte[2];
proxyIs.read(response);
if (response[0] != 0x05 || response[1] != 0x00) {
throw new SocksConnectionException("Socks 5 handshake failed");
}
byte[] dest = destination.getBytes();
ByteBuffer request = ByteBuffer.allocate(7 + dest.length);
request.put(new byte[]{0x05, 0x01, 0x00, 0x03});
@ -30,10 +33,25 @@ public class SocksSocketFactory {
response = new byte[7 + dest.length];
proxyIs.read(response);
if (response[1] != 0x00) {
throw new SocksConnectionException();
if (response[1] == 0x04) {
throw new HostNotFoundException("Host unreachable");
}
if (response[1] == 0x05) {
throw new HostNotFoundException("Connection refused");
}
throw new SocksConnectionException("Unable to connect to destination "+(int) (response[1]));
}
}
public static boolean contains(byte needle, byte[] haystack) {
for(byte hay : haystack) {
if (hay == needle) {
return true;
}
}
return false;
}
public static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException {
Socket socket = new Socket();
try {
@ -49,11 +67,19 @@ public class SocksSocketFactory {
return createSocket(new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050), destination, port);
}
static class SocksConnectionException extends IOException {
private static class SocksConnectionException extends IOException {
SocksConnectionException(String message) {
super(message);
}
}
public static class SocksProxyNotFoundException extends IOException {
}
public static class HostNotFoundException extends SocksConnectionException {
HostNotFoundException(String message) {
super(message);
}
}
}

View File

@ -30,4 +30,6 @@ public final class Namespace {
public static final String PING = "urn:xmpp:ping";
public static final String PUSH = "urn:xmpp:push:0";
public static final String COMMANDS = "http://jabber.org/protocol/commands";
public static final String JINGLE_ENCRYPTED_TRANSPORT = "urn:xmpp:jingle:jet:0";
public static final String JINGLE_ENCRYPTED_TRANSPORT_OMEMO = "urn:xmpp:jingle:jet-omemo:0";
}

View File

@ -267,8 +267,18 @@ public class XmppConnection implements Runnable {
destination = account.getHostname();
this.verifiedHostname = destination;
}
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": connect to " + destination + " via Tor");
localSocket = SocksSocketFactory.createSocketOverTor(destination, account.getPort());
final int port = account.getPort();
final boolean directTls = Resolver.useDirectTls(port);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": connect to " + destination + " via Tor. directTls="+directTls);
localSocket = SocksSocketFactory.createSocketOverTor(destination, port);
if (directTls) {
localSocket = upgradeSocketToTls(localSocket);
features.encryptionEnabled = true;
}
try {
startXmpp(localSocket);
} catch (InterruptedException e) {
@ -310,29 +320,13 @@ public class XmppConnection implements Runnable {
+ ": using values from resolver "
+ result.getHostname().toString() + ":" + result.getPort() + " tls: " + features.encryptionEnabled);
if (!features.encryptionEnabled) {
localSocket = new Socket();
localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
} else {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
localSocket = tlsFactoryVerifier.factory.createSocket();
localSocket = new Socket();
localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
if (localSocket == null) {
throw new IOException("could not initialize ssl socket");
}
SSLSocketHelper.setSecurity((SSLSocket) localSocket);
SSLSocketHelper.setHostname((SSLSocket) localSocket, account.getServer());
SSLSocketHelper.setApplicationProtocol((SSLSocket) localSocket, "xmpp-client");
localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
if (!tlsFactoryVerifier.verifier.verify(account.getServer(), verifiedHostname, ((SSLSocket) localSocket).getSession())) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed");
FileBackend.close(localSocket);
throw new StateChangingException(Account.State.TLS_ERROR);
}
if (features.encryptionEnabled) {
localSocket = upgradeSocketToTls(localSocket);
}
localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000);
if (startXmpp(localSocket)) {
localSocket.setSoTimeout(0); //reset to 0; once the connection is established we dont want this
@ -363,6 +357,8 @@ public class XmppConnection implements Runnable {
this.changeStatus(e.state);
} catch (final UnknownHostException | ConnectException e) {
this.changeStatus(Account.State.SERVER_NOT_FOUND);
} catch (final SocksSocketFactory.HostNotFoundException e) {
this.changeStatus(Account.State.SERVER_NOT_FOUND);
} catch (final SocksSocketFactory.SocksProxyNotFoundException e) {
this.changeStatus(Account.State.TOR_NOT_AVAILABLE);
} catch (final IOException | XmlPullParserException e) {
@ -775,46 +771,41 @@ public class XmppConnection implements Runnable {
private void switchOverToTls() throws XmlPullParserException, IOException {
tagReader.readTag();
final Socket socket = this.socket;
final SSLSocket sslSocket = upgradeSocketToTls(socket);
tagReader.setInputStream(sslSocket.getInputStream());
tagWriter.setOutputStream(sslSocket.getOutputStream());
sendStartStream();
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS connection established");
features.encryptionEnabled = true;
final Tag tag = tagReader.readTag();
if (tag != null && tag.isStart("stream")) {
SSLSocketHelper.log(account, sslSocket);
processStream();
} else {
throw new StateChangingException(Account.State.STREAM_OPENING_ERROR);
}
sslSocket.close();
}
private SSLSocket upgradeSocketToTls(final Socket socket) throws IOException {
final TlsFactoryVerifier tlsFactoryVerifier;
try {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
final InetAddress address = socket == null ? null : socket.getInetAddress();
if (address == null) {
throw new IOException("could not setup ssl");
}
final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true);
if (sslSocket == null) {
throw new IOException("could not initialize ssl socket");
}
SSLSocketHelper.setSecurity(sslSocket);
SSLSocketHelper.setHostname(sslSocket, account.getServer());
SSLSocketHelper.setApplicationProtocol(sslSocket, "xmpp-client");
if (!tlsFactoryVerifier.verifier.verify(account.getServer(), this.verifiedHostname, sslSocket.getSession())) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed");
throw new StateChangingException(Account.State.TLS_ERROR);
}
tagReader.setInputStream(sslSocket.getInputStream());
tagWriter.setOutputStream(sslSocket.getOutputStream());
sendStartStream();
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS connection established");
features.encryptionEnabled = true;
final Tag tag = tagReader.readTag();
if (tag != null && tag.isStart("stream")) {
SSLSocketHelper.log(account, sslSocket);
processStream();
} else {
throw new StateChangingException(Account.State.STREAM_OPENING_ERROR);
}
sslSocket.close();
} catch (final NoSuchAlgorithmException | KeyManagementException e1) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed");
tlsFactoryVerifier = getTlsFactoryVerifier();
} catch (final NoSuchAlgorithmException | KeyManagementException e) {
throw new StateChangingException(Account.State.TLS_ERROR);
}
final InetAddress address = socket.getInetAddress();
final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true);
SSLSocketHelper.setSecurity(sslSocket);
SSLSocketHelper.setHostname(sslSocket, account.getServer());
SSLSocketHelper.setApplicationProtocol(sslSocket, "xmpp-client");
if (!tlsFactoryVerifier.verifier.verify(account.getServer(), this.verifiedHostname, sslSocket.getSession())) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed");
FileBackend.close(sslSocket);
throw new StateChangingException(Account.State.TLS_ERROR);
}
return sslSocket;
}
private void processStreamFeatures(final Tag currentTag) throws XmlPullParserException, IOException {

View File

@ -0,0 +1,64 @@
package eu.siacs.conversations.xmpp.jingle;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.UUID;
import rocks.xmpp.addr.Jid;
public class DirectConnectionUtils {
private static List<InetAddress> getLocalAddresses() {
final List<InetAddress> addresses = new ArrayList<>();
final Enumeration<NetworkInterface> interfaces;
try {
interfaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
return addresses;
}
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
final Enumeration<InetAddress> inetAddressEnumeration = networkInterface.getInetAddresses();
while (inetAddressEnumeration.hasMoreElements()) {
final InetAddress inetAddress = inetAddressEnumeration.nextElement();
if (inetAddress.isLoopbackAddress() || inetAddress.isLinkLocalAddress()) {
continue;
}
if (inetAddress instanceof Inet6Address) {
//let's get rid of scope
try {
addresses.add(Inet6Address.getByAddress(inetAddress.getAddress()));
} catch (UnknownHostException e) {
//ignored
}
} else {
addresses.add(inetAddress);
}
}
}
return addresses;
}
public static List<JingleCandidate> getLocalCandidates(Jid jid) {
SecureRandom random = new SecureRandom();
ArrayList<JingleCandidate> candidates = new ArrayList<>();
for (InetAddress inetAddress : getLocalAddresses()) {
final JingleCandidate candidate = new JingleCandidate(UUID.randomUUID().toString(), true);
candidate.setHost(inetAddress.getHostAddress());
candidate.setPort(random.nextInt(60000) + 1024);
candidate.setType(JingleCandidate.TYPE_DIRECT);
candidate.setJid(jid);
candidate.setPriority(8257536 + candidates.size());
candidates.add(candidate);
}
return candidates;
}
}

View File

@ -110,15 +110,12 @@ public class JingleCandidate {
}
public static JingleCandidate parse(Element candidate) {
JingleCandidate parsedCandidate = new JingleCandidate(
candidate.getAttribute("cid"), false);
JingleCandidate parsedCandidate = new JingleCandidate(candidate.getAttribute("cid"), false);
parsedCandidate.setHost(candidate.getAttribute("host"));
parsedCandidate.setJid(InvalidJid.getNullForInvalid(candidate.getAttributeAsJid("jid")));
parsedCandidate.setType(candidate.getAttribute("type"));
parsedCandidate.setPriority(Integer.parseInt(candidate
.getAttribute("priority")));
parsedCandidate
.setPort(Integer.parseInt(candidate.getAttribute("port")));
parsedCandidate.setPriority(Integer.parseInt(candidate.getAttribute("priority")));
parsedCandidate.setPort(Integer.parseInt(candidate.getAttribute("port")));
return parsedCandidate;
}
@ -127,7 +124,9 @@ public class JingleCandidate {
element.setAttribute("cid", this.getCid());
element.setAttribute("host", this.getHost());
element.setAttribute("port", Integer.toString(this.getPort()));
element.setAttribute("jid", this.getJid().toString());
if (jid != null) {
element.setAttribute("jid", jid.toEscapedString());
}
element.setAttribute("priority", Integer.toString(this.getPriority()));
if (this.getType() == TYPE_DIRECT) {
element.setAttribute("type", "direct");

View File

@ -43,6 +43,8 @@ import rocks.xmpp.addr.Jid;
public class JingleConnection implements Transferable {
private static final String JET_OMEMO_CIPHER = "urn:xmpp:ciphers:aes-128-gcm-nopadding";
private static final int JINGLE_STATUS_INITIATED = 0;
private static final int JINGLE_STATUS_ACCEPTED = 1;
private static final int JINGLE_STATUS_FINISHED = 4;
@ -72,6 +74,7 @@ public class JingleConnection implements Transferable {
private String contentName;
private String contentCreator;
private Transport initialTransport;
private boolean remoteSupportsOmemoJet;
private int mProgress = 0;
@ -171,7 +174,10 @@ public class JingleConnection implements Transferable {
@Override
public void failed() {
Log.d(Config.LOGTAG, "proxy activation failed");
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": proxy activation failed");
if (initiating()) {
sendFallbackToIbb();
}
}
};
@ -292,8 +298,10 @@ public class JingleConnection implements Transferable {
this.contentName = this.mJingleConnectionManager.nextRandomId();
this.message = message;
this.account = message.getConversation().getAccount();
upgradeNamespace();
this.initialTransport = getRemoteFeatures().contains(Namespace.JINGLE_TRANSPORTS_S5B) ? Transport.SOCKS : Transport.IBB;
final List<String> remoteFeatures = getRemoteFeatures();
upgradeNamespace(remoteFeatures);
this.initialTransport = remoteFeatures.contains(Namespace.JINGLE_TRANSPORTS_S5B) ? Transport.SOCKS : Transport.IBB;
this.remoteSupportsOmemoJet = remoteFeatures.contains(Namespace.JINGLE_ENCRYPTED_TRANSPORT_OMEMO);
this.message.setTransferable(this);
this.mStatus = Transferable.STATUS_UPLOADING;
this.initiator = this.account.getJid();
@ -302,10 +310,9 @@ public class JingleConnection implements Transferable {
this.transportId = this.mJingleConnectionManager.nextRandomId();
if (this.initialTransport == Transport.IBB) {
this.sendInitRequest();
} else if (this.candidates.size() > 0) {
this.sendInitRequest();
} else {
this.mJingleConnectionManager.getPrimaryCandidate(account, (success, candidate) -> {
gatherAndConnectDirectCandidates();
this.mJingleConnectionManager.getPrimaryCandidate(account, initiating(), (success, candidate) -> {
if (success) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this, candidate);
connections.put(candidate.getCid(), socksConnection);
@ -313,22 +320,20 @@ public class JingleConnection implements Transferable {
@Override
public void failed() {
Log.d(Config.LOGTAG,
"connection to our own primary candidete failed");
Log.d(Config.LOGTAG, "connection to our own proxy65 candidate failed");
sendInitRequest();
}
@Override
public void established() {
Log.d(Config.LOGTAG,
"successfully connected to our own primary candidate");
Log.d(Config.LOGTAG, "successfully connected to our own proxy65 candidate");
mergeCandidate(candidate);
sendInitRequest();
}
});
mergeCandidate(candidate);
} else {
Log.d(Config.LOGTAG, "no primary candidate of our own was found");
Log.d(Config.LOGTAG, "no proxy65 candidate of our own was found");
sendInitRequest();
}
});
@ -336,11 +341,28 @@ public class JingleConnection implements Transferable {
}
private void upgradeNamespace() {
List<String> features = getRemoteFeatures();
if (features.contains(Content.Version.FT_5.getNamespace())) {
private void gatherAndConnectDirectCandidates() {
final List<JingleCandidate> directCandidates;
if (Config.USE_DIRECT_JINGLE_CANDIDATES) {
if (account.isOnion() || mXmppConnectionService.useTorToConnect()) {
directCandidates = Collections.emptyList();
} else {
directCandidates = DirectConnectionUtils.getLocalCandidates(account.getJid());
}
} else {
directCandidates = Collections.emptyList();
}
for (JingleCandidate directCandidate : directCandidates) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this, directCandidate);
connections.put(directCandidate.getCid(), socksConnection);
candidates.add(directCandidate);
}
}
private void upgradeNamespace(List<String> remoteFeatures) {
if (remoteFeatures.contains(Content.Version.FT_5.getNamespace())) {
this.ftVersion = Content.Version.FT_5;
} else if (features.contains(Content.Version.FT_4.getNamespace())) {
} else if (remoteFeatures.contains(Content.Version.FT_4.getNamespace())) {
this.ftVersion = Content.Version.FT_4;
}
}
@ -408,8 +430,18 @@ public class JingleConnection implements Transferable {
}
this.fileOffer = content.getFileOffer(this.ftVersion);
if (fileOffer != null) {
boolean remoteIsUsingJet = false;
Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX);
if (encrypted == null) {
final Element security = content.findChild("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
if (security != null && AxolotlService.PEP_PREFIX.equals(security.getAttribute("type"))) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received jingle file offer with JET");
encrypted = security.findChild("encrypted", AxolotlService.PEP_PREFIX);
remoteIsUsingJet = true;
}
}
if (encrypted != null) {
this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().asBareJid());
}
@ -450,7 +482,10 @@ public class JingleConnection implements Transferable {
}
}
message.resetFileParams();
this.file.setExpectedSize(size);
//legacy OMEMO encrypted file transfers reported the file size after encryption
//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));
if (mJingleConnectionManager.hasStoragePermission()
&& size < this.mJingleConnectionManager.getAutoAcceptFileSize()
&& mXmppConnectionService.isDataSaverDisabled()) {
@ -499,8 +534,21 @@ public class JingleConnection implements Transferable {
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
this.file.setKey(mXmppAxolotlMessage.getInnerKey());
this.file.setIv(mXmppAxolotlMessage.getIV());
this.file.setExpectedSize(file.getSize() + 16);
content.setFileOffer(this.file, false, this.ftVersion).addChild(mXmppAxolotlMessage.toElement());
//legacy OMEMO encrypted file transfer reported file size of the encrypted file
//JET uses the file size of the plain text file. The difference is only 16 bytes (auth tag)
this.file.setExpectedSize(file.getSize() + (this.remoteSupportsOmemoJet ? 0 : 16));
final Element file = content.setFileOffer(this.file, false, this.ftVersion);
if (remoteSupportsOmemoJet) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": remote announced support for JET");
final Element security = new Element("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
security.setAttribute("name", this.contentName);
security.setAttribute("cipher", JET_OMEMO_CIPHER);
security.setAttribute("type", AxolotlService.PEP_PREFIX);
security.addChild(mXmppAxolotlMessage.toElement());
content.addChild(security);
} else {
file.addChild(mXmppAxolotlMessage.toElement());
}
} else {
this.file.setExpectedSize(file.getSize());
content.setFileOffer(this.file, false, this.ftVersion);
@ -564,7 +612,8 @@ public class JingleConnection implements Transferable {
}
private void sendAcceptSocks() {
this.mJingleConnectionManager.getPrimaryCandidate(this.account, (success, candidate) -> {
gatherAndConnectDirectCandidates();
this.mJingleConnectionManager.getPrimaryCandidate(this.account, initiating(), (success, candidate) -> {
final JinglePacket packet = bootstrapPacket("session-accept");
final Content content = new Content(contentCreator, contentName);
content.setFileOffer(fileOffer, ftVersion);
@ -576,7 +625,7 @@ public class JingleConnection implements Transferable {
@Override
public void failed() {
Log.d(Config.LOGTAG, "connection to our own primary candidate failed");
Log.d(Config.LOGTAG, "connection to our own proxy65 candidate failed");
content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content);
sendJinglePacket(packet);
@ -585,7 +634,7 @@ public class JingleConnection implements Transferable {
@Override
public void established() {
Log.d(Config.LOGTAG, "connected to primary candidate");
Log.d(Config.LOGTAG, "connected to proxy65 candidate");
mergeCandidate(candidate);
content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content);
@ -594,7 +643,7 @@ public class JingleConnection implements Transferable {
}
});
} else {
Log.d(Config.LOGTAG, "did not find a primary candidate for ourself");
Log.d(Config.LOGTAG, "did not find a proxy65 candidate for ourselves");
content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content);
sendJinglePacket(packet);
@ -635,7 +684,7 @@ public class JingleConnection implements Transferable {
private boolean receiveAccept(JinglePacket packet) {
if (this.mJingleStatus != JINGLE_STATUS_INITIATED) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": received out of order session-accept");
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received out of order session-accept");
return false;
}
this.mJingleStatus = JINGLE_STATUS_ACCEPTED;
@ -654,7 +703,7 @@ public class JingleConnection implements Transferable {
this.ibbBlockSize = bs;
}
} catch (Exception e) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": unable to parse block size in session-accept");
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to parse block size in session-accept");
}
}
this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
@ -689,7 +738,7 @@ public class JingleConnection implements Transferable {
onProxyActivated.failed();
return true;
} else if (content.socks5transport().hasChild("candidate-error")) {
Log.d(Config.LOGTAG, "received candidate error");
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received candidate error");
this.receivedCandidate = true;
if (mJingleStatus == JINGLE_STATUS_ACCEPTED && this.sentCandidate) {
this.connect();
@ -727,12 +776,14 @@ public class JingleConnection implements Transferable {
final JingleSocks5Transport connection = chooseConnection();
this.transport = connection;
if (connection == null) {
Log.d(Config.LOGTAG, "could not find suitable candidate");
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not find suitable candidate");
this.disconnectSocks5Connections();
if (initiating()) {
this.sendFallbackToIbb();
}
} else {
final JingleCandidate candidate = connection.getCandidate();
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": elected candidate " + candidate.getHost() + ":" + candidate.getPort());
this.mJingleStatus = JINGLE_STATUS_TRANSMITTING;
if (connection.needsActivation()) {
if (connection.getCandidate().isOurs()) {
@ -754,10 +805,12 @@ public class JingleConnection implements Transferable {
.setContent(this.getCounterPart().toString());
mXmppConnectionService.sendIqPacket(account, activation, (account, response) -> {
if (response.getType() != IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + response.toString());
sendProxyError();
onProxyActivated.failed();
} else {
onProxyActivated.success();
sendProxyActivated(connection.getCandidate().getCid());
onProxyActivated.success();
}
});
} else {
@ -841,7 +894,7 @@ public class JingleConnection implements Transferable {
private boolean receiveFallbackToIbb(JinglePacket packet) {
Log.d(Config.LOGTAG, "receiving fallback to ibb");
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": receiving fallback to ibb");
final String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size");
if (receivedBlockSize != null) {
try {
@ -850,7 +903,7 @@ public class JingleConnection implements Transferable {
this.ibbBlockSize = bs;
}
} catch (NumberFormatException e) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": unable to parse block size in transport-replace");
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to parse block size in transport-replace");
}
}
this.transportId = packet.getJingleContent().getTransportId();
@ -889,7 +942,7 @@ public class JingleConnection implements Transferable {
this.ibbBlockSize = bs;
}
} catch (NumberFormatException e) {
Log.d(Config.LOGTAG, account.getJid().asBareJid()+": unable to parse block size in transport-accept");
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to parse block size in transport-accept");
}
}
this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
@ -1029,14 +1082,23 @@ public class JingleConnection implements Transferable {
}
private void sendProxyActivated(String cid) {
JinglePacket packet = bootstrapPacket("transport-info");
Content content = new Content(this.contentCreator, this.contentName);
final JinglePacket packet = bootstrapPacket("transport-info");
final Content content = new Content(this.contentCreator, this.contentName);
content.setTransportId(this.transportId);
content.socks5transport().addChild("activated").setAttribute("cid", cid);
packet.setContent(content);
this.sendJinglePacket(packet);
}
private void sendProxyError() {
final JinglePacket packet = bootstrapPacket("transport-info");
final Content content = new Content(this.contentCreator, this.contentName);
content.setTransportId(this.transportId);
content.socks5transport().addChild("proxy-error");
packet.setContent(content);
this.sendJinglePacket(packet);
}
private void sendCandidateUsed(final String cid) {
JinglePacket packet = bootstrapPacket("transport-info");
Content content = new Content(this.contentCreator, this.contentName);
@ -1051,7 +1113,7 @@ public class JingleConnection implements Transferable {
}
private void sendCandidateError() {
Log.d(Config.LOGTAG, "sending candidate error");
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": sending candidate error");
JinglePacket packet = bootstrapPacket("transport-info");
Content content = new Content(this.contentCreator, this.contentName);
content.setTransportId(this.transportId);
@ -1087,6 +1149,7 @@ public class JingleConnection implements Transferable {
}
private void mergeCandidates(List<JingleCandidate> candidates) {
Collections.sort(candidates, (a, b) -> Integer.compare(b.getPriority(), a.getPriority()));
for (JingleCandidate c : candidates) {
mergeCandidate(c);
}

View File

@ -81,8 +81,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
this.connections.remove(connection);
}
public void getPrimaryCandidate(Account account,
final OnPrimaryCandidateFound listener) {
public void getPrimaryCandidate(final Account account, final boolean initiator, final OnPrimaryCandidateFound listener) {
if (Config.DISABLE_PROXY_LOOKUP) {
listener.onPrimaryCandidateFound(false, null);
return;
@ -107,7 +106,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
candidate.setPort(Integer.parseInt(port));
candidate.setType(JingleCandidate.TYPE_PROXY);
candidate.setJid(proxy);
candidate.setPriority(655360 + 65535);
candidate.setPriority(655360 + (initiator ? 10 : 20));
primaryCandidates.put(account.getJid().asBareJid(),candidate);
listener.onPrimaryCandidateFound(true,candidate);
} catch (final NumberFormatException e) {

View File

@ -6,13 +6,17 @@ import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.services.AbstractConnectionManager;
@ -22,177 +26,268 @@ import eu.siacs.conversations.utils.WakeLockHelper;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
public class JingleSocks5Transport extends JingleTransport {
private JingleCandidate candidate;
private JingleConnection connection;
private String destination;
private OutputStream outputStream;
private InputStream inputStream;
private boolean isEstablished = false;
private boolean activated = false;
private Socket socket;
private final JingleCandidate candidate;
private final JingleConnection connection;
private final String destination;
private OutputStream outputStream;
private InputStream inputStream;
private boolean isEstablished = false;
private boolean activated = false;
private ServerSocket serverSocket;
private Socket socket;
JingleSocks5Transport(JingleConnection jingleConnection, JingleCandidate candidate) {
this.candidate = candidate;
this.connection = jingleConnection;
try {
MessageDigest mDigest = MessageDigest.getInstance("SHA-1");
StringBuilder destBuilder = new StringBuilder();
if (jingleConnection.getFtVersion() == Content.Version.FT_3) {
Log.d(Config.LOGTAG, this.connection.getAccount().getJid().asBareJid() + ": using session Id instead of transport Id for proxy destination");
destBuilder.append(jingleConnection.getSessionId());
} else {
destBuilder.append(jingleConnection.getTransportId());
}
if (candidate.isOurs()) {
destBuilder.append(jingleConnection.getAccount().getJid());
destBuilder.append(jingleConnection.getCounterPart());
} else {
destBuilder.append(jingleConnection.getCounterPart());
destBuilder.append(jingleConnection.getAccount().getJid());
}
mDigest.reset();
this.destination = CryptoHelper.bytesToHex(mDigest
.digest(destBuilder.toString().getBytes()));
} catch (NoSuchAlgorithmException e) {
JingleSocks5Transport(JingleConnection jingleConnection, JingleCandidate candidate) {
final MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
this.candidate = candidate;
this.connection = jingleConnection;
final StringBuilder destBuilder = new StringBuilder();
if (jingleConnection.getFtVersion() == Content.Version.FT_3) {
Log.d(Config.LOGTAG, this.connection.getAccount().getJid().asBareJid() + ": using session Id instead of transport Id for proxy destination");
destBuilder.append(jingleConnection.getSessionId());
} else {
destBuilder.append(jingleConnection.getTransportId());
}
if (candidate.isOurs()) {
destBuilder.append(jingleConnection.getAccount().getJid());
destBuilder.append(jingleConnection.getCounterPart());
} else {
destBuilder.append(jingleConnection.getCounterPart());
destBuilder.append(jingleConnection.getAccount().getJid());
}
messageDigest.reset();
this.destination = CryptoHelper.bytesToHex(messageDigest.digest(destBuilder.toString().getBytes()));
if (candidate.isOurs() && candidate.getType() == JingleCandidate.TYPE_DIRECT) {
createServerSocket();
}
}
}
}
private void createServerSocket() {
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(InetAddress.getByName(candidate.getHost()), candidate.getPort()));
new Thread(() -> {
try {
final Socket socket = serverSocket.accept();
new Thread(() -> {
try {
acceptIncomingSocketConnection(socket);
} catch (IOException e) {
Log.d(Config.LOGTAG, "unable to read from socket", e);
public void connect(final OnTransportConnected callback) {
new Thread(() -> {
try {
final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect();
if (useTor) {
socket = SocksSocketFactory.createSocketOverTor(candidate.getHost(), candidate.getPort());
} else {
socket = new Socket();
SocketAddress address = new InetSocketAddress(candidate.getHost(), candidate.getPort());
socket.connect(address, Config.SOCKET_TIMEOUT * 1000);
}
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
SocksSocketFactory.createSocksConnection(socket, destination, 0);
isEstablished = true;
callback.established();
} catch (IOException e) {
callback.failed();
}
}).start();
}
}).start();
} catch (IOException e) {
if (!serverSocket.isClosed()) {
Log.d(Config.LOGTAG, "unable to accept socket", e);
}
}
}).start();
} catch (IOException e) {
Log.d(Config.LOGTAG, "unable to bind server socket ", e);
}
}
}
private void acceptIncomingSocketConnection(Socket socket) throws IOException {
Log.d(Config.LOGTAG, "accepted connection from " + socket.getInetAddress().getHostAddress());
final byte[] authBegin = new byte[2];
final InputStream inputStream = socket.getInputStream();
final OutputStream outputStream = socket.getOutputStream();
inputStream.read(authBegin);
if (authBegin[0] != 0x5) {
socket.close();
}
final short methodCount = authBegin[1];
final byte[] methods = new byte[methodCount];
inputStream.read(methods);
if (SocksSocketFactory.contains((byte) 0x00, methods)) {
outputStream.write(new byte[]{0x05, 0x00});
} else {
outputStream.write(new byte[]{0x05, (byte) 0xff});
}
byte[] connectCommand = new byte[4];
inputStream.read(connectCommand);
if (connectCommand[0] == 0x05 && connectCommand[1] == 0x01 && connectCommand[3] == 0x03) {
int destinationCount = inputStream.read();
final byte[] destination = new byte[destinationCount];
inputStream.read(destination);
final int port = inputStream.read();
final String receivedDestination = new String(destination);
final ByteBuffer response = ByteBuffer.allocate(7 + destination.length);
final byte[] responseHeader;
final boolean success;
if (receivedDestination.equals(this.destination) && this.socket == null) {
responseHeader = new byte[]{0x05, 0x00, 0x00, 0x03};
success = true;
} else {
Log.d(Config.LOGTAG,connection.getAccount().getJid().asBareJid()+": destination mismatch. received "+receivedDestination+" (expected "+this.destination+")");
responseHeader = new byte[]{0x05, 0x04, 0x00, 0x03};
success = false;
}
response.put(responseHeader);
response.put((byte) destination.length);
response.put(destination);
response.putShort((short) port);
outputStream.write(response.array());
outputStream.flush();
if (success) {
Log.d(Config.LOGTAG,connection.getAccount().getJid().asBareJid()+": successfully processed connection to candidate "+candidate.getHost()+":"+candidate.getPort());
this.socket = socket;
this.inputStream = inputStream;
this.outputStream = outputStream;
this.isEstablished = true;
FileBackend.close(serverSocket);
} else {
this.socket.close();
}
} else {
socket.close();
}
}
public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
new Thread(() -> {
InputStream fileInputStream = null;
final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_" + connection.getSessionId());
try {
wakeLock.acquire();
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
fileInputStream = connection.getFileInputStream();
if (fileInputStream == null) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": could not create input stream");
callback.onFileTransferAborted();
return;
}
final InputStream innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream);
long size = file.getExpectedSize();
long transmitted = 0;
int count;
byte[] buffer = new byte[8192];
while ((count = innerInputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, count);
digest.update(buffer, 0, count);
transmitted += count;
connection.updateProgress((int) ((((double) transmitted) / size) * 100));
}
outputStream.flush();
file.setSha1Sum(digest.digest());
if (callback != null) {
callback.onFileTransmitted(file);
}
} catch (Exception e) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": " + e.getMessage());
callback.onFileTransferAborted();
} finally {
FileBackend.close(fileInputStream);
WakeLockHelper.release(wakeLock);
}
}).start();
public void connect(final OnTransportConnected callback) {
new Thread(() -> {
try {
final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect();
if (useTor) {
socket = SocksSocketFactory.createSocketOverTor(candidate.getHost(), candidate.getPort());
} else {
socket = new Socket();
SocketAddress address = new InetSocketAddress(candidate.getHost(), candidate.getPort());
socket.connect(address, 5000);
}
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
socket.setSoTimeout(5000);
SocksSocketFactory.createSocksConnection(socket, destination, 0);
socket.setSoTimeout(0);
isEstablished = true;
callback.established();
} catch (IOException e) {
callback.failed();
}
}).start();
}
}
public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
new Thread(() -> {
OutputStream fileOutputStream = null;
final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_" + connection.getSessionId());
try {
wakeLock.acquire();
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
//inputStream.skip(45);
socket.setSoTimeout(30000);
fileOutputStream = connection.getFileOutputStream();
if (fileOutputStream == null) {
callback.onFileTransferAborted();
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": could not create output stream");
return;
}
double size = file.getExpectedSize();
long remainingSize = file.getExpectedSize();
byte[] buffer = new byte[8192];
int count;
while (remainingSize > 0) {
count = inputStream.read(buffer);
if (count == -1) {
callback.onFileTransferAborted();
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": file ended prematurely with " + remainingSize + " bytes remaining");
return;
} else {
fileOutputStream.write(buffer, 0, count);
digest.update(buffer, 0, count);
remainingSize -= count;
}
connection.updateProgress((int) (((size - remainingSize) / size) * 100));
}
fileOutputStream.flush();
fileOutputStream.close();
file.setSha1Sum(digest.digest());
callback.onFileTransmitted(file);
} catch (Exception e) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": " + e.getMessage());
callback.onFileTransferAborted();
} finally {
WakeLockHelper.release(wakeLock);
FileBackend.close(fileOutputStream);
FileBackend.close(inputStream);
}
}).start();
}
public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
new Thread(() -> {
InputStream fileInputStream = null;
final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_" + connection.getSessionId());
long transmitted = 0;
try {
wakeLock.acquire();
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
fileInputStream = connection.getFileInputStream();
if (fileInputStream == null) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": could not create input stream");
callback.onFileTransferAborted();
return;
}
final InputStream innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream);
long size = file.getExpectedSize();
int count;
byte[] buffer = new byte[8192];
while ((count = innerInputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, count);
digest.update(buffer, 0, count);
transmitted += count;
connection.updateProgress((int) ((((double) transmitted) / size) * 100));
}
outputStream.flush();
file.setSha1Sum(digest.digest());
if (callback != null) {
callback.onFileTransmitted(file);
}
} catch (Exception e) {
final Account account = connection.getAccount();
Log.d(Config.LOGTAG, account.getJid().asBareJid()+": failed sending file after "+transmitted+"/"+file.getExpectedSize()+" ("+ socket.getInetAddress()+":"+socket.getPort()+")", e);
callback.onFileTransferAborted();
} finally {
FileBackend.close(fileInputStream);
WakeLockHelper.release(wakeLock);
}
}).start();
public boolean isProxy() {
return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
}
}
public boolean needsActivation() {
return (this.isProxy() && !this.activated);
}
public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
new Thread(() -> {
OutputStream fileOutputStream = null;
final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_" + connection.getSessionId());
try {
wakeLock.acquire();
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
//inputStream.skip(45);
socket.setSoTimeout(30000);
fileOutputStream = connection.getFileOutputStream();
if (fileOutputStream == null) {
callback.onFileTransferAborted();
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": could not create output stream");
return;
}
double size = file.getExpectedSize();
long remainingSize = file.getExpectedSize();
byte[] buffer = new byte[8192];
int count;
while (remainingSize > 0) {
count = inputStream.read(buffer);
if (count == -1) {
callback.onFileTransferAborted();
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": file ended prematurely with " + remainingSize + " bytes remaining");
return;
} else {
fileOutputStream.write(buffer, 0, count);
digest.update(buffer, 0, count);
remainingSize -= count;
}
connection.updateProgress((int) (((size - remainingSize) / size) * 100));
}
fileOutputStream.flush();
fileOutputStream.close();
file.setSha1Sum(digest.digest());
callback.onFileTransmitted(file);
} catch (Exception e) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": " + e.getMessage());
callback.onFileTransferAborted();
} finally {
WakeLockHelper.release(wakeLock);
FileBackend.close(fileOutputStream);
FileBackend.close(inputStream);
}
}).start();
}
public void disconnect() {
FileBackend.close(inputStream);
FileBackend.close(outputStream);
FileBackend.close(socket);
}
public boolean isProxy() {
return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
}
public boolean isEstablished() {
return this.isEstablished;
}
public boolean needsActivation() {
return (this.isProxy() && !this.activated);
}
public JingleCandidate getCandidate() {
return this.candidate;
}
public void disconnect() {
FileBackend.close(inputStream);
FileBackend.close(outputStream);
FileBackend.close(socket);
FileBackend.close(serverSocket);
}
public void setActivated(boolean activated) {
this.activated = activated;
}
public boolean isEstablished() {
return this.isEstablished;
}
public JingleCandidate getCandidate() {
return this.candidate;
}
public void setActivated(boolean activated) {
this.activated = activated;
}
}

View File

@ -27,7 +27,6 @@
<string name="just_now">الآن</string>
<string name="minute_ago">منذ 1 دقيقة</string>
<string name="minutes_ago">دقائق %d منذ</string>
<string name="unread_conversations">محادثات غير مقروءة</string>
<string name="sending">ارسال</string>
<string name="message_decrypting">حل شيفرة الرسالة. الرجاء الإنتظار ...</string>
<string name="pgp_message">رسالة مشفرة عبر OpenPGP</string>

View File

@ -28,7 +28,6 @@
<string name="just_now">току-що</string>
<string name="minute_ago">преди 1 минута</string>
<string name="minutes_ago">преди %d минути</string>
<string name="unread_conversations">непрочетени разговори</string>
<string name="sending">изпращане…</string>
<string name="message_decrypting">Дешифроване на съобщението. Моля, изчакайте…</string>
<string name="pgp_message">Съобщение, шифр. чрез OpenPGP</string>

View File

@ -27,7 +27,6 @@
<string name="just_now">Ara</string>
<string name="minute_ago">fa 1 min</string>
<string name="minutes_ago">fa %d mins</string>
<string name="unread_conversations">Converses sense llegir o no llegides</string>
<string name="sending">enviant…</string>
<string name="message_decrypting">Desxifrant el missatge. Espereu…</string>
<string name="pgp_message">Missatge xifrat amb OpenPGP</string>

View File

@ -21,7 +21,6 @@
<string name="just_now">právě teď</string>
<string name="minute_ago">před minutou</string>
<string name="minutes_ago">před %d minutami</string>
<string name="unread_conversations">nepřečtené konverzace</string>
<string name="sending">odesílám…</string>
<string name="message_decrypting">Dešifrování zprávy. Chvíli strpení...</string>
<string name="pgp_message">OpenPGP šifrovaná zpráva</string>

View File

@ -30,7 +30,7 @@
<string name="just_now">gerade</string>
<string name="minute_ago">vor einer Minute</string>
<string name="minutes_ago">vor %d Minuten</string>
<string name="unread_conversations">ungelesene Unterhaltungen</string>
<string name="x_unread_conversations">%d ungelesene Unterhaltungen</string>
<string name="sending">senden…</string>
<string name="message_decrypting">Nachricht wird entschlüsselt. Bitte warten …</string>
<string name="pgp_message">OpenPGP-verschlüsselte Nachricht</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">μόλις τώρα</string>
<string name="minute_ago">πριν από 1 λεπτό</string>
<string name="minutes_ago">πριν από %d λεπτά</string>
<string name="unread_conversations">μη αναγνωσμένες Συζητήσεις</string>
<string name="sending">αποστολή...</string>
<string name="message_decrypting">Αποκρυπτογράφηση μηνύματος. Παρακαλώ περιμένετε...</string>
<string name="pgp_message">OpenPGP κρυπτογραφημένο μήνυμα</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">ahora</string>
<string name="minute_ago">hace 1 min</string>
<string name="minutes_ago">hace %d min</string>
<string name="unread_conversations">conversaciones por leer</string>
<string name="sending">enviando…</string>
<string name="message_decrypting">Descifrando mensaje. Por favor, espera...</string>
<string name="pgp_message">Mensaje cifrado con OpenPGP</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">orain</string>
<string name="minute_ago">min 1 lehenago</string>
<string name="minutes_ago">%d min lehenago</string>
<string name="unread_conversations">irakurri gabeko elkarrizketak</string>
<string name="sending">bidaltzen…</string>
<string name="message_decrypting">Mezua desenkriptatzen. Mesedez itxaron…</string>
<string name="pgp_message">OpenPGPz enkriptatutako mezua</string>

View File

@ -25,7 +25,6 @@
<string name="just_now">هم اکنون</string>
<string name="minute_ago">1 دقیقه قبل</string>
<string name="minutes_ago">%d دقیقه قبل</string>
<string name="unread_conversations">گفتگو های خوانده نشده</string>
<string name="sending">در حال ارسال...</string>
<string name="message_decrypting">در حال رمزگشایی پیام. لطفا صبور باشید...</string>
<string name="pgp_message">پیام رمز شده به وسیله OpenPGP</string>

View File

@ -28,7 +28,6 @@
<string name="just_now">À l\'instant</string>
<string name="minute_ago">Il y a 1 minute</string>
<string name="minutes_ago">Il y a %d minutes</string>
<string name="unread_conversations">Conversations non lues</string>
<string name="sending">Envoi…</string>
<string name="message_decrypting">Déchiffrement du message. Veuillez patienter...</string>
<string name="pgp_message">Message chiffré avec OpenPGP</string>

View File

@ -30,7 +30,7 @@
<string name="just_now">agora</string>
<string name="minute_ago">Hai 1 min</string>
<string name="minutes_ago">hai %d minutos</string>
<string name="unread_conversations">conversas sen ler</string>
<string name="x_unread_conversations">%d conversas non lidas</string>
<string name="sending">enviando…</string>
<string name="message_decrypting">Descifrando a mensaxe. Por favor agarde...</string>
<string name="pgp_message">Mensaxe cifrado con OpenPGP</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">Éppen most</string>
<string name="minute_ago">1 perce</string>
<string name="minutes_ago">%d perce</string>
<string name="unread_conversations">olvasatlan beszélgetés</string>
<string name="sending">küldés...</string>
<string name="message_decrypting">Üzenet dekódolása. Kérem várjon...</string>
<string name="pgp_message">OpenPGP kódolású üzenet</string>
@ -871,4 +870,5 @@
<string name="not_a_backup_file">A kiválasztott fájl nem a Conversations biztonsági mentése</string>
<string name="account_already_setup">Ez a fiók már be lett állítva</string>
<string name="please_enter_password">Kérem, adja meg a fiókhoz tartozó jelszót</string>
<string name="unable_to_perform_this_action">Nem sikerült ezt a cselekvést elvégezni</string>
</resources>

View File

@ -21,7 +21,6 @@
<string name="just_now">sekarang</string>
<string name="minute_ago">1 min lalu</string>
<string name="minutes_ago">%d min lalu</string>
<string name="unread_conversations">Percakapan belum dibaca</string>
<string name="sending">mengirim...</string>
<string name="message_decrypting">Mendekripsi pesan. Mohon tunggu…</string>
<string name="pgp_message">Pesan terenkripsi OpenPGP</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">adesso</string>
<string name="minute_ago">1 min fa</string>
<string name="minutes_ago">%d min fa</string>
<string name="unread_conversations">Conversazioni non lette</string>
<string name="sending">invio…</string>
<string name="message_decrypting">Decifrazione messaggio. Attendere prego...</string>
<string name="pgp_message">Messaggio cifrato con OpenPGP</string>

View File

@ -20,7 +20,6 @@
<string name="just_now">ממש עכשיו</string>
<string name="minute_ago">לפני דקה</string>
<string name="minutes_ago">לפני %d דקות</string>
<string name="unread_conversations">שיחות שלא נקראו</string>
<string name="sending">שולח...</string>
<string name="message_decrypting">כעת מפענח צופן הודעה. אנא המתן…</string>
<string name="pgp_message">הודעה מוצפנת OpenPGP</string>

View File

@ -28,7 +28,6 @@
<string name="just_now">ちょうど今</string>
<string name="minute_ago">1 分前</string>
<string name="minutes_ago">%d 分前</string>
<string name="unread_conversations">未読の会話</string>
<string name="sending">送信中…</string>
<string name="message_decrypting">メッセージを復号化しています。しばらくお待ちください…</string>
<string name="pgp_message">OpenPGP 暗号化メッセージ</string>

View File

@ -21,7 +21,6 @@
<string name="just_now">방금 </string>
<string name="minute_ago">1분 전 </string>
<string name="minutes_ago">%d 분 전 </string>
<string name="unread_conversations">읽지 않은 대화 </string>
<string name="sending">보내는중... </string>
<string name="message_decrypting">메세지 복호화중입니다. 기다리세요...</string>
<string name="pgp_message">OpenPGP로 암호화된 메세지</string>

View File

@ -24,7 +24,6 @@
<string name="just_now">akkurat nå</string>
<string name="minute_ago">1 minutt siden</string>
<string name="minutes_ago">%d minutter siden</string>
<string name="unread_conversations">uleste samtaler</string>
<string name="sending">sender...</string>
<string name="message_decrypting">Dekrypterer melding mens du venter.</string>
<string name="pgp_message">OpenPGP-kryptert melding</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">zojuist</string>
<string name="minute_ago">1 min. geleden</string>
<string name="minutes_ago">%d min. geleden</string>
<string name="unread_conversations">ongelezen gesprekken</string>
<string name="sending">versturen…</string>
<string name="message_decrypting">Bericht aan het ontsleutelen. Even geduld…</string>
<string name="pgp_message">OpenPGP-versleuteld bericht</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">przed chwilą</string>
<string name="minute_ago">minutę temu</string>
<string name="minutes_ago">%d minut temu</string>
<string name="unread_conversations">nieprzeczytanych konwersacji</string>
<string name="sending">wysyłanie...</string>
<string name="message_decrypting">Odszyfrowywanie wiadomości. To zajmie tylko chwilę...</string>
<string name="pgp_message">Wiadomość zaszyfrowana OpenPGP</string>

View File

@ -17,6 +17,8 @@
<string name="action_unblock_contact">Desbloquear contato</string>
<string name="action_block_domain">Bloquear domínio</string>
<string name="action_unblock_domain">Desbloquear domínio</string>
<string name="action_block_participant">Bloquear participante</string>
<string name="action_unblock_participant">Desbloquear participante</string>
<string name="title_activity_manage_accounts">Gerenciar contas</string>
<string name="title_activity_settings">Configurações</string>
<string name="title_activity_sharewith">Compartilhar com a conversa</string>
@ -28,7 +30,7 @@
<string name="just_now">agora</string>
<string name="minute_ago">1 minuto atrás</string>
<string name="minutes_ago">%d minutos atrás</string>
<string name="unread_conversations">conversas não lidas</string>
<string name="x_unread_conversations">%d conversas não lidas</string>
<string name="sending">enviando...</string>
<string name="message_decrypting">Descriptografando a mensagem. Por favor, aguarde...</string>
<string name="pgp_message">Mensagem criptografada via OpenPGP</string>
@ -861,4 +863,12 @@
<string name="this_looks_like_a_domain">Isso se parece com um endereço de domínio</string>
<string name="add_anway">Adicionar mesmo assim</string>
<string name="this_looks_like_channel">Isso se parece com um endereço de canal</string>
<string name="share_backup_files">Compartilhar arquivos de backup</string>
<string name="conversations_backup">Backup do Conversations</string>
<string name="event">Evento</string>
<string name="open_backup">Abrir backup</string>
<string name="not_a_backup_file">O arquivo que você selecionou não é um backup do Conversations</string>
<string name="account_already_setup">Esta conta já foi configurada</string>
<string name="please_enter_password">Por favor, digite a senha para esta conta</string>
<string name="unable_to_perform_this_action">Não foi possível executar essa ação</string>
</resources>

View File

@ -23,7 +23,6 @@
<string name="just_now">agora há pouco</string>
<string name="minute_ago">1 minuto atrás</string>
<string name="minutes_ago">%d minutos atrás</string>
<string name="unread_conversations">Conversas não lidas</string>
<string name="sending">enviando...</string>
<string name="message_decrypting">Decifrando a mensagem. Por favor aguarde...</string>
<string name="pgp_message">Mensagem cifrada OpenPGP</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">în acest moment</string>
<string name="minute_ago">acum un minut</string>
<string name="minutes_ago">acum %d minute</string>
<string name="unread_conversations">conversații necitite</string>
<string name="sending">trimitere...</string>
<string name="message_decrypting">Decriptez mesaj. Te rog așteaptă...</string>
<string name="pgp_message">Mesaj criptat cu OpenPGP</string>

View File

@ -27,7 +27,6 @@
<string name="just_now">только что</string>
<string name="minute_ago">1 минуту назад</string>
<string name="minutes_ago">%d мин. назад</string>
<string name="unread_conversations">сообщен. не прочитано</string>
<string name="sending">отправка…</string>
<string name="message_decrypting">Расшифровка сообщения. Подождите…</string>
<string name="pgp_message">OpenPGP зашифр. сообщение</string>

View File

@ -20,7 +20,6 @@
<string name="just_now">práve teraz</string>
<string name="minute_ago">pred 1 minútou</string>
<string name="minutes_ago">pred %d minútami</string>
<string name="unread_conversations">neprečítané konverzácie</string>
<string name="sending">posielam...</string>
<string name="nick_in_use">Prezývka už existuje</string>
<string name="admin">Administrátor</string>

View File

@ -25,7 +25,6 @@
<string name="just_now">управо сад</string>
<string name="minute_ago">пре минут</string>
<string name="minutes_ago">пре %d минута</string>
<string name="unread_conversations">непрочитане преписке</string>
<string name="sending">шаљем…</string>
<string name="message_decrypting">Дешифрујем поруку, сачекајте…</string>
<string name="pgp_message">ОпенПГП шифрована порука</string>

View File

@ -21,7 +21,6 @@
<string name="just_now">just nu</string>
<string name="minute_ago">1 min sedan</string>
<string name="minutes_ago">%d min sedan</string>
<string name="unread_conversations">olästa konversationer</string>
<string name="sending">skickar…</string>
<string name="message_decrypting">Avkrypterar meddelande. Vänta…</string>
<string name="pgp_message">OpenPGP-krypterat meddelande</string>

View File

@ -21,7 +21,6 @@
<string name="just_now">şimdi</string>
<string name="minute_ago">1 dakika önce</string>
<string name="minutes_ago">%d dakika önc</string>
<string name="unread_conversations">okunmamış konuşmalar</string>
<string name="sending">gönderiyor…</string>
<string name="message_decrypting">İleti deşifre ediliyor. Lütfen bekleyin…</string>
<string name="pgp_message">OpenPGP şifreli ileti</string>

View File

@ -28,7 +28,6 @@
<string name="just_now">щойно</string>
<string name="minute_ago">1 хвилину тому</string>
<string name="minutes_ago">%d хвилин тому</string>
<string name="unread_conversations">не переглянуті розмови </string>
<string name="sending">відправляю…</string>
<string name="message_decrypting">Розшифровую повідомлення. Зачекайте, будь ласка…</string>
<string name="pgp_message">Повідомлення, зашифроване OpenPGP</string>

View File

@ -21,7 +21,6 @@
<string name="just_now">mới đây</string>
<string name="minute_ago">1 phút trước</string>
<string name="minutes_ago">%d phút trước</string>
<string name="unread_conversations">Các hội thoại chưa đọc</string>
<string name="sending">đang gửi...</string>
<string name="message_decrypting">Đang giải mã tin nhắn. Xin chờ...</string>
<string name="pgp_message">Tin nhắn mã hoá bằng OpenPGP</string>

View File

@ -28,7 +28,6 @@
<string name="just_now">刚刚</string>
<string name="minute_ago">1分钟前</string>
<string name="minutes_ago">%d分钟前</string>
<string name="unread_conversations">未读会话</string>
<string name="sending">正在发送…</string>
<string name="message_decrypting">解密信息中. 请稍候…</string>
<string name="pgp_message">OpenPGP 加密的信息</string>

View File

@ -21,7 +21,6 @@
<string name="just_now">剛剛</string>
<string name="minute_ago">1 分鐘前</string>
<string name="minutes_ago">%d分鐘前</string>
<string name="unread_conversations">未讀會話</string>
<string name="sending">正在發送…</string>
<string name="message_decrypting">訊息解密中,請稍候…</string>
<string name="pgp_message">OpenPGP 加密的信息</string>

View File

@ -30,7 +30,7 @@
<string name="just_now">just now</string>
<string name="minute_ago">1 min ago</string>
<string name="minutes_ago">%d mins ago</string>
<string name="unread_conversations">unread Conversations</string>
<string name="x_unread_conversations">%d unread conversations</string>
<string name="sending">sending…</string>
<string name="message_decrypting">Decrypting message. Please wait…</string>
<string name="pgp_message">OpenPGP encrypted message</string>

View File

@ -19,4 +19,8 @@
<string name="no_microphone_permission">O Quicksy necessita de acesso ao microfone</string>
<string name="foreground_service_channel_description">Essa categoria de notificação é utilizada para exibir uma notificação permanente indicando que o Quicksy está em execução.</string>
<string name="set_profile_picture">Imagem de perfil do Quicksy</string>
</resources>
<string name="not_available_in_your_country">Quicksy agora está disponível no seu país.</string>
<string name="unable_to_verify_server_identity">Não foi possível verificar a identidade do servidor.</string>
<string name="unknown_security_error">Erro de segurança desconhecido.</string>
<string name="timeout_while_connecting_to_server">Tempo esgotado ao tentar conectar ao servidor.</string>
</resources>