HttpDownloadConnection.java

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