HttpDownloadConnection.java

  1package eu.siacs.conversations.http;
  2
  3import android.content.Intent;
  4import android.net.Uri;
  5import android.os.SystemClock;
  6import android.util.Log;
  7
  8import java.io.BufferedInputStream;
  9import java.io.IOException;
 10import java.io.OutputStream;
 11import java.net.HttpURLConnection;
 12import java.net.MalformedURLException;
 13import java.net.URL;
 14import java.util.Arrays;
 15
 16import javax.net.ssl.HttpsURLConnection;
 17import javax.net.ssl.SSLHandshakeException;
 18
 19import eu.siacs.conversations.Config;
 20import eu.siacs.conversations.R;
 21import eu.siacs.conversations.entities.DownloadableFile;
 22import eu.siacs.conversations.entities.Message;
 23import eu.siacs.conversations.entities.Transferable;
 24import eu.siacs.conversations.services.XmppConnectionService;
 25import eu.siacs.conversations.utils.CryptoHelper;
 26
 27public class HttpDownloadConnection implements Transferable {
 28
 29	private HttpConnectionManager mHttpConnectionManager;
 30	private XmppConnectionService mXmppConnectionService;
 31
 32	private URL mUrl;
 33	private Message message;
 34	private DownloadableFile file;
 35	private int mStatus = Transferable.STATUS_UNKNOWN;
 36	private boolean acceptedAutomatically = false;
 37	private int mProgress = 0;
 38
 39	public HttpDownloadConnection(HttpConnectionManager manager) {
 40		this.mHttpConnectionManager = manager;
 41		this.mXmppConnectionService = manager.getXmppConnectionService();
 42	}
 43
 44	@Override
 45	public boolean start() {
 46		if (mXmppConnectionService.hasInternetConnection()) {
 47			if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) {
 48				checkFileSize(true);
 49			} else {
 50				new Thread(new FileDownloader(true)).start();
 51			}
 52			return true;
 53		} else {
 54			return false;
 55		}
 56	}
 57
 58	public void init(Message message) {
 59		init(message, false);
 60	}
 61
 62	public void init(Message message, boolean interactive) {
 63		this.message = message;
 64		this.message.setTransferable(this);
 65		try {
 66			mUrl = new URL(message.getBody());
 67			String[] parts = mUrl.getPath().toLowerCase().split("\\.");
 68			String lastPart = parts.length >= 1 ? parts[parts.length - 1] : null;
 69			String secondToLast = parts.length >= 2 ? parts[parts.length -2] : null;
 70			if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) {
 71				this.message.setEncryption(Message.ENCRYPTION_PGP);
 72			} else if (message.getEncryption() != Message.ENCRYPTION_OTR
 73					&& message.getEncryption() != Message.ENCRYPTION_AXOLOTL) {
 74				this.message.setEncryption(Message.ENCRYPTION_NONE);
 75			}
 76			String extension;
 77			if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(lastPart)) {
 78				extension = secondToLast;
 79			} else {
 80				extension = lastPart;
 81			}
 82			message.setRelativeFilePath(message.getUuid()+"."+extension);
 83			this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
 84			String reference = mUrl.getRef();
 85			if (reference != null && reference.length() == 96) {
 86				this.file.setKey(CryptoHelper.hexToBytes(reference));
 87			}
 88
 89			if ((this.message.getEncryption() == Message.ENCRYPTION_OTR
 90					|| this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL)
 91					&& this.file.getKey() == null) {
 92				this.message.setEncryption(Message.ENCRYPTION_NONE);
 93					}
 94			checkFileSize(interactive);
 95		} catch (MalformedURLException e) {
 96			this.cancel();
 97		}
 98	}
 99
100	private void checkFileSize(boolean interactive) {
101		new Thread(new FileSizeChecker(interactive)).start();
102	}
103
104	public void cancel() {
105		mHttpConnectionManager.finishConnection(this);
106		message.setTransferable(null);
107		mXmppConnectionService.updateConversationUi();
108	}
109
110	private void finish() {
111		Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
112		intent.setData(Uri.fromFile(file));
113		mXmppConnectionService.sendBroadcast(intent);
114		message.setTransferable(null);
115		mHttpConnectionManager.finishConnection(this);
116		mXmppConnectionService.updateConversationUi();
117		if (acceptedAutomatically) {
118			mXmppConnectionService.getNotificationService().push(message);
119		}
120	}
121
122	private void changeStatus(int status) {
123		this.mStatus = status;
124		mXmppConnectionService.updateConversationUi();
125	}
126
127	private class FileSizeChecker implements Runnable {
128
129		private boolean interactive = false;
130
131		public FileSizeChecker(boolean interactive) {
132			this.interactive = interactive;
133		}
134
135		@Override
136		public void run() {
137			long size;
138			try {
139				size = retrieveFileSize();
140			} catch (SSLHandshakeException e) {
141				changeStatus(STATUS_OFFER_CHECK_FILESIZE);
142				HttpDownloadConnection.this.acceptedAutomatically = false;
143				HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
144				return;
145			} catch (IOException e) {
146				Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage());
147				if (interactive) {
148					mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
149				}
150				cancel();
151				return;
152			}
153			file.setExpectedSize(size);
154			if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
155				HttpDownloadConnection.this.acceptedAutomatically = true;
156				new Thread(new FileDownloader(interactive)).start();
157			} else {
158				changeStatus(STATUS_OFFER);
159				HttpDownloadConnection.this.acceptedAutomatically = false;
160				HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
161			}
162		}
163
164		private long retrieveFileSize() throws IOException {
165			Log.d(Config.LOGTAG,"retrieve file size. interactive:"+String.valueOf(interactive));
166			changeStatus(STATUS_CHECKING);
167			HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
168			connection.setRequestMethod("HEAD");
169			if (connection instanceof HttpsURLConnection) {
170				mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
171			}
172			connection.connect();
173			String contentLength = connection.getHeaderField("Content-Length");
174			if (contentLength == null) {
175				throw new IOException();
176			}
177			try {
178				return Long.parseLong(contentLength, 10);
179			} catch (NumberFormatException e) {
180				throw new IOException();
181			}
182		}
183
184	}
185
186	private class FileDownloader implements Runnable {
187
188		private boolean interactive = false;
189
190		public FileDownloader(boolean interactive) {
191			this.interactive = interactive;
192		}
193
194		@Override
195		public void run() {
196			try {
197				changeStatus(STATUS_DOWNLOADING);
198				download();
199				updateImageBounds();
200				finish();
201			} catch (SSLHandshakeException e) {
202				changeStatus(STATUS_OFFER);
203			} catch (IOException e) {
204				mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
205				cancel();
206			}
207		}
208
209		private void download() throws SSLHandshakeException, IOException {
210			HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
211			if (connection instanceof HttpsURLConnection) {
212				mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
213			}
214			connection.connect();
215			BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
216			file.getParentFile().mkdirs();
217			file.createNewFile();
218			OutputStream os = file.createOutputStream();
219			if (os == null) {
220				throw new IOException();
221			}
222			long transmitted = 0;
223			long expected = file.getExpectedSize();
224			int count = -1;
225			byte[] buffer = new byte[1024];
226			while ((count = is.read(buffer)) != -1) {
227				transmitted += count;
228				os.write(buffer, 0, count);
229				updateProgress((int) ((((double) transmitted) / expected) * 100));
230			}
231			os.flush();
232			os.close();
233			is.close();
234		}
235
236		private void updateImageBounds() {
237			message.setType(Message.TYPE_FILE);
238			mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl);
239			mXmppConnectionService.updateMessage(message);
240		}
241
242	}
243
244	public void updateProgress(int i) {
245		this.mProgress = i;
246		mXmppConnectionService.updateConversationUi();
247	}
248
249	@Override
250	public int getStatus() {
251		return this.mStatus;
252	}
253
254	@Override
255	public long getFileSize() {
256		if (this.file != null) {
257			return this.file.getExpectedSize();
258		} else {
259			return 0;
260		}
261	}
262
263	@Override
264	public int getProgress() {
265		return this.mProgress;
266	}
267}