HttpConnection.java

  1package eu.siacs.conversations.http;
  2
  3import android.content.Intent;
  4import android.net.Uri;
  5import android.os.SystemClock;
  6
  7import org.apache.http.conn.ssl.StrictHostnameVerifier;
  8
  9import java.io.BufferedInputStream;
 10import java.io.IOException;
 11import java.io.OutputStream;
 12import java.net.HttpURLConnection;
 13import java.net.MalformedURLException;
 14import java.net.URL;
 15import java.security.KeyManagementException;
 16import java.security.NoSuchAlgorithmException;
 17import java.util.Arrays;
 18
 19import javax.net.ssl.HostnameVerifier;
 20import javax.net.ssl.HttpsURLConnection;
 21import javax.net.ssl.SSLContext;
 22import javax.net.ssl.SSLHandshakeException;
 23import javax.net.ssl.X509TrustManager;
 24
 25import eu.siacs.conversations.Config;
 26import eu.siacs.conversations.entities.Downloadable;
 27import eu.siacs.conversations.entities.DownloadableFile;
 28import eu.siacs.conversations.entities.Message;
 29import eu.siacs.conversations.services.XmppConnectionService;
 30import eu.siacs.conversations.utils.CryptoHelper;
 31
 32public class HttpConnection implements Downloadable {
 33
 34	private HttpConnectionManager mHttpConnectionManager;
 35	private XmppConnectionService mXmppConnectionService;
 36
 37	private URL mUrl;
 38	private Message message;
 39	private DownloadableFile file;
 40	private int mStatus = Downloadable.STATUS_UNKNOWN;
 41	private boolean acceptedAutomatically = false;
 42	private int mProgress = 0;
 43	private long mLastGuiRefresh = 0;
 44
 45	public HttpConnection(HttpConnectionManager manager) {
 46		this.mHttpConnectionManager = manager;
 47		this.mXmppConnectionService = manager.getXmppConnectionService();
 48	}
 49
 50	@Override
 51	public boolean start() {
 52		if (mXmppConnectionService.hasInternetConnection()) {
 53			if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) {
 54				checkFileSize(true);
 55			} else {
 56				new Thread(new FileDownloader(true)).start();
 57			}
 58			return true;
 59		} else {
 60			return false;
 61		}
 62	}
 63
 64	public void init(Message message) {
 65		this.message = message;
 66		this.message.setDownloadable(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				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.setKey(CryptoHelper.hexToBytes(reference));
 88			}
 89
 90			if (this.message.getEncryption() == Message.ENCRYPTION_OTR
 91					&& this.file.getKey() == null) {
 92				this.message.setEncryption(Message.ENCRYPTION_NONE);
 93			}
 94			checkFileSize(false);
 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.setDownloadable(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.setDownloadable(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 void setupTrustManager(HttpsURLConnection connection,
128								   boolean interactive) {
129		X509TrustManager trustManager;
130		HostnameVerifier hostnameVerifier;
131		if (interactive) {
132			trustManager = mXmppConnectionService.getMemorizingTrustManager();
133			hostnameVerifier = mXmppConnectionService
134					.getMemorizingTrustManager().wrapHostnameVerifier(
135							new StrictHostnameVerifier());
136		} else {
137			trustManager = mXmppConnectionService.getMemorizingTrustManager()
138					.getNonInteractive();
139			hostnameVerifier = mXmppConnectionService
140					.getMemorizingTrustManager()
141					.wrapHostnameVerifierNonInteractive(
142							new StrictHostnameVerifier());
143		}
144		try {
145			SSLContext sc = SSLContext.getInstance("TLS");
146			sc.init(null, new X509TrustManager[]{trustManager},
147					mXmppConnectionService.getRNG());
148			connection.setSSLSocketFactory(sc.getSocketFactory());
149			connection.setHostnameVerifier(hostnameVerifier);
150		} catch (KeyManagementException e) {
151			return;
152		} catch (NoSuchAlgorithmException e) {
153			return;
154		}
155	}
156
157	private class FileSizeChecker implements Runnable {
158
159		private boolean interactive = false;
160
161		public FileSizeChecker(boolean interactive) {
162			this.interactive = interactive;
163		}
164
165		@Override
166		public void run() {
167			long size;
168			try {
169				size = retrieveFileSize();
170			} catch (SSLHandshakeException e) {
171				changeStatus(STATUS_OFFER_CHECK_FILESIZE);
172				HttpConnection.this.acceptedAutomatically = false;
173				HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
174				return;
175			} catch (IOException e) {
176				cancel();
177				return;
178			}
179			file.setExpectedSize(size);
180			if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
181				HttpConnection.this.acceptedAutomatically = true;
182				new Thread(new FileDownloader(interactive)).start();
183			} else {
184				changeStatus(STATUS_OFFER);
185				HttpConnection.this.acceptedAutomatically = false;
186				HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
187			}
188		}
189
190		private long retrieveFileSize() throws IOException,
191				SSLHandshakeException {
192			changeStatus(STATUS_CHECKING);
193			HttpURLConnection connection = (HttpURLConnection) mUrl
194					.openConnection();
195			connection.setRequestMethod("HEAD");
196			if (connection instanceof HttpsURLConnection) {
197				setupTrustManager((HttpsURLConnection) connection, interactive);
198			}
199			connection.connect();
200			String contentLength = connection.getHeaderField("Content-Length");
201			if (contentLength == null) {
202				throw new IOException();
203			}
204			try {
205				return Long.parseLong(contentLength, 10);
206			} catch (NumberFormatException e) {
207				throw new IOException();
208			}
209		}
210
211	}
212
213	private class FileDownloader implements Runnable {
214
215		private boolean interactive = false;
216
217		public FileDownloader(boolean interactive) {
218			this.interactive = interactive;
219		}
220
221		@Override
222		public void run() {
223			try {
224				changeStatus(STATUS_DOWNLOADING);
225				download();
226				updateImageBounds();
227				finish();
228			} catch (SSLHandshakeException e) {
229				changeStatus(STATUS_OFFER);
230			} catch (IOException e) {
231				cancel();
232			}
233		}
234
235		private void download() throws SSLHandshakeException, IOException {
236			HttpURLConnection connection = (HttpURLConnection) mUrl
237					.openConnection();
238			if (connection instanceof HttpsURLConnection) {
239				setupTrustManager((HttpsURLConnection) connection, interactive);
240			}
241			connection.connect();
242			BufferedInputStream is = new BufferedInputStream(
243					connection.getInputStream());
244			file.getParentFile().mkdirs();
245			file.createNewFile();
246			OutputStream os = file.createOutputStream();
247			if (os == null) {
248				throw new IOException();
249			}
250			long transmitted = 0;
251			long expected = file.getExpectedSize();
252			int count = -1;
253			byte[] buffer = new byte[1024];
254			while ((count = is.read(buffer)) != -1) {
255				transmitted += count;
256				os.write(buffer, 0, count);
257				updateProgress((int) ((((double) transmitted) / expected) * 100));
258			}
259			os.flush();
260			os.close();
261			is.close();
262		}
263
264		private void updateImageBounds() {
265			message.setType(Message.TYPE_IMAGE);
266			mXmppConnectionService.getFileBackend().updateFileParams(message,mUrl);
267			mXmppConnectionService.updateMessage(message);
268		}
269
270	}
271
272	public void updateProgress(int i) {
273		this.mProgress = i;
274		if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) {
275			this.mLastGuiRefresh = SystemClock.elapsedRealtime();
276			mXmppConnectionService.updateConversationUi();
277		}
278	}
279
280	@Override
281	public int getStatus() {
282		return this.mStatus;
283	}
284
285	@Override
286	public long getFileSize() {
287		if (this.file != null) {
288			return this.file.getExpectedSize();
289		} else {
290			return 0;
291		}
292	}
293
294	@Override
295	public int getProgress() {
296		return this.mProgress;
297	}
298
299	@Override
300	public String getMimeType() {
301		return "";
302	}
303}