HttpConnection.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 org.apache.http.conn.ssl.StrictHostnameVerifier;
  9
 10import java.io.BufferedInputStream;
 11import java.io.IOException;
 12import java.io.OutputStream;
 13import java.net.HttpURLConnection;
 14import java.net.MalformedURLException;
 15import java.net.URL;
 16import java.security.KeyManagementException;
 17import java.security.NoSuchAlgorithmException;
 18import java.util.Arrays;
 19
 20import javax.net.ssl.HostnameVerifier;
 21import javax.net.ssl.HttpsURLConnection;
 22import javax.net.ssl.SSLContext;
 23import javax.net.ssl.SSLHandshakeException;
 24import javax.net.ssl.SSLSocketFactory;
 25import javax.net.ssl.X509TrustManager;
 26
 27import eu.siacs.conversations.Config;
 28import eu.siacs.conversations.R;
 29import eu.siacs.conversations.entities.Downloadable;
 30import eu.siacs.conversations.entities.DownloadableFile;
 31import eu.siacs.conversations.entities.Message;
 32import eu.siacs.conversations.services.XmppConnectionService;
 33import eu.siacs.conversations.utils.CryptoHelper;
 34
 35public class HttpConnection implements Downloadable {
 36
 37	private HttpConnectionManager mHttpConnectionManager;
 38	private XmppConnectionService mXmppConnectionService;
 39
 40	private URL mUrl;
 41	private Message message;
 42	private DownloadableFile file;
 43	private int mStatus = Downloadable.STATUS_UNKNOWN;
 44	private boolean acceptedAutomatically = false;
 45	private int mProgress = 0;
 46	private long mLastGuiRefresh = 0;
 47
 48	public HttpConnection(HttpConnectionManager manager) {
 49		this.mHttpConnectionManager = manager;
 50		this.mXmppConnectionService = manager.getXmppConnectionService();
 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.setDownloadable(this);
 74		try {
 75			mUrl = new URL(message.getBody());
 76			String[] parts = mUrl.getPath().toLowerCase().split("\\.");
 77			String lastPart = parts.length >= 1 ? parts[parts.length - 1] : null;
 78			String secondToLast = parts.length >= 2 ? parts[parts.length -2] : null;
 79			if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) {
 80				this.message.setEncryption(Message.ENCRYPTION_PGP);
 81			} else if (message.getEncryption() != Message.ENCRYPTION_OTR) {
 82				this.message.setEncryption(Message.ENCRYPTION_NONE);
 83			}
 84			String extension;
 85			if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(lastPart)) {
 86				extension = secondToLast;
 87			} else {
 88				extension = lastPart;
 89			}
 90			message.setRelativeFilePath(message.getUuid()+"."+extension);
 91			this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
 92			String reference = mUrl.getRef();
 93			if (reference != null && reference.length() == 96) {
 94				this.file.setKey(CryptoHelper.hexToBytes(reference));
 95			}
 96
 97			if (this.message.getEncryption() == Message.ENCRYPTION_OTR
 98					&& this.file.getKey() == null) {
 99				this.message.setEncryption(Message.ENCRYPTION_NONE);
100					}
101			checkFileSize(true);
102		} catch (MalformedURLException e) {
103			this.cancel();
104		}
105	}
106
107	private void checkFileSize(boolean interactive) {
108		new Thread(new FileSizeChecker(interactive)).start();
109	}
110
111	public void cancel() {
112		mHttpConnectionManager.finishConnection(this);
113		message.setDownloadable(null);
114		mXmppConnectionService.updateConversationUi();
115	}
116
117	private void finish() {
118		Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
119		intent.setData(Uri.fromFile(file));
120		mXmppConnectionService.sendBroadcast(intent);
121		message.setDownloadable(null);
122		mHttpConnectionManager.finishConnection(this);
123		mXmppConnectionService.updateConversationUi();
124		if (acceptedAutomatically) {
125			mXmppConnectionService.getNotificationService().push(message);
126		}
127	}
128
129	private void changeStatus(int status) {
130		this.mStatus = status;
131		mXmppConnectionService.updateConversationUi();
132	}
133
134	private void setupTrustManager(final HttpsURLConnection connection,
135			final boolean interactive) {
136		final X509TrustManager trustManager;
137		final HostnameVerifier hostnameVerifier;
138		if (interactive) {
139			trustManager = mXmppConnectionService.getMemorizingTrustManager();
140			hostnameVerifier = mXmppConnectionService
141				.getMemorizingTrustManager().wrapHostnameVerifier(
142						new StrictHostnameVerifier());
143		} else {
144			trustManager = mXmppConnectionService.getMemorizingTrustManager()
145				.getNonInteractive();
146			hostnameVerifier = mXmppConnectionService
147				.getMemorizingTrustManager()
148				.wrapHostnameVerifierNonInteractive(
149						new StrictHostnameVerifier());
150		}
151		try {
152			final SSLContext sc = SSLContext.getInstance("TLS");
153			sc.init(null, new X509TrustManager[]{trustManager},
154					mXmppConnectionService.getRNG());
155
156			final SSLSocketFactory sf = sc.getSocketFactory();
157			final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
158					sf.getSupportedCipherSuites());
159			if (cipherSuites.length > 0) {
160				sc.getDefaultSSLParameters().setCipherSuites(cipherSuites);
161
162			}
163
164			connection.setSSLSocketFactory(sf);
165			connection.setHostnameVerifier(hostnameVerifier);
166		} catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
167		}
168	}
169
170	private class FileSizeChecker implements Runnable {
171
172		private boolean interactive = false;
173
174		public FileSizeChecker(boolean interactive) {
175			this.interactive = interactive;
176		}
177
178		@Override
179		public void run() {
180			long size;
181			try {
182				size = retrieveFileSize();
183			} catch (SSLHandshakeException e) {
184				changeStatus(STATUS_OFFER_CHECK_FILESIZE);
185				HttpConnection.this.acceptedAutomatically = false;
186				HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
187				return;
188			} catch (IOException e) {
189				Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage());
190				if (interactive) {
191					mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
192				}
193				cancel();
194				return;
195			}
196			file.setExpectedSize(size);
197			if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
198				HttpConnection.this.acceptedAutomatically = true;
199				new Thread(new FileDownloader(interactive)).start();
200			} else {
201				changeStatus(STATUS_OFFER);
202				HttpConnection.this.acceptedAutomatically = false;
203				HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
204			}
205		}
206
207		private long retrieveFileSize() throws IOException {
208			Log.d(Config.LOGTAG,"retrieve file size. interactive:"+String.valueOf(interactive));
209			changeStatus(STATUS_CHECKING);
210			HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
211			connection.setRequestMethod("HEAD");
212			if (connection instanceof HttpsURLConnection) {
213				setupTrustManager((HttpsURLConnection) connection, interactive);
214			}
215			connection.connect();
216			String contentLength = connection.getHeaderField("Content-Length");
217			if (contentLength == null) {
218				throw new IOException();
219			}
220			try {
221				return Long.parseLong(contentLength, 10);
222			} catch (NumberFormatException e) {
223				throw new IOException();
224			}
225		}
226
227	}
228
229	private class FileDownloader implements Runnable {
230
231		private boolean interactive = false;
232
233		public FileDownloader(boolean interactive) {
234			this.interactive = interactive;
235		}
236
237		@Override
238		public void run() {
239			try {
240				changeStatus(STATUS_DOWNLOADING);
241				download();
242				updateImageBounds();
243				finish();
244			} catch (SSLHandshakeException e) {
245				changeStatus(STATUS_OFFER);
246			} catch (IOException e) {
247				mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
248				cancel();
249			}
250		}
251
252		private void download() throws SSLHandshakeException, IOException {
253			HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
254			if (connection instanceof HttpsURLConnection) {
255				setupTrustManager((HttpsURLConnection) connection, interactive);
256			}
257			connection.connect();
258			BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
259			file.getParentFile().mkdirs();
260			file.createNewFile();
261			OutputStream os = file.createOutputStream();
262			if (os == null) {
263				throw new IOException();
264			}
265			long transmitted = 0;
266			long expected = file.getExpectedSize();
267			int count = -1;
268			byte[] buffer = new byte[1024];
269			while ((count = is.read(buffer)) != -1) {
270				transmitted += count;
271				os.write(buffer, 0, count);
272				updateProgress((int) ((((double) transmitted) / expected) * 100));
273			}
274			os.flush();
275			os.close();
276			is.close();
277		}
278
279		private void updateImageBounds() {
280			message.setType(Message.TYPE_FILE);
281			mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl);
282			mXmppConnectionService.updateMessage(message);
283		}
284
285	}
286
287	public void updateProgress(int i) {
288		this.mProgress = i;
289		if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) {
290			this.mLastGuiRefresh = SystemClock.elapsedRealtime();
291			mXmppConnectionService.updateConversationUi();
292		}
293	}
294
295	@Override
296	public int getStatus() {
297		return this.mStatus;
298	}
299
300	@Override
301	public long getFileSize() {
302		if (this.file != null) {
303			return this.file.getExpectedSize();
304		} else {
305			return 0;
306		}
307	}
308
309	@Override
310	public int getProgress() {
311		return this.mProgress;
312	}
313}