1package eu.siacs.conversations.http;
2
3import android.content.Intent;
4import android.net.Uri;
5import android.util.Log;
6
7import java.io.BufferedInputStream;
8import java.io.IOException;
9import java.io.InputStream;
10import java.io.OutputStream;
11import java.net.HttpURLConnection;
12import java.net.MalformedURLException;
13import java.net.URL;
14import java.util.Arrays;
15
16import javax.net.ssl.HttpsURLConnection;
17import javax.net.ssl.SSLHandshakeException;
18
19import eu.siacs.conversations.Config;
20import eu.siacs.conversations.R;
21import eu.siacs.conversations.entities.DownloadableFile;
22import eu.siacs.conversations.entities.Message;
23import eu.siacs.conversations.entities.Transferable;
24import eu.siacs.conversations.persistance.FileBackend;
25import eu.siacs.conversations.services.AbstractConnectionManager;
26import eu.siacs.conversations.services.XmppConnectionService;
27import eu.siacs.conversations.utils.CryptoHelper;
28
29public class HttpDownloadConnection implements Transferable {
30
31 private HttpConnectionManager mHttpConnectionManager;
32 private XmppConnectionService mXmppConnectionService;
33
34 private URL mUrl;
35 private Message message;
36 private DownloadableFile file;
37 private int mStatus = Transferable.STATUS_UNKNOWN;
38 private boolean acceptedAutomatically = false;
39 private int mProgress = 0;
40
41 public HttpDownloadConnection(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 init(message, false);
62 }
63
64 public void init(Message message, boolean interactive) {
65 this.message = message;
66 this.message.setTransferable(this);
67 try {
68 mUrl = new URL(message.getBody());
69 String[] parts = mUrl.getPath().toLowerCase().split("\\.");
70 String lastPart = parts.length >= 1 ? parts[parts.length - 1] : null;
71 String secondToLast = parts.length >= 2 ? parts[parts.length -2] : null;
72 if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) {
73 this.message.setEncryption(Message.ENCRYPTION_PGP);
74 } else if (message.getEncryption() != Message.ENCRYPTION_OTR
75 && message.getEncryption() != Message.ENCRYPTION_AXOLOTL) {
76 this.message.setEncryption(Message.ENCRYPTION_NONE);
77 }
78 String extension;
79 if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(lastPart)) {
80 extension = secondToLast;
81 } else {
82 extension = lastPart;
83 }
84 message.setRelativeFilePath(message.getUuid()+"."+extension);
85 this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
86 String reference = mUrl.getRef();
87 if (reference != null && reference.length() == 96) {
88 this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference));
89 }
90
91 if ((this.message.getEncryption() == Message.ENCRYPTION_OTR
92 || this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL)
93 && this.file.getKey() == null) {
94 this.message.setEncryption(Message.ENCRYPTION_NONE);
95 }
96 checkFileSize(interactive);
97 } catch (MalformedURLException e) {
98 this.cancel();
99 }
100 }
101
102 private void checkFileSize(boolean interactive) {
103 new Thread(new FileSizeChecker(interactive)).start();
104 }
105
106 public void cancel() {
107 mHttpConnectionManager.finishConnection(this);
108 message.setTransferable(null);
109 mXmppConnectionService.updateConversationUi();
110 }
111
112 private void finish() {
113 Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
114 intent.setData(Uri.fromFile(file));
115 mXmppConnectionService.sendBroadcast(intent);
116 message.setTransferable(null);
117 mHttpConnectionManager.finishConnection(this);
118 mXmppConnectionService.updateConversationUi();
119 if (acceptedAutomatically) {
120 mXmppConnectionService.getNotificationService().push(message);
121 }
122 }
123
124 private void changeStatus(int status) {
125 this.mStatus = status;
126 mXmppConnectionService.updateConversationUi();
127 }
128
129 private void showToastForException(Exception e) {
130 e.printStackTrace();
131 if (e instanceof java.net.UnknownHostException) {
132 mXmppConnectionService.showErrorToastInUi(R.string.download_failed_server_not_found);
133 } else if (e instanceof java.net.ConnectException) {
134 mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_connect);
135 } else {
136 mXmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found);
137 }
138 }
139
140 private class FileSizeChecker implements Runnable {
141
142 private boolean interactive = false;
143
144 public FileSizeChecker(boolean interactive) {
145 this.interactive = interactive;
146 }
147
148 @Override
149 public void run() {
150 long size;
151 try {
152 size = retrieveFileSize();
153 } catch (SSLHandshakeException e) {
154 changeStatus(STATUS_OFFER_CHECK_FILESIZE);
155 HttpDownloadConnection.this.acceptedAutomatically = false;
156 HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
157 return;
158 } catch (IOException e) {
159 Log.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage());
160 if (interactive) {
161 showToastForException(e);
162 }
163 cancel();
164 return;
165 }
166 file.setExpectedSize(size);
167 if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
168 HttpDownloadConnection.this.acceptedAutomatically = true;
169 new Thread(new FileDownloader(interactive)).start();
170 } else {
171 changeStatus(STATUS_OFFER);
172 HttpDownloadConnection.this.acceptedAutomatically = false;
173 HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
174 }
175 }
176
177 private long retrieveFileSize() throws IOException {
178 try {
179 Log.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive));
180 changeStatus(STATUS_CHECKING);
181 HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
182 connection.setRequestMethod("HEAD");
183 if (connection instanceof HttpsURLConnection) {
184 mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
185 }
186 connection.connect();
187 String contentLength = connection.getHeaderField("Content-Length");
188 connection.disconnect();
189 if (contentLength == null) {
190 throw new IOException();
191 }
192 return Long.parseLong(contentLength, 10);
193 } catch (IOException e) {
194 throw e;
195 } catch (NumberFormatException e) {
196 throw new IOException();
197 }
198 }
199
200 }
201
202 private class FileDownloader implements Runnable {
203
204 private boolean interactive = false;
205
206 private OutputStream os;
207
208 public FileDownloader(boolean interactive) {
209 this.interactive = interactive;
210 }
211
212 @Override
213 public void run() {
214 try {
215 changeStatus(STATUS_DOWNLOADING);
216 download();
217 updateImageBounds();
218 finish();
219 } catch (SSLHandshakeException e) {
220 changeStatus(STATUS_OFFER);
221 } catch (IOException e) {
222 if (interactive) {
223 showToastForException(e);
224 }
225 cancel();
226 }
227 }
228
229 private void download() throws IOException {
230 InputStream is = null;
231 try {
232 HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
233 if (connection instanceof HttpsURLConnection) {
234 mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
235 }
236 connection.connect();
237 is = new BufferedInputStream(connection.getInputStream());
238 file.getParentFile().mkdirs();
239 file.createNewFile();
240 os = AbstractConnectionManager.createOutputStream(file, true);
241 long transmitted = 0;
242 long expected = file.getExpectedSize();
243 int count = -1;
244 byte[] buffer = new byte[1024];
245 while ((count = is.read(buffer)) != -1) {
246 transmitted += count;
247 os.write(buffer, 0, count);
248 updateProgress((int) ((((double) transmitted) / expected) * 100));
249 }
250 os.flush();
251 } catch (IOException e) {
252 throw e;
253 } finally {
254 FileBackend.close(os);
255 FileBackend.close(is);
256 }
257 }
258
259 private void updateImageBounds() {
260 message.setType(Message.TYPE_FILE);
261 mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl);
262 mXmppConnectionService.updateMessage(message);
263 }
264
265 }
266
267 public void updateProgress(int i) {
268 this.mProgress = i;
269 mXmppConnectionService.updateConversationUi();
270 }
271
272 @Override
273 public int getStatus() {
274 return this.mStatus;
275 }
276
277 @Override
278 public long getFileSize() {
279 if (this.file != null) {
280 return this.file.getExpectedSize();
281 } else {
282 return 0;
283 }
284 }
285
286 @Override
287 public int getProgress() {
288 return this.mProgress;
289 }
290}