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