some code cleanup. added setting to auto accept files. socks5 connections are now threaded

This commit is contained in:
Daniel Gultsch 2014-04-13 18:09:40 +02:00
parent 27d5966ac3
commit 7dfe4ae082
10 changed files with 234 additions and 78 deletions

View file

@ -7,4 +7,16 @@
<item>Conversations</item> <item>Conversations</item>
<item>Android</item> <item>Android</item>
</array> </array>
<string-array name="filesizes">
<item>never</item>
<item>256 KB</item>
<item>512 KB</item>
<item>1 MB</item>
</string-array>
<string-array name="filesizes_values">
<item>0</item>
<item>262144</item>
<item>524288</item>
<item>1048576</item>
</string-array>
</resources> </resources>

View file

@ -15,6 +15,13 @@
android:entries="@array/resources" android:entries="@array/resources"
android:entryValues="@array/resources" android:entryValues="@array/resources"
android:defaultValue="Mobile"/> android:defaultValue="Mobile"/>
<ListPreference
android:key="auto_accept_file_size"
android:title="Accept files"
android:summary="Automatically accept files smaller than"
android:entries="@array/filesizes"
android:entryValues="@array/filesizes_values"
android:defaultValue="524288"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:title="Notification Settings"> android:title="Notification Settings">

View file

@ -38,7 +38,7 @@ public class FileBackend {
} }
public JingleFile getImageFile(Message message) { public JingleFile getJingleFile(Message message) {
Conversation conversation = message.getConversation(); Conversation conversation = message.getConversation();
String prefix = context.getFilesDir().getAbsolutePath(); String prefix = context.getFilesDir().getAbsolutePath();
String path = prefix + "/" + conversation.getAccount().getJid() + "/" String path = prefix + "/" + conversation.getAccount().getJid() + "/"
@ -72,7 +72,7 @@ public class FileBackend {
try { try {
InputStream is = context.getContentResolver() InputStream is = context.getContentResolver()
.openInputStream(image); .openInputStream(image);
JingleFile file = getImageFile(message); JingleFile file = getJingleFile(message);
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
file.createNewFile(); file.createNewFile();
OutputStream os = new FileOutputStream(file); OutputStream os = new FileOutputStream(file);
@ -98,14 +98,14 @@ public class FileBackend {
public Bitmap getImageFromMessage(Message message) { public Bitmap getImageFromMessage(Message message) {
return BitmapFactory return BitmapFactory
.decodeFile(getImageFile(message).getAbsolutePath()); .decodeFile(getJingleFile(message).getAbsolutePath());
} }
public Bitmap getThumbnailFromMessage(Message message, int size) { public Bitmap getThumbnailFromMessage(Message message, int size) {
Bitmap thumbnail = thumbnailCache.get(message.getUuid()); Bitmap thumbnail = thumbnailCache.get(message.getUuid());
if (thumbnail==null) { if (thumbnail==null) {
Log.d("xmppService","creating new thumbnail" + message.getUuid()); Log.d("xmppService","creating new thumbnail" + message.getUuid());
Bitmap fullsize = BitmapFactory.decodeFile(getImageFile(message) Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message)
.getAbsolutePath()); .getAbsolutePath());
thumbnail = resize(fullsize, size); thumbnail = resize(fullsize, size);
this.thumbnailCache.put(message.getUuid(), thumbnail); this.thumbnailCache.put(message.getUuid(), thumbnail);

View file

@ -124,9 +124,7 @@ public class XmppConnectionService extends Service {
MessagePacket packet) { MessagePacket packet) {
Message message = null; Message message = null;
boolean notify = true; boolean notify = true;
if(PreferenceManager if(getPreferences().getBoolean("notification_grace_period_after_carbon_received", true)){
.getDefaultSharedPreferences(getApplicationContext())
.getBoolean("notification_grace_period_after_carbon_received", true)){
notify=(SystemClock.elapsedRealtime() - lastCarbonMessageReceived) > CARBON_GRACE_PERIOD; notify=(SystemClock.elapsedRealtime() - lastCarbonMessageReceived) > CARBON_GRACE_PERIOD;
} }
@ -625,8 +623,7 @@ public class XmppConnectionService extends Service {
} }
public XmppConnection createConnection(Account account) { public XmppConnection createConnection(Account account) {
SharedPreferences sharedPref = PreferenceManager SharedPreferences sharedPref = getPreferences();
.getDefaultSharedPreferences(getApplicationContext());
account.setResource(sharedPref.getString("resource", "mobile").toLowerCase(Locale.getDefault())); account.setResource(sharedPref.getString("resource", "mobile").toLowerCase(Locale.getDefault()));
XmppConnection connection = new XmppConnection(account, this.pm); XmppConnection connection = new XmppConnection(account, this.pm);
connection.setOnMessagePacketReceivedListener(this.messageListener); connection.setOnMessagePacketReceivedListener(this.messageListener);
@ -1204,8 +1201,7 @@ public class XmppConnectionService extends Service {
} }
public void createContact(Contact contact) { public void createContact(Contact contact) {
SharedPreferences sharedPref = PreferenceManager SharedPreferences sharedPref = getPreferences();
.getDefaultSharedPreferences(getApplicationContext());
boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true); boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
if (autoGrant) { if (autoGrant) {
contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT); contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
@ -1396,4 +1392,8 @@ public class XmppConnectionService extends Service {
convChangedListener.onConversationListChanged(); convChangedListener.onConversationListChanged();
} }
} }
public SharedPreferences getPreferences() {
return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
}
} }

View file

@ -419,7 +419,7 @@ public class UIHelper {
mBuilder.setContentText(names.toString()); mBuilder.setContentText(names.toString());
mBuilder.setStyle(style); mBuilder.setStyle(style);
} }
if (currentCon!=null) { if ((currentCon!=null)&&(notify)) {
targetUuid=currentCon.getUuid(); targetUuid=currentCon.getUuid();
} }
if (unread.size() != 0) { if (unread.size() != 0) {

View file

@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import android.util.Log; import android.util.Log;
@ -39,7 +38,9 @@ public class JingleConnection {
private String initiator; private String initiator;
private String responder; private String responder;
private List<Element> candidates = new ArrayList<Element>(); private List<Element> candidates = new ArrayList<Element>();
private List<String> candidatesUsedByCounterpart = new ArrayList<String>();
private HashMap<String, SocksConnection> connections = new HashMap<String, SocksConnection>(); private HashMap<String, SocksConnection> connections = new HashMap<String, SocksConnection>();
private Content content = new Content();
private JingleFile file = null; private JingleFile file = null;
private OnIqPacketReceived responseListener = new OnIqPacketReceived() { private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
@ -100,9 +101,9 @@ public class JingleConnection {
this.mJingleConnectionManager.getPrimaryCandidate(account, new OnPrimaryCandidateFound() { this.mJingleConnectionManager.getPrimaryCandidate(account, new OnPrimaryCandidateFound() {
@Override @Override
public void onPrimaryCandidateFound(boolean success, Element canditate) { public void onPrimaryCandidateFound(boolean success, Element candidate) {
if (success) { if (success) {
candidates.add(canditate); mergeCandidate(candidate);
} }
sendInitRequest(); sendInitRequest();
} }
@ -116,24 +117,40 @@ public class JingleConnection {
this.message = new Message(conversation, "receiving image file", Message.ENCRYPTION_NONE); this.message = new Message(conversation, "receiving image file", Message.ENCRYPTION_NONE);
this.message.setType(Message.TYPE_IMAGE); this.message.setType(Message.TYPE_IMAGE);
this.message.setStatus(Message.STATUS_RECIEVING); this.message.setStatus(Message.STATUS_RECIEVING);
String[] fromParts = packet.getFrom().split("/");
this.message.setPresence(fromParts[1]);
this.account = account; this.account = account;
this.initiator = packet.getFrom(); this.initiator = packet.getFrom();
this.responder = this.account.getFullJid(); this.responder = this.account.getFullJid();
this.sessionId = packet.getSessionId(); this.sessionId = packet.getSessionId();
this.candidates.addAll(packet.getJingleContent().getCanditates()); this.content = packet.getJingleContent();
Log.d("xmppService","new incomming jingle session "+this.sessionId+" num canditaes:"+this.candidates.size()); this.mergeCandidates(this.content.getCanditates());
Element fileOffer = packet.getJingleContent().getFileOffer();
if (fileOffer!=null) {
this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message);
Element fileSize = fileOffer.findChild("size");
Element fileName = fileOffer.findChild("name");
this.file.setExpectedSize(Long.parseLong(fileSize.getContent()));
if (this.file.getExpectedSize()>=this.mJingleConnectionManager.getAutoAcceptFileSize()) {
Log.d("xmppService","auto accepting file from "+packet.getFrom());
this.sendAccept();
} else {
Log.d("xmppService","not auto accepting new file offer with size: "+this.file.getExpectedSize()+" allowed size:"+this.mJingleConnectionManager.getAutoAcceptFileSize());
}
} else {
Log.d("xmppService","no file offer was attached. aborting");
}
} }
private void sendInitRequest() { private void sendInitRequest() {
JinglePacket packet = this.bootstrapPacket(); JinglePacket packet = this.bootstrapPacket();
packet.setAction("session-initiate"); packet.setAction("session-initiate");
packet.setInitiator(this.account.getFullJid()); this.content = new Content();
Content content = new Content();
if (message.getType() == Message.TYPE_IMAGE) { if (message.getType() == Message.TYPE_IMAGE) {
content.setAttribute("creator", "initiator"); content.setAttribute("creator", "initiator");
content.setAttribute("name", "a-file-offer"); content.setAttribute("name", "a-file-offer");
this.file = this.mXmppConnectionService.getFileBackend().getImageFile(message); this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message);
content.offerFile(file,message.getBody()); content.setFileOffer(this.file);
content.setCandidates(this.mJingleConnectionManager.nextRandomId(),this.candidates); content.setCandidates(this.mJingleConnectionManager.nextRandomId(),this.candidates);
packet.setContent(content); packet.setContent(content);
Log.d("xmppService",packet.toString()); Log.d("xmppService",packet.toString());
@ -142,58 +159,103 @@ public class JingleConnection {
} }
} }
private void sendAccept() {
this.mJingleConnectionManager.getPrimaryCandidate(this.account, new OnPrimaryCandidateFound() {
@Override
public void onPrimaryCandidateFound(boolean success, Element candidate) {
if (success) {
if (mergeCandidate(candidate)) {
content.addCandidate(candidate);
}
}
JinglePacket packet = bootstrapPacket();
packet.setAction("session-accept");
packet.setContent(content);
Log.d("xmppService","sending session accept: "+packet.toString());
account.getXmppConnection().sendIqPacket(packet, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() != IqPacket.TYPE_ERROR) {
Log.d("xmppService","opsing side has acked our session-accept");
connectWithCandidates();
}
}
});
}
});
}
private JinglePacket bootstrapPacket() { private JinglePacket bootstrapPacket() {
JinglePacket packet = new JinglePacket(); JinglePacket packet = new JinglePacket();
packet.setFrom(account.getFullJid()); packet.setFrom(account.getFullJid());
packet.setTo(this.message.getCounterpart()); //fixme, not right in all cases; packet.setTo(this.message.getCounterpart()); //fixme, not right in all cases;
packet.setSessionId(this.sessionId); packet.setSessionId(this.sessionId);
packet.setInitiator(this.initiator);
return packet; return packet;
} }
private void accept(JinglePacket packet) { private void accept(JinglePacket packet) {
Log.d("xmppService","session-accept: "+packet.toString()); Log.d("xmppService","session-accept: "+packet.toString());
Content content = packet.getJingleContent(); Content content = packet.getJingleContent();
this.candidates.addAll(content.getCanditates()); this.mergeCandidates(content.getCanditates());
this.status = STATUS_ACCEPTED; this.status = STATUS_ACCEPTED;
this.connectWithCandidates(); this.connectWithCandidates();
IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT); IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
Log.d("xmppService","response "+response.toString());
account.getXmppConnection().sendIqPacket(response, null); account.getXmppConnection().sendIqPacket(response, null);
} }
private void transportInfo(JinglePacket packet) { private void transportInfo(JinglePacket packet) {
Content content = packet.getJingleContent(); Content content = packet.getJingleContent();
Log.d("xmppService","transport info : "+content.toString()); Log.d("xmppService","transport info : "+content.toString());
String cid = content.getUsedCandidate(); String cid = content.getUsedCandidate();
if (cid!=null) { if (cid!=null) {
final JingleFile file = this.mXmppConnectionService.getFileBackend().getImageFile(this.message); Log.d("xmppService","candidate used by counterpart:"+cid);
final SocksConnection connection = this.connections.get(cid); this.candidatesUsedByCounterpart.add(cid);
final OnFileTransmitted callback = new OnFileTransmitted() { if (this.connections.containsKey(cid)) {
this.connect(this.connections.get(cid));
}
}
IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
account.getXmppConnection().sendIqPacket(response, null);
}
private void connect(final SocksConnection connection) {
final OnFileTransmitted callback = new OnFileTransmitted() {
@Override
public void onFileTransmitted(JingleFile file) {
Log.d("xmppService","sucessfully transmitted file. sha1:"+file.getSha1Sum());
}
};
if (connection.isProxy()) {
IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
activation.setTo(connection.getJid());
activation.query("http://jabber.org/protocol/bytestreams").setAttribute("sid", this.getSessionId());
activation.query().addChild("activate").setContent(this.getResponder());
Log.d("xmppService","connection is proxy. need to activate "+activation.toString());
this.account.getXmppConnection().sendIqPacket(activation, new OnIqPacketReceived() {
@Override @Override
public void onFileTransmitted(JingleFile file) { public void onIqPacketReceived(Account account, IqPacket packet) {
Log.d("xmppService","sucessfully transmitted file. sha1:"+file.getSha1Sum()); Log.d("xmppService","activation result: "+packet.toString());
} if (initiator.equals(account.getFullJid())) {
}; Log.d("xmppService","we were initiating. sending file");
final IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
if (connection.isProxy()) {
IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
activation.setTo(connection.getJid());
activation.query("http://jabber.org/protocol/bytestreams").setAttribute("sid", this.getSessionId());
activation.query().addChild("activate").setContent(this.getResponder());
Log.d("xmppService","connection is proxy. need to activate "+activation.toString());
this.account.getXmppConnection().sendIqPacket(activation, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
account.getXmppConnection().sendIqPacket(response, null);
Log.d("xmppService","activation result: "+packet.toString());
connection.send(file,callback); connection.send(file,callback);
} else {
Log.d("xmppService","we were responding. receiving file");
} }
});
} else { }
account.getXmppConnection().sendIqPacket(response, null); });
} else {
if (initiator.equals(account.getFullJid())) {
Log.d("xmppService","we were initiating. sending file");
connection.send(file,callback); connection.send(file,callback);
} else {
Log.d("xmppService","we were responding. receiving file");
} }
} }
} }
@ -212,13 +274,25 @@ public class JingleConnection {
private void connectWithCandidates() { private void connectWithCandidates() {
for(Element canditate : this.candidates) { for(Element canditate : this.candidates) {
String host = canditate.getAttribute("host"); String host = canditate.getAttribute("host");
int port = Integer.parseInt(canditate.getAttribute("port")); int port = Integer.parseInt(canditate.getAttribute("port"));
String type = canditate.getAttribute("type"); String type = canditate.getAttribute("type");
String jid = canditate.getAttribute("jid"); String jid = canditate.getAttribute("jid");
SocksConnection socksConnection = new SocksConnection(this, host, jid, port,type); SocksConnection socksConnection = new SocksConnection(this, host, jid, port,type);
socksConnection.connect(); connections.put(canditate.getAttribute("cid"), socksConnection);
this.connections.put(canditate.getAttribute("cid"), socksConnection); socksConnection.connect(new OnSocksConnection() {
@Override
public void failed() {
Log.d("xmppService","socks5 failed");
}
@Override
public void established() {
Log.d("xmppService","established socks5");
}
});
} }
} }
@ -246,4 +320,20 @@ public class JingleConnection {
public int getStatus() { public int getStatus() {
return this.status; return this.status;
} }
private boolean mergeCandidate(Element candidate) {
for(Element c : this.candidates) {
if (c.getAttribute("host").equals(candidate.getAttribute("host"))&&(c.getAttribute("port").equals(candidate.getAttribute("port")))) {
return false;
}
}
this.candidates.add(candidate);
return true;
}
private void mergeCandidates(List<Element> canditates) {
for(Element c : canditates) {
this.mergeCandidate(c);
}
}
} }

View file

@ -122,4 +122,8 @@ public class JingleConnectionManager {
public String nextRandomId() { public String nextRandomId() {
return new BigInteger(50, random).toString(32); return new BigInteger(50, random).toString(32);
} }
public long getAutoAcceptFileSize() {
return this.xmppConnectionService.getPreferences().getLong("auto_accept_file_size", 0);
}
} }

View file

@ -0,0 +1,6 @@
package eu.siacs.conversations.xmpp.jingle;
public interface OnSocksConnection {
public void failed();
public void established();
}

View file

@ -25,6 +25,7 @@ public class SocksConnection {
private boolean isProxy = false; private boolean isProxy = false;
private String destination; private String destination;
private OutputStream outputStream; private OutputStream outputStream;
private boolean isEstablished = false;
public SocksConnection(JingleConnection jingleConnection, String host, public SocksConnection(JingleConnection jingleConnection, String host,
String jid, int port, String type) { String jid, int port, String type) {
@ -42,40 +43,52 @@ public class SocksConnection {
mDigest.reset(); mDigest.reset();
this.destination = CryptoHelper.bytesToHex(mDigest this.destination = CryptoHelper.bytesToHex(mDigest
.digest(destBuilder.toString().getBytes())); .digest(destBuilder.toString().getBytes()));
Log.d("xmppService", "host=" + host + ", port=" + port
+ ", destination: " + destination);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
} }
} }
public boolean connect() { public void connect(final OnSocksConnection callback) {
try { new Thread(new Runnable() {
this.socket = new Socket(this.host, this.port);
InputStream is = socket.getInputStream(); @Override
this.outputStream = socket.getOutputStream(); public void run() {
byte[] login = { 0x05, 0x01, 0x00 }; try {
byte[] expectedReply = { 0x05, 0x00 }; socket = new Socket(host, port);
byte[] reply = new byte[2]; InputStream is = socket.getInputStream();
this.outputStream.write(login); outputStream = socket.getOutputStream();
is.read(reply); byte[] login = { 0x05, 0x01, 0x00 };
if (Arrays.equals(reply, expectedReply)) { byte[] expectedReply = { 0x05, 0x00 };
String connect = "" + '\u0005' + '\u0001' + '\u0000' + '\u0003' byte[] reply = new byte[2];
+ '\u0028' + this.destination + '\u0000' + '\u0000'; outputStream.write(login);
this.outputStream.write(connect.getBytes()); is.read(reply);
byte[] result = new byte[2]; if (Arrays.equals(reply, expectedReply)) {
is.read(result); String connect = "" + '\u0005' + '\u0001' + '\u0000' + '\u0003'
int status = result[0]; + '\u0028' + destination + '\u0000' + '\u0000';
return (status == 0); outputStream.write(connect.getBytes());
} else { byte[] result = new byte[2];
socket.close(); is.read(result);
return false; int status = result[1];
if (status == 0) {
Log.d("xmppService", "established connection with "+host + ":" + port
+ "/" + destination);
isEstablished = true;
callback.established();
} else {
callback.failed();
}
} else {
socket.close();
callback.failed();
}
} catch (UnknownHostException e) {
callback.failed();
} catch (IOException e) {
callback.failed();
}
} }
} catch (UnknownHostException e) { }).start();
return false;
} catch (IOException e) {
return false;
}
} }
public void send(final JingleFile file, final OnFileTransmitted callback) { public void send(final JingleFile file, final OnFileTransmitted callback) {
@ -141,4 +154,8 @@ public class SocksConnection {
} }
} }
} }
public boolean isEstablished() {
return this.isEstablished;
}
} }

View file

@ -15,13 +15,25 @@ public class Content extends Element {
super("content"); super("content");
} }
public void offerFile(JingleFile actualFile, String hash) { public void setFileOffer(JingleFile actualFile) {
Element description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); Element description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3");
Element offer = description.addChild("offer"); Element offer = description.addChild("offer");
Element file = offer.addChild("file"); Element file = offer.addChild("file");
file.addChild("size").setContent(""+actualFile.getSize()); file.addChild("size").setContent(""+actualFile.getSize());
file.addChild("name").setContent(actualFile.getName()); file.addChild("name").setContent(actualFile.getName());
} }
public Element getFileOffer() {
Element description = this.findChild("description", "urn:xmpp:jingle:apps:file-transfer:3");
if (description==null) {
return null;
}
Element offer = description.findChild("offer");
if (offer==null) {
return null;
}
return offer.findChild("file");
}
public void setCandidates(String transportId, List<Element> canditates) { public void setCandidates(String transportId, List<Element> canditates) {
Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1"); Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
@ -56,4 +68,12 @@ public class Content extends Element {
return usedCandidate.getAttribute("cid"); return usedCandidate.getAttribute("cid");
} }
} }
public void addCandidate(Element candidate) {
Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
if (transport==null) {
transport = this.addChild("transport", "urn:xmpp:jingle:transports:s5b:1");
}
transport.addChild(candidate);
}
} }