Skip to content

Commit c73817e

Browse files
authored
fix: reject stray trailing token after a valid auth-param in AuthChallengeParser (#199)
PR: #199
1 parent a385c99 commit c73817e

2 files changed

Lines changed: 57 additions & 1 deletion

File tree

sdk-core/src/main/kotlin/org/dexpace/sdk/core/auth/AuthChallengeParser.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,20 @@ public object AuthChallengeParser {
9797
// param (likely the next challenge's scheme).
9898
while (true) {
9999
cursor.skipOws()
100-
if (!cursor.hasMore() || cursor.peek() != ',') break
100+
if (!cursor.hasMore()) break
101+
if (cursor.peek() != ',') {
102+
// After a valid auth-param the grammar only permits a comma
103+
// (another param or the next challenge) or end-of-input. Anything
104+
// else is a stray trailing token with no separating comma — e.g.
105+
// `Bearer realm="x" garbage`. RFC 7235 §2.1 has no production for
106+
// it, so the tail is malformed. Skip it to the next top-level
107+
// comma so it is not silently misread as a phantom second
108+
// challenge's scheme on the next outer iteration, then emit this
109+
// challenge with the params parsed before the garbage — matching
110+
// the parser's lenient "preserve prior params" recovery contract.
111+
cursor.recoverToNextChallenge()
112+
break
113+
}
101114
// Save position before consuming the comma — if what follows is the
102115
// next scheme rather than a param of THIS challenge, we need to leave
103116
// the comma in place for the outer loop.

sdk-core/src/test/kotlin/org/dexpace/sdk/core/auth/AuthChallengeParserTest.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,49 @@ class AuthChallengeParserTest {
412412
)
413413
}
414414

415+
@Test
416+
fun `stray token after an unquoted param is rejected not parsed as a phantom challenge`() {
417+
// `Digest realm=value extra` — `realm=value` is a valid auth-param, but `extra`
418+
// is a bare token with no separating comma. RFC 7235 §2.1 permits only a comma
419+
// (or EOF) after an auth-param, so the trailing token is malformed. The parser
420+
// skips it (rather than silently dropping it and re-reading `extra` as the scheme
421+
// of a second challenge) and emits the single Digest challenge with `realm=value`.
422+
val challenges = AuthChallengeParser.parse("Digest realm=value extra")
423+
assertEquals(1, challenges.size, "the stray token must not produce a second challenge")
424+
assertEquals("digest", challenges[0].scheme)
425+
assertEquals("value", challenges[0].parameters["realm"])
426+
assertTrue(
427+
challenges.none { it.scheme == "extra" },
428+
"the stray trailing token must not be parsed as a phantom challenge scheme",
429+
)
430+
}
431+
432+
@Test
433+
fun `stray token after a quoted param is rejected not parsed as a phantom challenge`() {
434+
// Quoted-value variant of the stray-trailing-token case: `Digest realm="value" extra`.
435+
val challenges = AuthChallengeParser.parse("""Digest realm="value" extra""")
436+
assertEquals(1, challenges.size, "the stray token must not produce a second challenge")
437+
assertEquals("digest", challenges[0].scheme)
438+
assertEquals("value", challenges[0].parameters["realm"])
439+
assertTrue(
440+
challenges.none { it.scheme == "extra" },
441+
"the stray trailing token must not be parsed as a phantom challenge scheme",
442+
)
443+
}
444+
445+
@Test
446+
fun `stray token between a valid param and a comma-separated next challenge is dropped`() {
447+
// `Digest realm=value extra, Basic realm="x"` — the stray `extra` after the
448+
// first challenge's param is skipped up to the next top-level comma, and the
449+
// following Basic challenge is still picked up cleanly.
450+
val challenges = AuthChallengeParser.parse("""Digest realm=value extra, Basic realm="x"""")
451+
assertEquals(2, challenges.size)
452+
assertEquals("digest", challenges[0].scheme)
453+
assertEquals("value", challenges[0].parameters["realm"])
454+
assertEquals("basic", challenges[1].scheme)
455+
assertEquals("x", challenges[1].parameters["realm"])
456+
}
457+
415458
@Test
416459
fun `quote with only a backslash inside before close quote`() {
417460
// `"\"` — opens, sees backslash, advances, sees `"` (the close), appends

0 commit comments

Comments
 (0)