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