SlotRequester.java

  1/*
  2 * Copyright (c) 2018, Daniel Gultsch All rights reserved.
  3 *
  4 * Redistribution and use in source and binary forms, with or without modification,
  5 * are permitted provided that the following conditions are met:
  6 *
  7 * 1. Redistributions of source code must retain the above copyright notice, this
  8 * list of conditions and the following disclaimer.
  9 *
 10 * 2. Redistributions in binary form must reproduce the above copyright notice,
 11 * this list of conditions and the following disclaimer in the documentation and/or
 12 * other materials provided with the distribution.
 13 *
 14 * 3. Neither the name of the copyright holder nor the names of its contributors
 15 * may be used to endorse or promote products derived from this software without
 16 * specific prior written permission.
 17 *
 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 21 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 22 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28 */
 29
 30package eu.siacs.conversations.http;
 31
 32import com.google.common.collect.ImmutableMap;
 33import com.google.common.util.concurrent.ListenableFuture;
 34import com.google.common.util.concurrent.SettableFuture;
 35
 36import java.util.Map;
 37
 38import eu.siacs.conversations.entities.Account;
 39import eu.siacs.conversations.entities.DownloadableFile;
 40import eu.siacs.conversations.parser.IqParser;
 41import eu.siacs.conversations.services.XmppConnectionService;
 42import eu.siacs.conversations.xml.Element;
 43import eu.siacs.conversations.xml.Namespace;
 44import eu.siacs.conversations.xmpp.IqResponseException;
 45import eu.siacs.conversations.xmpp.Jid;
 46import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 47import okhttp3.Headers;
 48import okhttp3.HttpUrl;
 49
 50public class SlotRequester {
 51
 52    private final XmppConnectionService service;
 53
 54    public SlotRequester(XmppConnectionService service) {
 55        this.service = service;
 56    }
 57
 58    public ListenableFuture<Slot> request(Method method, Account account, DownloadableFile file, String mime) {
 59        if (method == Method.HTTP_UPLOAD_LEGACY) {
 60            final Jid host = account.getXmppConnection().findDiscoItemByFeature(Namespace.HTTP_UPLOAD_LEGACY);
 61            return requestHttpUploadLegacy(account, host, file, mime);
 62        } else {
 63            final Jid host = account.getXmppConnection().findDiscoItemByFeature(Namespace.HTTP_UPLOAD);
 64            return requestHttpUpload(account, host, file, mime);
 65        }
 66    }
 67
 68    private ListenableFuture<Slot> requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) {
 69        final SettableFuture<Slot> future = SettableFuture.create();
 70        final IqPacket request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime);
 71        service.sendIqPacket(account, request, (a, packet) -> {
 72            if (packet.getType() == IqPacket.TYPE.RESULT) {
 73                final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD_LEGACY);
 74                if (slotElement != null) {
 75                    try {
 76                        final String putUrl = slotElement.findChildContent("put");
 77                        final String getUrl = slotElement.findChildContent("get");
 78                        if (getUrl != null && putUrl != null) {
 79                            final Slot slot = new Slot(
 80                                    HttpUrl.get(putUrl),
 81                                    HttpUrl.get(getUrl),
 82                                    Headers.of("Content-Type", mime == null ? "application/octet-stream" : mime)
 83                            );
 84                            future.set(slot);
 85                            return;
 86                        }
 87                    } catch (final IllegalArgumentException e) {
 88                        future.setException(e);
 89                        return;
 90                    }
 91                }
 92            }
 93            future.setException(new IqResponseException(IqParser.extractErrorMessage(packet)));
 94        });
 95        return future;
 96    }
 97
 98    private ListenableFuture<Slot> requestHttpUpload(Account account, Jid host, DownloadableFile file, String mime) {
 99        final SettableFuture<Slot> future = SettableFuture.create();
100        final IqPacket request = service.getIqGenerator().requestHttpUploadSlot(host, file, mime);
101        service.sendIqPacket(account, request, (a, packet) -> {
102            if (packet.getType() == IqPacket.TYPE.RESULT) {
103                final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD);
104                if (slotElement != null) {
105                    try {
106                        final Element put = slotElement.findChild("put");
107                        final Element get = slotElement.findChild("get");
108                        final String putUrl = put == null ? null : put.getAttribute("url");
109                        final String getUrl = get == null ? null : get.getAttribute("url");
110                        if (getUrl != null && putUrl != null) {
111                            final ImmutableMap.Builder<String, String> headers = new ImmutableMap.Builder<>();
112                            for (final Element child : put.getChildren()) {
113                                if ("header".equals(child.getName())) {
114                                    final String name = child.getAttribute("name");
115                                    final String value = child.getContent();
116                                    if (HttpUploadConnection.WHITE_LISTED_HEADERS.contains(name) && value != null && !value.trim().contains("\n")) {
117                                        headers.put(name, value.trim());
118                                    }
119                                }
120                            }
121                            headers.put("Content-Type", mime == null ? "application/octet-stream" : mime);
122                            final Slot slot = new Slot(HttpUrl.get(putUrl), HttpUrl.get(getUrl), headers.build());
123                            future.set(slot);
124                            return;
125                        }
126                    } catch (final IllegalArgumentException e) {
127                        future.setException(e);
128                        return;
129                    }
130                }
131            }
132            future.setException(new IqResponseException(IqParser.extractErrorMessage(packet)));
133        });
134        return future;
135    }
136
137    public static class Slot {
138        public final HttpUrl put;
139        public final HttpUrl get;
140        public final Headers headers;
141
142        private Slot(HttpUrl put, HttpUrl get, Headers headers) {
143            this.put = put;
144            this.get = get;
145            this.headers = headers;
146        }
147
148        private Slot(HttpUrl put, HttpUrl getUrl, Map<String, String> headers) {
149            this.put = put;
150            this.get = getUrl;
151            this.headers = Headers.of(headers);
152        }
153    }
154}