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 BitmapFactory.Options options = new BitmapFactory.Options();
255 options.inJustDecodeBounds = true;
256 BitmapFactory.decodeFile(file.getAbsolutePath(), options);
257 int imageHeight = options.outHeight;
258 int imageWidth = options.outWidth;
259 message.setBody(mUrl.toString() + "|" + file.getSize() + '|'
260 + imageWidth + '|' + imageHeight);
261 message.setType(Message.TYPE_IMAGE);
262 mXmppConnectionService.updateMessage(message);
263 }
264
265 }
266
267 @Override
268 public int getStatus() {
269 return this.mStatus;
270 }
271
272 @Override
273 public long getFileSize() {
274 if (this.file != null) {
275 return this.file.getExpectedSize();
276 } else {
277 return 0;
278 }
279 }
280
281 @Override
282 public int getProgress() {
283 return this.mProgress;
284 }
285
286 @Override
287 public String getMimeType() {
288 return "";
289 }
290}