HttpDownloadConnection.java

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