New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve incorrect cyclic redirect detection logic #5548
Improve incorrect cyclic redirect detection logic #5548
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5548 +/- ##
============================================
+ Coverage 74.09% 74.11% +0.02%
- Complexity 20907 20974 +67
============================================
Files 1809 1818 +9
Lines 76940 77259 +319
Branches 9833 9860 +27
============================================
+ Hits 57005 57259 +254
- Misses 15291 15321 +30
- Partials 4644 4679 +35 ☔ View full report in Codecov by Sentry. |
// Minus 1 because the original signature is also included. | ||
if (redirectSignatures.size() - 1 > maxRedirects) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for -1 😆
core/src/main/java/com/linecorp/armeria/client/RedirectingClient.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/RedirectingClient.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/RedirectingClient.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're getting there :-) Nice!
if (redirectUris.size() > maxRedirects) { | ||
final Set<RedirectSignature> redirectSignatures = redirectCtx.redirectSignatures(); | ||
// Plus 1 to maxRedirects because the original signature is also included. | ||
if (redirectSignatures.size() > maxRedirects + 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend using -1 to avoid potential overflow. Imagine a user specifies Integer.MAX_VALUE
for maxRedirects
.
return protocol.equals(that.protocol) && | ||
authority.equals(that.authority) && | ||
pathAndQuery.equals(that.pathAndQuery) && | ||
method == that.method; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about ordering the comparison in more cost-effective way? For example, we could compare pathAndQuery
and authority
before others because they are more likely to change, although it may not always be the case.
import com.linecorp.armeria.client.RedirectingClient.RedirectSignature; | ||
import com.linecorp.armeria.common.HttpMethod; | ||
|
||
public class RedirectSignatureTest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public
is redundant in JUnit 5.
public class RedirectSignatureTest { | |
class RedirectSignatureTest { |
new RedirectSignature(PROTOCOL, AUTHORITY, PATH_AND_QUERY, METHOD); | ||
|
||
@Test | ||
public void equalityWithSameObject() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public
is redundant in JUnit 5 test methods. Could you remove all public
keywords where applicable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (redirectSignatures == null) { | ||
redirectSignatures = new LinkedHashSet<>(); | ||
} | ||
|
||
if (!isAddedOriginalSignature) { | ||
final String originalProtocol = ctx.sessionProtocol().isTls() ? "https" : "http"; | ||
final RedirectSignature originalSignature = new RedirectSignature(originalProtocol, | ||
ctx.authority(), | ||
request.headers().path(), | ||
request.method()); | ||
redirectSignatures.add(originalSignature); | ||
isAddedOriginalSignature = true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) Could we add originalSignature
as redirectSignatures
is allocated?
For example:
if (redirectSignatures == null) { | |
redirectSignatures = new LinkedHashSet<>(); | |
} | |
if (!isAddedOriginalSignature) { | |
final String originalProtocol = ctx.sessionProtocol().isTls() ? "https" : "http"; | |
final RedirectSignature originalSignature = new RedirectSignature(originalProtocol, | |
ctx.authority(), | |
request.headers().path(), | |
request.method()); | |
redirectSignatures.add(originalSignature); | |
isAddedOriginalSignature = true; | |
} | |
if (redirectSignatures == null) { | |
redirectSignatures = new LinkedHashSet<>(); | |
final String originalProtocol = ctx.sessionProtocol().isTls() ? "https" : "http"; | |
final RedirectSignature originalSignature = new RedirectSignature(originalProtocol, | |
ctx.authority(), | |
request.headers().path(), | |
request.method()); | |
redirectSignatures.add(originalSignature); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I will also remove isAddedOriginalSignature
flag.
// Always called after addRedirectUri is called. | ||
assert redirectUris != null; | ||
return redirectUris; | ||
public String uri() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public String uri() { | |
String uri() { |
final boolean isCyclicRedirects = | ||
!redirectCtx.addRedirectSignature(nextReqTarget, | ||
newReqDuplicator.headers().method()); | ||
if (isCyclicRedirects) { | ||
handleException(ctx, derivedCtx, reqDuplicator, responseFuture, response, | ||
CyclicRedirectsException.of(redirectCtx.originalUri(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional) What do you think of raising either CyclicRedirectsException
or TooManyRedirectsException
by addRedirectSignature()
which may streamline the code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will try it 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me 👍 Thanks @moromin ! 🙇 👍 🙇
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Thanks a lot! 😄
core/src/main/java/com/linecorp/armeria/client/RedirectingClient.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks solid. Thanks, @moromin! 🙇♂️👍
Motivation:
Current implements can detect only cyclic redirect which comes back to original URL like
a --> b --> c --> a
.And cannot detect some cases, for example, partial cyclic one like
a --> b --> c --> b
, however it returns TooManyRedirectsException instead of CyclicRedirectsException.This is caused because adding redirect info to
Multimap
to check the cyclic always returnstrue
. (code)And I think the reasons we couldn't notice was that test cases were insufficient and didn't validate the exception type.
To resolve the above problems, I add the following changes.
Modifications:
RedirectSignature
class to store redirect information.Set
instead ofMultimap
to check cyclic redirections with the above class.Set.add()
returnsfalse
when the same data is added.CyclicRedirectsException
is thrown.Result:
RedirectingClient
#5491