basic image over http downloading
This commit is contained in:
parent
7e373bc89f
commit
bbb0693f4a
25
src/eu/siacs/conversations/AbstractConnectionManager.java
Normal file
25
src/eu/siacs/conversations/AbstractConnectionManager.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
package eu.siacs.conversations;
|
||||
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
|
||||
public class AbstractConnectionManager {
|
||||
protected XmppConnectionService mXmppConnectionService;
|
||||
|
||||
public AbstractConnectionManager(XmppConnectionService service) {
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
public XmppConnectionService getXmppConnectionService() {
|
||||
return this.mXmppConnectionService;
|
||||
}
|
||||
|
||||
public long getAutoAcceptFileSize() {
|
||||
String config = this.mXmppConnectionService.getPreferences().getString(
|
||||
"auto_accept_file_size", "524288");
|
||||
try {
|
||||
return Long.parseLong(config);
|
||||
} catch (NumberFormatException e) {
|
||||
return 524288;
|
||||
}
|
||||
}
|
||||
}
|
148
src/eu/siacs/conversations/DownloadableFile.java
Normal file
148
src/eu/siacs/conversations/DownloadableFile.java
Normal file
|
@ -0,0 +1,148 @@
|
|||
package eu.siacs.conversations;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
import android.util.Log;
|
||||
|
||||
public class DownloadableFile extends File {
|
||||
|
||||
private static final long serialVersionUID = 2247012619505115863L;
|
||||
|
||||
private long expectedSize = 0;
|
||||
private String sha1sum;
|
||||
private Key aeskey;
|
||||
|
||||
private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
|
||||
|
||||
public DownloadableFile(String path) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return super.length();
|
||||
}
|
||||
|
||||
public long getExpectedSize() {
|
||||
if (this.aeskey != null) {
|
||||
return (this.expectedSize / 16 + 1) * 16;
|
||||
} else {
|
||||
return this.expectedSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void setExpectedSize(long size) {
|
||||
this.expectedSize = size;
|
||||
}
|
||||
|
||||
public String getSha1Sum() {
|
||||
return this.sha1sum;
|
||||
}
|
||||
|
||||
public void setSha1Sum(String sum) {
|
||||
this.sha1sum = sum;
|
||||
}
|
||||
|
||||
public void setKey(byte[] key) {
|
||||
if (key.length >= 32) {
|
||||
byte[] secretKey = new byte[32];
|
||||
System.arraycopy(key, 0, secretKey, 0, 32);
|
||||
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
||||
} else if (key.length >= 16) {
|
||||
byte[] secretKey = new byte[16];
|
||||
System.arraycopy(key, 0, secretKey, 0, 16);
|
||||
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
||||
} else {
|
||||
Log.d(Config.LOGTAG, "weird key");
|
||||
}
|
||||
Log.d(Config.LOGTAG,
|
||||
"using aes key "
|
||||
+ CryptoHelper.bytesToHex(this.aeskey.getEncoded()));
|
||||
}
|
||||
|
||||
public Key getKey() {
|
||||
return this.aeskey;
|
||||
}
|
||||
|
||||
public InputStream createInputStream() {
|
||||
if (this.getKey() == null) {
|
||||
try {
|
||||
return new FileInputStream(this);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
IvParameterSpec ips = new IvParameterSpec(iv);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, this.getKey(), ips);
|
||||
Log.d(Config.LOGTAG, "opening encrypted input stream");
|
||||
return new CipherInputStream(new FileInputStream(this), cipher);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
|
||||
return null;
|
||||
} catch (NoSuchPaddingException e) {
|
||||
Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
|
||||
return null;
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
|
||||
return null;
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
|
||||
return null;
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public OutputStream createOutputStream() {
|
||||
if (this.getKey() == null) {
|
||||
try {
|
||||
return new FileOutputStream(this);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
IvParameterSpec ips = new IvParameterSpec(iv);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, this.getKey(), ips);
|
||||
Log.d(Config.LOGTAG, "opening encrypted output stream");
|
||||
return new CipherOutputStream(new FileOutputStream(this),
|
||||
cipher);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
|
||||
return null;
|
||||
} catch (NoSuchPaddingException e) {
|
||||
Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
|
||||
return null;
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
|
||||
return null;
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
|
||||
return null;
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import org.openintents.openpgp.util.OpenPgpApi;
|
|||
import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.DownloadableFile;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
|
@ -22,7 +23,6 @@ import eu.siacs.conversations.entities.Conversation;
|
|||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.ui.UiCallback;
|
||||
import eu.siacs.conversations.xmpp.jingle.JingleFile;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.graphics.BitmapFactory;
|
||||
|
@ -86,10 +86,10 @@ public class PgpEngine {
|
|||
});
|
||||
} else if (message.getType() == Message.TYPE_IMAGE) {
|
||||
try {
|
||||
final JingleFile inputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getJingleFile(message, false);
|
||||
final JingleFile outputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getJingleFile(message, true);
|
||||
final DownloadableFile inputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getConversationsFile(message, false);
|
||||
final DownloadableFile outputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getConversationsFile(message, true);
|
||||
outputFile.createNewFile();
|
||||
InputStream is = new FileInputStream(inputFile);
|
||||
OutputStream os = new FileOutputStream(outputFile);
|
||||
|
@ -197,10 +197,10 @@ public class PgpEngine {
|
|||
});
|
||||
} else if (message.getType() == Message.TYPE_IMAGE) {
|
||||
try {
|
||||
JingleFile inputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getJingleFile(message, true);
|
||||
JingleFile outputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getJingleFile(message, false);
|
||||
DownloadableFile inputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getConversationsFile(message, true);
|
||||
DownloadableFile outputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getConversationsFile(message, false);
|
||||
outputFile.createNewFile();
|
||||
InputStream is = new FileInputStream(inputFile);
|
||||
OutputStream os = new FileOutputStream(outputFile);
|
||||
|
|
|
@ -374,4 +374,8 @@ public class Contact implements ListItem {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean trusted() {
|
||||
return getOption(Options.FROM) && getOption(Options.TO);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package eu.siacs.conversations.entities;
|
||||
|
||||
public interface Downloadable {
|
||||
|
||||
public final String[] VALID_EXTENSIONS = { "webp", "jpeg", "jpg", "png" };
|
||||
public final String[] VALID_CRYPTO_EXTENSIONS = { "pgp", "gpg", "otr" };
|
||||
|
||||
public void start();
|
||||
}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
package eu.siacs.conversations.entities;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.text.InputFilter.LengthFilter;
|
||||
|
||||
public class Message extends AbstractEntity {
|
||||
|
||||
|
@ -131,14 +136,8 @@ public class Message extends AbstractEntity {
|
|||
if (this.trueCounterpart == null) {
|
||||
return null;
|
||||
} else {
|
||||
Account account = this.conversation.getAccount();
|
||||
Contact contact = account.getRoster().getContact(
|
||||
return this.conversation.getAccount().getRoster().getContactFromRoster(
|
||||
this.trueCounterpart);
|
||||
if (contact.showInRoster()) {
|
||||
return contact;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -369,4 +368,32 @@ public class Message extends AbstractEntity {
|
|||
return prev.mergable(this);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean bodyContainsDownloadable() {
|
||||
Contact contact = this.getContact();
|
||||
if (contact == null || !contact.trusted()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
URL url = new URL(this.getBody());
|
||||
if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
|
||||
return false;
|
||||
}
|
||||
if (url.getPath()==null) {
|
||||
return false;
|
||||
}
|
||||
String[] pathParts = url.getPath().split("/");
|
||||
String filename = pathParts[pathParts.length - 1];
|
||||
String[] extensionParts = filename.split("\\.");
|
||||
if (extensionParts.length == 2 && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(extensionParts[extensionParts.length -1])) {
|
||||
return true;
|
||||
} else if (extensionParts.length == 3 && Arrays.asList(Downloadable.VALID_CRYPTO_EXTENSIONS).contains(extensionParts.length -1) && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(extensionParts[extensionParts.length -2])) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ public class Roster {
|
|||
this.account = account;
|
||||
}
|
||||
|
||||
public Contact getContactAsShownInRoster(String jid) {
|
||||
public Contact getContactFromRoster(String jid) {
|
||||
String cleanJid = jid.split("/", 2)[0];
|
||||
Contact contact = contacts.get(cleanJid);
|
||||
if (contact != null && contact.showInRoster()) {
|
||||
|
|
129
src/eu/siacs/conversations/http/HttpConnection.java
Normal file
129
src/eu/siacs/conversations/http/HttpConnection.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
package eu.siacs.conversations.http;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Downloadable;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
|
||||
public class HttpConnection implements Downloadable {
|
||||
|
||||
private HttpConnectionManager mHttpConnectionManager;
|
||||
private XmppConnectionService mXmppConnectionService;
|
||||
|
||||
private URL mUrl;
|
||||
private Message message;
|
||||
private DownloadableFile file;
|
||||
|
||||
public HttpConnection(HttpConnectionManager manager) {
|
||||
this.mHttpConnectionManager = manager;
|
||||
this.mXmppConnectionService = manager.getXmppConnectionService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
new Thread(new FileDownloader()).start();
|
||||
}
|
||||
|
||||
public void init(Message message) {
|
||||
this.message = message;
|
||||
this.message.setDownloadable(this);
|
||||
try {
|
||||
mUrl = new URL(message.getBody());
|
||||
this.file = mXmppConnectionService.getFileBackend().getConversationsFile(message,false);
|
||||
message.setType(Message.TYPE_IMAGE);
|
||||
mXmppConnectionService.markMessage(message, Message.STATUS_RECEIVED_OFFER);
|
||||
checkFileSize();
|
||||
} catch (MalformedURLException e) {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFileSize() {
|
||||
new Thread(new FileSizeChecker()).start();
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
mXmppConnectionService.markMessage(message, Message.STATUS_RECEPTION_FAILED);
|
||||
Log.d(Config.LOGTAG,"canceled download");
|
||||
}
|
||||
|
||||
private class FileSizeChecker implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long size = retrieveFileSize();
|
||||
file.setExpectedSize(size);
|
||||
if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
|
||||
start();
|
||||
}
|
||||
Log.d(Config.LOGTAG,"file size: "+size);
|
||||
} catch (IOException e) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private long retrieveFileSize() throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
|
||||
connection.setRequestMethod("HEAD");
|
||||
if (connection instanceof HttpsURLConnection) {
|
||||
|
||||
}
|
||||
String contentLength = connection.getHeaderField("Content-Length");
|
||||
if (contentLength == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(contentLength, 10);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class FileDownloader implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
mXmppConnectionService.markMessage(message, Message.STATUS_RECEIVING);
|
||||
download();
|
||||
mXmppConnectionService.markMessage(message, Message.STATUS_RECEIVED);
|
||||
} catch (IOException e) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void download() throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
|
||||
if (connection instanceof HttpsURLConnection) {
|
||||
|
||||
}
|
||||
BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
|
||||
OutputStream os = file.createOutputStream();
|
||||
int count = -1;
|
||||
byte[] buffer = new byte[1024];
|
||||
while ((count = is.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, count);
|
||||
}
|
||||
os.flush();
|
||||
os.close();
|
||||
is.close();
|
||||
Log.d(Config.LOGTAG,"finished downloading "+file.getAbsolutePath().toString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
27
src/eu/siacs/conversations/http/HttpConnectionManager.java
Normal file
27
src/eu/siacs/conversations/http/HttpConnectionManager.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
package eu.siacs.conversations.http;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import eu.siacs.conversations.AbstractConnectionManager;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
|
||||
public class HttpConnectionManager extends AbstractConnectionManager {
|
||||
|
||||
public HttpConnectionManager(XmppConnectionService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
private XmppConnectionService mXmppConnectionService;
|
||||
|
||||
private List<HttpConnection> connections = new CopyOnWriteArrayList<HttpConnection>();
|
||||
|
||||
|
||||
public HttpConnection createNewConnection(Message message) {
|
||||
HttpConnection connection = new HttpConnection(this);
|
||||
connection.init(message);
|
||||
this.connections.add(connection);
|
||||
return connection;
|
||||
}
|
||||
}
|
|
@ -478,6 +478,9 @@ public class MessageParser extends AbstractParser implements
|
|||
mXmppConnectionService.databaseBackend.createMessage(message);
|
||||
}
|
||||
}
|
||||
if (message.getStatus() == Message.STATUS_RECEIVED && message.bodyContainsDownloadable()) {
|
||||
this.mXmppConnectionService.getHttpConnectionManager().createNewConnection(message);
|
||||
}
|
||||
notify = notify && !conversation.isMuted();
|
||||
if (notify) {
|
||||
mXmppConnectionService.getNotificationService().push(message);
|
||||
|
|
|
@ -30,13 +30,13 @@ import android.util.Base64OutputStream;
|
|||
import android.util.Log;
|
||||
import android.util.LruCache;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.DownloadableFile;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.ImageProvider;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
import eu.siacs.conversations.xmpp.jingle.JingleFile;
|
||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||
|
||||
public class FileBackend {
|
||||
|
@ -66,11 +66,11 @@ public class FileBackend {
|
|||
return thumbnailCache;
|
||||
}
|
||||
|
||||
public JingleFile getJingleFileLegacy(Message message) {
|
||||
public DownloadableFile getJingleFileLegacy(Message message) {
|
||||
return getJingleFileLegacy(message, true);
|
||||
}
|
||||
|
||||
public JingleFile getJingleFileLegacy(Message message, boolean decrypted) {
|
||||
public DownloadableFile getJingleFileLegacy(Message message, boolean decrypted) {
|
||||
Conversation conversation = message.getConversation();
|
||||
String prefix = context.getFilesDir().getAbsolutePath();
|
||||
String path = prefix + "/" + conversation.getAccount().getJid() + "/"
|
||||
|
@ -85,14 +85,14 @@ public class FileBackend {
|
|||
filename = message.getUuid() + ".webp.pgp";
|
||||
}
|
||||
}
|
||||
return new JingleFile(path + "/" + filename);
|
||||
return new DownloadableFile(path + "/" + filename);
|
||||
}
|
||||
|
||||
public JingleFile getJingleFile(Message message) {
|
||||
return getJingleFile(message, true);
|
||||
public DownloadableFile getJingleFile(Message message) {
|
||||
return getConversationsFile(message, true);
|
||||
}
|
||||
|
||||
public JingleFile getJingleFile(Message message, boolean decrypted) {
|
||||
public DownloadableFile getConversationsFile(Message message, boolean decrypted) {
|
||||
StringBuilder filename = new StringBuilder();
|
||||
filename.append(Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_PICTURES).getAbsolutePath());
|
||||
|
@ -107,7 +107,7 @@ public class FileBackend {
|
|||
filename.append(".webp.pgp");
|
||||
}
|
||||
}
|
||||
return new JingleFile(filename.toString());
|
||||
return new DownloadableFile(filename.toString());
|
||||
}
|
||||
|
||||
public Bitmap resize(Bitmap originalBitmap, int size) {
|
||||
|
@ -139,17 +139,17 @@ public class FileBackend {
|
|||
return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
|
||||
}
|
||||
|
||||
public JingleFile copyImageToPrivateStorage(Message message, Uri image)
|
||||
public DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
|
||||
throws ImageCopyException {
|
||||
return this.copyImageToPrivateStorage(message, image, 0);
|
||||
}
|
||||
|
||||
private JingleFile copyImageToPrivateStorage(Message message, Uri image,
|
||||
private DownloadableFile copyImageToPrivateStorage(Message message, Uri image,
|
||||
int sampleSize) throws ImageCopyException {
|
||||
try {
|
||||
InputStream is = context.getContentResolver()
|
||||
.openInputStream(image);
|
||||
JingleFile file = getJingleFile(message);
|
||||
DownloadableFile file = getJingleFile(message);
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
Bitmap originalBitmap;
|
||||
|
|
|
@ -34,6 +34,7 @@ import eu.siacs.conversations.entities.Presences;
|
|||
import eu.siacs.conversations.generator.IqGenerator;
|
||||
import eu.siacs.conversations.generator.MessageGenerator;
|
||||
import eu.siacs.conversations.generator.PresenceGenerator;
|
||||
import eu.siacs.conversations.http.HttpConnectionManager;
|
||||
import eu.siacs.conversations.parser.IqParser;
|
||||
import eu.siacs.conversations.parser.MessageParser;
|
||||
import eu.siacs.conversations.parser.PresenceParser;
|
||||
|
@ -106,6 +107,7 @@ public class XmppConnectionService extends Service {
|
|||
private CopyOnWriteArrayList<Conversation> conversations = null;
|
||||
private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
|
||||
this);
|
||||
private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(this);
|
||||
|
||||
private OnConversationUpdate mOnConversationUpdate = null;
|
||||
private int convChangedListenerCount = 0;
|
||||
|
@ -1780,7 +1782,7 @@ public class XmppConnectionService extends Service {
|
|||
for (Account account : getAccounts()) {
|
||||
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
||||
Contact contact = account.getRoster()
|
||||
.getContactAsShownInRoster(jid);
|
||||
.getContactFromRoster(jid);
|
||||
if (contact != null) {
|
||||
contacts.add(contact);
|
||||
}
|
||||
|
@ -1792,4 +1794,8 @@ public class XmppConnectionService extends Service {
|
|||
public NotificationService getNotificationService() {
|
||||
return this.mNotificationService;
|
||||
}
|
||||
|
||||
public HttpConnectionManager getHttpConnectionManager() {
|
||||
return this.mHttpConnectionManager;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import android.graphics.BitmapFactory;
|
|||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Downloadable;
|
||||
|
@ -54,7 +55,7 @@ public class JingleConnection implements Downloadable {
|
|||
|
||||
private String transportId;
|
||||
private Element fileOffer;
|
||||
private JingleFile file = null;
|
||||
private DownloadableFile file = null;
|
||||
|
||||
private String contentName;
|
||||
private String contentCreator;
|
||||
|
@ -83,7 +84,7 @@ public class JingleConnection implements Downloadable {
|
|||
final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() {
|
||||
|
||||
@Override
|
||||
public void onFileTransmitted(JingleFile file) {
|
||||
public void onFileTransmitted(DownloadableFile file) {
|
||||
if (responder.equals(account.getFullJid())) {
|
||||
sendSuccess();
|
||||
if (acceptedAutomatically) {
|
||||
|
@ -323,7 +324,7 @@ public class JingleConnection implements Downloadable {
|
|||
.push(message);
|
||||
}
|
||||
this.file = this.mXmppConnectionService.getFileBackend()
|
||||
.getJingleFile(message, false);
|
||||
.getConversationsFile(message, false);
|
||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||
byte[] key = conversation.getSymmetricKey();
|
||||
if (key == null) {
|
||||
|
@ -355,7 +356,7 @@ public class JingleConnection implements Downloadable {
|
|||
if (message.getType() == Message.TYPE_IMAGE) {
|
||||
content.setTransportId(this.transportId);
|
||||
this.file = this.mXmppConnectionService.getFileBackend()
|
||||
.getJingleFile(message, false);
|
||||
.getConversationsFile(message, false);
|
||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||
Conversation conversation = this.message.getConversation();
|
||||
this.mXmppConnectionService.renewSymmetricKey(conversation);
|
||||
|
|
|
@ -7,6 +7,7 @@ import java.util.List;
|
|||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.util.Log;
|
||||
import eu.siacs.conversations.AbstractConnectionManager;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
|
@ -16,10 +17,7 @@ import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
|||
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
|
||||
public class JingleConnectionManager {
|
||||
|
||||
private XmppConnectionService xmppConnectionService;
|
||||
|
||||
public class JingleConnectionManager extends AbstractConnectionManager {
|
||||
private List<JingleConnection> connections = new CopyOnWriteArrayList<JingleConnection>();
|
||||
|
||||
private HashMap<String, JingleCandidate> primaryCandidates = new HashMap<String, JingleCandidate>();
|
||||
|
@ -28,7 +26,7 @@ public class JingleConnectionManager {
|
|||
private SecureRandom random = new SecureRandom();
|
||||
|
||||
public JingleConnectionManager(XmppConnectionService service) {
|
||||
this.xmppConnectionService = service;
|
||||
super(service);
|
||||
}
|
||||
|
||||
public void deliverPacket(Account account, JinglePacket packet) {
|
||||
|
@ -68,10 +66,6 @@ public class JingleConnectionManager {
|
|||
this.connections.remove(connection);
|
||||
}
|
||||
|
||||
public XmppConnectionService getXmppConnectionService() {
|
||||
return this.xmppConnectionService;
|
||||
}
|
||||
|
||||
public void getPrimaryCandidate(Account account,
|
||||
final OnPrimaryCandidateFound listener) {
|
||||
if (!this.primaryCandidates.containsKey(account.getJid())) {
|
||||
|
@ -128,16 +122,6 @@ public class JingleConnectionManager {
|
|||
return new BigInteger(50, random).toString(32);
|
||||
}
|
||||
|
||||
public long getAutoAcceptFileSize() {
|
||||
String config = this.xmppConnectionService.getPreferences().getString(
|
||||
"auto_accept_file_size", "524288");
|
||||
try {
|
||||
return Long.parseLong(config);
|
||||
} catch (NumberFormatException e) {
|
||||
return 524288;
|
||||
}
|
||||
}
|
||||
|
||||
public void deliverIbbPacket(Account account, IqPacket packet) {
|
||||
String sid = null;
|
||||
Element payload = null;
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.Key;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
import android.util.Log;
|
||||
|
||||
public class JingleFile extends File {
|
||||
|
||||
private static final long serialVersionUID = 2247012619505115863L;
|
||||
|
||||
private long expectedSize = 0;
|
||||
private String sha1sum;
|
||||
private Key aeskey;
|
||||
|
||||
public JingleFile(String path) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return super.length();
|
||||
}
|
||||
|
||||
public long getExpectedSize() {
|
||||
if (this.aeskey != null) {
|
||||
return (this.expectedSize / 16 + 1) * 16;
|
||||
} else {
|
||||
return this.expectedSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void setExpectedSize(long size) {
|
||||
this.expectedSize = size;
|
||||
}
|
||||
|
||||
public String getSha1Sum() {
|
||||
return this.sha1sum;
|
||||
}
|
||||
|
||||
public void setSha1Sum(String sum) {
|
||||
this.sha1sum = sum;
|
||||
}
|
||||
|
||||
public void setKey(byte[] key) {
|
||||
if (key.length >= 32) {
|
||||
byte[] secretKey = new byte[32];
|
||||
System.arraycopy(key, 0, secretKey, 0, 32);
|
||||
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
||||
} else if (key.length >= 16) {
|
||||
byte[] secretKey = new byte[16];
|
||||
System.arraycopy(key, 0, secretKey, 0, 16);
|
||||
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
||||
} else {
|
||||
Log.d(Config.LOGTAG, "weird key");
|
||||
}
|
||||
Log.d(Config.LOGTAG,
|
||||
"using aes key "
|
||||
+ CryptoHelper.bytesToHex(this.aeskey.getEncoded()));
|
||||
}
|
||||
|
||||
public Key getKey() {
|
||||
return this.aeskey;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
@ -9,6 +8,7 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.util.Arrays;
|
||||
|
||||
import android.util.Base64;
|
||||
import eu.siacs.conversations.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
@ -26,7 +26,7 @@ public class JingleInbandTransport extends JingleTransport {
|
|||
|
||||
private boolean established = false;
|
||||
|
||||
private JingleFile file;
|
||||
private DownloadableFile file;
|
||||
|
||||
private InputStream fileInputStream = null;
|
||||
private OutputStream fileOutputStream;
|
||||
|
@ -77,7 +77,7 @@ public class JingleInbandTransport extends JingleTransport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void receive(JingleFile file,
|
||||
public void receive(DownloadableFile file,
|
||||
OnFileTransmissionStatusChanged callback) {
|
||||
this.onFileTransmissionStatusChanged = callback;
|
||||
this.file = file;
|
||||
|
@ -86,7 +86,7 @@ public class JingleInbandTransport extends JingleTransport {
|
|||
digest.reset();
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
this.fileOutputStream = getOutputStream(file);
|
||||
this.fileOutputStream = file.createOutputStream();
|
||||
if (this.fileOutputStream == null) {
|
||||
callback.onFileTransferAborted();
|
||||
return;
|
||||
|
@ -100,20 +100,18 @@ public class JingleInbandTransport extends JingleTransport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void send(JingleFile file, OnFileTransmissionStatusChanged callback) {
|
||||
public void send(DownloadableFile file, OnFileTransmissionStatusChanged callback) {
|
||||
this.onFileTransmissionStatusChanged = callback;
|
||||
this.file = file;
|
||||
try {
|
||||
this.digest = MessageDigest.getInstance("SHA-1");
|
||||
this.digest.reset();
|
||||
fileInputStream = this.getInputStream(file);
|
||||
fileInputStream = this.file.createInputStream();
|
||||
if (fileInputStream == null) {
|
||||
callback.onFileTransferAborted();
|
||||
return;
|
||||
}
|
||||
this.sendNextBlock();
|
||||
} catch (FileNotFoundException e) {
|
||||
callback.onFileTransferAborted();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
callback.onFileTransferAborted();
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.security.MessageDigest;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import eu.siacs.conversations.DownloadableFile;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
|
||||
public class JingleSocks5Transport extends JingleTransport {
|
||||
|
@ -86,7 +87,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
|||
|
||||
}
|
||||
|
||||
public void send(final JingleFile file,
|
||||
public void send(final DownloadableFile file,
|
||||
final OnFileTransmissionStatusChanged callback) {
|
||||
new Thread(new Runnable() {
|
||||
|
||||
|
@ -96,7 +97,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
|||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
digest.reset();
|
||||
fileInputStream = getInputStream(file);
|
||||
fileInputStream = file.createInputStream();
|
||||
if (fileInputStream == null) {
|
||||
callback.onFileTransferAborted();
|
||||
return;
|
||||
|
@ -132,7 +133,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
|||
|
||||
}
|
||||
|
||||
public void receive(final JingleFile file,
|
||||
public void receive(final DownloadableFile file,
|
||||
final OnFileTransmissionStatusChanged callback) {
|
||||
new Thread(new Runnable() {
|
||||
|
||||
|
@ -145,7 +146,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
|||
socket.setSoTimeout(30000);
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
OutputStream fileOutputStream = getOutputStream(file);
|
||||
OutputStream fileOutputStream = file.createOutputStream();
|
||||
if (fileOutputStream == null) {
|
||||
callback.onFileTransferAborted();
|
||||
return;
|
||||
|
|
|
@ -1,88 +1,13 @@
|
|||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
|
||||
import android.util.Log;
|
||||
import eu.siacs.conversations.DownloadableFile;
|
||||
|
||||
public abstract class JingleTransport {
|
||||
public abstract void connect(final OnTransportConnected callback);
|
||||
|
||||
public abstract void receive(final JingleFile file,
|
||||
public abstract void receive(final DownloadableFile file,
|
||||
final OnFileTransmissionStatusChanged callback);
|
||||
|
||||
public abstract void send(final JingleFile file,
|
||||
public abstract void send(final DownloadableFile file,
|
||||
final OnFileTransmissionStatusChanged callback);
|
||||
|
||||
private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
|
||||
|
||||
protected InputStream getInputStream(JingleFile file)
|
||||
throws FileNotFoundException {
|
||||
if (file.getKey() == null) {
|
||||
return new FileInputStream(file);
|
||||
} else {
|
||||
try {
|
||||
IvParameterSpec ips = new IvParameterSpec(iv);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, file.getKey(), ips);
|
||||
Log.d(Config.LOGTAG, "opening encrypted input stream");
|
||||
return new CipherInputStream(new FileInputStream(file), cipher);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
|
||||
return null;
|
||||
} catch (NoSuchPaddingException e) {
|
||||
Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
|
||||
return null;
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
|
||||
return null;
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected OutputStream getOutputStream(JingleFile file)
|
||||
throws FileNotFoundException {
|
||||
if (file.getKey() == null) {
|
||||
return new FileOutputStream(file);
|
||||
} else {
|
||||
try {
|
||||
IvParameterSpec ips = new IvParameterSpec(iv);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, file.getKey(), ips);
|
||||
Log.d(Config.LOGTAG, "opening encrypted output stream");
|
||||
return new CipherOutputStream(new FileOutputStream(file),
|
||||
cipher);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
|
||||
return null;
|
||||
} catch (NoSuchPaddingException e) {
|
||||
Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
|
||||
return null;
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
|
||||
return null;
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import eu.siacs.conversations.DownloadableFile;
|
||||
|
||||
public interface OnFileTransmissionStatusChanged {
|
||||
public void onFileTransmitted(JingleFile file);
|
||||
public void onFileTransmitted(DownloadableFile file);
|
||||
|
||||
public void onFileTransferAborted();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package eu.siacs.conversations.xmpp.jingle.stanzas;
|
||||
|
||||
import eu.siacs.conversations.DownloadableFile;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.jingle.JingleFile;
|
||||
|
||||
public class Content extends Element {
|
||||
|
||||
|
@ -25,7 +25,7 @@ public class Content extends Element {
|
|||
this.transportId = sid;
|
||||
}
|
||||
|
||||
public void setFileOffer(JingleFile actualFile, boolean otr) {
|
||||
public void setFileOffer(DownloadableFile actualFile, boolean otr) {
|
||||
Element description = this.addChild("description",
|
||||
"urn:xmpp:jingle:apps:file-transfer:3");
|
||||
Element offer = description.addChild("offer");
|
||||
|
|
Loading…
Reference in a new issue