HttpConnection.java

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