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