HttpUploadConnection.java

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