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