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