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