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 "modules/fetch/MultipartParser.h" |
| 6 |
| 7 #include "platform/HTTPNames.h" |
| 8 #include "testing/gtest/include/gtest/gtest.h" |
| 9 |
| 10 #include <string.h> |
| 11 #include <algorithm> |
| 12 |
| 13 namespace blink { |
| 14 |
| 15 namespace { |
| 16 |
| 17 String toString(const Vector<char>& data) { |
| 18 if (data.IsEmpty()) |
| 19 return String(""); |
| 20 return String(data.data(), data.size()); |
| 21 } |
| 22 |
| 23 class MockClient final : public GarbageCollectedFinalized<MockClient>, |
| 24 public MultipartParser::Client { |
| 25 USING_GARBAGE_COLLECTED_MIXIN(MockClient); |
| 26 |
| 27 public: |
| 28 struct Part { |
| 29 Part() = default; |
| 30 explicit Part(const HTTPHeaderMap& header_fields) |
| 31 : header_fields(header_fields), data_fully_received(false) {} |
| 32 HTTPHeaderMap header_fields; |
| 33 Vector<char> data; |
| 34 bool data_fully_received; |
| 35 }; |
| 36 void PartHeaderFieldsInMultipartReceived( |
| 37 const HTTPHeaderMap& header_fields) override { |
| 38 parts_.push_back(header_fields); |
| 39 } |
| 40 void PartDataInMultipartReceived(const char* bytes, size_t size) override { |
| 41 parts_.back().data.Append(bytes, size); |
| 42 } |
| 43 void PartDataInMultipartFullyReceived() override { |
| 44 parts_.back().data_fully_received = true; |
| 45 } |
| 46 const Part& GetPart(size_t part_index) const { |
| 47 EXPECT_LT(part_index, NumberOfParts()); |
| 48 return part_index < NumberOfParts() ? parts_[part_index] : empty_part_; |
| 49 } |
| 50 size_t NumberOfParts() const { return parts_.size(); } |
| 51 |
| 52 private: |
| 53 Part empty_part_; |
| 54 Vector<Part> parts_; |
| 55 }; |
| 56 |
| 57 constexpr char kBytes[] = |
| 58 "preamble" |
| 59 "\r\n--boundary\r\n\r\n" |
| 60 "\r\n--boundary\r\ncontent-type: application/xhtml+xml\r\n\r\n1" |
| 61 "\r\n--boundary\t\r\ncontent-type: " |
| 62 "text/html\r\n\r\n2\r\n--\r\n--bound--\r\n--\r\n2\r\n" |
| 63 "\r\n--boundary \r\ncontent-type: text/plain; charset=iso-8859-1\r\n\r\n333" |
| 64 "\r\n--boundary--\t \r\n" |
| 65 "epilogue"; |
| 66 |
| 67 TEST(MultipartParserTest, AppendDataInChunks) { |
| 68 const size_t sizes[] = {1u, 2u, strlen(kBytes)}; |
| 69 |
| 70 Vector<char> boundary; |
| 71 boundary.Append("boundary", 8u); |
| 72 for (const size_t size : sizes) { |
| 73 MockClient* client = new MockClient; |
| 74 MultipartParser* parser = new MultipartParser(boundary, client); |
| 75 |
| 76 for (size_t i = 0u, length = strlen(kBytes); i < length; i += size) |
| 77 EXPECT_TRUE(parser->AppendData(kBytes + i, std::min(size, length - i))); |
| 78 EXPECT_TRUE(parser->Finish()) << " size=" << size; |
| 79 EXPECT_EQ(4u, client->NumberOfParts()) << " size=" << size; |
| 80 EXPECT_EQ(0u, client->GetPart(0).header_fields.size()); |
| 81 EXPECT_EQ(0u, client->GetPart(0).data.size()); |
| 82 EXPECT_TRUE(client->GetPart(0).data_fully_received); |
| 83 EXPECT_EQ(1u, client->GetPart(1).header_fields.size()); |
| 84 EXPECT_EQ("application/xhtml+xml", |
| 85 client->GetPart(1).header_fields.Get(HTTPNames::Content_Type)); |
| 86 EXPECT_EQ("1", toString(client->GetPart(1).data)); |
| 87 EXPECT_TRUE(client->GetPart(1).data_fully_received); |
| 88 EXPECT_EQ(1u, client->GetPart(2).header_fields.size()); |
| 89 EXPECT_EQ("text/html", |
| 90 client->GetPart(2).header_fields.Get(HTTPNames::Content_Type)); |
| 91 EXPECT_EQ("2\r\n--\r\n--bound--\r\n--\r\n2\r\n", |
| 92 toString(client->GetPart(2).data)); |
| 93 EXPECT_TRUE(client->GetPart(2).data_fully_received); |
| 94 EXPECT_EQ(1u, client->GetPart(3).header_fields.size()); |
| 95 EXPECT_EQ("text/plain; charset=iso-8859-1", |
| 96 client->GetPart(3).header_fields.Get(HTTPNames::Content_Type)); |
| 97 EXPECT_EQ("333", toString(client->GetPart(3).data)); |
| 98 EXPECT_TRUE(client->GetPart(3).data_fully_received); |
| 99 } |
| 100 } |
| 101 |
| 102 TEST(MultipartParserTest, Epilogue) { |
| 103 constexpr size_t ends[] = { |
| 104 0u, // Non-empty epilogue in the end. |
| 105 8u, // Empty epilogue in the end. |
| 106 9u, // Partial CRLF after close delimiter in the end. |
| 107 10u, // No CRLF after close delimiter in the end. |
| 108 12u, // No transport padding nor CRLF after close delimiter in the end. |
| 109 13u, // Partial close delimiter in the end. |
| 110 14u, // No close delimiter but a delimiter in the end. |
| 111 15u // Partial delimiter in the end. |
| 112 }; |
| 113 |
| 114 Vector<char> boundary; |
| 115 boundary.Append("boundary", 8u); |
| 116 for (size_t end : ends) { |
| 117 MockClient* client = new MockClient; |
| 118 MultipartParser* parser = new MultipartParser(boundary, client); |
| 119 |
| 120 EXPECT_TRUE(parser->AppendData(kBytes, strlen(kBytes) - end)); |
| 121 EXPECT_EQ(end <= 12u, parser->Finish()) << " end=" << end; |
| 122 EXPECT_EQ(4u, client->NumberOfParts()) << " end=" << end; |
| 123 EXPECT_EQ(0u, client->GetPart(0).header_fields.size()); |
| 124 EXPECT_EQ(0u, client->GetPart(0).data.size()); |
| 125 EXPECT_TRUE(client->GetPart(0).data_fully_received); |
| 126 EXPECT_EQ(1u, client->GetPart(1).header_fields.size()); |
| 127 EXPECT_EQ("application/xhtml+xml", |
| 128 client->GetPart(1).header_fields.Get(HTTPNames::Content_Type)); |
| 129 EXPECT_EQ("1", toString(client->GetPart(1).data)); |
| 130 EXPECT_TRUE(client->GetPart(1).data_fully_received); |
| 131 EXPECT_EQ(1u, client->GetPart(2).header_fields.size()); |
| 132 EXPECT_EQ("text/html", |
| 133 client->GetPart(2).header_fields.Get(HTTPNames::Content_Type)); |
| 134 EXPECT_EQ("2\r\n--\r\n--bound--\r\n--\r\n2\r\n", |
| 135 toString(client->GetPart(2).data)); |
| 136 EXPECT_TRUE(client->GetPart(2).data_fully_received); |
| 137 EXPECT_EQ(1u, client->GetPart(3).header_fields.size()); |
| 138 EXPECT_EQ("text/plain; charset=iso-8859-1", |
| 139 client->GetPart(3).header_fields.Get(HTTPNames::Content_Type)); |
| 140 switch (end) { |
| 141 case 15u: |
| 142 EXPECT_EQ("333\r\n--boundar", toString(client->GetPart(3).data)); |
| 143 EXPECT_FALSE(client->GetPart(3).data_fully_received); |
| 144 break; |
| 145 default: |
| 146 EXPECT_EQ("333", toString(client->GetPart(3).data)); |
| 147 EXPECT_TRUE(client->GetPart(3).data_fully_received); |
| 148 break; |
| 149 } |
| 150 } |
| 151 } |
| 152 |
| 153 TEST(MultipartParserTest, NoEndBoundary) { |
| 154 constexpr char bytes[] = |
| 155 "--boundary\r\ncontent-type: application/xhtml+xml\r\n\r\n1"; |
| 156 |
| 157 Vector<char> boundary; |
| 158 boundary.Append("boundary", 8u); |
| 159 MockClient* client = new MockClient; |
| 160 MultipartParser* parser = new MultipartParser(boundary, client); |
| 161 |
| 162 EXPECT_TRUE(parser->AppendData(bytes, strlen(bytes))); |
| 163 EXPECT_FALSE(parser->Finish()); // No close delimiter. |
| 164 EXPECT_EQ(1u, client->NumberOfParts()); |
| 165 EXPECT_EQ(1u, client->GetPart(0).header_fields.size()); |
| 166 EXPECT_EQ("application/xhtml+xml", |
| 167 client->GetPart(0).header_fields.Get(HTTPNames::Content_Type)); |
| 168 EXPECT_EQ("1", toString(client->GetPart(0).data)); |
| 169 EXPECT_FALSE(client->GetPart(0).data_fully_received); |
| 170 } |
| 171 |
| 172 TEST(MultipartParserTest, NoStartBoundary) { |
| 173 constexpr char bytes[] = |
| 174 "content-type: application/xhtml+xml\r\n\r\n1\r\n--boundary--\r\n"; |
| 175 |
| 176 Vector<char> boundary; |
| 177 boundary.Append("boundary", 8u); |
| 178 MockClient* client = new MockClient; |
| 179 MultipartParser* parser = new MultipartParser(boundary, client); |
| 180 |
| 181 EXPECT_FALSE(parser->AppendData( |
| 182 bytes, strlen(bytes))); // Close delimiter before delimiter. |
| 183 EXPECT_EQ(0u, client->NumberOfParts()); |
| 184 } |
| 185 |
| 186 TEST(MultipartParserTest, NoStartNorEndBoundary) { |
| 187 constexpr char bytes[] = "content-type: application/xhtml+xml\r\n\r\n1"; |
| 188 |
| 189 Vector<char> boundary; |
| 190 boundary.Append("boundary", 8u); |
| 191 MockClient* client = new MockClient; |
| 192 MultipartParser* parser = new MultipartParser(boundary, client); |
| 193 |
| 194 EXPECT_TRUE(parser->AppendData(bytes, strlen(bytes))); // Valid preamble. |
| 195 EXPECT_FALSE(parser->Finish()); // No parts. |
| 196 EXPECT_EQ(0u, client->NumberOfParts()); |
| 197 } |
| 198 |
| 199 constexpr size_t kStarts[] = { |
| 200 0u, // Non-empty preamble in the beginning. |
| 201 8u, // Empty preamble in the beginning. |
| 202 9u, // Truncated delimiter in the beginning. |
| 203 10u, // No preamble in the beginning. |
| 204 11u // Truncated dash boundary in the beginning. |
| 205 }; |
| 206 |
| 207 TEST(MultipartParserTest, Preamble) { |
| 208 Vector<char> boundary; |
| 209 boundary.Append("boundary", 8u); |
| 210 for (const size_t start : kStarts) { |
| 211 MockClient* client = new MockClient; |
| 212 MultipartParser* parser = new MultipartParser(boundary, client); |
| 213 |
| 214 EXPECT_TRUE(parser->AppendData(kBytes + start, strlen(kBytes + start))); |
| 215 EXPECT_TRUE(parser->Finish()); |
| 216 switch (start) { |
| 217 case 9u: |
| 218 case 11u: |
| 219 EXPECT_EQ(3u, client->NumberOfParts()) << " start=" << start; |
| 220 EXPECT_EQ(1u, client->GetPart(0).header_fields.size()); |
| 221 EXPECT_EQ("application/xhtml+xml", client->GetPart(0).header_fields.Get( |
| 222 HTTPNames::Content_Type)); |
| 223 EXPECT_EQ("1", toString(client->GetPart(0).data)); |
| 224 EXPECT_TRUE(client->GetPart(0).data_fully_received); |
| 225 EXPECT_EQ(1u, client->GetPart(1).header_fields.size()); |
| 226 EXPECT_EQ("text/html", client->GetPart(1).header_fields.Get( |
| 227 HTTPNames::Content_Type)); |
| 228 EXPECT_EQ("2\r\n--\r\n--bound--\r\n--\r\n2\r\n", |
| 229 toString(client->GetPart(1).data)); |
| 230 EXPECT_TRUE(client->GetPart(1).data_fully_received); |
| 231 EXPECT_EQ(1u, client->GetPart(2).header_fields.size()); |
| 232 EXPECT_EQ( |
| 233 "text/plain; charset=iso-8859-1", |
| 234 client->GetPart(2).header_fields.Get(HTTPNames::Content_Type)); |
| 235 EXPECT_EQ("333", toString(client->GetPart(2).data)); |
| 236 EXPECT_TRUE(client->GetPart(2).data_fully_received); |
| 237 break; |
| 238 default: |
| 239 EXPECT_EQ(4u, client->NumberOfParts()) << " start=" << start; |
| 240 EXPECT_EQ(0u, client->GetPart(0).header_fields.size()); |
| 241 EXPECT_EQ(0u, client->GetPart(0).data.size()); |
| 242 EXPECT_TRUE(client->GetPart(0).data_fully_received); |
| 243 EXPECT_EQ(1u, client->GetPart(1).header_fields.size()); |
| 244 EXPECT_EQ("application/xhtml+xml", client->GetPart(1).header_fields.Get( |
| 245 HTTPNames::Content_Type)); |
| 246 EXPECT_EQ("1", toString(client->GetPart(1).data)); |
| 247 EXPECT_TRUE(client->GetPart(1).data_fully_received); |
| 248 EXPECT_EQ(1u, client->GetPart(2).header_fields.size()); |
| 249 EXPECT_EQ("text/html", client->GetPart(2).header_fields.Get( |
| 250 HTTPNames::Content_Type)); |
| 251 EXPECT_EQ("2\r\n--\r\n--bound--\r\n--\r\n2\r\n", |
| 252 toString(client->GetPart(2).data)); |
| 253 EXPECT_TRUE(client->GetPart(2).data_fully_received); |
| 254 EXPECT_EQ(1u, client->GetPart(3).header_fields.size()); |
| 255 EXPECT_EQ( |
| 256 "text/plain; charset=iso-8859-1", |
| 257 client->GetPart(3).header_fields.Get(HTTPNames::Content_Type)); |
| 258 EXPECT_EQ("333", toString(client->GetPart(3).data)); |
| 259 EXPECT_TRUE(client->GetPart(3).data_fully_received); |
| 260 break; |
| 261 } |
| 262 } |
| 263 } |
| 264 |
| 265 TEST(MultipartParserTest, PreambleWithMalformedBoundary) { |
| 266 Vector<char> boundary; |
| 267 boundary.Append("--boundary", 10u); |
| 268 for (const size_t start : kStarts) { |
| 269 MockClient* client = new MockClient; |
| 270 MultipartParser* parser = new MultipartParser(boundary, client); |
| 271 |
| 272 EXPECT_TRUE(parser->AppendData(kBytes + start, |
| 273 strlen(kBytes + start))); // Valid preamble. |
| 274 EXPECT_FALSE(parser->Finish()); // No parts. |
| 275 EXPECT_EQ(0u, client->NumberOfParts()); |
| 276 } |
| 277 } |
| 278 |
| 279 } // namespace |
| 280 |
| 281 } // namespace blink |
OLD | NEW |