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