1 // Copyright 2006-2008 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "net/http/http_chunked_decoder.h"
11
12 #include <memory>
13 #include <string>
14 #include <vector>
15
16 #include "base/format_macros.h"
17 #include "base/numerics/safe_conversions.h"
18 #include "base/strings/stringprintf.h"
19 #include "net/base/net_errors.h"
20 #include "net/test/gtest_util.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23
24 using net::test::IsError;
25 using net::test::IsOk;
26
27 namespace net {
28
29 namespace {
30
31 typedef testing::Test HttpChunkedDecoderTest;
32
RunTest(const char * const inputs[],size_t num_inputs,const char * expected_output,bool expected_eof,int bytes_after_eof)33 void RunTest(const char* const inputs[],
34 size_t num_inputs,
35 const char* expected_output,
36 bool expected_eof,
37 int bytes_after_eof) {
38 HttpChunkedDecoder decoder;
39 EXPECT_FALSE(decoder.reached_eof());
40
41 std::string result;
42
43 for (size_t i = 0; i < num_inputs; ++i) {
44 std::string input = inputs[i];
45 int n = decoder.FilterBuf(base::as_writable_byte_span(input));
46 EXPECT_GE(n, 0);
47 if (n > 0)
48 result.append(input.data(), n);
49 }
50
51 EXPECT_EQ(expected_output, result);
52 EXPECT_EQ(expected_eof, decoder.reached_eof());
53 EXPECT_EQ(bytes_after_eof, decoder.bytes_after_eof());
54 }
55
56 // Feed the inputs to the decoder, until it returns an error.
RunTestUntilFailure(const char * const inputs[],size_t num_inputs,size_t fail_index)57 void RunTestUntilFailure(const char* const inputs[],
58 size_t num_inputs,
59 size_t fail_index) {
60 HttpChunkedDecoder decoder;
61 EXPECT_FALSE(decoder.reached_eof());
62
63 for (size_t i = 0; i < num_inputs; ++i) {
64 std::string input = inputs[i];
65 int n = decoder.FilterBuf(base::as_writable_byte_span(input));
66 if (n < 0) {
67 EXPECT_THAT(n, IsError(ERR_INVALID_CHUNKED_ENCODING));
68 EXPECT_EQ(fail_index, i);
69 return;
70 }
71 }
72 FAIL(); // We should have failed on the |fail_index| iteration of the loop.
73 }
74
TEST(HttpChunkedDecoderTest,Basic)75 TEST(HttpChunkedDecoderTest, Basic) {
76 const char* const inputs[] = {
77 "B\r\nhello hello\r\n0\r\n\r\n"
78 };
79 RunTest(inputs, std::size(inputs), "hello hello", true, 0);
80 }
81
TEST(HttpChunkedDecoderTest,OneChunk)82 TEST(HttpChunkedDecoderTest, OneChunk) {
83 const char* const inputs[] = {
84 "5\r\nhello\r\n"
85 };
86 RunTest(inputs, std::size(inputs), "hello", false, 0);
87 }
88
TEST(HttpChunkedDecoderTest,Typical)89 TEST(HttpChunkedDecoderTest, Typical) {
90 const char* const inputs[] = {
91 "5\r\nhello\r\n",
92 "1\r\n \r\n",
93 "5\r\nworld\r\n",
94 "0\r\n\r\n"
95 };
96 RunTest(inputs, std::size(inputs), "hello world", true, 0);
97 }
98
TEST(HttpChunkedDecoderTest,Incremental)99 TEST(HttpChunkedDecoderTest, Incremental) {
100 const char* const inputs[] = {
101 "5",
102 "\r",
103 "\n",
104 "hello",
105 "\r",
106 "\n",
107 "0",
108 "\r",
109 "\n",
110 "\r",
111 "\n"
112 };
113 RunTest(inputs, std::size(inputs), "hello", true, 0);
114 }
115
116 // Same as above, but group carriage returns with previous input.
TEST(HttpChunkedDecoderTest,Incremental2)117 TEST(HttpChunkedDecoderTest, Incremental2) {
118 const char* const inputs[] = {
119 "5\r",
120 "\n",
121 "hello\r",
122 "\n",
123 "0\r",
124 "\n\r",
125 "\n"
126 };
127 RunTest(inputs, std::size(inputs), "hello", true, 0);
128 }
129
TEST(HttpChunkedDecoderTest,LF_InsteadOf_CRLF)130 TEST(HttpChunkedDecoderTest, LF_InsteadOf_CRLF) {
131 // Compatibility: [RFC 7230 - Invalid]
132 // {Firefox3} - Valid
133 // {IE7, Safari3.1, Opera9.51} - Invalid
134 const char* const inputs[] = {
135 "5\nhello\n",
136 "1\n \n",
137 "5\nworld\n",
138 "0\n\n"
139 };
140 RunTest(inputs, std::size(inputs), "hello world", true, 0);
141 }
142
TEST(HttpChunkedDecoderTest,Extensions)143 TEST(HttpChunkedDecoderTest, Extensions) {
144 const char* const inputs[] = {
145 "5;x=0\r\nhello\r\n",
146 "0;y=\"2 \"\r\n\r\n"
147 };
148 RunTest(inputs, std::size(inputs), "hello", true, 0);
149 }
150
TEST(HttpChunkedDecoderTest,Trailers)151 TEST(HttpChunkedDecoderTest, Trailers) {
152 const char* const inputs[] = {
153 "5\r\nhello\r\n",
154 "0\r\n",
155 "Foo: 1\r\n",
156 "Bar: 2\r\n",
157 "\r\n"
158 };
159 RunTest(inputs, std::size(inputs), "hello", true, 0);
160 }
161
TEST(HttpChunkedDecoderTest,TrailersUnfinished)162 TEST(HttpChunkedDecoderTest, TrailersUnfinished) {
163 const char* const inputs[] = {
164 "5\r\nhello\r\n",
165 "0\r\n",
166 "Foo: 1\r\n"
167 };
168 RunTest(inputs, std::size(inputs), "hello", false, 0);
169 }
170
TEST(HttpChunkedDecoderTest,InvalidChunkSize_TooBig)171 TEST(HttpChunkedDecoderTest, InvalidChunkSize_TooBig) {
172 const char* const inputs[] = {
173 // This chunked body is not terminated.
174 // However we will fail decoding because the chunk-size
175 // number is larger than we can handle.
176 "48469410265455838241\r\nhello\r\n",
177 "0\r\n\r\n"
178 };
179 RunTestUntilFailure(inputs, std::size(inputs), 0);
180 }
181
TEST(HttpChunkedDecoderTest,InvalidChunkSize_0X)182 TEST(HttpChunkedDecoderTest, InvalidChunkSize_0X) {
183 const char* const inputs[] = {
184 // Compatibility [RFC 7230 - Invalid]:
185 // {Safari3.1, IE7} - Invalid
186 // {Firefox3, Opera 9.51} - Valid
187 "0x5\r\nhello\r\n",
188 "0\r\n\r\n"
189 };
190 RunTestUntilFailure(inputs, std::size(inputs), 0);
191 }
192
TEST(HttpChunkedDecoderTest,ChunkSize_TrailingSpace)193 TEST(HttpChunkedDecoderTest, ChunkSize_TrailingSpace) {
194 const char* const inputs[] = {
195 // Compatibility [RFC 7230 - Invalid]:
196 // {IE7, Safari3.1, Firefox3, Opera 9.51} - Valid
197 //
198 // At least yahoo.com depends on this being valid.
199 "5 \r\nhello\r\n",
200 "0\r\n\r\n"
201 };
202 RunTest(inputs, std::size(inputs), "hello", true, 0);
203 }
204
TEST(HttpChunkedDecoderTest,InvalidChunkSize_TrailingTab)205 TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingTab) {
206 const char* const inputs[] = {
207 // Compatibility [RFC 7230 - Invalid]:
208 // {IE7, Safari3.1, Firefox3, Opera 9.51} - Valid
209 "5\t\r\nhello\r\n",
210 "0\r\n\r\n"
211 };
212 RunTestUntilFailure(inputs, std::size(inputs), 0);
213 }
214
TEST(HttpChunkedDecoderTest,InvalidChunkSize_TrailingFormFeed)215 TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingFormFeed) {
216 const char* const inputs[] = {
217 // Compatibility [RFC 7230- Invalid]:
218 // {Safari3.1} - Invalid
219 // {IE7, Firefox3, Opera 9.51} - Valid
220 "5\f\r\nhello\r\n",
221 "0\r\n\r\n"
222 };
223 RunTestUntilFailure(inputs, std::size(inputs), 0);
224 }
225
TEST(HttpChunkedDecoderTest,InvalidChunkSize_TrailingVerticalTab)226 TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingVerticalTab) {
227 const char* const inputs[] = {
228 // Compatibility [RFC 7230 - Invalid]:
229 // {Safari 3.1} - Invalid
230 // {IE7, Firefox3, Opera 9.51} - Valid
231 "5\v\r\nhello\r\n",
232 "0\r\n\r\n"
233 };
234 RunTestUntilFailure(inputs, std::size(inputs), 0);
235 }
236
TEST(HttpChunkedDecoderTest,InvalidChunkSize_TrailingNonHexDigit)237 TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingNonHexDigit) {
238 const char* const inputs[] = {
239 // Compatibility [RFC 7230 - Invalid]:
240 // {Safari 3.1} - Invalid
241 // {IE7, Firefox3, Opera 9.51} - Valid
242 "5H\r\nhello\r\n",
243 "0\r\n\r\n"
244 };
245 RunTestUntilFailure(inputs, std::size(inputs), 0);
246 }
247
TEST(HttpChunkedDecoderTest,InvalidChunkSize_LeadingSpace)248 TEST(HttpChunkedDecoderTest, InvalidChunkSize_LeadingSpace) {
249 const char* const inputs[] = {
250 // Compatibility [RFC 7230 - Invalid]:
251 // {IE7} - Invalid
252 // {Safari 3.1, Firefox3, Opera 9.51} - Valid
253 " 5\r\nhello\r\n",
254 "0\r\n\r\n"
255 };
256 RunTestUntilFailure(inputs, std::size(inputs), 0);
257 }
258
TEST(HttpChunkedDecoderTest,InvalidLeadingSeparator)259 TEST(HttpChunkedDecoderTest, InvalidLeadingSeparator) {
260 const char* const inputs[] = {
261 "\r\n5\r\nhello\r\n",
262 "0\r\n\r\n"
263 };
264 RunTestUntilFailure(inputs, std::size(inputs), 0);
265 }
266
TEST(HttpChunkedDecoderTest,InvalidChunkSize_NoSeparator)267 TEST(HttpChunkedDecoderTest, InvalidChunkSize_NoSeparator) {
268 const char* const inputs[] = {
269 "5\r\nhello",
270 "1\r\n \r\n",
271 "0\r\n\r\n"
272 };
273 RunTestUntilFailure(inputs, std::size(inputs), 1);
274 }
275
TEST(HttpChunkedDecoderTest,InvalidChunkSize_Negative)276 TEST(HttpChunkedDecoderTest, InvalidChunkSize_Negative) {
277 const char* const inputs[] = {
278 "8\r\n12345678\r\n-5\r\nhello\r\n",
279 "0\r\n\r\n"
280 };
281 RunTestUntilFailure(inputs, std::size(inputs), 0);
282 }
283
TEST(HttpChunkedDecoderTest,InvalidChunkSize_Plus)284 TEST(HttpChunkedDecoderTest, InvalidChunkSize_Plus) {
285 const char* const inputs[] = {
286 // Compatibility [RFC 7230 - Invalid]:
287 // {IE7, Safari 3.1} - Invalid
288 // {Firefox3, Opera 9.51} - Valid
289 "+5\r\nhello\r\n",
290 "0\r\n\r\n"
291 };
292 RunTestUntilFailure(inputs, std::size(inputs), 0);
293 }
294
TEST(HttpChunkedDecoderTest,InvalidConsecutiveCRLFs)295 TEST(HttpChunkedDecoderTest, InvalidConsecutiveCRLFs) {
296 const char* const inputs[] = {
297 "5\r\nhello\r\n",
298 "\r\n\r\n\r\n\r\n",
299 "0\r\n\r\n"
300 };
301 RunTestUntilFailure(inputs, std::size(inputs), 1);
302 }
303
TEST(HttpChunkedDecoderTest,ReallyBigChunks)304 TEST(HttpChunkedDecoderTest, ReallyBigChunks) {
305 // Number of bytes sent through the chunked decoder per loop iteration. To
306 // minimize runtime, should be the square root of the chunk lengths, below.
307 const size_t kWrittenBytesPerIteration = 0x10000;
308
309 // Length of chunks to test. Must be multiples of kWrittenBytesPerIteration.
310 int64_t kChunkLengths[] = {
311 // Overflows when cast to a signed int32.
312 0x0c0000000,
313 // Overflows when cast to an unsigned int32.
314 0x100000000,
315 };
316
317 for (int64_t chunk_length : kChunkLengths) {
318 HttpChunkedDecoder decoder;
319 EXPECT_FALSE(decoder.reached_eof());
320
321 // Feed just the header to the decode.
322 std::string chunk_header =
323 base::StringPrintf("%" PRIx64 "\r\n", chunk_length);
324 std::vector<char> data(chunk_header.begin(), chunk_header.end());
325 EXPECT_EQ(OK, decoder.FilterBuf(base::as_writable_byte_span(data)));
326 EXPECT_FALSE(decoder.reached_eof());
327
328 // Set |data| to be kWrittenBytesPerIteration long, and have a repeating
329 // pattern.
330 data.clear();
331 data.reserve(kWrittenBytesPerIteration);
332 for (size_t i = 0; i < kWrittenBytesPerIteration; i++) {
333 data.push_back(static_cast<char>(i));
334 }
335
336 // Repeatedly feed the data to the chunked decoder. Since the data doesn't
337 // include any chunk lengths, the decode will never have to move the data,
338 // and should run fairly quickly.
339 for (int64_t total_written = 0; total_written < chunk_length;
340 total_written += kWrittenBytesPerIteration) {
341 EXPECT_EQ(kWrittenBytesPerIteration,
342 base::checked_cast<size_t>(
343 decoder.FilterBuf(base::as_writable_byte_span(data).first(
344 kWrittenBytesPerIteration))));
345 EXPECT_FALSE(decoder.reached_eof());
346 }
347
348 // Chunk terminator and the final chunk.
349 char final_chunk[] = "\r\n0\r\n\r\n";
350 EXPECT_EQ(OK, decoder.FilterBuf(base::as_writable_byte_span(final_chunk)));
351 EXPECT_TRUE(decoder.reached_eof());
352
353 // Since |data| never included any chunk headers, it should not have been
354 // modified.
355 for (size_t i = 0; i < kWrittenBytesPerIteration; i++) {
356 EXPECT_EQ(static_cast<char>(i), data[i]);
357 }
358 }
359 }
360
TEST(HttpChunkedDecoderTest,ExcessiveChunkLen)361 TEST(HttpChunkedDecoderTest, ExcessiveChunkLen) {
362 // Smallest number that can't be represented as a signed int64.
363 const char* const inputs[] = {"8000000000000000\r\nhello\r\n"};
364 RunTestUntilFailure(inputs, std::size(inputs), 0);
365 }
366
TEST(HttpChunkedDecoderTest,ExcessiveChunkLen2)367 TEST(HttpChunkedDecoderTest, ExcessiveChunkLen2) {
368 // Smallest number that can't be represented as an unsigned int64.
369 const char* const inputs[] = {"10000000000000000\r\nhello\r\n"};
370 RunTestUntilFailure(inputs, std::size(inputs), 0);
371 }
372
TEST(HttpChunkedDecoderTest,BasicExtraData)373 TEST(HttpChunkedDecoderTest, BasicExtraData) {
374 const char* const inputs[] = {
375 "5\r\nhello\r\n0\r\n\r\nextra bytes"
376 };
377 RunTest(inputs, std::size(inputs), "hello", true, 11);
378 }
379
TEST(HttpChunkedDecoderTest,IncrementalExtraData)380 TEST(HttpChunkedDecoderTest, IncrementalExtraData) {
381 const char* const inputs[] = {
382 "5",
383 "\r",
384 "\n",
385 "hello",
386 "\r",
387 "\n",
388 "0",
389 "\r",
390 "\n",
391 "\r",
392 "\nextra bytes"
393 };
394 RunTest(inputs, std::size(inputs), "hello", true, 11);
395 }
396
TEST(HttpChunkedDecoderTest,MultipleExtraDataBlocks)397 TEST(HttpChunkedDecoderTest, MultipleExtraDataBlocks) {
398 const char* const inputs[] = {
399 "5\r\nhello\r\n0\r\n\r\nextra",
400 " bytes"
401 };
402 RunTest(inputs, std::size(inputs), "hello", true, 11);
403 }
404
405 // Test when the line with the chunk length is too long.
TEST(HttpChunkedDecoderTest,LongChunkLengthLine)406 TEST(HttpChunkedDecoderTest, LongChunkLengthLine) {
407 int big_chunk_length = HttpChunkedDecoder::kMaxLineBufLen;
408 auto big_chunk = std::make_unique<char[]>(big_chunk_length + 1);
409 memset(big_chunk.get(), '0', big_chunk_length);
410 big_chunk[big_chunk_length] = 0;
411 const char* const inputs[] = {
412 big_chunk.get(),
413 "5"
414 };
415 RunTestUntilFailure(inputs, std::size(inputs), 1);
416 }
417
418 // Test when the extension portion of the line with the chunk length is too
419 // long.
TEST(HttpChunkedDecoderTest,LongLengthLengthLine)420 TEST(HttpChunkedDecoderTest, LongLengthLengthLine) {
421 int big_chunk_length = HttpChunkedDecoder::kMaxLineBufLen;
422 auto big_chunk = std::make_unique<char[]>(big_chunk_length + 1);
423 memset(big_chunk.get(), '0', big_chunk_length);
424 big_chunk[big_chunk_length] = 0;
425 const char* const inputs[] = {
426 "5;",
427 big_chunk.get()
428 };
429 RunTestUntilFailure(inputs, std::size(inputs), 1);
430 }
431
432 } // namespace
433
434 } // namespace net
435