HttpConnection.java

  1package eu.siacs.conversations.http;
  2
  3import android.content.Intent;
  4import android.graphics.BitmapFactory;
  5import android.net.Uri;
  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.entities.Downloadable;
 25import eu.siacs.conversations.entities.DownloadableFile;
 26import eu.siacs.conversations.entities.Message;
 27import eu.siacs.conversations.services.XmppConnectionService;
 28import eu.siacs.conversations.utils.CryptoHelper;
 29
 30public class HttpConnection implements Downloadable {
 31
 32	private HttpConnectionManager mHttpConnectionManager;
 33	private XmppConnectionService mXmppConnectionService;
 34
 35	private URL mUrl;
 36	private Message message;
 37	private DownloadableFile file;
 38	private int mStatus = Downloadable.STATUS_UNKNOWN;
 39	private boolean acceptedAutomatically = false;
 40
 41	public HttpConnection(HttpConnectionManager manager) {
 42		this.mHttpConnectionManager = manager;
 43		this.mXmppConnectionService = manager.getXmppConnectionService();
 44	}
 45
 46	@Override
 47	public boolean start() {
 48		if (mXmppConnectionService.hasInternetConnection()) {
 49			if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) {
 50				checkFileSize(true);
 51			} else {
 52				new Thread(new FileDownloader(true)).start();
 53			}
 54			return true;
 55		} else {
 56			return false;
 57		}
 58	}
 59
 60	public void init(Message message) {
 61		this.message = message;
 62		this.message.setDownloadable(this);
 63		try {
 64			mUrl = new URL(message.getBody());
 65			String path = mUrl.getPath();
 66			if (path != null && (path.endsWith(".pgp") || path.endsWith(".gpg"))) {
 67				this.message.setEncryption(Message.ENCRYPTION_PGP);
 68			} else if (message.getEncryption() != Message.ENCRYPTION_OTR) {
 69				this.message.setEncryption(Message.ENCRYPTION_NONE);
 70			}
 71			this.file = mXmppConnectionService.getFileBackend().getFile(
 72					message, false);
 73			String reference = mUrl.getRef();
 74			if (reference != null && reference.length() == 96) {
 75				this.file.setKey(CryptoHelper.hexToBytes(reference));
 76			}
 77
 78			if (this.message.getEncryption() == Message.ENCRYPTION_OTR
 79					&& this.file.getKey() == null) {
 80				this.message.setEncryption(Message.ENCRYPTION_NONE);
 81			}
 82			checkFileSize(false);
 83		} catch (MalformedURLException e) {
 84			this.cancel();
 85		}
 86	}
 87
 88	private void checkFileSize(boolean interactive) {
 89		new Thread(new FileSizeChecker(interactive)).start();
 90	}
 91
 92	public void cancel() {
 93		mHttpConnectionManager.finishConnection(this);
 94		message.setDownloadable(null);
 95		mXmppConnectionService.updateConversationUi();
 96	}
 97
 98	private void finish() {
 99		Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
100		intent.setData(Uri.fromFile(file));
101		mXmppConnectionService.sendBroadcast(intent);
102		message.setDownloadable(null);
103		mHttpConnectionManager.finishConnection(this);
104		mXmppConnectionService.updateConversationUi();
105		if (acceptedAutomatically) {
106			mXmppConnectionService.getNotificationService().push(message);
107		}
108	}
109
110	private void changeStatus(int status) {
111		this.mStatus = status;
112		mXmppConnectionService.updateConversationUi();
113	}
114
115	private void setupTrustManager(HttpsURLConnection connection,
116								   boolean interactive) {
117		X509TrustManager trustManager;
118		HostnameVerifier hostnameVerifier;
119		if (interactive) {
120			trustManager = mXmppConnectionService.getMemorizingTrustManager();
121			hostnameVerifier = mXmppConnectionService
122					.getMemorizingTrustManager().wrapHostnameVerifier(
123							new StrictHostnameVerifier());
124		} else {
125			trustManager = mXmppConnectionService.getMemorizingTrustManager()
126					.getNonInteractive();
127			hostnameVerifier = mXmppConnectionService
128					.getMemorizingTrustManager()
129					.wrapHostnameVerifierNonInteractive(
130							new StrictHostnameVerifier());
131		}
132		try {
133			SSLContext sc = SSLContext.getInstance("TLS");
134			sc.init(null, new X509TrustManager[]{trustManager},
135					mXmppConnectionService.getRNG());
136			connection.setSSLSocketFactory(sc.getSocketFactory());
137			connection.setHostnameVerifier(hostnameVerifier);
138		} catch (KeyManagementException e) {
139			return;
140		} catch (NoSuchAlgorithmException e) {
141			return;
142		}
143	}
144
145	private class FileSizeChecker implements Runnable {
146
147		private boolean interactive = false;
148
149		public FileSizeChecker(boolean interactive) {
150			this.interactive = interactive;
151		}
152
153		@Override
154		public void run() {
155			long size;
156			try {
157				size = retrieveFileSize();
158			} catch (SSLHandshakeException e) {
159				changeStatus(STATUS_OFFER_CHECK_FILESIZE);
160				HttpConnection.this.acceptedAutomatically = false;
161				HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
162				return;
163			} catch (IOException e) {
164				cancel();
165				return;
166			}
167			file.setExpectedSize(size);
168			if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
169				HttpConnection.this.acceptedAutomatically = true;
170				new Thread(new FileDownloader(interactive)).start();
171			} else {
172				changeStatus(STATUS_OFFER);
173				HttpConnection.this.acceptedAutomatically = false;
174				HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
175			}
176		}
177
178		private long retrieveFileSize() throws IOException,
179				SSLHandshakeException {
180			changeStatus(STATUS_CHECKING);
181			HttpURLConnection connection = (HttpURLConnection) mUrl
182					.openConnection();
183			connection.setRequestMethod("HEAD");
184			if (connection instanceof HttpsURLConnection) {
185				setupTrustManager((HttpsURLConnection) connection, interactive);
186			}
187			connection.connect();
188			String contentLength = connection.getHeaderField("Content-Length");
189			if (contentLength == null) {
190				throw new IOException();
191			}
192			try {
193				return Long.parseLong(contentLength, 10);
194			} catch (NumberFormatException e) {
195				throw new IOException();
196			}
197		}
198
199	}
200
201	private class FileDownloader implements Runnable {
202
203		private boolean interactive = false;
204
205		public FileDownloader(boolean interactive) {
206			this.interactive = interactive;
207		}
208
209		@Override
210		public void run() {
211			try {
212				changeStatus(STATUS_DOWNLOADING);
213				download();
214				updateImageBounds();
215				finish();
216			} catch (SSLHandshakeException e) {
217				changeStatus(STATUS_OFFER);
218			} catch (IOException e) {
219				cancel();
220			}
221		}
222
223		private void download() throws SSLHandshakeException, IOException {
224			HttpURLConnection connection = (HttpURLConnection) mUrl
225					.openConnection();
226			if (connection instanceof HttpsURLConnection) {
227				setupTrustManager((HttpsURLConnection) connection, interactive);
228			}
229			connection.connect();
230			BufferedInputStream is = new BufferedInputStream(
231					connection.getInputStream());
232			file.getParentFile().mkdirs();
233			file.createNewFile();
234			OutputStream os = file.createOutputStream();
235			if (os == null) {
236				throw new IOException();
237			}
238			int count = -1;
239			byte[] buffer = new byte[1024];
240			while ((count = is.read(buffer)) != -1) {
241				os.write(buffer, 0, count);
242			}
243			os.flush();
244			os.close();
245			is.close();
246		}
247
248		private void updateImageBounds() {
249			BitmapFactory.Options options = new BitmapFactory.Options();
250			options.inJustDecodeBounds = true;
251			BitmapFactory.decodeFile(file.getAbsolutePath(), options);
252			int imageHeight = options.outHeight;
253			int imageWidth = options.outWidth;
254			message.setBody(mUrl.toString() + "|" + file.getSize() + '|'
255					+ imageWidth + '|' + imageHeight);
256			message.setType(Message.TYPE_IMAGE);
257			mXmppConnectionService.updateMessage(message);
258		}
259
260	}
261
262	@Override
263	public int getStatus() {
264		return this.mStatus;
265	}
266
267	@Override
268	public long getFileSize() {
269		if (this.file != null) {
270			return this.file.getExpectedSize();
271		} else {
272			return 0;
273		}
274	}
275}