Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.netty.handler.codec.http.HttpStatusClass;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http2.Http2StreamChannel;
import org.asynchttpclient.AsyncHttpClientConfig;
import org.asynchttpclient.Realm;
import org.asynchttpclient.Realm.AuthScheme;
Expand All @@ -32,11 +33,16 @@
import org.asynchttpclient.netty.channel.ChannelManager;
import org.asynchttpclient.netty.channel.PrincipalScopedPartitionKey;
import org.asynchttpclient.netty.request.NettyRequestSender;
import io.netty.handler.codec.http2.Http2StreamChannel;
import org.asynchttpclient.request.body.generator.FileBodyGenerator;
import org.asynchttpclient.request.body.generator.InputStreamBodyGenerator;
import org.asynchttpclient.request.body.multipart.InputStreamPart;
import org.asynchttpclient.request.body.multipart.Part;
import org.asynchttpclient.uri.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

Expand All @@ -56,7 +62,6 @@
import static org.asynchttpclient.util.HttpConstants.ResponseStatusCodes.SEE_OTHER_303;
import static org.asynchttpclient.util.HttpConstants.ResponseStatusCodes.TEMPORARY_REDIRECT_307;
import static org.asynchttpclient.util.HttpUtils.followRedirect;
import static org.asynchttpclient.util.MiscUtils.isNonEmpty;
import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace;

public class Redirect30xInterceptor {
Expand Down Expand Up @@ -132,13 +137,28 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture<?>
LOGGER.debug("Stripping credentials on redirect to {}", newUri);
}

final RequestBuilder requestBuilder = new RequestBuilder(switchToGet ? GET : originalMethod)
.setChannelPoolPartitioning(request.getChannelPoolPartitioning())
final RequestBuilder requestBuilder;
if (keepBody) {
ensureBodyReplayable(request);
requestBuilder = request.toBuilder();
if (!sameBase) {
// An explicitly resolved address and virtual host belong to the previous target.
requestBuilder.setAddress(null);
requestBuilder.setVirtualHost(null);
}
} else {
requestBuilder = new RequestBuilder(switchToGet ? GET : originalMethod)
.setChannelPoolPartitioning(request.getChannelPoolPartitioning())
.setLocalAddress(request.getLocalAddress())
.setNameResolver(request.getNameResolver())
.setProxyServer(request.getProxyServer())
.setRangeOffset(request.getRangeOffset());
}

requestBuilder.setMethod(switchToGet ? GET : originalMethod)
.setFollowRedirect(true)
.setLocalAddress(request.getLocalAddress())
.setNameResolver(request.getNameResolver())
.setProxyServer(request.getProxyServer())
.setRealm(stripAuth ? null : request.getRealm())
.setHeaders(propagatedHeaders(request, realm, keepBody, stripAuth))
.setRequestTimeout(request.getRequestTimeout())
.setReadTimeout(request.getReadTimeout());

Expand All @@ -154,27 +174,10 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture<?>
if (stripAuth) {
future.setRealm(null);
future.setProxyRealm(null);
// Request.toBuilder copies Cookie objects separately from the Cookie header.
requestBuilder.resetCookies();
}

if (keepBody) {
requestBuilder.setCharset(request.getCharset());
if (isNonEmpty(request.getFormParams())) {
requestBuilder.setFormParams(request.getFormParams());
} else if (request.getStringData() != null) {
requestBuilder.setBody(request.getStringData());
} else if (request.getByteData() != null) {
requestBuilder.setBody(request.getByteData());
} else if (request.getByteBufferData() != null) {
requestBuilder.setBody(request.getByteBufferData());
} else if (request.getBodyGenerator() != null) {
requestBuilder.setBody(request.getBodyGenerator());
} else if (isNonEmpty(request.getBodyParts())) {
requestBuilder.setBodyParts(request.getBodyParts());
}
}

requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody, stripAuth));

// in case of a redirect from HTTP to HTTPS, future
// attributes might change
final boolean initialConnectionKeepAlive = future.isKeepAlive();
Expand All @@ -192,7 +195,7 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture<?>
}
}

if (sameBase) {
if (sameBase && !keepBody) {
// we can only assume the virtual host is still valid if the baseUrl is the same
requestBuilder.setVirtualHost(request.getVirtualHost());
}
Expand Down Expand Up @@ -229,10 +232,76 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture<?>
return false;
}

private static void ensureBodyReplayable(Request request) throws IOException {
for (Part part : request.getBodyParts()) {
if (part instanceof InputStreamPart) {
throw new IOException("Multipart InputStream body part '" + part.getName()
+ "' cannot be replayed after redirect");
}
}

File file = selectedBodyFile(request);
Comment thread
hyperxpro marked this conversation as resolved.
if (file != null && !file.isFile()) {
throw new IOException("Redirect request body file " + file.getAbsolutePath()
+ " is not a file or does not exist");
}
}

private static File selectedBodyFile(Request request) {
// Keep this precedence aligned with NettyRequestFactory.body. A File can remain set alongside a
Comment thread
hyperxpro marked this conversation as resolved.
// higher-priority representation, so only validate it when the original request actually sent it.
if (hasBodyBeforeFile(request)) {
return null;
}
if (request.getFile() != null) {
return request.getFile();
}
return request.getBodyGenerator() instanceof FileBodyGenerator
? ((FileBodyGenerator) request.getBodyGenerator()).getFile()
: null;
}

private static boolean hasBodyBeforeFile(Request request) {
return hasBodyBeforeStream(request)
|| request.getStreamData() != null
|| !request.getFormParams().isEmpty()
|| !request.getBodyParts().isEmpty();
}

private static boolean hasBodyBeforeStream(Request request) {
return request.getByteData() != null
|| request.getCompositeByteData() != null
|| request.getStringData() != null
|| request.getByteBufferData() != null
|| request.getByteBufData() != null;
}

private static boolean selectedBodyHasUnknownLength(Request request) {
if (hasBodyBeforeStream(request)) {
return false;
}
if (request.getStreamData() != null) {
return true;
}
if (!request.getFormParams().isEmpty()
|| !request.getBodyParts().isEmpty()
|| request.getFile() != null) {
return false;
}
if (request.getBodyGenerator() instanceof InputStreamBodyGenerator) {
return ((InputStreamBodyGenerator) request.getBodyGenerator()).getContentLength() < 0;
}
return request.getBodyGenerator() != null
&& !(request.getBodyGenerator() instanceof FileBodyGenerator);
}

private static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody, boolean stripAuthorization) {
HttpHeaders headers = request.getHeaders()
.remove(HOST)
.remove(CONTENT_LENGTH);
HttpHeaders headers = request.getHeaders().copy().remove(HOST);

// Preserve an explicit length when the selected stream representation cannot rebuild it.
if (!keepBody || !selectedBodyHasUnknownLength(request)) {
headers.remove(CONTENT_LENGTH);
}

if (!keepBody) {
headers.remove(CONTENT_TYPE);
Expand Down
Loading
Loading