Skip to content

Commit a594c7e

Browse files
authored
test: cover redirect userinfo stripping for an IPv6 literal host (#89)
PR: #89
1 parent 0828efc commit a594c7e

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

sdk-core/src/test/kotlin/org/dexpace/sdk/core/http/pipeline/steps/RedirectStepTest.kt

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,77 @@ class RedirectStepTest {
964964
assertTrue(target.contains("x=%26"), "encoded query value %26 must be preserved: $target")
965965
}
966966

967+
@Test
968+
fun `stripping userinfo preserves an IPv6 literal host with brackets, port, path, and query`() {
969+
// An IPv6 literal authority carries its host inside square brackets in the URI
970+
// (URI.getHost() returns "[2001:db8::1]"), and the userinfo-stripping rebuild appends
971+
// that bracketed host verbatim. Clearing the userinfo must leave the IPv6 host, its
972+
// port, the path, and the query byte-exact — the brackets in particular must survive.
973+
val fake =
974+
FakeHttpClient()
975+
.enqueue {
976+
status(302).header(
977+
"Location",
978+
"https://user:pass@[2001:db8::1]:8443/v2/resource?q=1",
979+
)
980+
}.enqueue { status(200) }
981+
982+
val pipeline =
983+
HttpPipelineBuilder(fake)
984+
.append(DefaultRedirectStep())
985+
.build()
986+
987+
val response = pipeline.send(getRequest("https://api.example.com/v1"))
988+
989+
// Pin that the redirect was actually followed exactly once, so a regression that skips
990+
// the reissue fails on these assertions rather than an IndexOutOfBoundsException below.
991+
assertEquals(200, response.status.code)
992+
assertEquals(2, fake.callCount)
993+
994+
val reissued = fake.requests[1]
995+
assertNull(reissued.url.userInfo, "userinfo must be stripped from an IPv6 Location")
996+
// The bracketed IPv6 literal host and port are preserved exactly.
997+
assertEquals("[2001:db8::1]", reissued.url.host, "IPv6 literal host (with brackets) must be preserved")
998+
assertEquals(8443, reissued.url.port, "port must be preserved")
999+
assertEquals("/v2/resource", reissued.url.path, "path must be preserved")
1000+
assertEquals("q=1", reissued.url.query, "query must be preserved")
1001+
// The reissued target is byte-exact apart from the dropped userinfo.
1002+
assertEquals("https://[2001:db8::1]:8443/v2/resource?q=1", reissued.url.toString())
1003+
}
1004+
1005+
@Test
1006+
fun `IPv6 literal host without userinfo passes through with brackets preserved`() {
1007+
// The early-return branch (no userinfo to strip) hands the resolved IPv6 URI through
1008+
// unchanged via toURL(); confirm the bracketed host, port, path, and query survive on
1009+
// that non-rebuilding path too.
1010+
val fake =
1011+
FakeHttpClient()
1012+
.enqueue {
1013+
status(302).header(
1014+
"Location",
1015+
"https://[2001:db8::1]:8443/v2/resource?q=1",
1016+
)
1017+
}.enqueue { status(200) }
1018+
1019+
val pipeline =
1020+
HttpPipelineBuilder(fake)
1021+
.append(DefaultRedirectStep())
1022+
.build()
1023+
1024+
val response = pipeline.send(getRequest("https://api.example.com/v1"))
1025+
1026+
assertEquals(200, response.status.code)
1027+
assertEquals(2, fake.callCount)
1028+
1029+
val reissued = fake.requests[1]
1030+
assertNull(reissued.url.userInfo, "no userinfo was present")
1031+
assertEquals("[2001:db8::1]", reissued.url.host, "IPv6 literal host (with brackets) must be preserved")
1032+
assertEquals(8443, reissued.url.port, "port must be preserved")
1033+
assertEquals("/v2/resource", reissued.url.path, "path must be preserved")
1034+
assertEquals("q=1", reissued.url.query, "query must be preserved")
1035+
assertEquals("https://[2001:db8::1]:8443/v2/resource?q=1", reissued.url.toString())
1036+
}
1037+
9671038
// ----------------- Other non-3xx status codes don't trigger redirect -----------------
9681039

9691040
@Test

0 commit comments

Comments
 (0)