@@ -79,6 +79,49 @@ def test_parse_www_authenticate_unescapes_quoted_pair
7979 assert_equal ( 'value with "quoted" word and a back\\slash' , params [ "error_description" ] )
8080 end
8181
82+ def test_parse_www_authenticate_finds_bearer_after_a_challenge_with_multibyte_text
83+ # The Bearer challenge is located by byte position, so text before it that is wider than
84+ # one byte per character must not shift where its parameters are read from.
85+ params = Discovery . parse_www_authenticate ( %(Basic realm="café", Bearer scope="s") )
86+
87+ assert_equal ( { "scope" => "s" } , params )
88+ end
89+
90+ def test_parse_www_authenticate_tolerates_bytes_that_are_invalid_in_the_header_encoding
91+ # A value the server fills with bytes that are not valid UTF-8 must not turn the `401` into
92+ # an `ArgumentError`; the bytes are replaced and the other parameters still come through.
93+ header = %(Bearer error="invalid_token", scope="s\xff ", realm="r") . dup . force_encoding ( Encoding ::UTF_8 )
94+
95+ params = Discovery . parse_www_authenticate ( header )
96+
97+ assert_equal ( "invalid_token" , params [ "error" ] )
98+ assert_equal ( "s�" , params [ "scope" ] )
99+ assert_equal ( "r" , params [ "realm" ] )
100+ end
101+
102+ def test_parse_www_authenticate_reads_a_binary_header
103+ # Net::HTTP hands header values over as ASCII-8BIT; high bytes are kept as they are.
104+ params = Discovery . parse_www_authenticate ( %(Bearer scope="s\xff ", realm="r") . b )
105+
106+ assert_equal ( "s\xff " . b , params [ "scope" ] )
107+ assert_equal ( "r" , params [ "realm" ] )
108+ end
109+
110+ def test_parse_www_authenticate_walks_a_header_with_many_parameters_in_linear_time
111+ # The header is the server's to choose. Slicing off the consumed prefix for every pair copied
112+ # the remainder each time, so 200,000 pairs took tens of seconds; the bound below is loose enough
113+ # for a slow CI machine and far below that.
114+ header = "Bearer " + ( 1 ..200_000 ) . map { |i | %(k#{ i } ="v#{ i } ") } . join ( ", " )
115+
116+ started = Process . clock_gettime ( Process ::CLOCK_MONOTONIC )
117+ params = Discovery . parse_www_authenticate ( header )
118+ elapsed = Process . clock_gettime ( Process ::CLOCK_MONOTONIC ) - started
119+
120+ assert_equal ( 200_000 , params . size )
121+ assert_equal ( "v200000" , params [ "k200000" ] )
122+ assert_operator ( elapsed , :< , 5 )
123+ end
124+
82125 def test_protected_resource_metadata_urls_uses_explicit_url_first
83126 urls = Discovery . protected_resource_metadata_urls (
84127 server_url : "https://api.example.com/mcp" ,
0 commit comments