HttpUploadConnection.java

  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}