basic image over http downloading

iNPUTmice created

Change summary

src/eu/siacs/conversations/AbstractConnectionManager.java                   |  25 
src/eu/siacs/conversations/DownloadableFile.java                            | 148 
src/eu/siacs/conversations/crypto/PgpEngine.java                            |  18 
src/eu/siacs/conversations/entities/Contact.java                            |   4 
src/eu/siacs/conversations/entities/Downloadable.java                       |   4 
src/eu/siacs/conversations/entities/Message.java                            |  41 
src/eu/siacs/conversations/entities/Roster.java                             |   2 
src/eu/siacs/conversations/http/HttpConnection.java                         | 129 
src/eu/siacs/conversations/http/HttpConnectionManager.java                  |  27 
src/eu/siacs/conversations/parser/MessageParser.java                        |   3 
src/eu/siacs/conversations/persistance/FileBackend.java                     |  22 
src/eu/siacs/conversations/services/XmppConnectionService.java              |   8 
src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java                |   9 
src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java         |  22 
src/eu/siacs/conversations/xmpp/jingle/JingleFile.java                      |  68 
src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java           |  14 
src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java           |   9 
src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java                 |  81 
src/eu/siacs/conversations/xmpp/jingle/OnFileTransmissionStatusChanged.java |   4 
src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java                 |   4 
20 files changed, 429 insertions(+), 213 deletions(-)

Detailed changes

src/eu/siacs/conversations/AbstractConnectionManager.java 🔗

@@ -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;
+		}
+	}
+}

src/eu/siacs/conversations/DownloadableFile.java 🔗

@@ -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;
+			}
+		}
+	}
+}

src/eu/siacs/conversations/crypto/PgpEngine.java 🔗

@@ -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);

src/eu/siacs/conversations/entities/Downloadable.java 🔗

@@ -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();
 }

src/eu/siacs/conversations/entities/Message.java 🔗

@@ -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;
+		}
+	}
 }

src/eu/siacs/conversations/entities/Roster.java 🔗

@@ -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()) {

src/eu/siacs/conversations/http/HttpConnection.java 🔗

@@ -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());
+		}
+		
+	}
+}

src/eu/siacs/conversations/http/HttpConnectionManager.java 🔗

@@ -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;
+	}
+}

src/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -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);

src/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -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;

src/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -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;
+	}
 }

src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java 🔗

@@ -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);

src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java 🔗

@@ -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;

src/eu/siacs/conversations/xmpp/jingle/JingleFile.java 🔗

@@ -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;
-	}
-}

src/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java 🔗

@@ -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();
 		}

src/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java 🔗

@@ -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;

src/eu/siacs/conversations/xmpp/jingle/JingleTransport.java 🔗

@@ -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;
-			}
-		}
-	}
 }

src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java 🔗

@@ -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");