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