1package eu.siacs.conversations.http;
2
3import java.io.BufferedInputStream;
4import java.io.IOException;
5import java.io.OutputStream;
6import java.net.HttpURLConnection;
7import java.net.MalformedURLException;
8import java.net.URL;
9import java.security.KeyManagementException;
10import java.security.NoSuchAlgorithmException;
11
12import javax.net.ssl.HostnameVerifier;
13import javax.net.ssl.HttpsURLConnection;
14import javax.net.ssl.SSLContext;
15import javax.net.ssl.SSLHandshakeException;
16import javax.net.ssl.X509TrustManager;
17
18import org.apache.http.conn.ssl.StrictHostnameVerifier;
19
20import android.content.Intent;
21import android.graphics.BitmapFactory;
22import android.net.Uri;
23import android.util.Log;
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
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 this.file = mXmppConnectionService.getFileBackend().getFile(
67 message, false);
68 String reference = mUrl.getRef();
69 if (reference != null && reference.length() == 96) {
70 this.file.setKey(CryptoHelper.hexToBytes(reference));
71 }
72 if (this.message.getEncryption() == Message.ENCRYPTION_OTR
73 && this.file.getKey() == null) {
74 this.message.setEncryption(Message.ENCRYPTION_NONE);
75 }
76 checkFileSize(false);
77 } catch (MalformedURLException e) {
78 this.cancel();
79 }
80 }
81
82 private void checkFileSize(boolean interactive) {
83 new Thread(new FileSizeChecker(interactive)).start();
84 }
85
86 public void cancel() {
87 mHttpConnectionManager.finishConnection(this);
88 message.setDownloadable(null);
89 mXmppConnectionService.updateConversationUi();
90 }
91
92 private void finish() {
93 Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
94 intent.setData(Uri.fromFile(file));
95 mXmppConnectionService.sendBroadcast(intent);
96 message.setDownloadable(null);
97 mHttpConnectionManager.finishConnection(this);
98 }
99
100 private void changeStatus(int status) {
101 this.mStatus = status;
102 mXmppConnectionService.updateConversationUi();
103 }
104
105 private void setupTrustManager(HttpsURLConnection connection,
106 boolean interactive) {
107 X509TrustManager trustManager;
108 HostnameVerifier hostnameVerifier;
109 if (interactive) {
110 trustManager = mXmppConnectionService.getMemorizingTrustManager();
111 hostnameVerifier = mXmppConnectionService
112 .getMemorizingTrustManager().wrapHostnameVerifier(
113 new StrictHostnameVerifier());
114 } else {
115 trustManager = mXmppConnectionService.getMemorizingTrustManager()
116 .getNonInteractive();
117 hostnameVerifier = mXmppConnectionService
118 .getMemorizingTrustManager()
119 .wrapHostnameVerifierNonInteractive(
120 new StrictHostnameVerifier());
121 }
122 try {
123 SSLContext sc = SSLContext.getInstance("TLS");
124 sc.init(null, new X509TrustManager[] { trustManager },
125 mXmppConnectionService.getRNG());
126 connection.setSSLSocketFactory(sc.getSocketFactory());
127 connection.setHostnameVerifier(hostnameVerifier);
128 } catch (KeyManagementException e) {
129 return;
130 } catch (NoSuchAlgorithmException e) {
131 return;
132 }
133 }
134
135 private class FileSizeChecker implements Runnable {
136
137 private boolean interactive = false;
138
139 public FileSizeChecker(boolean interactive) {
140 this.interactive = interactive;
141 }
142
143 @Override
144 public void run() {
145 long size;
146 try {
147 size = retrieveFileSize();
148 } catch (SSLHandshakeException e) {
149 changeStatus(STATUS_OFFER_CHECK_FILESIZE);
150 return;
151 } catch (IOException e) {
152 cancel();
153 return;
154 }
155 file.setExpectedSize(size);
156 if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
157 new Thread(new FileDownloader(interactive)).start();
158 } else {
159 changeStatus(STATUS_OFFER);
160 }
161 }
162
163 private long retrieveFileSize() throws IOException,
164 SSLHandshakeException {
165 changeStatus(STATUS_CHECKING);
166 HttpURLConnection connection = (HttpURLConnection) mUrl
167 .openConnection();
168 connection.setRequestMethod("HEAD");
169 if (connection instanceof HttpsURLConnection) {
170 setupTrustManager((HttpsURLConnection) connection, interactive);
171 }
172 connection.connect();
173 String contentLength = connection.getHeaderField("Content-Length");
174 if (contentLength == null) {
175 throw new IOException();
176 }
177 try {
178 return Long.parseLong(contentLength, 10);
179 } catch (NumberFormatException e) {
180 throw new IOException();
181 }
182 }
183
184 }
185
186 private class FileDownloader implements Runnable {
187
188 private boolean interactive = false;
189
190 public FileDownloader(boolean interactive) {
191 this.interactive = interactive;
192 }
193
194 @Override
195 public void run() {
196 try {
197 changeStatus(STATUS_DOWNLOADING);
198 download();
199 updateImageBounds();
200 finish();
201 } catch (SSLHandshakeException e) {
202 changeStatus(STATUS_OFFER);
203 } catch (IOException e) {
204 cancel();
205 }
206 }
207
208 private void download() throws SSLHandshakeException, IOException {
209 HttpURLConnection connection = (HttpURLConnection) mUrl
210 .openConnection();
211 if (connection instanceof HttpsURLConnection) {
212 setupTrustManager((HttpsURLConnection) connection, interactive);
213 }
214 connection.connect();
215 BufferedInputStream is = new BufferedInputStream(
216 connection.getInputStream());
217 OutputStream os = file.createOutputStream();
218 int count = -1;
219 byte[] buffer = new byte[1024];
220 while ((count = is.read(buffer)) != -1) {
221 os.write(buffer, 0, count);
222 }
223 os.flush();
224 os.close();
225 is.close();
226 }
227
228 private void updateImageBounds() {
229 BitmapFactory.Options options = new BitmapFactory.Options();
230 options.inJustDecodeBounds = true;
231 BitmapFactory.decodeFile(file.getAbsolutePath(), options);
232 int imageHeight = options.outHeight;
233 int imageWidth = options.outWidth;
234 message.setBody(mUrl.toString() + "," + file.getSize() + ','
235 + imageWidth + ',' + imageHeight);
236 message.setType(Message.TYPE_IMAGE);
237 mXmppConnectionService.updateMessage(message);
238 }
239
240 }
241
242 @Override
243 public int getStatus() {
244 return this.mStatus;
245 }
246
247 @Override
248 public long getFileSize() {
249 if (this.file != null) {
250 return this.file.getExpectedSize();
251 } else {
252 return 0;
253 }
254 }
255}