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