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