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