diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 1ea59ba83c98f0aac81cf46e11766e811e82ab95..f48e2023d436a35b3a05476913bf89b408f85dbe 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -7,7 +7,6 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; @@ -16,14 +15,11 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.pep.Avatar; import im.conversations.android.xmpp.model.stanza.Iq; -import im.conversations.android.xmpp.model.upload.Request; -import java.nio.ByteBuffer; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.UUID; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.ecc.ECPublicKey; import org.whispersystems.libsignal.state.PreKeyRecord; @@ -262,35 +258,6 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public Iq requestHttpUploadSlot( - final Jid host, final DownloadableFile file, final String mime) { - final Iq packet = new Iq(Iq.Type.GET); - packet.setTo(host); - final var request = packet.addExtension(new Request()); - request.setFilename(convertFilename(file.getName())); - request.setSize(file.getExpectedSize()); - return packet; - } - - private static String convertFilename(String name) { - int pos = name.indexOf('.'); - if (pos != -1) { - try { - UUID uuid = UUID.fromString(name.substring(0, pos)); - ByteBuffer bb = ByteBuffer.wrap(new byte[16]); - bb.putLong(uuid.getMostSignificantBits()); - bb.putLong(uuid.getLeastSignificantBits()); - return Base64.encodeToString( - bb.array(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP) - + name.substring(pos); - } catch (Exception e) { - return name; - } - } else { - return name; - } - } - public static Iq generateCreateAccountWithCaptcha( final Account account, final String id, final Data data) { final Iq register = new Iq(Iq.Type.SET); diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index 9154c2c422783ed65d10cec2451db1c91220d94c..f476636dc25617b5eb4f8b0a5574874dc9166abc 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -17,6 +17,7 @@ import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.xmpp.manager.HttpUploadManager; import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -39,12 +40,12 @@ public class HttpUploadConnection private boolean delayed = false; private DownloadableFile file; private final Message message; - private SlotRequester.Slot slot; + private HttpUploadManager.Slot slot; private byte[] key = null; private long transmitted = 0; private Call mostRecentCall; - private ListenableFuture slotFuture; + private ListenableFuture slotFuture; public HttpUploadConnection( final Message message, final HttpConnectionManager httpConnectionManager) { @@ -78,7 +79,7 @@ public class HttpUploadConnection @Override public void cancel() { - final ListenableFuture slotFuture = this.slotFuture; + final ListenableFuture slotFuture = this.slotFuture; if (slotFuture != null && !slotFuture.isDone()) { if (slotFuture.cancel(true)) { Log.d(Config.LOGTAG, "cancelled slot requester"); @@ -94,7 +95,7 @@ public class HttpUploadConnection private void fail(String errorMessage) { finish(); final Call call = this.mostRecentCall; - final Future slotFuture = this.slotFuture; + final Future slotFuture = this.slotFuture; final boolean cancelled = (call != null && call.isCanceled()) || (slotFuture != null && slotFuture.isCancelled()); @@ -109,8 +110,9 @@ public class HttpUploadConnection message.setTransferable(null); } - public void init(boolean delay) { + public void init(final boolean delay) { final Account account = message.getConversation().getAccount(); + final var connection = account.getXmppConnection(); this.file = mXmppConnectionService.getFileBackend().getFile(message, false); final String mime; if (message.getEncryption() == Message.ENCRYPTION_PGP @@ -129,12 +131,12 @@ public class HttpUploadConnection } this.file.setExpectedSize(originalFileSize + (file.getKey() != null ? 16 : 0)); message.resetFileParams(); - this.slotFuture = new SlotRequester(mXmppConnectionService).request(account, file, mime); + this.slotFuture = connection.getManager(HttpUploadManager.class).request(file, mime); Futures.addCallback( this.slotFuture, new FutureCallback<>() { @Override - public void onSuccess(@Nullable SlotRequester.Slot result) { + public void onSuccess(@Nullable HttpUploadManager.Slot result) { HttpUploadConnection.this.slot = result; try { HttpUploadConnection.this.upload(); diff --git a/src/main/java/eu/siacs/conversations/http/SlotRequester.java b/src/main/java/eu/siacs/conversations/http/SlotRequester.java deleted file mode 100644 index 5bb5d7772b253857e7cc8eab81c60c4aedb9d1c2..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/http/SlotRequester.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2018, Daniel Gultsch All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package eu.siacs.conversations.http; - -import android.util.Log; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.DownloadableFile; -import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.xml.Namespace; -import eu.siacs.conversations.xmpp.Jid; -import im.conversations.android.xmpp.model.stanza.Iq; -import im.conversations.android.xmpp.model.upload.Header; -import im.conversations.android.xmpp.model.upload.Slot; -import java.util.Map; -import okhttp3.Headers; -import okhttp3.HttpUrl; - -public class SlotRequester { - - private final XmppConnectionService service; - - public SlotRequester(XmppConnectionService service) { - this.service = service; - } - - public ListenableFuture request( - final Account account, final DownloadableFile file, final String mime) { - final var result = - account.getXmppConnection() - .getServiceDiscoveryResultByFeature(Namespace.HTTP_UPLOAD); - if (result == null) { - return Futures.immediateFailedFuture( - new IllegalStateException("No HTTP upload host found")); - } - return requestHttpUpload(account, result.getKey(), file, mime); - } - - private ListenableFuture requestHttpUpload( - final Account account, final Jid host, final DownloadableFile file, final String mime) { - final Iq request = service.getIqGenerator().requestHttpUploadSlot(host, file, mime); - final var iqFuture = service.sendIqPacket(account, request); - return Futures.transform( - iqFuture, - response -> { - final var slot = - response.getExtension( - im.conversations.android.xmpp.model.upload.Slot.class); - if (slot == null) { - Log.d(Config.LOGTAG, "-->" + response); - throw new IllegalStateException("Slot not found in IQ response"); - } - final var getUrl = slot.getGetUrl(); - final var put = slot.getPut(); - if (getUrl == null || put == null) { - throw new IllegalStateException("Missing get or put in slot response"); - } - final var putUrl = put.getUrl(); - if (putUrl == null) { - throw new IllegalStateException("Missing put url"); - } - final var headers = new ImmutableMap.Builder(); - for (final Header header : put.getHeaders()) { - final String name = header.getHeaderName(); - final String value = header.getContent(); - if (Strings.isNullOrEmpty(value) || value.contains("\n")) { - continue; - } - headers.put(name, value.trim()); - } - headers.put("Content-Type", mime == null ? "application/octet-stream" : mime); - return new Slot(putUrl, getUrl, headers.buildKeepingLast()); - }, - MoreExecutors.directExecutor()); - } - - public static class Slot { - public final HttpUrl put; - public final HttpUrl get; - public final Headers headers; - - private Slot(HttpUrl put, HttpUrl get, Headers headers) { - this.put = put; - this.get = get; - this.headers = headers; - } - - private Slot(HttpUrl put, HttpUrl getUrl, Map headers) { - this.put = put; - this.get = getUrl; - this.headers = Headers.of(headers); - } - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/Managers.java b/src/main/java/eu/siacs/conversations/xmpp/Managers.java index f3c873a90c130d6d51984956dd8e165f6b308bbe..116427f371750c5a8909c8dff35e200678c7c1a1 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/Managers.java +++ b/src/main/java/eu/siacs/conversations/xmpp/Managers.java @@ -11,6 +11,7 @@ import eu.siacs.conversations.xmpp.manager.BookmarkManager; import eu.siacs.conversations.xmpp.manager.CarbonsManager; import eu.siacs.conversations.xmpp.manager.DiscoManager; import eu.siacs.conversations.xmpp.manager.EntityTimeManager; +import eu.siacs.conversations.xmpp.manager.HttpUploadManager; import eu.siacs.conversations.xmpp.manager.LegacyBookmarkManager; import eu.siacs.conversations.xmpp.manager.MessageDisplayedSynchronizationManager; import eu.siacs.conversations.xmpp.manager.NickManager; @@ -39,6 +40,7 @@ public class Managers { .put(CarbonsManager.class, new CarbonsManager(context, connection)) .put(DiscoManager.class, new DiscoManager(context, connection)) .put(EntityTimeManager.class, new EntityTimeManager(context, connection)) + .put(HttpUploadManager.class, new HttpUploadManager(context, connection)) .put(LegacyBookmarkManager.class, new LegacyBookmarkManager(context, connection)) .put( MessageDisplayedSynchronizationManager.class, diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 9cd3f96409e521757219dc1462912a6078e8fdb0..d932461384db3339d5ab15c499f1b036a93c0dd3 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -2730,29 +2730,6 @@ public class XmppConnection implements Runnable { return this.managers.getInstance(clazz); } - private List> findDiscoItemsByFeature(final String feature) { - final List> items = new ArrayList<>(); - for (final Entry cursor : - getManager(DiscoManager.class).getServerItems().entrySet()) { - if (cursor.getValue().getFeatureStrings().contains(feature)) { - items.add(cursor); - } - } - return items; - } - - public Entry getServiceDiscoveryResultByFeature(final String feature) { - return Iterables.getFirst(findDiscoItemsByFeature(feature), null); - } - - public Jid findDiscoItemByFeature(final String feature) { - final var items = findDiscoItemsByFeature(feature); - if (items.isEmpty()) { - return null; - } - return Iterables.getFirst(items, null).getKey(); - } - public List getMucServersWithholdAccount() { final List servers = getMucServers(); servers.remove(account.getDomain().toString()); @@ -3207,7 +3184,8 @@ public class XmppConnection implements Runnable { if (Config.DISABLE_HTTP_UPLOAD) { return false; } - final var result = getServiceDiscoveryResultByFeature(Namespace.HTTP_UPLOAD); + final var result = + getManager(DiscoManager.class).findDiscoItemByFeature(Namespace.HTTP_UPLOAD); if (result == null) { return false; } @@ -3238,7 +3216,8 @@ public class XmppConnection implements Runnable { } public long getMaxHttpUploadSize() { - final var result = getServiceDiscoveryResultByFeature(Namespace.HTTP_UPLOAD); + final var result = + getManager(DiscoManager.class).findDiscoItemByFeature(Namespace.HTTP_UPLOAD); if (result == null) { return -1; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java index a0e778d5f93839c8848a00af8d57e5c52b3e73cc..ef89de61f18bab68416f45babf2fb44ee421aa43 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java @@ -29,6 +29,7 @@ import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection; import eu.siacs.conversations.xmpp.jingle.DirectConnectionUtils; import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo; +import eu.siacs.conversations.xmpp.manager.DiscoManager; import im.conversations.android.xmpp.model.stanza.Iq; import java.io.IOException; import java.io.InputStream; @@ -306,14 +307,18 @@ public class SocksByteStreamsTransport implements Transport { return Futures.immediateFailedFuture( new IllegalStateException("Proxy look up is disabled")); } - final Jid streamer = xmppConnection.findDiscoItemByFeature(Namespace.BYTE_STREAMS); + final var streamer = + xmppConnection + .getManager(DiscoManager.class) + .findDiscoItemByFeature(Namespace.BYTE_STREAMS); if (streamer == null) { return Futures.immediateFailedFuture( new IllegalStateException("No proxy/streamer found")); } final Iq iqRequest = new Iq(Iq.Type.GET); - iqRequest.setTo(streamer); + iqRequest.setTo(streamer.getKey()); // TODO urgent refactor to extension + // TODO and maybe move to Manager iqRequest.query(Namespace.BYTE_STREAMS); final SettableFuture candidateFuture = SettableFuture.create(); xmppConnection.sendIqPacket( @@ -342,7 +347,7 @@ public class SocksByteStreamsTransport implements Transport { new Candidate( UUID.randomUUID().toString(), host, - streamer, + streamer.getKey(), port, 655360 + (initiator ? 0 : 15), CandidateType.PROXY)); diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java index 526b43b42228af8d60db2612f29967f7250e694f..20583c93cf30d69e919bea1b651e08c310ee71eb 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/manager/DiscoManager.java @@ -7,6 +7,8 @@ import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; import com.google.common.io.BaseEncoding; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -467,6 +469,15 @@ public class DiscoManager extends AbstractManager { } } + public Map findDiscoItemsByFeature(final String feature) { + return Maps.filterValues(getServerItems(), v -> v.hasFeature(feature)); + } + + public Map.Entry findDiscoItemByFeature(final String feature) { + final var items = findDiscoItemsByFeature(feature); + return Iterables.getFirst(items.entrySet(), null); + } + public static final class CapsHashMismatchException extends IllegalStateException { public CapsHashMismatchException(final String message) { super(message); diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/HttpUploadManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/HttpUploadManager.java new file mode 100644 index 0000000000000000000000000000000000000000..13c26b77a02b88a0020c168d1bb9f0ccd121761d --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/manager/HttpUploadManager.java @@ -0,0 +1,108 @@ +package eu.siacs.conversations.xmpp.manager; + +import android.content.Context; +import android.util.Base64; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import eu.siacs.conversations.entities.DownloadableFile; +import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.Jid; +import eu.siacs.conversations.xmpp.XmppConnection; +import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.upload.Request; +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.UUID; +import okhttp3.Headers; +import okhttp3.HttpUrl; + +public class HttpUploadManager extends AbstractManager { + + public HttpUploadManager(final Context context, final XmppConnection connection) { + super(context, connection); + } + + public ListenableFuture request(final DownloadableFile file, final String mime) { + final var result = + getManager(DiscoManager.class).findDiscoItemByFeature(Namespace.HTTP_UPLOAD); + if (result == null) { + return Futures.immediateFailedFuture( + new IllegalStateException("No HTTP upload host found")); + } + return requestHttpUpload(result.getKey(), file, mime); + } + + private ListenableFuture requestHttpUpload( + final Jid host, final DownloadableFile file, final String mime) { + final Iq iq = new Iq(Iq.Type.GET); + iq.setTo(host); + final var request = iq.addExtension(new Request()); + request.setFilename(convertFilename(file.getName())); + request.setSize(file.getExpectedSize()); + request.setContentType(mime); + final var iqFuture = this.connection.sendIqPacket(iq); + return Futures.transform( + iqFuture, + response -> { + final var slot = + response.getExtension( + im.conversations.android.xmpp.model.upload.Slot.class); + if (slot == null) { + throw new IllegalStateException("Slot not found in IQ response"); + } + final var getUrl = slot.getGetUrl(); + final var put = slot.getPut(); + if (getUrl == null || put == null) { + throw new IllegalStateException("Missing get or put in slot response"); + } + final var putUrl = put.getUrl(); + if (putUrl == null) { + throw new IllegalStateException("Missing put url"); + } + final var contentType = mime == null ? "application/octet-stream" : mime; + final var headers = + new ImmutableMap.Builder() + .putAll(put.getHeadersAllowList()) + .put("Content-Type", contentType) + .buildKeepingLast(); + return new Slot(putUrl, getUrl, headers); + }, + MoreExecutors.directExecutor()); + } + + private static String convertFilename(final String name) { + int pos = name.indexOf('.'); + if (pos < 0) { + return name; + } + try { + UUID uuid = UUID.fromString(name.substring(0, pos)); + ByteBuffer bb = ByteBuffer.wrap(new byte[16]); + bb.putLong(uuid.getMostSignificantBits()); + bb.putLong(uuid.getLeastSignificantBits()); + return Base64.encodeToString( + bb.array(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP) + + name.substring(pos); + } catch (final Exception e) { + return name; + } + } + + public static class Slot { + public final HttpUrl put; + public final HttpUrl get; + public final Headers headers; + + private Slot(final HttpUrl put, final HttpUrl get, final Headers headers) { + this.put = put; + this.get = get; + this.headers = headers; + } + + private Slot(final HttpUrl put, final HttpUrl get, final Map headers) { + this(put, get, Headers.of(headers)); + } + } +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/VCardManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/VCardManager.java index aa2765deb0d29e462ffa013311c94321b3f0ea44..a2d5873664a2d3ca75b386fc262ad2cd1d11e41c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/manager/VCardManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/manager/VCardManager.java @@ -37,11 +37,13 @@ public class VCardManager extends AbstractManager { vCard -> { final var photo = vCard.getPhoto(); if (photo == null) { - throw new IllegalStateException("No photo in vCard"); + throw new IllegalStateException( + String.format("No photo in vCard of %s", address)); } final var binaryValue = photo.getBinaryValue(); if (binaryValue == null) { - throw new IllegalStateException("Photo has no binary value"); + throw new IllegalStateException( + String.format("Photo has no binary value in vCard of %s", address)); } return binaryValue.asBytes(); }, diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Put.java b/src/main/java/im/conversations/android/xmpp/model/upload/Put.java index 1b52a495c551b4acb34778f09c45aff11999ed12..30913b039a70c5a4ac09b98f22477bf6d5b70891 100644 --- a/src/main/java/im/conversations/android/xmpp/model/upload/Put.java +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Put.java @@ -1,14 +1,21 @@ package im.conversations.android.xmpp.model.upload; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.Extension; +import java.util.Arrays; import java.util.Collection; +import java.util.List; +import java.util.Map; import okhttp3.HttpUrl; @XmlElement public class Put extends Extension { + private static final List HEADER_ALLOW_LIST = + Arrays.asList("Authorization", "Cookie", "Expires"); + public Put() { super(Put.class); } @@ -24,4 +31,19 @@ public class Put extends Extension { public Collection
getHeaders() { return this.getExtensions(Header.class); } + + public Map getHeadersAllowList() { + final var headers = new ImmutableMap.Builder(); + for (final Header header : this.getHeaders()) { + final String name = header.getHeaderName(); + final String value = Strings.nullToEmpty(header.getContent()).trim(); + if (Strings.isNullOrEmpty(value) || value.contains("\n")) { + continue; + } + if (HEADER_ALLOW_LIST.contains(name)) { + headers.put(name, value); + } + } + return headers.buildKeepingLast(); + } }