1package eu.siacs.conversations.http;
2
3import android.app.PendingIntent;
4import android.util.Log;
5
6import java.io.IOException;
7import java.io.InputStream;
8import java.io.OutputStream;
9import java.net.HttpURLConnection;
10import java.net.MalformedURLException;
11import java.net.URL;
12
13import eu.siacs.conversations.Config;
14import eu.siacs.conversations.entities.Account;
15import eu.siacs.conversations.entities.Downloadable;
16import eu.siacs.conversations.entities.DownloadableFile;
17import eu.siacs.conversations.entities.Message;
18import eu.siacs.conversations.persistance.FileBackend;
19import eu.siacs.conversations.services.XmppConnectionService;
20import eu.siacs.conversations.ui.UiCallback;
21import eu.siacs.conversations.utils.CryptoHelper;
22import eu.siacs.conversations.utils.Xmlns;
23import eu.siacs.conversations.xml.Element;
24import eu.siacs.conversations.xmpp.OnIqPacketReceived;
25import eu.siacs.conversations.xmpp.jid.Jid;
26import eu.siacs.conversations.xmpp.stanzas.IqPacket;
27
28public class HttpUploadConnection implements Downloadable {
29
30 private HttpConnectionManager mHttpConnectionManager;
31 private XmppConnectionService mXmppConnectionService;
32
33 private boolean canceled = false;
34 private Account account;
35 private DownloadableFile file;
36 private Message message;
37 private URL mGetUrl;
38 private URL mPutUrl;
39
40 private byte[] key = null;
41
42 private long transmitted = 0;
43 private long expected = 1;
44
45 public HttpUploadConnection(HttpConnectionManager httpConnectionManager) {
46 this.mHttpConnectionManager = httpConnectionManager;
47 this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService();
48 }
49
50 @Override
51 public boolean start() {
52 return false;
53 }
54
55 @Override
56 public int getStatus() {
57 return STATUS_UPLOADING;
58 }
59
60 @Override
61 public long getFileSize() {
62 return this.file.getExpectedSize();
63 }
64
65 @Override
66 public int getProgress() {
67 return (int) ((((double) transmitted) / expected) * 100);
68 }
69
70 @Override
71 public void cancel() {
72 this.canceled = true;
73 }
74
75 private void fail() {
76 mHttpConnectionManager.finishUploadConnection(this);
77 message.setDownloadable(null);
78 mXmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED);
79 }
80
81 public void init(Message message) {
82 this.message = message;
83 message.setDownloadable(this);
84 mXmppConnectionService.markMessage(message,Message.STATUS_UNSEND);
85 this.account = message.getConversation().getAccount();
86 this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
87 this.file.setExpectedSize(this.file.getSize());
88
89 if (Config.ENCRYPT_ON_HTTP_UPLOADED) {
90 this.key = new byte[48];
91 mXmppConnectionService.getRNG().nextBytes(this.key);
92 this.file.setKey(this.key);
93 }
94
95 Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD);
96 IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file);
97 mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() {
98 @Override
99 public void onIqPacketReceived(Account account, IqPacket packet) {
100 if (packet.getType() == IqPacket.TYPE.RESULT) {
101 Element slot = packet.findChild("slot",Xmlns.HTTP_UPLOAD);
102 if (slot != null) {
103 try {
104 mGetUrl = new URL(slot.findChildContent("get"));
105 mPutUrl = new URL(slot.findChildContent("put"));
106 if (!canceled) {
107 new Thread(new FileUploader()).start();
108 }
109 } catch (MalformedURLException e) {
110 fail();
111 }
112 } else {
113 fail();
114 }
115 } else {
116 fail();
117 }
118 }
119 });
120 }
121
122 private class FileUploader implements Runnable {
123
124 @Override
125 public void run() {
126 this.upload();
127 }
128
129 private void upload() {
130 OutputStream os = null;
131 InputStream is = null;
132 HttpURLConnection connection = null;
133 try {
134 Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString());
135 connection = (HttpURLConnection) mPutUrl.openConnection();
136 connection.setRequestMethod("PUT");
137 connection.setFixedLengthStreamingMode((int) file.getExpectedSize());
138 connection.setDoOutput(true);
139 connection.connect();
140 os = connection.getOutputStream();
141 is = file.createInputStream();
142 transmitted = 0;
143 expected = file.getExpectedSize();
144 int count = -1;
145 byte[] buffer = new byte[4096];
146 while (((count = is.read(buffer)) != -1) && !canceled) {
147 transmitted += count;
148 os.write(buffer, 0, count);
149 mXmppConnectionService.updateConversationUi();
150 }
151 os.flush();
152 os.close();
153 is.close();
154 int code = connection.getResponseCode();
155 if (code == 200) {
156 Log.d(Config.LOGTAG, "finished uploading file");
157 Message.ImageParams params = message.getImageParams();
158 if (key != null) {
159 mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key));
160 }
161 message.setBody(mGetUrl.toString()+"|"+String.valueOf(params.size)+"|"+String.valueOf(params.width)+"|"+String.valueOf(params.height));
162 message.setDownloadable(null);
163 message.setCounterpart(message.getConversation().getJid().toBareJid());
164 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
165 mXmppConnectionService.getPgpEngine().encrypt(message, new UiCallback<Message>() {
166 @Override
167 public void success(Message message) {
168 mXmppConnectionService.resendMessage(message);
169 }
170
171 @Override
172 public void error(int errorCode, Message object) {
173 fail();
174 }
175
176 @Override
177 public void userInputRequried(PendingIntent pi, Message object) {
178 fail();
179 }
180 });
181 } else {
182 mXmppConnectionService.resendMessage(message);
183 }
184 } else {
185 fail();
186 }
187 } catch (IOException e) {
188 Log.d(Config.LOGTAG, e.getMessage());
189 fail();
190 } finally {
191 FileBackend.close(is);
192 FileBackend.close(os);
193 if (connection != null) {
194 connection.disconnect();
195 }
196 }
197 }
198 }
199}