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