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