1package eu.siacs.conversations.http;
2
3import android.util.Log;
4
5import org.jetbrains.annotations.NotNull;
6
7import java.io.IOException;
8import java.util.Arrays;
9import java.util.List;
10import java.util.concurrent.TimeUnit;
11
12import eu.siacs.conversations.Config;
13import eu.siacs.conversations.entities.Account;
14import eu.siacs.conversations.entities.DownloadableFile;
15import eu.siacs.conversations.entities.Message;
16import eu.siacs.conversations.entities.Transferable;
17import eu.siacs.conversations.services.AbstractConnectionManager;
18import eu.siacs.conversations.services.XmppConnectionService;
19import eu.siacs.conversations.utils.CryptoHelper;
20import okhttp3.Call;
21import okhttp3.Callback;
22import okhttp3.OkHttpClient;
23import okhttp3.Request;
24import okhttp3.RequestBody;
25import okhttp3.Response;
26
27public class HttpUploadConnection implements Transferable, AbstractConnectionManager.ProgressListener {
28
29 static final List<String> WHITE_LISTED_HEADERS = Arrays.asList(
30 "Authorization",
31 "Cookie",
32 "Expires"
33 );
34
35 private final HttpConnectionManager mHttpConnectionManager;
36 private final XmppConnectionService mXmppConnectionService;
37 private final SlotRequester mSlotRequester;
38 private final Method method;
39 private final boolean mUseTor;
40 private boolean delayed = false;
41 private DownloadableFile file;
42 private final Message message;
43 private String mime;
44 private SlotRequester.Slot slot;
45 private byte[] key = null;
46
47 private long transmitted = 0;
48 private Call mostRecentCall;
49
50 public HttpUploadConnection(Message message, Method method, HttpConnectionManager httpConnectionManager) {
51 this.message = message;
52 this.method = method;
53 this.mHttpConnectionManager = httpConnectionManager;
54 this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService();
55 this.mSlotRequester = new SlotRequester(this.mXmppConnectionService);
56 this.mUseTor = mXmppConnectionService.useTorToConnect();
57 }
58
59 @Override
60 public boolean start() {
61 return false;
62 }
63
64 @Override
65 public int getStatus() {
66 return STATUS_UPLOADING;
67 }
68
69 @Override
70 public long getFileSize() {
71 return file == null ? 0 : file.getExpectedSize();
72 }
73
74 @Override
75 public int getProgress() {
76 if (file == null) {
77 return 0;
78 }
79 return (int) ((((double) transmitted) / file.getExpectedSize()) * 100);
80 }
81
82 @Override
83 public void cancel() {
84 final Call call = this.mostRecentCall;
85 if (call != null && !call.isCanceled()) {
86 call.cancel();
87 }
88 }
89
90 private void fail(String errorMessage) {
91 finish();
92 final Call call = this.mostRecentCall;
93 final boolean cancelled = call != null && call.isCanceled();
94 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, cancelled ? Message.ERROR_MESSAGE_CANCELLED : errorMessage);
95 }
96
97 private void finish() {
98 mHttpConnectionManager.finishUploadConnection(this);
99 message.setTransferable(null);
100 }
101
102 public void init(boolean delay) {
103 final Account account = message.getConversation().getAccount();
104 this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
105 if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
106 this.mime = "application/pgp-encrypted";
107 } else {
108 this.mime = this.file.getMimeType();
109 }
110 final long originalFileSize = file.getSize();
111 this.delayed = delay;
112 if (Config.ENCRYPT_ON_HTTP_UPLOADED
113 || message.getEncryption() == Message.ENCRYPTION_AXOLOTL
114 || message.getEncryption() == Message.ENCRYPTION_OTR) {
115 this.key = new byte[44];
116 mXmppConnectionService.getRNG().nextBytes(this.key);
117 this.file.setKeyAndIv(this.key);
118 }
119 this.file.setExpectedSize(originalFileSize + (file.getKey() != null ? 16 : 0));
120 message.resetFileParams();
121 this.mSlotRequester.request(method, account, file, mime, new SlotRequester.OnSlotRequested() {
122 @Override
123 public void success(final SlotRequester.Slot slot) {
124 //TODO needs to mark the message as cancelled afterwards (ie call fail())
125 HttpUploadConnection.this.slot = slot;
126 HttpUploadConnection.this.upload();
127 }
128
129 @Override
130 public void failure(String message) {
131 fail(message);
132 }
133 });
134 message.setTransferable(this);
135 mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
136 }
137
138 private void upload() {
139 final OkHttpClient client = mHttpConnectionManager.buildHttpClient(
140 slot.put,
141 message.getConversation().getAccount(),
142 true
143 );
144 final RequestBody requestBody = AbstractConnectionManager.requestBody(file, this);
145 final Request request = new Request.Builder()
146 .url(slot.put)
147 .put(requestBody)
148 .headers(slot.headers)
149 .build();
150 Log.d(Config.LOGTAG, "uploading file to " + slot.put);
151 this.mostRecentCall = client.newCall(request);
152 this.mostRecentCall.enqueue(new Callback() {
153 @Override
154 public void onFailure(@NotNull Call call, IOException e) {
155 Log.d(Config.LOGTAG, "http upload failed", e);
156 fail(e.getMessage());
157 }
158
159 @Override
160 public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
161 final int code = response.code();
162 if (code == 200 || code == 201) {
163 Log.d(Config.LOGTAG, "finished uploading file");
164 final String get;
165 if (key != null) {
166 get = AesGcmURL.toAesGcmUrl(slot.get.newBuilder().fragment(CryptoHelper.bytesToHex(key)).build());
167 } else {
168 get = slot.get.toString();
169 }
170 mXmppConnectionService.getFileBackend().updateFileParams(message, get);
171 mXmppConnectionService.getFileBackend().updateMediaScanner(file);
172 finish();
173 if (!message.isPrivateMessage()) {
174 message.setCounterpart(message.getConversation().getJid().asBareJid());
175 }
176 mXmppConnectionService.resendMessage(message, delayed);
177 } else {
178 Log.d(Config.LOGTAG, "http upload failed because response code was " + code);
179 fail("http upload failed because response code was " + code);
180 }
181 }
182 });
183 }
184
185 public Message getMessage() {
186 return message;
187 }
188
189 @Override
190 public void onProgress(final long progress) {
191 this.transmitted = progress;
192 mHttpConnectionManager.updateConversationUi(false);
193 }
194}