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 10 namespace { 11 12 typedef testing::Test HttpChunkedDecoderTest; 13 14 void 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. 37 void 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 57 TEST(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 64 TEST(HttpChunkedDecoderTest, OneChunk) { 65 const char* inputs[] = { 66 "5\r\nhello\r\n" 67 }; 68 RunTest(inputs, arraysize(inputs), "hello", false, 0); 69 } 70 71 TEST(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 81 TEST(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 98 TEST(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 111 TEST(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 119 TEST(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 130 TEST(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 139 TEST(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 150 TEST(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 161 TEST(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 173 TEST(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 183 TEST(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 194 TEST(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 205 TEST(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 216 TEST(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 227 TEST(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 235 TEST(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 244 TEST(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 252 TEST(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 263 TEST(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 272 TEST(HttpChunkedDecoderTest, ExcessiveChunkLen) { 273 const char* inputs[] = { 274 "c0000000\r\nhello\r\n" 275 }; 276 RunTestUntilFailure(inputs, arraysize(inputs), 0); 277 } 278 279 TEST(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 286 TEST(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 303 TEST(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