diff options
Diffstat (limited to 'src/eu')
10 files changed, 151 insertions, 54 deletions
diff --git a/src/eu/siacs/conversations/entities/Conversation.java b/src/eu/siacs/conversations/entities/Conversation.java index c984933f..600b9d38 100644 --- a/src/eu/siacs/conversations/entities/Conversation.java +++ b/src/eu/siacs/conversations/entities/Conversation.java @@ -1,8 +1,8 @@ package eu.siacs.conversations.entities; import java.security.interfaces.DSAPublicKey; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import org.json.JSONException; import org.json.JSONObject; @@ -57,7 +57,7 @@ public class Conversation extends AbstractEntity { private String nextPresence; - private transient CopyOnWriteArrayList<Message> messages = null; + private transient ArrayList<Message> messages = new ArrayList<Message>(); private transient Account account = null; private transient SessionImpl otrSession; @@ -104,17 +104,6 @@ public class Conversation extends AbstractEntity { } public List<Message> getMessages() { - if (messages == null) { - this.messages = new CopyOnWriteArrayList<Message>(); // prevent null - // pointer - } - - // populate with Conversation (this) - - for (Message msg : messages) { - msg.setConversation(this); - } - return messages; } @@ -165,7 +154,7 @@ public class Conversation extends AbstractEntity { } } - public void setMessages(CopyOnWriteArrayList<Message> msgs) { + public void setMessages(ArrayList<Message> msgs) { this.messages = msgs; } @@ -507,4 +496,17 @@ public class Conversation extends AbstractEntity { } } } + + public void add(Message message) { + message.setConversation(this); + synchronized (this.messages) { + this.messages.add(message); + } + } + + public void addAll(int index, List<Message> messages) { + synchronized (this.messages) { + this.messages.addAll(index, messages); + } + } } diff --git a/src/eu/siacs/conversations/entities/Downloadable.java b/src/eu/siacs/conversations/entities/Downloadable.java index 36584ab0..70516b20 100644 --- a/src/eu/siacs/conversations/entities/Downloadable.java +++ b/src/eu/siacs/conversations/entities/Downloadable.java @@ -11,6 +11,7 @@ public interface Downloadable { public static final int STATUS_OFFER = 0x203; public static final int STATUS_DOWNLOADING = 0x204; public static final int STATUS_DELETED = 0x205; + public static final int STATUS_OFFER_CHECK_FILESIZE = 0x206; public boolean start(); diff --git a/src/eu/siacs/conversations/http/HttpConnection.java b/src/eu/siacs/conversations/http/HttpConnection.java index 0254dc2f..3eb8bb84 100644 --- a/src/eu/siacs/conversations/http/HttpConnection.java +++ b/src/eu/siacs/conversations/http/HttpConnection.java @@ -6,15 +6,18 @@ import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.X509TrustManager; import android.content.Intent; import android.graphics.BitmapFactory; import android.net.Uri; -import android.util.Log; -import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Downloadable; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; @@ -39,8 +42,12 @@ public class HttpConnection implements Downloadable { @Override public boolean start() { if (mXmppConnectionService.hasInternetConnection()) { - changeStatus(STATUS_DOWNLOADING); - new Thread(new FileDownloader()).start(); + if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) { + checkFileSize(true); + } else { + changeStatus(STATUS_DOWNLOADING); + new Thread(new FileDownloader()).start(); + } return true; } else { return false; @@ -52,18 +59,18 @@ public class HttpConnection implements Downloadable { this.message.setDownloadable(this); try { mUrl = new URL(message.getBody()); - this.file = mXmppConnectionService.getFileBackend() - .getFile(message, false); + this.file = mXmppConnectionService.getFileBackend().getFile( + message, false); this.mAutostart = true; - checkFileSize(); + checkFileSize(false); } catch (MalformedURLException e) { this.cancel(); } } - private void checkFileSize() { + private void checkFileSize(boolean interactive) { changeStatus(STATUS_CHECKING); - new Thread(new FileSizeChecker()).start(); + new Thread(new FileSizeChecker(interactive)).start(); } public void cancel() { @@ -84,33 +91,62 @@ public class HttpConnection implements Downloadable { this.mStatus = status; mXmppConnectionService.updateConversationUi(); } + + private void setupTrustManager(HttpsURLConnection connection, boolean interactive) { + X509TrustManager trustManager; + if (interactive) { + trustManager = mXmppConnectionService.getMemorizingTrustManager(); + } else { + trustManager = mXmppConnectionService.getMemorizingTrustManager().getNonInteractive(); + } + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null,new X509TrustManager[] { trustManager },mXmppConnectionService.getRNG()); + connection.setSSLSocketFactory(sc.getSocketFactory()); + } catch (KeyManagementException e) { + return; + } catch (NoSuchAlgorithmException e) { + return; + } + } private class FileSizeChecker implements Runnable { + + private boolean interactive = false; + + public FileSizeChecker(boolean interactive) { + this.interactive = interactive; + } @Override public void run() { long size; try { size = retrieveFileSize(); + } catch (SSLHandshakeException e) { + changeStatus(STATUS_OFFER_CHECK_FILESIZE); + return; } catch (IOException e) { cancel(); return; } file.setExpectedSize(size); - if (size <= mHttpConnectionManager.getAutoAcceptFileSize() && mAutostart) { + if (size <= mHttpConnectionManager.getAutoAcceptFileSize() + && mAutostart) { start(); } else { changeStatus(STATUS_OFFER); } } - private long retrieveFileSize() throws IOException { + private long retrieveFileSize() throws IOException, SSLHandshakeException { HttpURLConnection connection = (HttpURLConnection) mUrl .openConnection(); connection.setRequestMethod("HEAD"); if (connection instanceof HttpsURLConnection) { - + setupTrustManager((HttpsURLConnection) connection, interactive); } + connection.connect(); String contentLength = connection.getHeaderField("Content-Length"); if (contentLength == null) { throw new IOException(); @@ -141,7 +177,7 @@ public class HttpConnection implements Downloadable { HttpURLConnection connection = (HttpURLConnection) mUrl .openConnection(); if (connection instanceof HttpsURLConnection) { - + setupTrustManager((HttpsURLConnection) connection, true); } BufferedInputStream is = new BufferedInputStream( connection.getInputStream()); diff --git a/src/eu/siacs/conversations/parser/MessageParser.java b/src/eu/siacs/conversations/parser/MessageParser.java index 4090ed39..daf0174c 100644 --- a/src/eu/siacs/conversations/parser/MessageParser.java +++ b/src/eu/siacs/conversations/parser/MessageParser.java @@ -469,7 +469,7 @@ public class MessageParser extends AbstractParser implements } } Conversation conversation = message.getConversation(); - conversation.getMessages().add(message); + conversation.add(message); if (packet.getType() != MessagePacket.TYPE_ERROR) { if (message.getEncryption() == Message.ENCRYPTION_NONE || mXmppConnectionService.saveEncryptedMessages()) { diff --git a/src/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/eu/siacs/conversations/persistance/DatabaseBackend.java index d90b5c62..d3d6ccf2 100644 --- a/src/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -152,14 +152,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { return list; } - public CopyOnWriteArrayList<Message> getMessages( + public ArrayList<Message> getMessages( Conversation conversations, int limit) { return getMessages(conversations, limit, -1); } - public CopyOnWriteArrayList<Message> getMessages(Conversation conversation, + public ArrayList<Message> getMessages(Conversation conversation, int limit, long timestamp) { - CopyOnWriteArrayList<Message> list = new CopyOnWriteArrayList<Message>(); + ArrayList<Message> list = new ArrayList<Message>(); SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor; if (timestamp == -1) { @@ -178,7 +178,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (cursor.getCount() > 0) { cursor.moveToLast(); do { - list.add(Message.fromCursor(cursor)); + Message message = Message.fromCursor(cursor); + message.setConversation(conversation); + list.add(message); } while (cursor.moveToPrevious()); } return list; diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java index 36ea350b..f557c3e1 100644 --- a/src/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/eu/siacs/conversations/services/XmppConnectionService.java @@ -529,7 +529,7 @@ public class XmppConnectionService extends Service { return connection; } - synchronized public void sendMessage(Message message) { + public void sendMessage(Message message) { Account account = message.getConversation().getAccount(); account.deactivateGracePeriod(); Conversation conv = message.getConversation(); @@ -615,7 +615,7 @@ public class XmppConnectionService extends Service { } } - conv.getMessages().add(message); + conv.add(message); if (saveInDb) { if (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages()) { @@ -881,7 +881,7 @@ public class XmppConnectionService extends Service { for (Message message : messages) { message.setConversation(conversation); } - conversation.getMessages().addAll(0, messages); + conversation.addAll(0, messages); return messages.size(); } diff --git a/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index bd37f7fc..20c7bee2 100644 --- a/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -92,6 +92,8 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { mLastMessage.setText(R.string.receiving_image); } else if (d.getStatus() == Downloadable.STATUS_OFFER) { mLastMessage.setText(R.string.image_offered_for_download); + } else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) { + mLastMessage.setText(R.string.image_offered_for_download); } else if (d.getStatus() == Downloadable.STATUS_DELETED) { mLastMessage.setText(R.string.image_file_deleted); } else { diff --git a/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 6b2a0094..db783b7f 100644 --- a/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -264,10 +264,11 @@ public class MessageAdapter extends ArrayAdapter<Message> { } private void displayDownloadableMessage(ViewHolder viewHolder, - final Message message) { + final Message message, int resid) { viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.VISIBLE); + viewHolder.download_button.setText(resid); viewHolder.download_button.setOnClickListener(new OnClickListener() { @Override @@ -493,7 +494,9 @@ public class MessageAdapter extends ArrayAdapter<Message> { && d.getStatus() == Downloadable.STATUS_DELETED) { displayInfoMessage(viewHolder, R.string.image_file_deleted); } else if (d != null && d.getStatus() == Downloadable.STATUS_OFFER) { - displayDownloadableMessage(viewHolder, item); + displayDownloadableMessage(viewHolder, item,R.string.download_image); + } else if (d != null && d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) { + displayDownloadableMessage(viewHolder, item,R.string.check_image_filesize); } else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED) || (item.getEncryption() == Message.ENCRYPTION_NONE) || (item.getEncryption() == Message.ENCRYPTION_OTR)) { diff --git a/src/eu/siacs/conversations/xmpp/XmppConnection.java b/src/eu/siacs/conversations/xmpp/XmppConnection.java index 43614f50..0252d80e 100644 --- a/src/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/eu/siacs/conversations/xmpp/XmppConnection.java @@ -10,8 +10,10 @@ import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Hashtable; +import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; @@ -26,15 +28,19 @@ import org.xmlpull.v1.XmlPullParserException; import de.duenndns.ssl.MemorizingTrustManager; +import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.util.Log; import android.util.SparseArray; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.StartConversationActivity; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.DNSHelper; import eu.siacs.conversations.utils.zlib.ZLibOutputStream; @@ -80,6 +86,7 @@ public class XmppConnection implements Runnable { private SparseArray<String> messageReceipts = new SparseArray<String>(); private boolean usingCompression = false; + private boolean usingEncryption = false; private int stanzasReceived = 0; private int stanzasSent = 0; @@ -104,6 +111,7 @@ public class XmppConnection implements Runnable { private OnBindListener bindListener = null; private OnMessageAcknowledged acknowledgedListener = null; private MemorizingTrustManager mMemorizingTrustManager; + private final Context applicationContext; public XmppConnection(Account account, XmppConnectionService service) { this.mRandom = service.getRNG(); @@ -112,6 +120,7 @@ public class XmppConnection implements Runnable { this.wakeLock = service.getPowerManager().newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, account.getJid()); tagWriter = new TagWriter(); + applicationContext = service.getApplicationContext(); } protected void changeStatus(int nextStatus) { @@ -135,6 +144,7 @@ public class XmppConnection implements Runnable { protected void connect() { Log.d(Config.LOGTAG, account.getJid() + ": connecting"); usingCompression = false; + usingEncryption = false; lastConnect = SystemClock.elapsedRealtime(); lastPingSent = SystemClock.elapsedRealtime(); this.attempt++; @@ -161,10 +171,23 @@ public class XmppConnection implements Runnable { + "[" + srvIpServer + "]:" + srvRecordPort); socket = new Socket(srvIpServer, srvRecordPort); } else { - Log.d(Config.LOGTAG, account.getJid() - + ": using values from dns " + srvRecordServer - + ":" + srvRecordPort); - socket = new Socket(srvRecordServer, srvRecordPort); + boolean socketError = true; + int srvIndex = 0; + while (socketError && namePort.containsKey("name" + srvIndex)){ + try { + srvRecordServer = namePort.getString("name" + srvIndex); + srvRecordPort = namePort.getInt("port" + srvIndex); + Log.d(Config.LOGTAG, account.getJid() + + ": using values from dns " + srvRecordServer + + ":" + srvRecordPort); + socket = new Socket(srvRecordServer, srvRecordPort); + socketError = false; + } catch (UnknownHostException e) { + srvIndex++; + } catch (IOException e) { + srvIndex++; + } + } } } else if (namePort.containsKey("error") && "nosrv".equals(namePort.getString("error", null))) { @@ -362,13 +385,13 @@ public class XmppConnection implements Runnable { iq.addChild("ping", "urn:xmpp:ping"); this.sendIqPacket(iq, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Log.d(Config.LOGTAG, account.getJid() - + ": online with resource " + account.getResource()); - changeStatus(Account.STATUS_ONLINE); - } - }); + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Log.d(Config.LOGTAG, account.getJid() + + ": online with resource " + account.getResource()); + changeStatus(Account.STATUS_ONLINE); + } + }); } private Element processPacket(Tag currentTag, int packetType) @@ -504,6 +527,14 @@ public class XmppConnection implements Runnable { tagWriter.writeTag(startTLS); } + private SharedPreferences getPreferences() { + return PreferenceManager.getDefaultSharedPreferences(applicationContext); + } + + private boolean enableLegacySSL() { + return getPreferences().getBoolean("enable_legacy_ssl", false); + } + private void switchOverToTls(Tag currentTag) throws XmlPullParserException, IOException { tagReader.readTag(); @@ -520,6 +551,21 @@ public class XmppConnection implements Runnable { socket.getInetAddress().getHostAddress(), socket.getPort(), true); + // Support all protocols except legacy SSL. + // The min SDK version prevents us having to worry about SSLv2. In future, this may be + // true of SSLv3 as well. + final String[] supportProtocols; + if (enableLegacySSL()) { + supportProtocols = sslSocket.getSupportedProtocols(); + } else { + final List<String> supportedProtocols = new LinkedList<String>(Arrays.asList( + sslSocket.getSupportedProtocols())); + supportedProtocols.remove("SSLv3"); + supportProtocols = new String[supportedProtocols.size()]; + supportedProtocols.toArray(supportProtocols); + } + sslSocket.setEnabledProtocols(supportProtocols); + if (verifier != null && !verifier.verify(account.getServer(), sslSocket.getSession())) { @@ -533,6 +579,7 @@ public class XmppConnection implements Runnable { sendStartStream(); Log.d(Config.LOGTAG, account.getJid() + ": TLS connection established"); + usingEncryption = true; processStream(tagReader.readTag()); sslSocket.close(); } catch (NoSuchAlgorithmException e1) { @@ -562,20 +609,19 @@ public class XmppConnection implements Runnable { private void processStreamFeatures(Tag currentTag) throws XmlPullParserException, IOException { this.streamFeatures = tagReader.readElement(currentTag); - if (this.streamFeatures.hasChild("starttls") - && account.isOptionSet(Account.OPTION_USETLS)) { + if (this.streamFeatures.hasChild("starttls") && !usingEncryption) { sendStartTLS(); } else if (compressionAvailable()) { sendCompressionZlib(); } else if (this.streamFeatures.hasChild("register") - && (account.isOptionSet(Account.OPTION_REGISTER))) { + && account.isOptionSet(Account.OPTION_REGISTER) && usingEncryption) { sendRegistryRequest(); } else if (!this.streamFeatures.hasChild("register") - && (account.isOptionSet(Account.OPTION_REGISTER))) { + && account.isOptionSet(Account.OPTION_REGISTER)) { changeStatus(Account.STATUS_REGISTRATION_NOT_SUPPORTED); disconnect(true); } else if (this.streamFeatures.hasChild("mechanisms") - && shouldAuthenticate) { + && shouldAuthenticate && usingEncryption) { List<String> mechanisms = extractMechanisms(streamFeatures .findChild("mechanisms")); if (mechanisms.contains("PLAIN")) { @@ -591,6 +637,9 @@ public class XmppConnection implements Runnable { this.tagWriter.writeStanzaAsync(resume); } else if (this.streamFeatures.hasChild("bind") && shouldBind) { sendBindRequest(); + } else { + Log.d(Config.LOGTAG,account.getJid()+": incompatible server. disconnecting"); + disconnect(true); } } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index e7e1a33d..5b3dfbff 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -307,7 +307,7 @@ public class JingleConnection implements Downloadable { if (supportedFile) { long size = Long.parseLong(fileSize.getContent()); message.setBody(Long.toString(size)); - conversation.getMessages().add(message); + conversation.add(message); mXmppConnectionService.updateConversationUi(); if (size <= this.mJingleConnectionManager .getAutoAcceptFileSize()) { @@ -634,6 +634,7 @@ public class JingleConnection implements Downloadable { } private void sendFallbackToIbb() { + Log.d(Config.LOGTAG,"sending fallback to ibb"); JinglePacket packet = this.bootstrapPacket("transport-replace"); Content content = new Content(this.contentCreator, this.contentName); this.transportId = this.mJingleConnectionManager.nextRandomId(); @@ -645,6 +646,7 @@ public class JingleConnection implements Downloadable { } private boolean receiveFallbackToIbb(JinglePacket packet) { + Log.d(Config.LOGTAG,"receiving fallack to ibb"); String receivedBlockSize = packet.getJingleContent().ibbTransport() .getAttribute("block-size"); if (receivedBlockSize != null) { |