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