OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 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 "core/fetch/MultipartImageResourceParser.h" |
| 6 |
| 7 #include "platform/network/ResourceResponse.h" |
| 8 #include "public/platform/Platform.h" |
| 9 #include "public/platform/WebURL.h" |
| 10 #include "public/platform/WebURLResponse.h" |
| 11 #include "testing/gtest/include/gtest/gtest.h" |
| 12 |
| 13 #include <stddef.h> |
| 14 #include <stdint.h> |
| 15 #include <string.h> |
| 16 |
| 17 namespace blink { |
| 18 |
| 19 namespace { |
| 20 |
| 21 String toString(const Vector<char>& data) |
| 22 { |
| 23 if (data.isEmpty()) |
| 24 return String(""); |
| 25 return String(data.data(), data.size()); |
| 26 } |
| 27 |
| 28 class MockClient final : public NoBaseWillBeGarbageCollectedFinalized<MockClient
>, public MultipartImageResourceParser::Client { |
| 29 WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(MockClient); |
| 30 |
| 31 public: |
| 32 void onePartInMultipartReceived(const ResourceResponse& response) override |
| 33 { |
| 34 m_responses.append(response); |
| 35 m_data.append(Vector<char>()); |
| 36 } |
| 37 void multipartDataReceived(const char* bytes, size_t size) override |
| 38 { |
| 39 m_data.last().append(bytes, size); |
| 40 } |
| 41 |
| 42 Vector<ResourceResponse> m_responses; |
| 43 Vector<Vector<char>> m_data; |
| 44 }; |
| 45 |
| 46 TEST(MultipartResponseTest, PushOverLine) |
| 47 { |
| 48 struct { |
| 49 const char* input; |
| 50 const size_t position; |
| 51 const size_t expected; |
| 52 } lineTests[] = { |
| 53 { "Line", 0, 0 }, |
| 54 { "Line", 2, 0 }, |
| 55 { "Line", 10, 0 }, |
| 56 { "\r\nLine", 0, 2 }, |
| 57 { "\nLine", 0, 1 }, |
| 58 { "\n\nLine", 0, 2 }, |
| 59 { "\rLine", 0, 1 }, |
| 60 { "Line\r\nLine", 4, 2 }, |
| 61 { "Line\nLine", 4, 1 }, |
| 62 { "Line\n\nLine", 4, 2 }, |
| 63 { "Line\rLine", 4, 1 }, |
| 64 { "Line\r\rLine", 4, 1 }, |
| 65 }; |
| 66 for (size_t i = 0; i < WTF_ARRAY_LENGTH(lineTests); ++i) { |
| 67 Vector<char> input; |
| 68 input.append(lineTests[i].input, strlen(lineTests[i].input)); |
| 69 EXPECT_EQ(lineTests[i].expected, |
| 70 MultipartImageResourceParser::pushOverLineForTest(input, lineTests[i
].position)); |
| 71 } |
| 72 } |
| 73 |
| 74 TEST(MultipartResponseTest, ParseMultipartHeadersResult) |
| 75 { |
| 76 struct { |
| 77 const char* data; |
| 78 const bool result; |
| 79 const size_t end; |
| 80 } tests[] = { |
| 81 { "This is junk", false, 0 }, |
| 82 { "Foo: bar\nBaz:\n\nAfter:\n", true, 15 }, |
| 83 { "Foo: bar\nBaz:\n", false, 0}, |
| 84 { "Foo: bar\r\nBaz:\r\n\r\nAfter:\r\n", true, 18 }, |
| 85 { "Foo: bar\r\nBaz:\r\n", false, 0 }, |
| 86 { "Foo: bar\nBaz:\r\n\r\nAfter:\n\n", true, 17 }, |
| 87 { "Foo: bar\r\nBaz:\n", false, 0 }, |
| 88 { "\r\n", true, 2 }, |
| 89 }; |
| 90 for (size_t i = 0; i < WTF_ARRAY_LENGTH(tests); ++i) { |
| 91 WebURLResponse response; |
| 92 response.initialize(); |
| 93 size_t end = 0; |
| 94 bool result = Platform::current()->parseMultipartHeadersFromBody(tests[i
].data, strlen(tests[i].data), &response, &end); |
| 95 EXPECT_EQ(tests[i].result, result); |
| 96 EXPECT_EQ(tests[i].end, end); |
| 97 } |
| 98 } |
| 99 |
| 100 TEST(MultipartResponseTest, ParseMultipartHeaders) |
| 101 { |
| 102 WebURLResponse webResponse; |
| 103 webResponse.initialize(); |
| 104 webResponse.addHTTPHeaderField(WebString::fromLatin1("foo"), WebString::from
Latin1("bar")); |
| 105 webResponse.addHTTPHeaderField(WebString::fromLatin1("range"), WebString::fr
omLatin1("piyo")); |
| 106 webResponse.addHTTPHeaderField(WebString::fromLatin1("content-length"), WebS
tring::fromLatin1("999")); |
| 107 |
| 108 const char data[] = "content-type: image/png\ncontent-length: 10\n\n"; |
| 109 size_t end = 0; |
| 110 bool result = Platform::current()->parseMultipartHeadersFromBody(data, strle
n(data), &webResponse, &end); |
| 111 const ResourceResponse& response = webResponse.toResourceResponse(); |
| 112 |
| 113 EXPECT_TRUE(result); |
| 114 EXPECT_EQ(strlen(data), end); |
| 115 EXPECT_EQ("image/png", response.httpHeaderField("content-type")); |
| 116 EXPECT_EQ("10", response.httpHeaderField("content-length")); |
| 117 EXPECT_EQ("bar", response.httpHeaderField("foo")); |
| 118 EXPECT_EQ(AtomicString(), response.httpHeaderField("range")); |
| 119 } |
| 120 |
| 121 TEST(MultipartResponseTest, ParseMultipartHeadersContentCharset) |
| 122 { |
| 123 WebURLResponse webResponse; |
| 124 webResponse.initialize(); |
| 125 |
| 126 const char data[] = "content-type: text/html; charset=utf-8\n\n"; |
| 127 size_t end = 0; |
| 128 bool result = Platform::current()->parseMultipartHeadersFromBody(data, strle
n(data), &webResponse, &end); |
| 129 const ResourceResponse& response = webResponse.toResourceResponse(); |
| 130 |
| 131 EXPECT_TRUE(result); |
| 132 EXPECT_EQ(strlen(data), end); |
| 133 EXPECT_EQ("text/html; charset=utf-8", response.httpHeaderField("content-type
")); |
| 134 EXPECT_EQ("utf-8", response.textEncodingName()); |
| 135 } |
| 136 |
| 137 TEST(MultipartResponseTest, FindBoundary) |
| 138 { |
| 139 struct { |
| 140 const char* boundary; |
| 141 const char* data; |
| 142 const size_t position; |
| 143 } boundaryTests[] = { |
| 144 { "bound", "bound", 0 }, |
| 145 { "bound", "--bound", 0 }, |
| 146 { "bound", "junkbound", 4 }, |
| 147 { "bound", "junk--bound", 4 }, |
| 148 { "foo", "bound", kNotFound }, |
| 149 { "bound", "--boundbound", 0 }, |
| 150 }; |
| 151 |
| 152 for (size_t i = 0; i < WTF_ARRAY_LENGTH(boundaryTests); ++i) { |
| 153 Vector<char> boundary, data; |
| 154 boundary.append(boundaryTests[i].boundary, strlen(boundaryTests[i].bound
ary)); |
| 155 data.append(boundaryTests[i].data, strlen(boundaryTests[i].data)); |
| 156 EXPECT_EQ(boundaryTests[i].position, MultipartImageResourceParser::findB
oundaryForTest(data, &boundary)); |
| 157 } |
| 158 } |
| 159 |
| 160 TEST(MultipartResponseTest, NoStartBoundary) |
| 161 { |
| 162 ResourceResponse response; |
| 163 response.setMimeType("multipart/x-mixed-replace"); |
| 164 response.setHTTPHeaderField("Foo", "Bar"); |
| 165 response.setHTTPHeaderField("Content-type", "text/plain"); |
| 166 MockClient* client = new MockClient; |
| 167 Vector<char> boundary; |
| 168 boundary.append("bound", 5); |
| 169 |
| 170 MultipartImageResourceParser* parser = new MultipartImageResourceParser(resp
onse, boundary, client); |
| 171 const char data[] = |
| 172 "Content-type: text/plain\n\n" |
| 173 "This is a sample response\n" |
| 174 "--bound--" |
| 175 "ignore junk after end token --bound\n\nTest2\n"; |
| 176 parser->appendData(data, strlen(data)); |
| 177 ASSERT_EQ(1u, client->m_responses.size()); |
| 178 ASSERT_EQ(1u, client->m_data.size()); |
| 179 EXPECT_EQ("This is a sample response", toString(client->m_data[0])); |
| 180 |
| 181 parser->finish(); |
| 182 ASSERT_EQ(1u, client->m_responses.size()); |
| 183 ASSERT_EQ(1u, client->m_data.size()); |
| 184 EXPECT_EQ("This is a sample response", toString(client->m_data[0])); |
| 185 } |
| 186 |
| 187 TEST(MultipartResponseTest, NoEndBoundary) |
| 188 { |
| 189 ResourceResponse response; |
| 190 response.setMimeType("multipart/x-mixed-replace"); |
| 191 response.setHTTPHeaderField("Foo", "Bar"); |
| 192 response.setHTTPHeaderField("Content-type", "text/plain"); |
| 193 MockClient* client = new MockClient; |
| 194 Vector<char> boundary; |
| 195 boundary.append("bound", 5); |
| 196 |
| 197 MultipartImageResourceParser* parser = new MultipartImageResourceParser(resp
onse, boundary, client); |
| 198 const char data[] = |
| 199 "bound\nContent-type: text/plain\n\n" |
| 200 "This is a sample response\n"; |
| 201 parser->appendData(data, strlen(data)); |
| 202 ASSERT_EQ(1u, client->m_responses.size()); |
| 203 ASSERT_EQ(1u, client->m_data.size()); |
| 204 EXPECT_EQ("This is a sample response\n", toString(client->m_data[0])); |
| 205 |
| 206 parser->finish(); |
| 207 ASSERT_EQ(1u, client->m_responses.size()); |
| 208 ASSERT_EQ(1u, client->m_data.size()); |
| 209 EXPECT_EQ("This is a sample response\n", toString(client->m_data[0])); |
| 210 } |
| 211 |
| 212 TEST(MultipartResponseTest, NoStartAndEndBoundary) |
| 213 { |
| 214 ResourceResponse response; |
| 215 response.setMimeType("multipart/x-mixed-replace"); |
| 216 response.setHTTPHeaderField("Foo", "Bar"); |
| 217 response.setHTTPHeaderField("Content-type", "text/plain"); |
| 218 MockClient* client = new MockClient; |
| 219 Vector<char> boundary; |
| 220 boundary.append("bound", 5); |
| 221 |
| 222 MultipartImageResourceParser* parser = new MultipartImageResourceParser(resp
onse, boundary, client); |
| 223 const char data[] = |
| 224 "Content-type: text/plain\n\n" |
| 225 "This is a sample response\n"; |
| 226 parser->appendData(data, strlen(data)); |
| 227 ASSERT_EQ(1u, client->m_responses.size()); |
| 228 ASSERT_EQ(1u, client->m_data.size()); |
| 229 EXPECT_EQ("This is a sample response\n", toString(client->m_data[0])); |
| 230 |
| 231 parser->finish(); |
| 232 ASSERT_EQ(1u, client->m_responses.size()); |
| 233 ASSERT_EQ(1u, client->m_data.size()); |
| 234 EXPECT_EQ("This is a sample response\n", toString(client->m_data[0])); |
| 235 } |
| 236 |
| 237 TEST(MultipartResponseTest, MalformedBoundary) |
| 238 { |
| 239 // Some servers send a boundary that is prefixed by "--". See bug 5786. |
| 240 ResourceResponse response; |
| 241 response.setMimeType("multipart/x-mixed-replace"); |
| 242 response.setHTTPHeaderField("Foo", "Bar"); |
| 243 response.setHTTPHeaderField("Content-type", "text/plain"); |
| 244 MockClient* client = new MockClient; |
| 245 Vector<char> boundary; |
| 246 boundary.append("--bound", 7); |
| 247 |
| 248 MultipartImageResourceParser* parser = new MultipartImageResourceParser(resp
onse, boundary, client); |
| 249 const char data[] = |
| 250 "--bound\n" |
| 251 "Content-type: text/plain\n\n" |
| 252 "This is a sample response\n" |
| 253 "--bound--" |
| 254 "ignore junk after end token --bound\n\nTest2\n"; |
| 255 parser->appendData(data, strlen(data)); |
| 256 ASSERT_EQ(1u, client->m_responses.size()); |
| 257 ASSERT_EQ(1u, client->m_data.size()); |
| 258 EXPECT_EQ("This is a sample response", toString(client->m_data[0])); |
| 259 |
| 260 parser->finish(); |
| 261 ASSERT_EQ(1u, client->m_responses.size()); |
| 262 ASSERT_EQ(1u, client->m_data.size()); |
| 263 EXPECT_EQ("This is a sample response", toString(client->m_data[0])); |
| 264 } |
| 265 |
| 266 // Used in for tests that break the data in various places. |
| 267 struct TestChunk { |
| 268 const int startPosition; // offset in data |
| 269 const int endPosition; // end offset in data |
| 270 const size_t expectedResponses; |
| 271 const char* expectedData; |
| 272 }; |
| 273 |
| 274 void variousChunkSizesTest(const TestChunk chunks[], int chunksSize, |
| 275 size_t responses, int receivedData, |
| 276 const char* completedData) |
| 277 { |
| 278 const char data[] = |
| 279 "--bound\n" // 0-7 |
| 280 "Content-type: image/png\n\n" // 8-32 |
| 281 "datadatadatadatadata" // 33-52 |
| 282 "--bound\n" // 53-60 |
| 283 "Content-type: image/jpg\n\n" // 61-85 |
| 284 "foofoofoofoofoo" // 86-100 |
| 285 "--bound--"; // 101-109 |
| 286 |
| 287 ResourceResponse response; |
| 288 response.setMimeType("multipart/x-mixed-replace"); |
| 289 MockClient* client = new MockClient; |
| 290 Vector<char> boundary; |
| 291 boundary.append("bound", 5); |
| 292 |
| 293 MultipartImageResourceParser* parser = new MultipartImageResourceParser(resp
onse, boundary, client); |
| 294 |
| 295 for (int i = 0; i < chunksSize; ++i) { |
| 296 ASSERT_LT(chunks[i].startPosition, chunks[i].endPosition); |
| 297 parser->appendData(data + chunks[i].startPosition, chunks[i].endPosition
- chunks[i].startPosition); |
| 298 EXPECT_EQ(chunks[i].expectedResponses, client->m_responses.size()); |
| 299 EXPECT_EQ(String(chunks[i].expectedData), client->m_data.size() > 0 ? to
String(client->m_data.last()) : String("")); |
| 300 } |
| 301 // Check final state |
| 302 parser->finish(); |
| 303 EXPECT_EQ(responses, client->m_responses.size()); |
| 304 EXPECT_EQ(completedData, toString(client->m_data.last())); |
| 305 } |
| 306 |
| 307 template <size_t N> |
| 308 void variousChunkSizesTest(const TestChunk (&chunks)[N], size_t responses, int r
eceivedData, const char* completedData) |
| 309 { |
| 310 variousChunkSizesTest(chunks, N, responses, receivedData, completedData); |
| 311 } |
| 312 |
| 313 TEST(MultipartResponseTest, BreakInBoundary) |
| 314 { |
| 315 // Break in the first boundary |
| 316 const TestChunk bound1[] = { |
| 317 { 0, 4, 0, "" }, |
| 318 { 4, 110, 2, "foofoofoofoofoo" }, |
| 319 }; |
| 320 variousChunkSizesTest(bound1, 2, 2, "foofoofoofoofoo"); |
| 321 |
| 322 // Break in first and second |
| 323 const TestChunk bound2[] = { |
| 324 { 0, 4, 0, "" }, |
| 325 { 4, 55, 1, "datadatadatadat" }, |
| 326 { 55, 65, 1, "datadatadatadatadata" }, |
| 327 { 65, 110, 2, "foofoofoofoofoo" }, |
| 328 }; |
| 329 variousChunkSizesTest(bound2, 2, 3, "foofoofoofoofoo"); |
| 330 |
| 331 // Break in second only |
| 332 const TestChunk bound3[] = { |
| 333 { 0, 55, 1, "datadatadatadat" }, |
| 334 { 55, 110, 2, "foofoofoofoofoo" }, |
| 335 }; |
| 336 variousChunkSizesTest(bound3, 2, 3, "foofoofoofoofoo"); |
| 337 } |
| 338 |
| 339 TEST(MultipartResponseTest, BreakInHeaders) |
| 340 { |
| 341 // Break in first header |
| 342 const TestChunk header1[] = { |
| 343 { 0, 10, 0, "" }, |
| 344 { 10, 35, 1, "" }, |
| 345 { 35, 110, 2, "foofoofoofoofoo" }, |
| 346 }; |
| 347 variousChunkSizesTest(header1, 2, 2, "foofoofoofoofoo"); |
| 348 |
| 349 // Break in both headers |
| 350 const TestChunk header2[] = { |
| 351 { 0, 10, 0, "" }, |
| 352 { 10, 65, 1, "datadatadatadatadata" }, |
| 353 { 65, 110, 2, "foofoofoofoofoo" }, |
| 354 }; |
| 355 variousChunkSizesTest(header2, 2, 2, "foofoofoofoofoo"); |
| 356 |
| 357 // Break at end of a header |
| 358 const TestChunk header3[] = { |
| 359 { 0, 33, 1, "" }, |
| 360 { 33, 65, 1, "datadatadatadatadata" }, |
| 361 { 65, 110, 2, "foofoofoofoofoo" }, |
| 362 }; |
| 363 variousChunkSizesTest(header3, 2, 2, "foofoofoofoofoo"); |
| 364 } |
| 365 |
| 366 TEST(MultipartResponseTest, BreakInData) |
| 367 { |
| 368 // All data as one chunk |
| 369 const TestChunk data1[] = { |
| 370 { 0, 110, 2, "foofoofoofoofoo" }, |
| 371 }; |
| 372 variousChunkSizesTest(data1, 2, 2, "foofoofoofoofoo"); |
| 373 |
| 374 // breaks in data segment |
| 375 const TestChunk data2[] = { |
| 376 { 0, 35, 1, "" }, |
| 377 { 35, 65, 1, "datadatadatadatadata" }, |
| 378 { 65, 90, 2, "" }, |
| 379 { 90, 110, 2, "foofoofoofoofoo" }, |
| 380 }; |
| 381 variousChunkSizesTest(data2, 2, 2, "foofoofoofoofoo"); |
| 382 |
| 383 // Incomplete send |
| 384 const TestChunk data3[] = { |
| 385 { 0, 35, 1, "" }, |
| 386 { 35, 90, 2, "" }, |
| 387 }; |
| 388 variousChunkSizesTest(data3, 2, 2, "foof"); |
| 389 } |
| 390 |
| 391 TEST(MultipartResponseTest, SmallChunk) |
| 392 { |
| 393 ResourceResponse response; |
| 394 response.setMimeType("multipart/x-mixed-replace"); |
| 395 response.setHTTPHeaderField("Content-type", "text/plain"); |
| 396 MockClient* client = new MockClient; |
| 397 Vector<char> boundary; |
| 398 boundary.append("bound", 5); |
| 399 |
| 400 MultipartImageResourceParser* parser = new MultipartImageResourceParser(resp
onse, boundary, client); |
| 401 |
| 402 // Test chunks of size 1, 2, and 0. |
| 403 const char data[] = |
| 404 "--boundContent-type: text/plain\n\n" |
| 405 "\n--boundContent-type: text/plain\n\n" |
| 406 "\n\n--boundContent-type: text/plain\n\n" |
| 407 "--boundContent-type: text/plain\n\n" |
| 408 "end--bound--"; |
| 409 parser->appendData(data, strlen(data)); |
| 410 ASSERT_EQ(4u, client->m_responses.size()); |
| 411 ASSERT_EQ(4u, client->m_data.size()); |
| 412 EXPECT_EQ("", toString(client->m_data[0])); |
| 413 EXPECT_EQ("\n", toString(client->m_data[1])); |
| 414 EXPECT_EQ("", toString(client->m_data[2])); |
| 415 EXPECT_EQ("end", toString(client->m_data[3])); |
| 416 |
| 417 parser->finish(); |
| 418 ASSERT_EQ(4u, client->m_responses.size()); |
| 419 ASSERT_EQ(4u, client->m_data.size()); |
| 420 EXPECT_EQ("", toString(client->m_data[0])); |
| 421 EXPECT_EQ("\n", toString(client->m_data[1])); |
| 422 EXPECT_EQ("", toString(client->m_data[2])); |
| 423 EXPECT_EQ("end", toString(client->m_data[3])); |
| 424 } |
| 425 |
| 426 TEST(MultipartResponseTest, MultipleBoundaries) |
| 427 { |
| 428 // Test multiple boundaries back to back |
| 429 ResourceResponse response; |
| 430 response.setMimeType("multipart/x-mixed-replace"); |
| 431 MockClient* client = new MockClient; |
| 432 Vector<char> boundary; |
| 433 boundary.append("bound", 5); |
| 434 |
| 435 MultipartImageResourceParser* parser = new MultipartImageResourceParser(resp
onse, boundary, client); |
| 436 |
| 437 const char data[] = "--bound\r\n\r\n--bound\r\n\r\nfoofoo--bound--"; |
| 438 parser->appendData(data, strlen(data)); |
| 439 ASSERT_EQ(2u, client->m_responses.size()); |
| 440 ASSERT_EQ(2u, client->m_data.size()); |
| 441 EXPECT_EQ("", toString(client->m_data[0])); |
| 442 EXPECT_EQ("foofoo", toString(client->m_data[1])); |
| 443 } |
| 444 |
| 445 TEST(MultipartResponseTest, MultipartPayloadSet) |
| 446 { |
| 447 ResourceResponse response; |
| 448 response.setMimeType("multipart/x-mixed-replace"); |
| 449 MockClient* client = new MockClient; |
| 450 Vector<char> boundary; |
| 451 boundary.append("bound", 5); |
| 452 |
| 453 MultipartImageResourceParser* parser = new MultipartImageResourceParser(resp
onse, boundary, client); |
| 454 |
| 455 const char data[] = |
| 456 "--bound\n" |
| 457 "Content-type: text/plain\n\n" |
| 458 "response data\n" |
| 459 "--bound\n"; |
| 460 parser->appendData(data, strlen(data)); |
| 461 ASSERT_EQ(1u, client->m_responses.size()); |
| 462 ASSERT_EQ(1u, client->m_data.size()); |
| 463 EXPECT_EQ("response data", toString(client->m_data[0])); |
| 464 EXPECT_FALSE(client->m_responses[0].isMultipartPayload()); |
| 465 |
| 466 const char data2[] = |
| 467 "Content-type: text/plain\n\n" |
| 468 "response data2\n" |
| 469 "--bound\n"; |
| 470 parser->appendData(data2, strlen(data2)); |
| 471 ASSERT_EQ(2u, client->m_responses.size()); |
| 472 ASSERT_EQ(2u, client->m_data.size()); |
| 473 EXPECT_EQ("response data2", toString(client->m_data[1])); |
| 474 EXPECT_TRUE(client->m_responses[1].isMultipartPayload()); |
| 475 } |
| 476 |
| 477 } // namespace |
| 478 |
| 479 } // namespace blink |
OLD | NEW |