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 "third_party/blink/renderer/core/loader/resource/multipart_image_resource_parser.h"
6 
7 #include <stddef.h>
8 #include <stdint.h>
9 #include <string.h>
10 
11 #include "base/stl_util.h"
12 #include "testing/gtest/include/gtest/gtest.h"
13 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
14 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
15 
16 namespace blink {
17 namespace multipart_image_resource_parser_test {
18 
ToString(const Vector<char> & data)19 String ToString(const Vector<char>& data) {
20   if (data.IsEmpty())
21     return String("");
22   return String(data.data(), data.size());
23 }
24 
25 class MockClient final : public GarbageCollected<MockClient>,
26                          public MultipartImageResourceParser::Client {
27  public:
OnePartInMultipartReceived(const ResourceResponse & response)28   void OnePartInMultipartReceived(const ResourceResponse& response) override {
29     responses_.push_back(response);
30     data_.push_back(Vector<char>());
31   }
MultipartDataReceived(const char * bytes,size_t size)32   void MultipartDataReceived(const char* bytes, size_t size) override {
33     data_.back().Append(bytes, SafeCast<wtf_size_t>(size));
34   }
35 
36   Vector<ResourceResponse> responses_;
37   Vector<Vector<char>> data_;
38 };
39 
TEST(MultipartResponseTest,SkippableLength)40 TEST(MultipartResponseTest, SkippableLength) {
41   struct {
42     const char* input;
43     const wtf_size_t position;
44     const wtf_size_t expected;
45   } line_tests[] = {
46       {"Line", 0, 0},         {"Line", 2, 0},         {"Line", 10, 0},
47       {"\r\nLine", 0, 2},     {"\nLine", 0, 1},       {"\n\nLine", 0, 1},
48       {"\rLine", 0, 0},       {"Line\r\nLine", 4, 2}, {"Line\nLine", 4, 1},
49       {"Line\n\nLine", 4, 1}, {"Line\rLine", 4, 0},   {"Line\r\rLine", 4, 0},
50   };
51   for (size_t i = 0; i < base::size(line_tests); ++i) {
52     Vector<char> input;
53     input.Append(line_tests[i].input,
54                  static_cast<wtf_size_t>(strlen(line_tests[i].input)));
55     EXPECT_EQ(line_tests[i].expected,
56               MultipartImageResourceParser::SkippableLengthForTest(
57                   input, line_tests[i].position));
58   }
59 }
60 
TEST(MultipartResponseTest,FindBoundary)61 TEST(MultipartResponseTest, FindBoundary) {
62   struct {
63     const char* boundary;
64     const char* data;
65     const size_t position;
66   } boundary_tests[] = {
67       {"bound", "bound", 0},       {"bound", "--bound", 0},
68       {"bound", "junkbound", 4},   {"bound", "junk--bound", 4},
69       {"foo", "bound", kNotFound}, {"bound", "--boundbound", 0},
70   };
71 
72   for (size_t i = 0; i < base::size(boundary_tests); ++i) {
73     Vector<char> boundary, data;
74     boundary.Append(boundary_tests[i].boundary,
75                     static_cast<uint32_t>(strlen(boundary_tests[i].boundary)));
76     data.Append(boundary_tests[i].data,
77                 static_cast<uint32_t>(strlen(boundary_tests[i].data)));
78     EXPECT_EQ(
79         boundary_tests[i].position,
80         MultipartImageResourceParser::FindBoundaryForTest(data, &boundary));
81   }
82 }
83 
TEST(MultipartResponseTest,NoStartBoundary)84 TEST(MultipartResponseTest, NoStartBoundary) {
85   ResourceResponse response(NullURL());
86   response.SetMimeType("multipart/x-mixed-replace");
87   response.SetHttpHeaderField("Foo", "Bar");
88   response.SetHttpHeaderField("Content-type", "text/plain");
89   MockClient* client = MakeGarbageCollected<MockClient>();
90   Vector<char> boundary;
91   boundary.Append("bound", 5);
92 
93   MultipartImageResourceParser* parser =
94       MakeGarbageCollected<MultipartImageResourceParser>(response, boundary,
95                                                          client);
96   const char kData[] =
97       "Content-type: text/plain\n\n"
98       "This is a sample response\n"
99       "--bound--"
100       "ignore junk after end token --bound\n\nTest2\n";
101   parser->AppendData(kData, strlen(kData));
102   ASSERT_EQ(1u, client->responses_.size());
103   ASSERT_EQ(1u, client->data_.size());
104   EXPECT_EQ("This is a sample response", ToString(client->data_[0]));
105 
106   parser->Finish();
107   ASSERT_EQ(1u, client->responses_.size());
108   ASSERT_EQ(1u, client->data_.size());
109   EXPECT_EQ("This is a sample response", ToString(client->data_[0]));
110 }
111 
TEST(MultipartResponseTest,NoEndBoundary)112 TEST(MultipartResponseTest, NoEndBoundary) {
113   ResourceResponse response(NullURL());
114   response.SetMimeType("multipart/x-mixed-replace");
115   response.SetHttpHeaderField("Foo", "Bar");
116   response.SetHttpHeaderField("Content-type", "text/plain");
117   MockClient* client = MakeGarbageCollected<MockClient>();
118   Vector<char> boundary;
119   boundary.Append("bound", 5);
120 
121   MultipartImageResourceParser* parser =
122       MakeGarbageCollected<MultipartImageResourceParser>(response, boundary,
123                                                          client);
124   const char kData[] =
125       "bound\nContent-type: text/plain\n\n"
126       "This is a sample response\n";
127   parser->AppendData(kData, strlen(kData));
128   ASSERT_EQ(1u, client->responses_.size());
129   ASSERT_EQ(1u, client->data_.size());
130   EXPECT_EQ("This is a sample ", ToString(client->data_[0]));
131 
132   parser->Finish();
133   ASSERT_EQ(1u, client->responses_.size());
134   ASSERT_EQ(1u, client->data_.size());
135   EXPECT_EQ("This is a sample response\n", ToString(client->data_[0]));
136 }
137 
TEST(MultipartResponseTest,NoStartAndEndBoundary)138 TEST(MultipartResponseTest, NoStartAndEndBoundary) {
139   ResourceResponse response(NullURL());
140   response.SetMimeType("multipart/x-mixed-replace");
141   response.SetHttpHeaderField("Foo", "Bar");
142   response.SetHttpHeaderField("Content-type", "text/plain");
143   MockClient* client = MakeGarbageCollected<MockClient>();
144   Vector<char> boundary;
145   boundary.Append("bound", 5);
146 
147   MultipartImageResourceParser* parser =
148       MakeGarbageCollected<MultipartImageResourceParser>(response, boundary,
149                                                          client);
150   const char kData[] =
151       "Content-type: text/plain\n\n"
152       "This is a sample response\n";
153   parser->AppendData(kData, strlen(kData));
154   ASSERT_EQ(1u, client->responses_.size());
155   ASSERT_EQ(1u, client->data_.size());
156   EXPECT_EQ("This is a sample ", ToString(client->data_[0]));
157 
158   parser->Finish();
159   ASSERT_EQ(1u, client->responses_.size());
160   ASSERT_EQ(1u, client->data_.size());
161   EXPECT_EQ("This is a sample response\n", ToString(client->data_[0]));
162 }
163 
TEST(MultipartResponseTest,MalformedBoundary)164 TEST(MultipartResponseTest, MalformedBoundary) {
165   // Some servers send a boundary that is prefixed by "--".  See bug 5786.
166   ResourceResponse response(NullURL());
167   response.SetMimeType("multipart/x-mixed-replace");
168   response.SetHttpHeaderField("Foo", "Bar");
169   response.SetHttpHeaderField("Content-type", "text/plain");
170   MockClient* client = MakeGarbageCollected<MockClient>();
171   Vector<char> boundary;
172   boundary.Append("--bound", 7);
173 
174   MultipartImageResourceParser* parser =
175       MakeGarbageCollected<MultipartImageResourceParser>(response, boundary,
176                                                          client);
177   const char kData[] =
178       "--bound\n"
179       "Content-type: text/plain\n\n"
180       "This is a sample response\n"
181       "--bound--"
182       "ignore junk after end token --bound\n\nTest2\n";
183   parser->AppendData(kData, strlen(kData));
184   ASSERT_EQ(1u, client->responses_.size());
185   ASSERT_EQ(1u, client->data_.size());
186   EXPECT_EQ("This is a sample response", ToString(client->data_[0]));
187 
188   parser->Finish();
189   ASSERT_EQ(1u, client->responses_.size());
190   ASSERT_EQ(1u, client->data_.size());
191   EXPECT_EQ("This is a sample response", ToString(client->data_[0]));
192 }
193 
194 // Used in for tests that break the data in various places.
195 struct TestChunk {
196   const int start_position;  // offset in data
197   const int end_position;    // end offset in data
198   const size_t expected_responses;
199   const char* expected_data;
200 };
201 
VariousChunkSizesTest(const TestChunk chunks[],int chunks_size,size_t responses,int received_data,const char * completed_data)202 void VariousChunkSizesTest(const TestChunk chunks[],
203                            int chunks_size,
204                            size_t responses,
205                            int received_data,
206                            const char* completed_data) {
207   const char kData[] =
208       "--bound\n"                    // 0-7
209       "Content-type: image/png\n\n"  // 8-32
210       "datadatadatadatadata"         // 33-52
211       "--bound\n"                    // 53-60
212       "Content-type: image/jpg\n\n"  // 61-85
213       "foofoofoofoofoo"              // 86-100
214       "--bound--";                   // 101-109
215 
216   ResourceResponse response(NullURL());
217   response.SetMimeType("multipart/x-mixed-replace");
218   MockClient* client = MakeGarbageCollected<MockClient>();
219   Vector<char> boundary;
220   boundary.Append("bound", 5);
221 
222   MultipartImageResourceParser* parser =
223       MakeGarbageCollected<MultipartImageResourceParser>(response, boundary,
224                                                          client);
225 
226   for (int i = 0; i < chunks_size; ++i) {
227     ASSERT_LT(chunks[i].start_position, chunks[i].end_position);
228     parser->AppendData(kData + chunks[i].start_position,
229                        chunks[i].end_position - chunks[i].start_position);
230     EXPECT_EQ(chunks[i].expected_responses, client->responses_.size());
231     EXPECT_EQ(
232         String(chunks[i].expected_data),
233         client->data_.size() > 0 ? ToString(client->data_.back()) : String(""));
234   }
235   // Check final state
236   parser->Finish();
237   EXPECT_EQ(responses, client->responses_.size());
238   EXPECT_EQ(completed_data, ToString(client->data_.back()));
239 }
240 
241 template <size_t N>
VariousChunkSizesTest(const TestChunk (& chunks)[N],size_t responses,int received_data,const char * completed_data)242 void VariousChunkSizesTest(const TestChunk (&chunks)[N],
243                            size_t responses,
244                            int received_data,
245                            const char* completed_data) {
246   VariousChunkSizesTest(chunks, N, responses, received_data, completed_data);
247 }
248 
TEST(MultipartResponseTest,BreakInBoundary)249 TEST(MultipartResponseTest, BreakInBoundary) {
250   // Break in the first boundary
251   const TestChunk kBound1[] = {
252       {0, 4, 0, ""}, {4, 110, 2, "foofoofoofoofoo"},
253   };
254   VariousChunkSizesTest(kBound1, 2, 2, "foofoofoofoofoo");
255 
256   // Break in first and second
257   const TestChunk kBound2[] = {
258       {0, 4, 0, ""},
259       {4, 55, 1, "datadatadatad"},
260       {55, 65, 1, "datadatadatadatadata"},
261       {65, 110, 2, "foofoofoofoofoo"},
262   };
263   VariousChunkSizesTest(kBound2, 2, 3, "foofoofoofoofoo");
264 
265   // Break in second only
266   const TestChunk kBound3[] = {
267       {0, 55, 1, "datadatadatad"}, {55, 110, 2, "foofoofoofoofoo"},
268   };
269   VariousChunkSizesTest(kBound3, 2, 3, "foofoofoofoofoo");
270 }
271 
TEST(MultipartResponseTest,BreakInHeaders)272 TEST(MultipartResponseTest, BreakInHeaders) {
273   // Break in first header
274   const TestChunk kHeader1[] = {
275       {0, 10, 0, ""}, {10, 35, 1, ""}, {35, 110, 2, "foofoofoofoofoo"},
276   };
277   VariousChunkSizesTest(kHeader1, 2, 2, "foofoofoofoofoo");
278 
279   // Break in both headers
280   const TestChunk kHeader2[] = {
281       {0, 10, 0, ""},
282       {10, 65, 1, "datadatadatadatadata"},
283       {65, 110, 2, "foofoofoofoofoo"},
284   };
285   VariousChunkSizesTest(kHeader2, 2, 2, "foofoofoofoofoo");
286 
287   // Break at end of a header
288   const TestChunk kHeader3[] = {
289       {0, 33, 1, ""},
290       {33, 65, 1, "datadatadatadatadata"},
291       {65, 110, 2, "foofoofoofoofoo"},
292   };
293   VariousChunkSizesTest(kHeader3, 2, 2, "foofoofoofoofoo");
294 }
295 
TEST(MultipartResponseTest,BreakInData)296 TEST(MultipartResponseTest, BreakInData) {
297   // All data as one chunk
298   const TestChunk kData1[] = {
299       {0, 110, 2, "foofoofoofoofoo"},
300   };
301   VariousChunkSizesTest(kData1, 2, 2, "foofoofoofoofoo");
302 
303   // breaks in data segment
304   const TestChunk kData2[] = {
305       {0, 35, 1, ""},
306       {35, 65, 1, "datadatadatadatadata"},
307       {65, 90, 2, ""},
308       {90, 110, 2, "foofoofoofoofoo"},
309   };
310   VariousChunkSizesTest(kData2, 2, 2, "foofoofoofoofoo");
311 
312   // Incomplete send
313   const TestChunk kData3[] = {
314       {0, 35, 1, ""}, {35, 90, 2, ""},
315   };
316   VariousChunkSizesTest(kData3, 2, 2, "foof");
317 }
318 
TEST(MultipartResponseTest,SmallChunk)319 TEST(MultipartResponseTest, SmallChunk) {
320   ResourceResponse response(NullURL());
321   response.SetMimeType("multipart/x-mixed-replace");
322   response.SetHttpHeaderField("Content-type", "text/plain");
323   MockClient* client = MakeGarbageCollected<MockClient>();
324   Vector<char> boundary;
325   boundary.Append("bound", 5);
326 
327   MultipartImageResourceParser* parser =
328       MakeGarbageCollected<MultipartImageResourceParser>(response, boundary,
329                                                          client);
330 
331   // Test chunks of size 1, 2, and 0.
332   const char kData[] =
333       "--boundContent-type: text/plain\n\n"
334       "\n--boundContent-type: text/plain\n\n"
335       "\n\n--boundContent-type: text/plain\n\n"
336       "--boundContent-type: text/plain\n\n"
337       "end--bound--";
338   parser->AppendData(kData, strlen(kData));
339   ASSERT_EQ(4u, client->responses_.size());
340   ASSERT_EQ(4u, client->data_.size());
341   EXPECT_EQ("", ToString(client->data_[0]));
342   EXPECT_EQ("\n", ToString(client->data_[1]));
343   EXPECT_EQ("", ToString(client->data_[2]));
344   EXPECT_EQ("end", ToString(client->data_[3]));
345 
346   parser->Finish();
347   ASSERT_EQ(4u, client->responses_.size());
348   ASSERT_EQ(4u, client->data_.size());
349   EXPECT_EQ("", ToString(client->data_[0]));
350   EXPECT_EQ("\n", ToString(client->data_[1]));
351   EXPECT_EQ("", ToString(client->data_[2]));
352   EXPECT_EQ("end", ToString(client->data_[3]));
353 }
354 
TEST(MultipartResponseTest,MultipleBoundaries)355 TEST(MultipartResponseTest, MultipleBoundaries) {
356   // Test multiple boundaries back to back
357   ResourceResponse response(NullURL());
358   response.SetMimeType("multipart/x-mixed-replace");
359   MockClient* client = MakeGarbageCollected<MockClient>();
360   Vector<char> boundary;
361   boundary.Append("bound", 5);
362 
363   MultipartImageResourceParser* parser =
364       MakeGarbageCollected<MultipartImageResourceParser>(response, boundary,
365                                                          client);
366 
367   const char kData[] = "--bound\r\n\r\n--bound\r\n\r\nfoofoo--bound--";
368   parser->AppendData(kData, strlen(kData));
369   ASSERT_EQ(2u, client->responses_.size());
370   ASSERT_EQ(2u, client->data_.size());
371   EXPECT_EQ("", ToString(client->data_[0]));
372   EXPECT_EQ("foofoo", ToString(client->data_[1]));
373 }
374 
TEST(MultipartResponseTest,EatLeadingLF)375 TEST(MultipartResponseTest, EatLeadingLF) {
376   ResourceResponse response(NullURL());
377   response.SetMimeType("multipart/x-mixed-replace");
378   MockClient* client = MakeGarbageCollected<MockClient>();
379   Vector<char> boundary;
380   boundary.Append("bound", 5);
381 
382   const char kData[] =
383       "\n\n\n--bound\n\n\ncontent-type: 1\n\n"
384       "\n\n\n--bound\n\ncontent-type: 2\n\n"
385       "\n\n\n--bound\ncontent-type: 3\n\n";
386   MultipartImageResourceParser* parser =
387       MakeGarbageCollected<MultipartImageResourceParser>(response, boundary,
388                                                          client);
389 
390   for (size_t i = 0; i < strlen(kData); ++i)
391     parser->AppendData(&kData[i], 1);
392   parser->Finish();
393 
394   ASSERT_EQ(4u, client->responses_.size());
395   ASSERT_EQ(4u, client->data_.size());
396   EXPECT_EQ(String(), client->responses_[0].HttpHeaderField("content-type"));
397   EXPECT_EQ("", ToString(client->data_[0]));
398   EXPECT_EQ(String(), client->responses_[1].HttpHeaderField("content-type"));
399   EXPECT_EQ("\ncontent-type: 1\n\n\n\n", ToString(client->data_[1]));
400   EXPECT_EQ(String(), client->responses_[2].HttpHeaderField("content-type"));
401   EXPECT_EQ("content-type: 2\n\n\n\n", ToString(client->data_[2]));
402   EXPECT_EQ("3", client->responses_[3].HttpHeaderField("content-type"));
403   EXPECT_EQ("", ToString(client->data_[3]));
404 }
405 
TEST(MultipartResponseTest,EatLeadingCRLF)406 TEST(MultipartResponseTest, EatLeadingCRLF) {
407   ResourceResponse response(NullURL());
408   response.SetMimeType("multipart/x-mixed-replace");
409   MockClient* client = MakeGarbageCollected<MockClient>();
410   Vector<char> boundary;
411   boundary.Append("bound", 5);
412 
413   const char kData[] =
414       "\r\n\r\n\r\n--bound\r\n\r\n\r\ncontent-type: 1\r\n\r\n"
415       "\r\n\r\n\r\n--bound\r\n\r\ncontent-type: 2\r\n\r\n"
416       "\r\n\r\n\r\n--bound\r\ncontent-type: 3\r\n\r\n";
417   MultipartImageResourceParser* parser =
418       MakeGarbageCollected<MultipartImageResourceParser>(response, boundary,
419                                                          client);
420 
421   for (size_t i = 0; i < strlen(kData); ++i)
422     parser->AppendData(&kData[i], 1);
423   parser->Finish();
424 
425   ASSERT_EQ(4u, client->responses_.size());
426   ASSERT_EQ(4u, client->data_.size());
427   EXPECT_EQ(String(), client->responses_[0].HttpHeaderField("content-type"));
428   EXPECT_EQ("", ToString(client->data_[0]));
429   EXPECT_EQ(String(), client->responses_[1].HttpHeaderField("content-type"));
430   EXPECT_EQ("\r\ncontent-type: 1\r\n\r\n\r\n\r\n", ToString(client->data_[1]));
431   EXPECT_EQ(String(), client->responses_[2].HttpHeaderField("content-type"));
432   EXPECT_EQ("content-type: 2\r\n\r\n\r\n\r\n", ToString(client->data_[2]));
433   EXPECT_EQ("3", client->responses_[3].HttpHeaderField("content-type"));
434   EXPECT_EQ("", ToString(client->data_[3]));
435 }
436 
437 }  // namespace multipart_image_resource_parser_test
438 }  // namespace blink
439