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