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>Android</item>
</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>

View file

@ -15,6 +15,13 @@
android:entries="@array/resources"
android:entryValues="@array/resources"
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
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();
String prefix = context.getFilesDir().getAbsolutePath();
String path = prefix + "/" + conversation.getAccount().getJid() + "/"
@ -72,7 +72,7 @@ public class FileBackend {
try {
InputStream is = context.getContentResolver()
.openInputStream(image);
JingleFile file = getImageFile(message);
JingleFile file = getJingleFile(message);
file.getParentFile().mkdirs();
file.createNewFile();
OutputStream os = new FileOutputStream(file);
@ -98,14 +98,14 @@ public class FileBackend {
public Bitmap getImageFromMessage(Message message) {
return BitmapFactory
.decodeFile(getImageFile(message).getAbsolutePath());
.decodeFile(getJingleFile(message).getAbsolutePath());
}
public Bitmap getThumbnailFromMessage(Message message, int size) {
Bitmap thumbnail = thumbnailCache.get(message.getUuid());
if (thumbnail==null) {
Log.d("xmppService","creating new thumbnail" + message.getUuid());
Bitmap fullsize = BitmapFactory.decodeFile(getImageFile(message)
Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message)
.getAbsolutePath());
thumbnail = resize(fullsize, size);
this.thumbnailCache.put(message.getUuid(), thumbnail);

View file

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

View file

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

View file

@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import android.util.Log;
@ -39,7 +38,9 @@ public class JingleConnection {
private String initiator;
private String responder;
private List<Element> candidates = new ArrayList<Element>();
private List<String> candidatesUsedByCounterpart = new ArrayList<String>();
private HashMap<String, SocksConnection> connections = new HashMap<String, SocksConnection>();
private Content content = new Content();
private JingleFile file = null;
private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
@ -100,9 +101,9 @@ public class JingleConnection {
this.mJingleConnectionManager.getPrimaryCandidate(account, new OnPrimaryCandidateFound() {
@Override
public void onPrimaryCandidateFound(boolean success, Element canditate) {
public void onPrimaryCandidateFound(boolean success, Element candidate) {
if (success) {
candidates.add(canditate);
mergeCandidate(candidate);
}
sendInitRequest();
}
@ -116,24 +117,40 @@ public class JingleConnection {
this.message = new Message(conversation, "receiving image file", Message.ENCRYPTION_NONE);
this.message.setType(Message.TYPE_IMAGE);
this.message.setStatus(Message.STATUS_RECIEVING);
String[] fromParts = packet.getFrom().split("/");
this.message.setPresence(fromParts[1]);
this.account = account;
this.initiator = packet.getFrom();
this.responder = this.account.getFullJid();
this.sessionId = packet.getSessionId();
this.candidates.addAll(packet.getJingleContent().getCanditates());
Log.d("xmppService","new incomming jingle session "+this.sessionId+" num canditaes:"+this.candidates.size());
this.content = packet.getJingleContent();
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() {
JinglePacket packet = this.bootstrapPacket();
packet.setAction("session-initiate");
packet.setInitiator(this.account.getFullJid());
Content content = new Content();
this.content = new Content();
if (message.getType() == Message.TYPE_IMAGE) {
content.setAttribute("creator", "initiator");
content.setAttribute("name", "a-file-offer");
this.file = this.mXmppConnectionService.getFileBackend().getImageFile(message);
content.offerFile(file,message.getBody());
this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message);
content.setFileOffer(this.file);
content.setCandidates(this.mJingleConnectionManager.nextRandomId(),this.candidates);
packet.setContent(content);
Log.d("xmppService",packet.toString());
@ -142,22 +159,51 @@ 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() {
JinglePacket packet = new JinglePacket();
packet.setFrom(account.getFullJid());
packet.setTo(this.message.getCounterpart()); //fixme, not right in all cases;
packet.setSessionId(this.sessionId);
packet.setInitiator(this.initiator);
return packet;
}
private void accept(JinglePacket packet) {
Log.d("xmppService","session-accept: "+packet.toString());
Content content = packet.getJingleContent();
this.candidates.addAll(content.getCanditates());
this.mergeCandidates(content.getCanditates());
this.status = STATUS_ACCEPTED;
this.connectWithCandidates();
IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
Log.d("xmppService","response "+response.toString());
account.getXmppConnection().sendIqPacket(response, null);
}
@ -166,34 +212,50 @@ public class JingleConnection {
Log.d("xmppService","transport info : "+content.toString());
String cid = content.getUsedCandidate();
if (cid!=null) {
final JingleFile file = this.mXmppConnectionService.getFileBackend().getImageFile(this.message);
final SocksConnection connection = this.connections.get(cid);
final OnFileTransmitted callback = new OnFileTransmitted() {
Log.d("xmppService","candidate used by counterpart:"+cid);
this.candidatesUsedByCounterpart.add(cid);
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
public void onFileTransmitted(JingleFile file) {
Log.d("xmppService","sucessfully transmitted file. sha1:"+file.getSha1Sum());
}
};
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());
public void onIqPacketReceived(Account account, IqPacket packet) {
Log.d("xmppService","activation result: "+packet.toString());
if (initiator.equals(account.getFullJid())) {
Log.d("xmppService","we were initiating. sending file");
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);
} else {
Log.d("xmppService","we were responding. receiving file");
}
}
}
@ -212,13 +274,25 @@ public class JingleConnection {
private void connectWithCandidates() {
for(Element canditate : this.candidates) {
String host = canditate.getAttribute("host");
int port = Integer.parseInt(canditate.getAttribute("port"));
String type = canditate.getAttribute("type");
String jid = canditate.getAttribute("jid");
SocksConnection socksConnection = new SocksConnection(this, host, jid, port,type);
socksConnection.connect();
this.connections.put(canditate.getAttribute("cid"), socksConnection);
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() {
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() {
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 String destination;
private OutputStream outputStream;
private boolean isEstablished = false;
public SocksConnection(JingleConnection jingleConnection, String host,
String jid, int port, String type) {
@ -42,40 +43,52 @@ public class SocksConnection {
mDigest.reset();
this.destination = CryptoHelper.bytesToHex(mDigest
.digest(destBuilder.toString().getBytes()));
Log.d("xmppService", "host=" + host + ", port=" + port
+ ", destination: " + destination);
} catch (NoSuchAlgorithmException e) {
}
}
public boolean connect() {
try {
this.socket = new Socket(this.host, this.port);
InputStream is = socket.getInputStream();
this.outputStream = socket.getOutputStream();
byte[] login = { 0x05, 0x01, 0x00 };
byte[] expectedReply = { 0x05, 0x00 };
byte[] reply = new byte[2];
this.outputStream.write(login);
is.read(reply);
if (Arrays.equals(reply, expectedReply)) {
String connect = "" + '\u0005' + '\u0001' + '\u0000' + '\u0003'
+ '\u0028' + this.destination + '\u0000' + '\u0000';
this.outputStream.write(connect.getBytes());
byte[] result = new byte[2];
is.read(result);
int status = result[0];
return (status == 0);
} else {
socket.close();
return false;
public void connect(final OnSocksConnection callback) {
new Thread(new Runnable() {
@Override
public void run() {
try {
socket = new Socket(host, port);
InputStream is = socket.getInputStream();
outputStream = socket.getOutputStream();
byte[] login = { 0x05, 0x01, 0x00 };
byte[] expectedReply = { 0x05, 0x00 };
byte[] reply = new byte[2];
outputStream.write(login);
is.read(reply);
if (Arrays.equals(reply, expectedReply)) {
String connect = "" + '\u0005' + '\u0001' + '\u0000' + '\u0003'
+ '\u0028' + destination + '\u0000' + '\u0000';
outputStream.write(connect.getBytes());
byte[] result = new byte[2];
is.read(result);
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) {
return false;
} catch (IOException e) {
return false;
}
}).start();
}
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,7 +15,7 @@ public class Content extends Element {
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 offer = description.addChild("offer");
Element file = offer.addChild("file");
@ -23,6 +23,18 @@ public class Content extends Element {
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) {
Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
if (transport==null) {
@ -56,4 +68,12 @@ public class Content extends Element {
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);
}
}