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