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;
 17
 18import javax.net.ssl.HostnameVerifier;
 19import javax.net.ssl.HttpsURLConnection;
 20import javax.net.ssl.SSLContext;
 21import javax.net.ssl.SSLHandshakeException;
 22import javax.net.ssl.X509TrustManager;
 23
 24import eu.siacs.conversations.Config;
 25import eu.siacs.conversations.entities.Downloadable;
 26import eu.siacs.conversations.entities.DownloadableFile;
 27import eu.siacs.conversations.entities.Message;
 28import eu.siacs.conversations.services.XmppConnectionService;
 29import eu.siacs.conversations.utils.CryptoHelper;
 30
 31public class HttpConnection implements Downloadable {
 32
 33	private HttpConnectionManager mHttpConnectionManager;
 34	private XmppConnectionService mXmppConnectionService;
 35
 36	private URL mUrl;
 37	private Message message;
 38	private DownloadableFile file;
 39	private int mStatus = Downloadable.STATUS_UNKNOWN;
 40	private boolean acceptedAutomatically = false;
 41	private int mProgress = 0;
 42	private long mLastGuiRefresh = 0;
 43
 44	public HttpConnection(HttpConnectionManager manager) {
 45		this.mHttpConnectionManager = manager;
 46		this.mXmppConnectionService = manager.getXmppConnectionService();
 47	}
 48
 49	@Override
 50	public boolean start() {
 51		if (mXmppConnectionService.hasInternetConnection()) {
 52			if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) {
 53				checkFileSize(true);
 54			} else {
 55				new Thread(new FileDownloader(true)).start();
 56			}
 57			return true;
 58		} else {
 59			return false;
 60		}
 61	}
 62
 63	public void init(Message message) {
 64		this.message = message;
 65		this.message.setDownloadable(this);
 66		try {
 67			mUrl = new URL(message.getBody());
 68			String path = mUrl.getPath().toLowerCase();
 69			if (path != null && (path.endsWith(".pgp") || path.endsWith(".gpg"))) {
 70				this.message.setEncryption(Message.ENCRYPTION_PGP);
 71			} else if (message.getEncryption() != Message.ENCRYPTION_OTR) {
 72				this.message.setEncryption(Message.ENCRYPTION_NONE);
 73			}
 74			this.file = mXmppConnectionService.getFileBackend().getFile(
 75					message, false);
 76			String reference = mUrl.getRef();
 77			if (reference != null && reference.length() == 96) {
 78				this.file.setKey(CryptoHelper.hexToBytes(reference));
 79			}
 80
 81			if (this.message.getEncryption() == Message.ENCRYPTION_OTR
 82					&& this.file.getKey() == null) {
 83				this.message.setEncryption(Message.ENCRYPTION_NONE);
 84			}
 85			checkFileSize(false);
 86		} catch (MalformedURLException e) {
 87			this.cancel();
 88		}
 89	}
 90
 91	private void checkFileSize(boolean interactive) {
 92		new Thread(new FileSizeChecker(interactive)).start();
 93	}
 94
 95	public void cancel() {
 96		mHttpConnectionManager.finishConnection(this);
 97		message.setDownloadable(null);
 98		mXmppConnectionService.updateConversationUi();
 99	}
100
101	private void finish() {
102		Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
103		intent.setData(Uri.fromFile(file));
104		mXmppConnectionService.sendBroadcast(intent);
105		message.setDownloadable(null);
106		mHttpConnectionManager.finishConnection(this);
107		mXmppConnectionService.updateConversationUi();
108		if (acceptedAutomatically) {
109			mXmppConnectionService.getNotificationService().push(message);
110		}
111	}
112
113	private void changeStatus(int status) {
114		this.mStatus = status;
115		mXmppConnectionService.updateConversationUi();
116	}
117
118	private void setupTrustManager(HttpsURLConnection connection,
119								   boolean interactive) {
120		X509TrustManager trustManager;
121		HostnameVerifier hostnameVerifier;
122		if (interactive) {
123			trustManager = mXmppConnectionService.getMemorizingTrustManager();
124			hostnameVerifier = mXmppConnectionService
125					.getMemorizingTrustManager().wrapHostnameVerifier(
126							new StrictHostnameVerifier());
127		} else {
128			trustManager = mXmppConnectionService.getMemorizingTrustManager()
129					.getNonInteractive();
130			hostnameVerifier = mXmppConnectionService
131					.getMemorizingTrustManager()
132					.wrapHostnameVerifierNonInteractive(
133							new StrictHostnameVerifier());
134		}
135		try {
136			SSLContext sc = SSLContext.getInstance("TLS");
137			sc.init(null, new X509TrustManager[]{trustManager},
138					mXmppConnectionService.getRNG());
139			connection.setSSLSocketFactory(sc.getSocketFactory());
140			connection.setHostnameVerifier(hostnameVerifier);
141		} catch (KeyManagementException e) {
142			return;
143		} catch (NoSuchAlgorithmException e) {
144			return;
145		}
146	}
147
148	private class FileSizeChecker implements Runnable {
149
150		private boolean interactive = false;
151
152		public FileSizeChecker(boolean interactive) {
153			this.interactive = interactive;
154		}
155
156		@Override
157		public void run() {
158			long size;
159			try {
160				size = retrieveFileSize();
161			} catch (SSLHandshakeException e) {
162				changeStatus(STATUS_OFFER_CHECK_FILESIZE);
163				HttpConnection.this.acceptedAutomatically = false;
164				HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
165				return;
166			} catch (IOException e) {
167				cancel();
168				return;
169			}
170			file.setExpectedSize(size);
171			if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
172				HttpConnection.this.acceptedAutomatically = true;
173				new Thread(new FileDownloader(interactive)).start();
174			} else {
175				changeStatus(STATUS_OFFER);
176				HttpConnection.this.acceptedAutomatically = false;
177				HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
178			}
179		}
180
181		private long retrieveFileSize() throws IOException,
182				SSLHandshakeException {
183			changeStatus(STATUS_CHECKING);
184			HttpURLConnection connection = (HttpURLConnection) mUrl
185					.openConnection();
186			connection.setRequestMethod("HEAD");
187			if (connection instanceof HttpsURLConnection) {
188				setupTrustManager((HttpsURLConnection) connection, interactive);
189			}
190			connection.connect();
191			String contentLength = connection.getHeaderField("Content-Length");
192			if (contentLength == null) {
193				throw new IOException();
194			}
195			try {
196				return Long.parseLong(contentLength, 10);
197			} catch (NumberFormatException e) {
198				throw new IOException();
199			}
200		}
201
202	}
203
204	private class FileDownloader implements Runnable {
205
206		private boolean interactive = false;
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				cancel();
223			}
224		}
225
226		private void download() throws SSLHandshakeException, IOException {
227			HttpURLConnection connection = (HttpURLConnection) mUrl
228					.openConnection();
229			if (connection instanceof HttpsURLConnection) {
230				setupTrustManager((HttpsURLConnection) connection, interactive);
231			}
232			connection.connect();
233			BufferedInputStream is = new BufferedInputStream(
234					connection.getInputStream());
235			file.getParentFile().mkdirs();
236			file.createNewFile();
237			OutputStream os = file.createOutputStream();
238			if (os == null) {
239				throw new IOException();
240			}
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			os.close();
252			is.close();
253		}
254
255		private void updateImageBounds() {
256			message.setType(Message.TYPE_IMAGE);
257			mXmppConnectionService.getFileBackend().updateFileParams(message,mUrl);
258			mXmppConnectionService.updateMessage(message);
259		}
260
261	}
262
263	public void updateProgress(int i) {
264		this.mProgress = i;
265		if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) {
266			this.mLastGuiRefresh = SystemClock.elapsedRealtime();
267			mXmppConnectionService.updateConversationUi();
268		}
269	}
270
271	@Override
272	public int getStatus() {
273		return this.mStatus;
274	}
275
276	@Override
277	public long getFileSize() {
278		if (this.file != null) {
279			return this.file.getExpectedSize();
280		} else {
281			return 0;
282		}
283	}
284
285	@Override
286	public int getProgress() {
287		return this.mProgress;
288	}
289
290	@Override
291	public String getMimeType() {
292		return "";
293	}
294}