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.HttpsURLConnection;
13import javax.net.ssl.SSLContext;
14import javax.net.ssl.SSLHandshakeException;
15import javax.net.ssl.X509TrustManager;
16
17import android.content.Intent;
18import android.graphics.BitmapFactory;
19import android.net.Uri;
20
21import eu.siacs.conversations.entities.Downloadable;
22import eu.siacs.conversations.entities.DownloadableFile;
23import eu.siacs.conversations.entities.Message;
24import eu.siacs.conversations.services.XmppConnectionService;
25
26public class HttpConnection implements Downloadable {
27
28 private HttpConnectionManager mHttpConnectionManager;
29 private XmppConnectionService mXmppConnectionService;
30
31 private URL mUrl;
32 private Message message;
33 private DownloadableFile file;
34 private int mStatus = Downloadable.STATUS_UNKNOWN;
35 private boolean mAutostart = true;
36
37 public HttpConnection(HttpConnectionManager manager) {
38 this.mHttpConnectionManager = manager;
39 this.mXmppConnectionService = manager.getXmppConnectionService();
40 }
41
42 @Override
43 public boolean start() {
44 if (mXmppConnectionService.hasInternetConnection()) {
45 if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) {
46 checkFileSize(true);
47 } else {
48 changeStatus(STATUS_DOWNLOADING);
49 new Thread(new FileDownloader()).start();
50 }
51 return true;
52 } else {
53 return false;
54 }
55 }
56
57 public void init(Message message) {
58 this.message = message;
59 this.message.setDownloadable(this);
60 try {
61 mUrl = new URL(message.getBody());
62 this.file = mXmppConnectionService.getFileBackend().getFile(
63 message, false);
64 this.mAutostart = true;
65 checkFileSize(false);
66 } catch (MalformedURLException e) {
67 this.cancel();
68 }
69 }
70
71 private void checkFileSize(boolean interactive) {
72 changeStatus(STATUS_CHECKING);
73 new Thread(new FileSizeChecker(interactive)).start();
74 }
75
76 public void cancel() {
77 mHttpConnectionManager.finishConnection(this);
78 message.setDownloadable(null);
79 mXmppConnectionService.updateConversationUi();
80 }
81
82 private void finish() {
83 Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
84 intent.setData(Uri.fromFile(file));
85 mXmppConnectionService.sendBroadcast(intent);
86 message.setDownloadable(null);
87 mHttpConnectionManager.finishConnection(this);
88 }
89
90 private void changeStatus(int status) {
91 this.mStatus = status;
92 mXmppConnectionService.updateConversationUi();
93 }
94
95 private void setupTrustManager(HttpsURLConnection connection,
96 boolean interactive) {
97 X509TrustManager trustManager;
98 if (interactive) {
99 trustManager = mXmppConnectionService.getMemorizingTrustManager();
100 } else {
101 trustManager = mXmppConnectionService.getMemorizingTrustManager()
102 .getNonInteractive();
103 }
104 try {
105 SSLContext sc = SSLContext.getInstance("TLS");
106 sc.init(null, new X509TrustManager[] { trustManager },
107 mXmppConnectionService.getRNG());
108 connection.setSSLSocketFactory(sc.getSocketFactory());
109 } catch (KeyManagementException e) {
110 return;
111 } catch (NoSuchAlgorithmException e) {
112 return;
113 }
114 }
115
116 private class FileSizeChecker implements Runnable {
117
118 private boolean interactive = false;
119
120 public FileSizeChecker(boolean interactive) {
121 this.interactive = interactive;
122 }
123
124 @Override
125 public void run() {
126 long size;
127 try {
128 size = retrieveFileSize();
129 } catch (SSLHandshakeException e) {
130 changeStatus(STATUS_OFFER_CHECK_FILESIZE);
131 return;
132 } catch (IOException e) {
133 cancel();
134 return;
135 }
136 file.setExpectedSize(size);
137 if (size <= mHttpConnectionManager.getAutoAcceptFileSize()
138 && mAutostart) {
139 start();
140 } else {
141 changeStatus(STATUS_OFFER);
142 }
143 }
144
145 private long retrieveFileSize() throws IOException,
146 SSLHandshakeException {
147 HttpURLConnection connection = (HttpURLConnection) mUrl
148 .openConnection();
149 connection.setRequestMethod("HEAD");
150 if (connection instanceof HttpsURLConnection) {
151 setupTrustManager((HttpsURLConnection) connection, interactive);
152 }
153 connection.connect();
154 String contentLength = connection.getHeaderField("Content-Length");
155 if (contentLength == null) {
156 throw new IOException();
157 }
158 try {
159 return Long.parseLong(contentLength, 10);
160 } catch (NumberFormatException e) {
161 throw new IOException();
162 }
163 }
164
165 }
166
167 private class FileDownloader implements Runnable {
168
169 @Override
170 public void run() {
171 try {
172 download();
173 updateImageBounds();
174 finish();
175 } catch (IOException e) {
176 cancel();
177 }
178 }
179
180 private void download() throws IOException {
181 HttpURLConnection connection = (HttpURLConnection) mUrl
182 .openConnection();
183 if (connection instanceof HttpsURLConnection) {
184 setupTrustManager((HttpsURLConnection) connection, true);
185 }
186 BufferedInputStream is = new BufferedInputStream(
187 connection.getInputStream());
188 OutputStream os = file.createOutputStream();
189 int count = -1;
190 byte[] buffer = new byte[1024];
191 while ((count = is.read(buffer)) != -1) {
192 os.write(buffer, 0, count);
193 }
194 os.flush();
195 os.close();
196 is.close();
197 }
198
199 private void updateImageBounds() {
200 BitmapFactory.Options options = new BitmapFactory.Options();
201 options.inJustDecodeBounds = true;
202 BitmapFactory.decodeFile(file.getAbsolutePath(), options);
203 int imageHeight = options.outHeight;
204 int imageWidth = options.outWidth;
205 message.setBody(mUrl.toString() + "," + file.getSize() + ','
206 + imageWidth + ',' + imageHeight);
207 message.setType(Message.TYPE_IMAGE);
208 mXmppConnectionService.updateMessage(message);
209 }
210
211 }
212
213 @Override
214 public int getStatus() {
215 return this.mStatus;
216 }
217
218 @Override
219 public long getFileSize() {
220 if (this.file != null) {
221 return this.file.getExpectedSize();
222 } else {
223 return 0;
224 }
225 }
226}