HttpConnection.java

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