1// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
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 "base/basictypes.h"
8#include "base/memory/scoped_ptr.h"
9#include "net/base/net_errors.h"
10#include "testing/gtest/include/gtest/gtest.h"
11
12namespace net {
13
14namespace {
15
16typedef testing::Test HttpChunkedDecoderTest;
17
18void RunTest(const char* inputs[], size_t num_inputs,
19             const char* expected_output,
20             bool expected_eof,
21             int bytes_after_eof) {
22  HttpChunkedDecoder decoder;
23  EXPECT_FALSE(decoder.reached_eof());
24
25  std::string result;
26
27  for (size_t i = 0; i < num_inputs; ++i) {
28    std::string input = inputs[i];
29    int n = decoder.FilterBuf(&input[0], static_cast<int>(input.size()));
30    EXPECT_GE(n, 0);
31    if (n > 0)
32      result.append(input.data(), n);
33  }
34
35  EXPECT_EQ(expected_output, result);
36  EXPECT_EQ(expected_eof, decoder.reached_eof());
37  EXPECT_EQ(bytes_after_eof, decoder.bytes_after_eof());
38}
39
40// Feed the inputs to the decoder, until it returns an error.
41void RunTestUntilFailure(const char* inputs[],
42                         size_t num_inputs,
43                         size_t fail_index) {
44  HttpChunkedDecoder decoder;
45  EXPECT_FALSE(decoder.reached_eof());
46
47  for (size_t i = 0; i < num_inputs; ++i) {
48    std::string input = inputs[i];
49    int n = decoder.FilterBuf(&input[0], static_cast<int>(input.size()));
50    if (n < 0) {
51      EXPECT_EQ(ERR_INVALID_CHUNKED_ENCODING, n);
52      EXPECT_EQ(fail_index, i);
53      return;
54    }
55  }
56  FAIL();  // We should have failed on the |fail_index| iteration of the loop.
57}
58
59TEST(HttpChunkedDecoderTest, Basic) {
60  const char* inputs[] = {
61    "B\r\nhello hello\r\n0\r\n\r\n"
62  };
63  RunTest(inputs, arraysize(inputs), "hello hello", true, 0);
64}
65
66TEST(HttpChunkedDecoderTest, OneChunk) {
67  const char* inputs[] = {
68    "5\r\nhello\r\n"
69  };
70  RunTest(inputs, arraysize(inputs), "hello", false, 0);
71}
72
73TEST(HttpChunkedDecoderTest, Typical) {
74  const char* inputs[] = {
75    "5\r\nhello\r\n",
76    "1\r\n \r\n",
77    "5\r\nworld\r\n",
78    "0\r\n\r\n"
79  };
80  RunTest(inputs, arraysize(inputs), "hello world", true, 0);
81}
82
83TEST(HttpChunkedDecoderTest, Incremental) {
84  const char* inputs[] = {
85    "5",
86    "\r",
87    "\n",
88    "hello",
89    "\r",
90    "\n",
91    "0",
92    "\r",
93    "\n",
94    "\r",
95    "\n"
96  };
97  RunTest(inputs, arraysize(inputs), "hello", true, 0);
98}
99
100// Same as above, but group carriage returns with previous input.
101TEST(HttpChunkedDecoderTest, Incremental2) {
102  const char* inputs[] = {
103    "5\r",
104    "\n",
105    "hello\r",
106    "\n",
107    "0\r",
108    "\n\r",
109    "\n"
110  };
111  RunTest(inputs, arraysize(inputs), "hello", true, 0);
112}
113
114TEST(HttpChunkedDecoderTest, LF_InsteadOf_CRLF) {
115  // Compatibility: [RFC 2616 - Invalid]
116  // {Firefox3} - Valid
117  // {IE7, Safari3.1, Opera9.51} - Invalid
118  const char* inputs[] = {
119    "5\nhello\n",
120    "1\n \n",
121    "5\nworld\n",
122    "0\n\n"
123  };
124  RunTest(inputs, arraysize(inputs), "hello world", true, 0);
125}
126
127TEST(HttpChunkedDecoderTest, Extensions) {
128  const char* inputs[] = {
129    "5;x=0\r\nhello\r\n",
130    "0;y=\"2 \"\r\n\r\n"
131  };
132  RunTest(inputs, arraysize(inputs), "hello", true, 0);
133}
134
135TEST(HttpChunkedDecoderTest, Trailers) {
136  const char* inputs[] = {
137    "5\r\nhello\r\n",
138    "0\r\n",
139    "Foo: 1\r\n",
140    "Bar: 2\r\n",
141    "\r\n"
142  };
143  RunTest(inputs, arraysize(inputs), "hello", true, 0);
144}
145
146TEST(HttpChunkedDecoderTest, TrailersUnfinished) {
147  const char* inputs[] = {
148    "5\r\nhello\r\n",
149    "0\r\n",
150    "Foo: 1\r\n"
151  };
152  RunTest(inputs, arraysize(inputs), "hello", false, 0);
153}
154
155TEST(HttpChunkedDecoderTest, InvalidChunkSize_TooBig) {
156  const char* inputs[] = {
157    // This chunked body is not terminated.
158    // However we will fail decoding because the chunk-size
159    // number is larger than we can handle.
160    "48469410265455838241\r\nhello\r\n",
161    "0\r\n\r\n"
162  };
163  RunTestUntilFailure(inputs, arraysize(inputs), 0);
164}
165
166TEST(HttpChunkedDecoderTest, InvalidChunkSize_0X) {
167  const char* inputs[] = {
168    // Compatibility [RFC 2616 - Invalid]:
169    // {Safari3.1, IE7} - Invalid
170    // {Firefox3, Opera 9.51} - Valid
171    "0x5\r\nhello\r\n",
172    "0\r\n\r\n"
173  };
174  RunTestUntilFailure(inputs, arraysize(inputs), 0);
175}
176
177TEST(HttpChunkedDecoderTest, ChunkSize_TrailingSpace) {
178  const char* inputs[] = {
179    // Compatibility [RFC 2616 - Invalid]:
180    // {IE7, Safari3.1, Firefox3, Opera 9.51} - Valid
181    //
182    // At least yahoo.com depends on this being valid.
183    "5      \r\nhello\r\n",
184    "0\r\n\r\n"
185  };
186  RunTest(inputs, arraysize(inputs), "hello", true, 0);
187}
188
189TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingTab) {
190  const char* inputs[] = {
191    // Compatibility [RFC 2616 - Invalid]:
192    // {IE7, Safari3.1, Firefox3, Opera 9.51} - Valid
193    "5\t\r\nhello\r\n",
194    "0\r\n\r\n"
195  };
196  RunTestUntilFailure(inputs, arraysize(inputs), 0);
197}
198
199TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingFormFeed) {
200  const char* inputs[] = {
201    // Compatibility [RFC 2616- Invalid]:
202    // {Safari3.1} - Invalid
203    // {IE7, Firefox3, Opera 9.51} - Valid
204    "5\f\r\nhello\r\n",
205    "0\r\n\r\n"
206  };
207  RunTestUntilFailure(inputs, arraysize(inputs), 0);
208}
209
210TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingVerticalTab) {
211  const char* inputs[] = {
212    // Compatibility [RFC 2616 - Invalid]:
213    // {Safari 3.1} - Invalid
214    // {IE7, Firefox3, Opera 9.51} - Valid
215    "5\v\r\nhello\r\n",
216    "0\r\n\r\n"
217  };
218  RunTestUntilFailure(inputs, arraysize(inputs), 0);
219}
220
221TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingNonHexDigit) {
222  const char* inputs[] = {
223    // Compatibility [RFC 2616 - Invalid]:
224    // {Safari 3.1} - Invalid
225    // {IE7, Firefox3, Opera 9.51} - Valid
226    "5H\r\nhello\r\n",
227    "0\r\n\r\n"
228  };
229  RunTestUntilFailure(inputs, arraysize(inputs), 0);
230}
231
232TEST(HttpChunkedDecoderTest, InvalidChunkSize_LeadingSpace) {
233  const char* inputs[] = {
234    // Compatibility [RFC 2616 - Invalid]:
235    // {IE7} - Invalid
236    // {Safari 3.1, Firefox3, Opera 9.51} - Valid
237    " 5\r\nhello\r\n",
238    "0\r\n\r\n"
239  };
240  RunTestUntilFailure(inputs, arraysize(inputs), 0);
241}
242
243TEST(HttpChunkedDecoderTest, InvalidLeadingSeparator) {
244  const char* inputs[] = {
245    "\r\n5\r\nhello\r\n",
246    "0\r\n\r\n"
247  };
248  RunTestUntilFailure(inputs, arraysize(inputs), 0);
249}
250
251TEST(HttpChunkedDecoderTest, InvalidChunkSize_NoSeparator) {
252  const char* inputs[] = {
253    "5\r\nhello",
254    "1\r\n \r\n",
255    "0\r\n\r\n"
256  };
257  RunTestUntilFailure(inputs, arraysize(inputs), 1);
258}
259
260TEST(HttpChunkedDecoderTest, InvalidChunkSize_Negative) {
261  const char* inputs[] = {
262    "8\r\n12345678\r\n-5\r\nhello\r\n",
263    "0\r\n\r\n"
264  };
265  RunTestUntilFailure(inputs, arraysize(inputs), 0);
266}
267
268TEST(HttpChunkedDecoderTest, InvalidChunkSize_Plus) {
269  const char* inputs[] = {
270    // Compatibility [RFC 2616 - Invalid]:
271    // {IE7, Safari 3.1} - Invalid
272    // {Firefox3, Opera 9.51} - Valid
273    "+5\r\nhello\r\n",
274    "0\r\n\r\n"
275  };
276  RunTestUntilFailure(inputs, arraysize(inputs), 0);
277}
278
279TEST(HttpChunkedDecoderTest, InvalidConsecutiveCRLFs) {
280  const char* inputs[] = {
281    "5\r\nhello\r\n",
282    "\r\n\r\n\r\n\r\n",
283    "0\r\n\r\n"
284  };
285  RunTestUntilFailure(inputs, arraysize(inputs), 1);
286}
287
288TEST(HttpChunkedDecoderTest, ExcessiveChunkLen) {
289  const char* inputs[] = {
290    "c0000000\r\nhello\r\n"
291  };
292  RunTestUntilFailure(inputs, arraysize(inputs), 0);
293}
294
295TEST(HttpChunkedDecoderTest, BasicExtraData) {
296  const char* inputs[] = {
297    "5\r\nhello\r\n0\r\n\r\nextra bytes"
298  };
299  RunTest(inputs, arraysize(inputs), "hello", true, 11);
300}
301
302TEST(HttpChunkedDecoderTest, IncrementalExtraData) {
303  const char* inputs[] = {
304    "5",
305    "\r",
306    "\n",
307    "hello",
308    "\r",
309    "\n",
310    "0",
311    "\r",
312    "\n",
313    "\r",
314    "\nextra bytes"
315  };
316  RunTest(inputs, arraysize(inputs), "hello", true, 11);
317}
318
319TEST(HttpChunkedDecoderTest, MultipleExtraDataBlocks) {
320  const char* inputs[] = {
321    "5\r\nhello\r\n0\r\n\r\nextra",
322    " bytes"
323  };
324  RunTest(inputs, arraysize(inputs), "hello", true, 11);
325}
326
327// Test when the line with the chunk length is too long.
328TEST(HttpChunkedDecoderTest, LongChunkLengthLine) {
329  int big_chunk_length = HttpChunkedDecoder::kMaxLineBufLen;
330  scoped_ptr<char[]> big_chunk(new char[big_chunk_length + 1]);
331  memset(big_chunk.get(), '0', big_chunk_length);
332  big_chunk[big_chunk_length] = 0;
333  const char* inputs[] = {
334    big_chunk.get(),
335    "5"
336  };
337  RunTestUntilFailure(inputs, arraysize(inputs), 1);
338}
339
340// Test when the extension portion of the line with the chunk length is too
341// long.
342TEST(HttpChunkedDecoderTest, LongLengthLengthLine) {
343  int big_chunk_length = HttpChunkedDecoder::kMaxLineBufLen;
344  scoped_ptr<char[]> big_chunk(new char[big_chunk_length + 1]);
345  memset(big_chunk.get(), '0', big_chunk_length);
346  big_chunk[big_chunk_length] = 0;
347  const char* inputs[] = {
348    "5;",
349    big_chunk.get()
350  };
351  RunTestUntilFailure(inputs, arraysize(inputs), 1);
352}
353
354}  // namespace
355
356}  // namespace net
357