@@ -67,6 +67,69 @@ parse_chunked_response_trailers_test() ->
6767 {more , P4 } = hackney_http :execute (P3 , <<" 0\r\n Foo: " >>),
6868 ? assertEqual ({done , <<>>}, hackney_http :execute (P4 , <<" Bar\r\n\r\n " >>)).
6969
70+ % % Issue #901: the CRLF terminating a chunk-size line is split across two
71+ % % reads, leaving the buffer on a lone \r. The parser must wait for the \n
72+ % % instead of failing with invalid_chunk_size.
73+ parse_chunked_size_crlf_split_test () ->
74+ ? assertEqual ({done , <<" HELLO" >>},
75+ run_chunked ([<<" 5\r " >>, <<" \n HELLO\r\n 0\r\n\r\n " >>])).
76+
77+ parse_chunked_last_chunk_crlf_split_test () ->
78+ ? assertEqual ({done , <<" HELLO" >>},
79+ run_chunked ([<<" 5\r\n HELLO\r\n 0\r " >>, <<" \n\r\n " >>])).
80+
81+ % % Exhaustive read-boundary sweep: a two-chunk body with a trailer, split at
82+ % % every possible byte position, must always decode to the same body.
83+ parse_chunked_split_sweep_test () ->
84+ Body = <<" 5\r\n HELLO\r\n 6\r\n WORLD\r\n 0\r\n X-Trail: 1\r\n\r\n " >>,
85+ [? assertEqual ({done , <<" HELLO WORLD" >>},
86+ run_chunked ([binary :part (Body , 0 , I ),
87+ binary :part (Body , I , byte_size (Body ) - I )]))
88+ || I <- lists :seq (0 , byte_size (Body ))].
89+
90+ % % A genuinely malformed size line must fail cleanly, not crash te_chunked.
91+ parse_chunked_invalid_size_test () ->
92+ ? assertEqual ({error , invalid_chunk_size },
93+ run_chunked ([<<" 5\r X\n HELLO\r\n 0\r\n\r\n " >>])).
94+
95+ % % A malformed chunk terminator must fail cleanly as well
96+ % % (read_chunk's error was unhandled in te_chunked).
97+ parse_chunked_bad_chunk_terminator_test () ->
98+ ? assertEqual ({error , poorly_formatted_chunked_size },
99+ run_chunked ([<<" 5\r\n HELLOXX0\r\n\r\n " >>])).
100+
101+ % % Drive a chunked response body through the parser, feeding Segments as
102+ % % separate socket reads. Contract: after {ok, Chunk, P}, drain buffered data
103+ % % with execute/1 until the parser asks for more, only then feed the next
104+ % % segment. Returns {done, Body} when the response completed with no
105+ % % leftover bytes.
106+ run_chunked (Segments ) ->
107+ P0 = hackney_http :parser ([response ]),
108+ {_ , _ , _ , _ , P1 } = hackney_http :execute (P0 , <<" HTTP/1.1 200 OK\r\n " >>),
109+ {_ , _ , P2 } = hackney_http :execute (P1 , <<" Transfer-Encoding: chunked\r\n " >>),
110+ {headers_complete , P3 } = hackney_http :execute (P2 , <<" \r\n " >>),
111+ feed_chunked (P3 , Segments , <<>>).
112+
113+ feed_chunked (_Parser , [], Acc ) ->
114+ {incomplete , Acc };
115+ feed_chunked (Parser , [Seg | Rest ], Acc ) ->
116+ chunked_step (hackney_http :execute (Parser , Seg ), Rest , Acc ).
117+
118+ chunked_step ({ok , Chunk , Parser }, Segments , Acc ) ->
119+ chunked_step (hackney_http :execute (Parser ), Segments , <<Acc /binary , Chunk /binary >>);
120+ chunked_step ({more , Parser }, Segments , Acc ) ->
121+ feed_chunked (Parser , Segments , Acc );
122+ chunked_step ({more , Parser , _Buffer }, Segments , Acc ) ->
123+ feed_chunked (Parser , Segments , Acc );
124+ chunked_step ({done , <<>>}, _Segments , Acc ) ->
125+ {done , Acc };
126+ chunked_step ({done , Leftover }, _Segments , Acc ) ->
127+ {done_with_leftover , Acc , Leftover };
128+ chunked_step (done , _Segments , Acc ) ->
129+ {done , Acc };
130+ chunked_step ({error , Reason }, _Segments , _Acc ) ->
131+ {error , Reason }.
132+
70133% % Issue #697: Handle non-standard decimal status codes (e.g., 401.1 from IIS)
71134parse_response_decimal_status_code_test () ->
72135 Response = <<" HTTP/1.1 401.1 Access Denied" >>,
0 commit comments