Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,48 @@ default boolean isUseAbsoluteRequestDeadline() {
*/
int getMaxRedirects();

/**
* Whether to refuse a redirect that leaves an {@code https} or {@code wss} exchange for an unsecured
* scheme. The redirected request is never sent; the exchange fails with a
* {@link org.asynchttpclient.handler.RedirectRefusedException}.
* <p>
* Applies to every hop, bodies or not, and is judged against the scheme of the request sent on that hop,
* so a cleartext hop has no downgrade to refuse but a later secured one in the same exchange still does.
* <p>
* {@link org.asynchttpclient.Request#getRefuseSchemeDowngradeOnRedirect()} can refuse a hop this allows,
* but cannot permit one this refuses. An implementation that wraps another configuration has to forward
* this, or the operator's setting on the wrapped one is lost to the {@code false} default here.
*
* @return true to refuse such a hop, false to follow it
*/
default boolean isRefuseSchemeDowngradeOnRedirect() {
return false;
}

/**
* Whether to refuse a redirect that would resend this request's content to a different origin. It applies
* only to a hop that keeps the body and only when the request carries content, so a bodyless cross-origin
* hop and a same-origin hop are untouched, while a {@code GET} carrying content is refused like any
* other. Moving the same host onto TLS is exempt, keeping the port or moving between the two scheme
* defaults. The exempted hop keeps the host, not the path: the target path and query are the server's
* choice.
* <p>
* The hop is refused rather than stripped of its body, because a {@code PUT} arriving empty would
* overwrite the target with nothing. Weigh that against what it blocks: an upload answered with a
* redirect to another host stops working.
* <p>
* The hop is still not same-base, so credentials are stripped from it as they always were.
* <p>
* {@link org.asynchttpclient.Request#getRefuseCrossOriginBodyOnRedirect()} can refuse a hop this allows,
* but cannot permit one this refuses. An implementation that wraps another configuration has to forward
* this, or the operator's setting on the wrapped one is lost to the {@code false} default here.
*
* @return true to refuse such a hop, false to follow it
*/
default boolean isRefuseCrossOriginBodyOnRedirect() {
return false;
}

/**
* Is the {@link ChannelPool} support enabled.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultMaxRequestRetry;
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultPooledConnectionIdleTimeout;
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultReadTimeout;
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultRefuseCrossOriginBodyOnRedirect;
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultRefuseSchemeDowngradeOnRedirect;
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultLoadBalance;
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultRequestTimeout;
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultShutdownQuietPeriod;
Expand Down Expand Up @@ -131,6 +133,8 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
private final boolean followRedirect;
private final int maxRedirects;
private final boolean strict302Handling;
private final boolean refuseSchemeDowngradeOnRedirect;
private final boolean refuseCrossOriginBodyOnRedirect;
private final boolean compressionEnforced;

private final boolean enableAutomaticDecompression;
Expand Down Expand Up @@ -239,6 +243,8 @@ private DefaultAsyncHttpClientConfig(// http
boolean followRedirect,
int maxRedirects,
boolean strict302Handling,
boolean refuseSchemeDowngradeOnRedirect,
boolean refuseCrossOriginBodyOnRedirect,
boolean compressionEnforced,
boolean enableAutomaticDecompression,
long maxDecompressedResponseSize,
Expand Down Expand Up @@ -346,6 +352,8 @@ private DefaultAsyncHttpClientConfig(// http
this.followRedirect = followRedirect;
this.maxRedirects = maxRedirects;
this.strict302Handling = strict302Handling;
this.refuseSchemeDowngradeOnRedirect = refuseSchemeDowngradeOnRedirect;
this.refuseCrossOriginBodyOnRedirect = refuseCrossOriginBodyOnRedirect;
this.compressionEnforced = compressionEnforced;
this.enableAutomaticDecompression = enableAutomaticDecompression;
this.maxDecompressedResponseSize = maxDecompressedResponseSize;
Expand Down Expand Up @@ -499,6 +507,16 @@ public boolean isStrict302Handling() {
return strict302Handling;
}

@Override
public boolean isRefuseSchemeDowngradeOnRedirect() {
return refuseSchemeDowngradeOnRedirect;
}

@Override
public boolean isRefuseCrossOriginBodyOnRedirect() {
return refuseCrossOriginBodyOnRedirect;
}

@Override
public boolean isCompressionEnforced() {
return compressionEnforced;
Expand Down Expand Up @@ -960,6 +978,8 @@ public static class Builder {
private boolean followRedirect = defaultFollowRedirect();
private int maxRedirects = defaultMaxRedirects();
private boolean strict302Handling = defaultStrict302Handling();
private boolean refuseSchemeDowngradeOnRedirect = defaultRefuseSchemeDowngradeOnRedirect();
private boolean refuseCrossOriginBodyOnRedirect = defaultRefuseCrossOriginBodyOnRedirect();
private boolean compressionEnforced = defaultCompressionEnforced();
private boolean enableAutomaticDecompression = defaultEnableAutomaticDecompression();
private long maxDecompressedResponseSize = defaultMaxDecompressedResponseSize();
Expand Down Expand Up @@ -1070,6 +1090,8 @@ public Builder(AsyncHttpClientConfig config) {
followRedirect = config.isFollowRedirect();
maxRedirects = config.getMaxRedirects();
strict302Handling = config.isStrict302Handling();
refuseSchemeDowngradeOnRedirect = config.isRefuseSchemeDowngradeOnRedirect();
refuseCrossOriginBodyOnRedirect = config.isRefuseCrossOriginBodyOnRedirect();
compressionEnforced = config.isCompressionEnforced();
enableAutomaticDecompression = config.isEnableAutomaticDecompression();
maxDecompressedResponseSize = config.getMaxDecompressedResponseSize();
Expand Down Expand Up @@ -1193,6 +1215,16 @@ public Builder setStrict302Handling(final boolean strict302Handling) {
return this;
}

public Builder setRefuseSchemeDowngradeOnRedirect(final boolean refuseSchemeDowngradeOnRedirect) {
this.refuseSchemeDowngradeOnRedirect = refuseSchemeDowngradeOnRedirect;
return this;
}

public Builder setRefuseCrossOriginBodyOnRedirect(final boolean refuseCrossOriginBodyOnRedirect) {
this.refuseCrossOriginBodyOnRedirect = refuseCrossOriginBodyOnRedirect;
return this;
}

/**
* If true, AHC will add Accept-Encoding HTTP header to each request
* <p>
Expand Down Expand Up @@ -1800,6 +1832,8 @@ public DefaultAsyncHttpClientConfig build() {
followRedirect,
maxRedirects,
strict302Handling,
refuseSchemeDowngradeOnRedirect,
refuseCrossOriginBodyOnRedirect,
compressionEnforced,
enableAutomaticDecompression,
maxDecompressedResponseSize,
Expand Down
24 changes: 22 additions & 2 deletions client/src/main/java/org/asynchttpclient/DefaultRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public class DefaultRequest implements Request {
private final @Nullable File file;
private final @Nullable Boolean followRedirect;
private final @Nullable Boolean useAbsoluteRequestDeadline;
private final @Nullable Boolean refuseSchemeDowngradeOnRedirect;
private final @Nullable Boolean refuseCrossOriginBodyOnRedirect;
private final Duration requestTimeout;
private final Duration readTimeout;
private final long rangeOffset;
Expand Down Expand Up @@ -103,7 +105,7 @@ public DefaultRequest(String method,
this(method, uri, address, localAddress, headers, cookies, byteData, compositeByteData, stringData,
byteBufferData, byteBufData, streamData, bodyGenerator, formParams, bodyParts, virtualHost,
proxyServer, realm, file, followRedirect, requestTimeout, readTimeout, rangeOffset, charset,
channelPoolPartitioning, nameResolver, null);
channelPoolPartitioning, nameResolver, null, null, null);
}

/**
Expand All @@ -114,6 +116,10 @@ public DefaultRequest(String method,
*
* @param useAbsoluteRequestDeadline whether {@code requestTimeout} bounds the whole exchange rather than
* each attempt within it, or null to defer to the client config
* @param refuseSchemeDowngradeOnRedirect whether to refuse a redirect to an unsecured scheme, or null to
* defer to the client config
* @param refuseCrossOriginBodyOnRedirect whether to refuse a redirect that resends the content to another
* origin, or null to defer to the client config
*/
DefaultRequest(String method,
Uri uri,
Expand Down Expand Up @@ -141,7 +147,9 @@ public DefaultRequest(String method,
@Nullable Charset charset,
ChannelPoolPartitioning channelPoolPartitioning,
NameResolver<InetAddress> nameResolver,
@Nullable Boolean useAbsoluteRequestDeadline) {
@Nullable Boolean useAbsoluteRequestDeadline,
@Nullable Boolean refuseSchemeDowngradeOnRedirect,
@Nullable Boolean refuseCrossOriginBodyOnRedirect) {
this.method = method;
this.uri = uri;
this.address = address;
Expand All @@ -163,6 +171,8 @@ public DefaultRequest(String method,
this.file = file;
this.followRedirect = followRedirect;
this.useAbsoluteRequestDeadline = useAbsoluteRequestDeadline;
this.refuseSchemeDowngradeOnRedirect = refuseSchemeDowngradeOnRedirect;
this.refuseCrossOriginBodyOnRedirect = refuseCrossOriginBodyOnRedirect;
this.requestTimeout = requestTimeout == null ? Duration.ZERO : requestTimeout;
this.readTimeout = readTimeout == null ? Duration.ZERO : readTimeout;
this.rangeOffset = rangeOffset;
Expand Down Expand Up @@ -281,6 +291,16 @@ public List<Part> getBodyParts() {
return useAbsoluteRequestDeadline;
}

@Override
public @Nullable Boolean getRefuseSchemeDowngradeOnRedirect() {
return refuseSchemeDowngradeOnRedirect;
}

@Override
public @Nullable Boolean getRefuseCrossOriginBodyOnRedirect() {
return refuseCrossOriginBodyOnRedirect;
}

@Override
public Duration getRequestTimeout() {
return requestTimeout;
Expand Down
28 changes: 28 additions & 0 deletions client/src/main/java/org/asynchttpclient/Request.java
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,34 @@ default Boolean getUseAbsoluteRequestDeadline() {
return null;
}

/**
* Refuses a scheme downgrade on this request and the hops it leads to, even when
* {@link AsyncHttpClientConfig#isRefuseSchemeDowngradeOnRedirect()} is off. Tightening only, so
* {@code false} behaves as null and cannot re-enable a hop the client configuration refuses. A filter
* that builds a fresh request rather than deriving one from {@link #toBuilder()} drops it, so the
* client-wide option is what holds a posture across replays.
*
* @return true to refuse, or null or false to use the config value
*/
@Nullable
default Boolean getRefuseSchemeDowngradeOnRedirect() {
return null;
}

/**
* Refuses a cross-origin body replay on this request and the hops it leads to, even when
* {@link AsyncHttpClientConfig#isRefuseCrossOriginBodyOnRedirect()} is off. Tightening only, so
* {@code false} behaves as null and cannot re-enable a hop the client configuration refuses. A filter
* that builds a fresh request rather than deriving one from {@link #toBuilder()} drops it, so the
* client-wide option is what holds a posture across replays.
*
* @return true to refuse, or null or false to use the config value
*/
@Nullable
default Boolean getRefuseCrossOriginBodyOnRedirect() {
return null;
}

/**
* @return the request timeout. Non zero values means "override config value".
*/
Expand Down
38 changes: 37 additions & 1 deletion client/src/main/java/org/asynchttpclient/RequestBuilderBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ public abstract class RequestBuilderBase<T extends RequestBuilderBase<T>> {
protected @Nullable File file;
protected @Nullable Boolean followRedirect;
protected @Nullable Boolean useAbsoluteRequestDeadline;
protected @Nullable Boolean refuseSchemeDowngradeOnRedirect;
protected @Nullable Boolean refuseCrossOriginBodyOnRedirect;
protected @Nullable Duration requestTimeout;
protected @Nullable Duration readTimeout;
protected long rangeOffset;
Expand Down Expand Up @@ -167,6 +169,8 @@ protected RequestBuilderBase(Request prototype, boolean disableUrlEncoding, bool
file = prototype.getFile();
followRedirect = prototype.getFollowRedirect();
useAbsoluteRequestDeadline = prototype.getUseAbsoluteRequestDeadline();
refuseSchemeDowngradeOnRedirect = prototype.getRefuseSchemeDowngradeOnRedirect();
refuseCrossOriginBodyOnRedirect = prototype.getRefuseCrossOriginBodyOnRedirect();
requestTimeout = prototype.getRequestTimeout();
readTimeout = prototype.getReadTimeout();
rangeOffset = prototype.getRangeOffset();
Expand Down Expand Up @@ -611,6 +615,34 @@ public T setUseAbsoluteRequestDeadline(boolean useAbsoluteRequestDeadline) {
return asDerivedType();
}

/**
* Refuses a scheme downgrade on this request even when the client allows it.
*
* @param refuseSchemeDowngradeOnRedirect true to refuse a redirect off this request that leaves a secured
* scheme for one that is not; false leaves
* {@link AsyncHttpClientConfig#isRefuseSchemeDowngradeOnRedirect()}
* in charge and cannot relax it
* @return {@code this}
*/
public T setRefuseSchemeDowngradeOnRedirect(boolean refuseSchemeDowngradeOnRedirect) {
this.refuseSchemeDowngradeOnRedirect = refuseSchemeDowngradeOnRedirect;
return asDerivedType();
}

/**
* Refuses a cross-origin body replay on this request even when the client allows it.
*
* @param refuseCrossOriginBodyOnRedirect true to refuse a redirect off this request that would resend its
* content to another origin; false leaves
* {@link AsyncHttpClientConfig#isRefuseCrossOriginBodyOnRedirect()}
* in charge and cannot relax it
* @return {@code this}
*/
public T setRefuseCrossOriginBodyOnRedirect(boolean refuseCrossOriginBodyOnRedirect) {
this.refuseCrossOriginBodyOnRedirect = refuseCrossOriginBodyOnRedirect;
return asDerivedType();
}

public T setFollowRedirect(boolean followRedirect) {
this.followRedirect = followRedirect;
return asDerivedType();
Expand Down Expand Up @@ -699,6 +731,8 @@ private RequestBuilderBase<?> executeSignatureCalculator() {
rb.file = file;
rb.followRedirect = followRedirect;
rb.useAbsoluteRequestDeadline = useAbsoluteRequestDeadline;
rb.refuseSchemeDowngradeOnRedirect = refuseSchemeDowngradeOnRedirect;
rb.refuseCrossOriginBodyOnRedirect = refuseCrossOriginBodyOnRedirect;
rb.requestTimeout = requestTimeout;
rb.readTimeout = readTimeout;
rb.rangeOffset = rangeOffset;
Expand Down Expand Up @@ -771,6 +805,8 @@ public Request build() {
rb.charset,
rb.channelPoolPartitioning,
rb.nameResolver,
rb.useAbsoluteRequestDeadline);
rb.useAbsoluteRequestDeadline,
rb.refuseSchemeDowngradeOnRedirect,
rb.refuseCrossOriginBodyOnRedirect);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public final class AsyncHttpClientConfigDefaults {
public static final String KEEP_ALIVE_CONFIG = "keepAlive";
public static final String MAX_REQUEST_RETRY_CONFIG = "maxRequestRetry";
public static final String LOAD_BALANCE_CONFIG = "loadBalance";
public static final String REFUSE_SCHEME_DOWNGRADE_ON_REDIRECT_CONFIG = "refuseSchemeDowngradeOnRedirect";
public static final String REFUSE_CROSS_ORIGIN_BODY_ON_REDIRECT_CONFIG = "refuseCrossOriginBodyOnRedirect";
public static final String FAILED_IP_COOLDOWN_ENABLED_CONFIG = "failedIpCooldownEnabled";
public static final String USE_ABSOLUTE_REQUEST_DEADLINE_CONFIG = "useAbsoluteRequestDeadline";
public static final String FAILED_IP_COOLDOWN_PERIOD_CONFIG = "failedIpCooldownPeriod";
Expand Down Expand Up @@ -408,6 +410,14 @@ public static boolean defaultHttp2CleartextEnabled() {
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + HTTP2_CLEARTEXT_ENABLED_CONFIG);
}

public static boolean defaultRefuseSchemeDowngradeOnRedirect() {
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + REFUSE_SCHEME_DOWNGRADE_ON_REDIRECT_CONFIG);
}

public static boolean defaultRefuseCrossOriginBodyOnRedirect() {
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + REFUSE_CROSS_ORIGIN_BODY_ON_REDIRECT_CONFIG);
}

public static LoadBalance defaultLoadBalance() {
String value = AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getString(ASYNC_CLIENT_CONFIG_ROOT + LOAD_BALANCE_CONFIG);
if (value == null || value.trim().isEmpty()) {
Expand Down
Loading
Loading