• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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