1 // Copyright 2013 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 <math.h> 6 7 #include "base/base64.h" 8 #include "base/file_util.h" 9 #include "base/path_service.h" 10 #include "base/pickle.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/stringprintf.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "content/common/page_state_serialization.h" 15 #include "content/public/common/content_paths.h" 16 #include "testing/gtest/include/gtest/gtest.h" 17 18 namespace content { 19 namespace { 20 21 #if defined(OS_WIN) 22 inline bool isnan(double num) { return !!_isnan(num); } 23 #endif 24 25 base::NullableString16 NS16(const char* s) { 26 return s ? base::NullableString16(ASCIIToUTF16(s), false) : 27 base::NullableString16(); 28 } 29 30 //----------------------------------------------------------------------------- 31 32 template <typename T> 33 void ExpectEquality(const T& a, const T& b) { 34 EXPECT_EQ(a, b); 35 } 36 37 template <typename T> 38 void ExpectEquality(const std::vector<T>& a, const std::vector<T>& b) { 39 EXPECT_EQ(a.size(), b.size()); 40 for (size_t i = 0; i < std::min(a.size(), b.size()); ++i) 41 ExpectEquality(a[i], b[i]); 42 } 43 44 template <> 45 void ExpectEquality(const ExplodedHttpBodyElement& a, 46 const ExplodedHttpBodyElement& b) { 47 EXPECT_EQ(a.type, b.type); 48 EXPECT_EQ(a.data, b.data); 49 EXPECT_EQ(a.file_path, b.file_path); 50 EXPECT_EQ(a.url, b.url); 51 EXPECT_EQ(a.file_start, b.file_start); 52 EXPECT_EQ(a.file_length, b.file_length); 53 if (!(isnan(a.file_modification_time) && isnan(b.file_modification_time))) 54 EXPECT_DOUBLE_EQ(a.file_modification_time, b.file_modification_time); 55 } 56 57 template <> 58 void ExpectEquality(const ExplodedHttpBody& a, const ExplodedHttpBody& b) { 59 EXPECT_EQ(a.http_content_type, b.http_content_type); 60 EXPECT_EQ(a.identifier, b.identifier); 61 EXPECT_EQ(a.contains_passwords, b.contains_passwords); 62 EXPECT_EQ(a.is_null, b.is_null); 63 ExpectEquality(a.elements, b.elements); 64 } 65 66 template <> 67 void ExpectEquality(const ExplodedFrameState& a, const ExplodedFrameState& b) { 68 EXPECT_EQ(a.url_string, b.url_string); 69 EXPECT_EQ(a.original_url_string, b.original_url_string); 70 EXPECT_EQ(a.referrer, b.referrer); 71 EXPECT_EQ(a.target, b.target); 72 EXPECT_EQ(a.parent, b.parent); 73 EXPECT_EQ(a.title, b.title); 74 EXPECT_EQ(a.alternate_title, b.alternate_title); 75 EXPECT_EQ(a.state_object, b.state_object); 76 ExpectEquality(a.document_state, b.document_state); 77 EXPECT_EQ(a.scroll_offset, b.scroll_offset); 78 EXPECT_EQ(a.item_sequence_number, b.item_sequence_number); 79 EXPECT_EQ(a.document_sequence_number, b.document_sequence_number); 80 EXPECT_EQ(a.visit_count, b.visit_count); 81 EXPECT_EQ(a.visited_time, b.visited_time); 82 EXPECT_EQ(a.page_scale_factor, b.page_scale_factor); 83 EXPECT_EQ(a.is_target_item, b.is_target_item); 84 ExpectEquality(a.http_body, b.http_body); 85 ExpectEquality(a.children, b.children); 86 } 87 88 void ExpectEquality(const ExplodedPageState& a, const ExplodedPageState& b) { 89 ExpectEquality(a.referenced_files, b.referenced_files); 90 ExpectEquality(a.top, b.top); 91 } 92 93 //----------------------------------------------------------------------------- 94 95 class PageStateSerializationTest : public testing::Test { 96 public: 97 void PopulateFrameState(ExplodedFrameState* frame_state) { 98 // Invent some data for the various fields. 99 frame_state->url_string = NS16("http://dev.chromium.org/"); 100 frame_state->original_url_string = frame_state->url_string; 101 frame_state->referrer = NS16("https://www.google.com/search?q=dev.chromium.org"); 102 frame_state->target = NS16("foo"); 103 frame_state->parent = NS16("bar"); 104 frame_state->title = NS16("The Chromium Projects"); 105 frame_state->alternate_title = NS16(NULL); 106 frame_state->state_object = NS16(NULL); 107 frame_state->document_state.push_back(NS16("1")); 108 frame_state->document_state.push_back(NS16("q")); 109 frame_state->document_state.push_back(NS16("text")); 110 frame_state->document_state.push_back(NS16("dev.chromium.org")); 111 frame_state->scroll_offset = gfx::Point(0, 100); 112 frame_state->item_sequence_number = 1; 113 frame_state->document_sequence_number = 2; 114 frame_state->visit_count = 10; 115 frame_state->visited_time = 12345.0; 116 frame_state->page_scale_factor = 2.0; 117 frame_state->is_target_item = true; 118 } 119 120 void PopulateHttpBody(ExplodedHttpBody* http_body, 121 std::vector<base::NullableString16>* referenced_files) { 122 http_body->is_null = false; 123 http_body->identifier = 12345; 124 http_body->contains_passwords = false; 125 http_body->http_content_type = NS16("text/foo"); 126 127 ExplodedHttpBodyElement e1; 128 e1.type = WebKit::WebHTTPBody::Element::TypeData; 129 e1.data = "foo"; 130 http_body->elements.push_back(e1); 131 132 ExplodedHttpBodyElement e2; 133 e2.type = WebKit::WebHTTPBody::Element::TypeFile; 134 e2.file_path = NS16("file.txt"); 135 e2.file_start = 100; 136 e2.file_length = 1024; 137 e2.file_modification_time = 9999.0; 138 http_body->elements.push_back(e2); 139 140 referenced_files->push_back(e2.file_path); 141 } 142 143 void PopulateFrameStateForBackwardsCompatTest( 144 ExplodedFrameState* frame_state, 145 bool is_child) { 146 frame_state->url_string = NS16("http://chromium.org/"); 147 frame_state->original_url_string = frame_state->url_string; 148 frame_state->referrer = NS16("http://google.com/"); 149 if (!is_child) 150 frame_state->target = NS16("target"); 151 frame_state->parent = NS16("parent"); 152 frame_state->title = NS16("title"); 153 frame_state->alternate_title = NS16("alternateTitle"); 154 frame_state->scroll_offset = gfx::Point(42, -42); 155 frame_state->item_sequence_number = 123; 156 frame_state->document_sequence_number = 456; 157 frame_state->visit_count = 42*42; 158 frame_state->visited_time = 13.37; 159 frame_state->page_scale_factor = 2.0f; 160 frame_state->is_target_item = true; 161 162 frame_state->document_state.push_back( 163 NS16("\n\r?% WebKit serialized form state version 8 \n\r=&")); 164 frame_state->document_state.push_back(NS16("form key")); 165 frame_state->document_state.push_back(NS16("1")); 166 frame_state->document_state.push_back(NS16("foo")); 167 frame_state->document_state.push_back(NS16("file")); 168 frame_state->document_state.push_back(NS16("2")); 169 frame_state->document_state.push_back(NS16("file.txt")); 170 frame_state->document_state.push_back(NS16("displayName")); 171 172 if (!is_child) { 173 frame_state->http_body.http_content_type = NS16("foo/bar"); 174 frame_state->http_body.identifier = 789; 175 frame_state->http_body.is_null = false; 176 177 ExplodedHttpBodyElement e1; 178 e1.type = WebKit::WebHTTPBody::Element::TypeData; 179 e1.data = "first data block"; 180 frame_state->http_body.elements.push_back(e1); 181 182 ExplodedHttpBodyElement e2; 183 e2.type = WebKit::WebHTTPBody::Element::TypeFile; 184 e2.file_path = NS16("file.txt"); 185 frame_state->http_body.elements.push_back(e2); 186 187 ExplodedHttpBodyElement e3; 188 e3.type = WebKit::WebHTTPBody::Element::TypeData; 189 e3.data = "data the second"; 190 frame_state->http_body.elements.push_back(e3); 191 192 ExplodedFrameState child_state; 193 PopulateFrameStateForBackwardsCompatTest(&child_state, true); 194 frame_state->children.push_back(child_state); 195 } 196 } 197 198 void PopulatePageStateForBackwardsCompatTest(ExplodedPageState* page_state) { 199 page_state->referenced_files.push_back(NS16("file.txt")); 200 PopulateFrameStateForBackwardsCompatTest(&page_state->top, false); 201 } 202 203 void TestBackwardsCompat(int version) { 204 const char* suffix = ""; 205 206 #if defined(OS_ANDROID) 207 // Unfortunately, the format of version 11 is different on Android, so we 208 // need to use a special reference file. 209 if (version == 11) 210 suffix = "_android"; 211 #endif 212 213 base::FilePath path; 214 PathService::Get(content::DIR_TEST_DATA, &path); 215 path = path.AppendASCII("page_state").AppendASCII( 216 base::StringPrintf("serialized_v%d%s.dat", version, suffix)); 217 218 std::string file_contents; 219 if (!file_util::ReadFileToString(path, &file_contents)) { 220 ADD_FAILURE() << "File not found: " << path.value(); 221 return; 222 } 223 224 std::string trimmed_contents; 225 EXPECT_TRUE(RemoveChars(file_contents, "\r\n", &trimmed_contents)); 226 227 std::string encoded; 228 EXPECT_TRUE(base::Base64Decode(trimmed_contents, &encoded)); 229 230 ExplodedPageState output; 231 #if defined(OS_ANDROID) 232 // Because version 11 of the file format unfortunately bakes in the device 233 // scale factor on Android, perform this test by assuming a preset device 234 // scale factor, ignoring the device scale factor of the current device. 235 const float kPresetDeviceScaleFactor = 2.0f; 236 EXPECT_TRUE(DecodePageStateWithDeviceScaleFactorForTesting( 237 encoded, 238 kPresetDeviceScaleFactor, 239 &output)); 240 #else 241 EXPECT_TRUE(DecodePageState(encoded, &output)); 242 #endif 243 244 ExplodedPageState expected; 245 PopulatePageStateForBackwardsCompatTest(&expected); 246 247 ExpectEquality(expected, output); 248 } 249 }; 250 251 TEST_F(PageStateSerializationTest, BasicEmpty) { 252 ExplodedPageState input; 253 254 std::string encoded; 255 EXPECT_TRUE(EncodePageState(input, &encoded)); 256 257 ExplodedPageState output; 258 EXPECT_TRUE(DecodePageState(encoded, &output)); 259 260 ExpectEquality(input, output); 261 } 262 263 TEST_F(PageStateSerializationTest, BasicFrame) { 264 ExplodedPageState input; 265 PopulateFrameState(&input.top); 266 267 std::string encoded; 268 EXPECT_TRUE(EncodePageState(input, &encoded)); 269 270 ExplodedPageState output; 271 EXPECT_TRUE(DecodePageState(encoded, &output)); 272 273 ExpectEquality(input, output); 274 } 275 276 TEST_F(PageStateSerializationTest, BasicFramePOST) { 277 ExplodedPageState input; 278 PopulateFrameState(&input.top); 279 PopulateHttpBody(&input.top.http_body, &input.referenced_files); 280 281 std::string encoded; 282 EXPECT_TRUE(EncodePageState(input, &encoded)); 283 284 ExplodedPageState output; 285 EXPECT_TRUE(DecodePageState(encoded, &output)); 286 287 ExpectEquality(input, output); 288 } 289 290 TEST_F(PageStateSerializationTest, BasicFrameSet) { 291 ExplodedPageState input; 292 PopulateFrameState(&input.top); 293 294 // Add some child frames. 295 for (int i = 0; i < 4; ++i) { 296 ExplodedFrameState child_state; 297 PopulateFrameState(&child_state); 298 input.top.children.push_back(child_state); 299 } 300 301 std::string encoded; 302 EXPECT_TRUE(EncodePageState(input, &encoded)); 303 304 ExplodedPageState output; 305 EXPECT_TRUE(DecodePageState(encoded, &output)); 306 307 ExpectEquality(input, output); 308 } 309 310 TEST_F(PageStateSerializationTest, BasicFrameSetPOST) { 311 ExplodedPageState input; 312 PopulateFrameState(&input.top); 313 314 // Add some child frames. 315 for (int i = 0; i < 4; ++i) { 316 ExplodedFrameState child_state; 317 PopulateFrameState(&child_state); 318 319 // Simulate a form POST on a subframe. 320 if (i == 2) 321 PopulateHttpBody(&child_state.http_body, &input.referenced_files); 322 323 input.top.children.push_back(child_state); 324 } 325 326 std::string encoded; 327 EncodePageState(input, &encoded); 328 329 ExplodedPageState output; 330 DecodePageState(encoded, &output); 331 332 ExpectEquality(input, output); 333 } 334 335 TEST_F(PageStateSerializationTest, BadMessagesTest1) { 336 Pickle p; 337 // Version 14 338 p.WriteInt(14); 339 // Empty strings. 340 for (int i = 0; i < 6; ++i) 341 p.WriteInt(-1); 342 // Bad real number. 343 p.WriteInt(-1); 344 345 std::string s(static_cast<const char*>(p.data()), p.size()); 346 347 ExplodedPageState output; 348 EXPECT_FALSE(DecodePageState(s, &output)); 349 } 350 351 TEST_F(PageStateSerializationTest, BadMessagesTest2) { 352 double d = 0; 353 Pickle p; 354 // Version 14 355 p.WriteInt(14); 356 // Empty strings. 357 for (int i = 0; i < 6; ++i) 358 p.WriteInt(-1); 359 // More misc fields. 360 p.WriteData(reinterpret_cast<const char*>(&d), sizeof(d)); 361 p.WriteInt(1); 362 p.WriteInt(1); 363 p.WriteInt(0); 364 p.WriteInt(0); 365 p.WriteInt(-1); 366 p.WriteInt(0); 367 // WebForm 368 p.WriteInt(1); 369 p.WriteInt(WebKit::WebHTTPBody::Element::TypeData); 370 371 std::string s(static_cast<const char*>(p.data()), p.size()); 372 373 ExplodedPageState output; 374 EXPECT_FALSE(DecodePageState(s, &output)); 375 } 376 377 TEST_F(PageStateSerializationTest, DumpExpectedPageStateForBackwardsCompat) { 378 // Comment out this return statement to enable this code. Use this code to 379 // generate data, based on the current serialization format, for the 380 // BackwardsCompat_vXX tests. 381 return; 382 383 ExplodedPageState state; 384 PopulatePageStateForBackwardsCompatTest(&state); 385 386 std::string encoded; 387 EXPECT_TRUE(EncodePageState(state, &encoded)); 388 389 std::string base64; 390 EXPECT_TRUE(base::Base64Encode(encoded, &base64)); 391 392 base::FilePath path; 393 PathService::Get(base::DIR_TEMP, &path); 394 path = path.AppendASCII("expected.dat"); 395 396 FILE* fp = file_util::OpenFile(path, "wb"); 397 ASSERT_TRUE(fp); 398 399 const size_t kRowSize = 76; 400 for (size_t offset = 0; offset < base64.size(); offset += kRowSize) { 401 size_t length = std::min(base64.size() - offset, kRowSize); 402 std::string segment(&base64[offset], length); 403 segment.push_back('\n'); 404 fwrite(segment.data(), segment.size(), 1, fp); 405 } 406 407 fclose(fp); 408 } 409 410 #if !defined(OS_ANDROID) 411 // TODO(darin): Re-enable for Android once this test accounts for systems with 412 // a device scale factor not equal to 2. 413 TEST_F(PageStateSerializationTest, BackwardsCompat_v11) { 414 TestBackwardsCompat(11); 415 } 416 #endif 417 418 TEST_F(PageStateSerializationTest, BackwardsCompat_v12) { 419 TestBackwardsCompat(12); 420 } 421 422 TEST_F(PageStateSerializationTest, BackwardsCompat_v13) { 423 TestBackwardsCompat(13); 424 } 425 426 TEST_F(PageStateSerializationTest, BackwardsCompat_v14) { 427 TestBackwardsCompat(14); 428 } 429 430 } // namespace 431 } // namespace content 432