HttpConnection.java

  1package eu.siacs.conversations.http;
  2
  3import android.content.Intent;
  4import android.graphics.BitmapFactory;
  5import android.net.Uri;
  6import android.os.SystemClock;
  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;
 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 path = mUrl.getPath();
 70			if (path != null && (path.endsWith(".pgp") || path.endsWith(".gpg"))) {
 71				this.message.setEncryption(Message.ENCRYPTION_PGP);
 72			} else if (message.getEncryption() != Message.ENCRYPTION_OTR) {
 73				this.message.setEncryption(Message.ENCRYPTION_NONE);
 74			}
 75			this.file = mXmppConnectionService.getFileBackend().getFile(
 76					message, false);
 77			String reference = mUrl.getRef();
 78			if (reference != null && reference.length() == 96) {
 79				this.file.setKey(CryptoHelper.hexToBytes(reference));
 80			}
 81
 82			if (this.message.getEncryption() == Message.ENCRYPTION_OTR
 83					&& this.file.getKey() == null) {
 84				this.message.setEncryption(Message.ENCRYPTION_NONE);
 85			}
 86			checkFileSize(false);
 87		} catch (MalformedURLException e) {
 88			this.cancel();
 89		}
 90	}
 91
 92	private void checkFileSize(boolean interactive) {
 93		new Thread(new FileSizeChecker(interactive)).start();
 94	}
 95
 96	public void cancel() {
 97		mHttpConnectionManager.finishConnection(this);
 98		message.setDownloadable(null);
 99		mXmppConnectionService.updateConversationUi();
100	}
101
102	private void finish() {
103		Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
104		intent.setData(Uri.fromFile(file));
105		mXmppConnectionService.sendBroadcast(intent);
106		message.setDownloadable(null);
107		mHttpConnectionManager.finishConnection(this);
108		mXmppConnectionService.updateConversationUi();
109		if (acceptedAutomatically) {
110			mXmppConnectionService.getNotificationService().push(message);
111		}
112	}
113
114	private void changeStatus(int status) {
115		this.mStatus = status;
116		mXmppConnectionService.updateConversationUi();
117	}
118
119	private void setupTrustManager(HttpsURLConnection connection,
120								   boolean interactive) {
121		X509TrustManager trustManager;
122		HostnameVerifier hostnameVerifier;
123		if (interactive) {
124			trustManager = mXmppConnectionService.getMemorizingTrustManager();
125			hostnameVerifier = mXmppConnectionService
126					.getMemorizingTrustManager().wrapHostnameVerifier(
127							new StrictHostnameVerifier());
128		} else {
129			trustManager = mXmppConnectionService.getMemorizingTrustManager()
130					.getNonInteractive();
131			hostnameVerifier = mXmppConnectionService
132					.getMemorizingTrustManager()
133					.wrapHostnameVerifierNonInteractive(
134							new StrictHostnameVerifier());
135		}
136		try {
137			SSLContext sc = SSLContext.getInstance("TLS");
138			sc.init(null, new X509TrustManager[]{trustManager},
139					mXmppConnectionService.getRNG());
140			connection.setSSLSocketFactory(sc.getSocketFactory());
141			connection.setHostnameVerifier(hostnameVerifier);
142		} catch (KeyManagementException e) {
143			return;
144		} catch (NoSuchAlgorithmException e) {
145			return;
146		}
147	}
148
149	private class FileSizeChecker implements Runnable {
150
151		private boolean interactive = false;
152
153		public FileSizeChecker(boolean interactive) {
154			this.interactive = interactive;
155		}
156
157		@Override
158		public void run() {
159			long size;
160			try {
161				size = retrieveFileSize();
162			} catch (SSLHandshakeException e) {
163				changeStatus(STATUS_OFFER_CHECK_FILESIZE);
164				HttpConnection.this.acceptedAutomatically = false;
165				HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
166				return;
167			} catch (IOException e) {
168				cancel();
169				return;
170			}
171			file.setExpectedSize(size);
172			if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
173				HttpConnection.this.acceptedAutomatically = true;
174				new Thread(new FileDownloader(interactive)).start();
175			} else {
176				changeStatus(STATUS_OFFER);
177				HttpConnection.this.acceptedAutomatically = false;
178				HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
179			}
180		}
181
182		private long retrieveFileSize() throws IOException,
183				SSLHandshakeException {
184			changeStatus(STATUS_CHECKING);
185			HttpURLConnection connection = (HttpURLConnection) mUrl
186					.openConnection();
187			connection.setRequestMethod("HEAD");
188			if (connection instanceof HttpsURLConnection) {
189				setupTrustManager((HttpsURLConnection) connection, interactive);
190			}
191			connection.connect();
192			String contentLength = connection.getHeaderField("Content-Length");
193			if (contentLength == null) {
194				throw new IOException();
195			}
196			try {
197				return Long.parseLong(contentLength, 10);
198			} catch (NumberFormatException e) {
199				throw new IOException();
200			}
201		}
202
203	}
204
205	private class FileDownloader implements Runnable {
206
207		private boolean interactive = false;
208
209		public FileDownloader(boolean interactive) {
210			this.interactive = interactive;
211		}
212
213		@Override
214		public void run() {
215			try {
216				changeStatus(STATUS_DOWNLOADING);
217				download();
218				updateImageBounds();
219				finish();
220			} catch (SSLHandshakeException e) {
221				changeStatus(STATUS_OFFER);
222			} catch (IOException e) {
223				cancel();
224			}
225		}
226
227		private void download() throws SSLHandshakeException, IOException {
228			HttpURLConnection connection = (HttpURLConnection) mUrl
229					.openConnection();
230			if (connection instanceof HttpsURLConnection) {
231				setupTrustManager((HttpsURLConnection) connection, interactive);
232			}
233			connection.connect();
234			BufferedInputStream is = new BufferedInputStream(
235					connection.getInputStream());
236			file.getParentFile().mkdirs();
237			file.createNewFile();
238			OutputStream os = file.createOutputStream();
239			if (os == null) {
240				throw new IOException();
241			}
242			long transmitted = 0;
243			long expected = file.getExpectedSize();
244			int count = -1;
245			byte[] buffer = new byte[1024];
246			while ((count = is.read(buffer)) != -1) {
247				transmitted += count;
248				os.write(buffer, 0, count);
249				updateProgress((int) ((((double) transmitted) / expected) * 100));
250			}
251			os.flush();
252			os.close();
253			is.close();
254		}
255
256		private void updateImageBounds() {
257			message.setType(Message.TYPE_IMAGE);
258			mXmppConnectionService.getFileBackend().updateFileParams(message,mUrl);
259			mXmppConnectionService.updateMessage(message);
260		}
261
262	}
263
264	public void updateProgress(int i) {
265		this.mProgress = i;
266		if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) {
267			this.mLastGuiRefresh = SystemClock.elapsedRealtime();
268			mXmppConnectionService.updateConversationUi();
269		}
270	}
271
272	@Override
273	public int getStatus() {
274		return this.mStatus;
275	}
276
277	@Override
278	public long getFileSize() {
279		if (this.file != null) {
280			return this.file.getExpectedSize();
281		} else {
282			return 0;
283		}
284	}
285
286	@Override
287	public int getProgress() {
288		return this.mProgress;
289	}
290
291	@Override
292	public String getMimeType() {
293		return "";
294	}
295}