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 "components/sessions/serialized_navigation_entry.h"
6
7 #include "base/pickle.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "content/public/browser/favicon_status.h"
10 #include "content/public/browser/navigation_controller.h"
11 #include "content/public/browser/navigation_entry.h"
12 #include "sync/protocol/session_specifics.pb.h"
13 #include "sync/util/time.h"
14 #include "third_party/WebKit/public/platform/WebReferrerPolicy.h"
15
16 using content::NavigationEntry;
17
18 namespace sessions {
19
20 const char kSearchTermsKey[] = "search_terms";
21
SerializedNavigationEntry()22 SerializedNavigationEntry::SerializedNavigationEntry()
23 : index_(-1),
24 unique_id_(0),
25 transition_type_(ui::PAGE_TRANSITION_TYPED),
26 has_post_data_(false),
27 post_id_(-1),
28 is_overriding_user_agent_(false),
29 http_status_code_(0),
30 is_restored_(false),
31 blocked_state_(STATE_INVALID) {}
32
~SerializedNavigationEntry()33 SerializedNavigationEntry::~SerializedNavigationEntry() {}
34
35 // static
FromNavigationEntry(int index,const NavigationEntry & entry)36 SerializedNavigationEntry SerializedNavigationEntry::FromNavigationEntry(
37 int index,
38 const NavigationEntry& entry) {
39 SerializedNavigationEntry navigation;
40 navigation.index_ = index;
41 navigation.unique_id_ = entry.GetUniqueID();
42 navigation.referrer_ = entry.GetReferrer();
43 navigation.virtual_url_ = entry.GetVirtualURL();
44 navigation.title_ = entry.GetTitle();
45 navigation.page_state_ = entry.GetPageState();
46 navigation.transition_type_ = entry.GetTransitionType();
47 navigation.has_post_data_ = entry.GetHasPostData();
48 navigation.post_id_ = entry.GetPostID();
49 navigation.original_request_url_ = entry.GetOriginalRequestURL();
50 navigation.is_overriding_user_agent_ = entry.GetIsOverridingUserAgent();
51 navigation.timestamp_ = entry.GetTimestamp();
52 navigation.is_restored_ = entry.IsRestored();
53 // If you want to navigate a named frame in Chrome, you will first need to
54 // add support for persisting it. It is currently only used for layout tests.
55 CHECK(entry.GetFrameToNavigate().empty());
56 entry.GetExtraData(kSearchTermsKey, &navigation.search_terms_);
57 if (entry.GetFavicon().valid)
58 navigation.favicon_url_ = entry.GetFavicon().url;
59 navigation.http_status_code_ = entry.GetHttpStatusCode();
60 navigation.redirect_chain_ = entry.GetRedirectChain();
61
62 return navigation;
63 }
64
FromSyncData(int index,const sync_pb::TabNavigation & sync_data)65 SerializedNavigationEntry SerializedNavigationEntry::FromSyncData(
66 int index,
67 const sync_pb::TabNavigation& sync_data) {
68 SerializedNavigationEntry navigation;
69 navigation.index_ = index;
70 navigation.unique_id_ = sync_data.unique_id();
71 navigation.referrer_ = content::Referrer(
72 GURL(sync_data.referrer()),
73 static_cast<blink::WebReferrerPolicy>(sync_data.referrer_policy()));
74 navigation.virtual_url_ = GURL(sync_data.virtual_url());
75 navigation.title_ = base::UTF8ToUTF16(sync_data.title());
76 navigation.page_state_ =
77 content::PageState::CreateFromEncodedData(sync_data.state());
78
79 uint32 transition = 0;
80 if (sync_data.has_page_transition()) {
81 switch (sync_data.page_transition()) {
82 case sync_pb::SyncEnums_PageTransition_LINK:
83 transition = ui::PAGE_TRANSITION_LINK;
84 break;
85 case sync_pb::SyncEnums_PageTransition_TYPED:
86 transition = ui::PAGE_TRANSITION_TYPED;
87 break;
88 case sync_pb::SyncEnums_PageTransition_AUTO_BOOKMARK:
89 transition = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
90 break;
91 case sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME:
92 transition = ui::PAGE_TRANSITION_AUTO_SUBFRAME;
93 break;
94 case sync_pb::SyncEnums_PageTransition_MANUAL_SUBFRAME:
95 transition = ui::PAGE_TRANSITION_MANUAL_SUBFRAME;
96 break;
97 case sync_pb::SyncEnums_PageTransition_GENERATED:
98 transition = ui::PAGE_TRANSITION_GENERATED;
99 break;
100 case sync_pb::SyncEnums_PageTransition_AUTO_TOPLEVEL:
101 transition = ui::PAGE_TRANSITION_AUTO_TOPLEVEL;
102 break;
103 case sync_pb::SyncEnums_PageTransition_FORM_SUBMIT:
104 transition = ui::PAGE_TRANSITION_FORM_SUBMIT;
105 break;
106 case sync_pb::SyncEnums_PageTransition_RELOAD:
107 transition = ui::PAGE_TRANSITION_RELOAD;
108 break;
109 case sync_pb::SyncEnums_PageTransition_KEYWORD:
110 transition = ui::PAGE_TRANSITION_KEYWORD;
111 break;
112 case sync_pb::SyncEnums_PageTransition_KEYWORD_GENERATED:
113 transition = ui::PAGE_TRANSITION_KEYWORD_GENERATED;
114 break;
115 default:
116 transition = ui::PAGE_TRANSITION_LINK;
117 break;
118 }
119 }
120
121 if (sync_data.has_redirect_type()) {
122 switch (sync_data.redirect_type()) {
123 case sync_pb::SyncEnums_PageTransitionRedirectType_CLIENT_REDIRECT:
124 transition |= ui::PAGE_TRANSITION_CLIENT_REDIRECT;
125 break;
126 case sync_pb::SyncEnums_PageTransitionRedirectType_SERVER_REDIRECT:
127 transition |= ui::PAGE_TRANSITION_SERVER_REDIRECT;
128 break;
129 }
130 }
131 if (sync_data.navigation_forward_back())
132 transition |= ui::PAGE_TRANSITION_FORWARD_BACK;
133 if (sync_data.navigation_from_address_bar())
134 transition |= ui::PAGE_TRANSITION_FROM_ADDRESS_BAR;
135 if (sync_data.navigation_home_page())
136 transition |= ui::PAGE_TRANSITION_HOME_PAGE;
137 if (sync_data.navigation_chain_start())
138 transition |= ui::PAGE_TRANSITION_CHAIN_START;
139 if (sync_data.navigation_chain_end())
140 transition |= ui::PAGE_TRANSITION_CHAIN_END;
141
142 navigation.transition_type_ = static_cast<ui::PageTransition>(transition);
143
144 navigation.timestamp_ = base::Time();
145 navigation.search_terms_ = base::UTF8ToUTF16(sync_data.search_terms());
146 if (sync_data.has_favicon_url())
147 navigation.favicon_url_ = GURL(sync_data.favicon_url());
148
149 navigation.http_status_code_ = sync_data.http_status_code();
150
151 navigation.Sanitize();
152
153 navigation.is_restored_ = true;
154
155 return navigation;
156 }
157
158 namespace {
159
160 // Helper used by SerializedNavigationEntry::WriteToPickle(). It writes |str| to
161 // |pickle|, if and only if |str| fits within (|max_bytes| -
162 // |*bytes_written|). |bytes_written| is incremented to reflect the
163 // data written.
164 //
165 // TODO(akalin): Unify this with the same function in
166 // base_session_service.cc.
WriteStringToPickle(Pickle * pickle,int * bytes_written,int max_bytes,const std::string & str)167 void WriteStringToPickle(Pickle* pickle,
168 int* bytes_written,
169 int max_bytes,
170 const std::string& str) {
171 int num_bytes = str.size() * sizeof(char);
172 if (*bytes_written + num_bytes < max_bytes) {
173 *bytes_written += num_bytes;
174 pickle->WriteString(str);
175 } else {
176 pickle->WriteString(std::string());
177 }
178 }
179
180 // base::string16 version of WriteStringToPickle.
181 //
182 // TODO(akalin): Unify this, too.
WriteString16ToPickle(Pickle * pickle,int * bytes_written,int max_bytes,const base::string16 & str)183 void WriteString16ToPickle(Pickle* pickle,
184 int* bytes_written,
185 int max_bytes,
186 const base::string16& str) {
187 int num_bytes = str.size() * sizeof(base::char16);
188 if (*bytes_written + num_bytes < max_bytes) {
189 *bytes_written += num_bytes;
190 pickle->WriteString16(str);
191 } else {
192 pickle->WriteString16(base::string16());
193 }
194 }
195
196 // A mask used for arbitrary boolean values needed to represent a
197 // NavigationEntry. Currently only contains HAS_POST_DATA.
198 //
199 // NOTE(akalin): We may want to just serialize |has_post_data_|
200 // directly. Other bools (|is_overriding_user_agent_|) haven't been
201 // added to this mask.
202 enum TypeMask {
203 HAS_POST_DATA = 1
204 };
205
206 } // namespace
207
208 // Pickle order:
209 //
210 // index_
211 // virtual_url_
212 // title_
213 // page_state_
214 // transition_type_
215 //
216 // Added on later:
217 //
218 // type_mask (has_post_data_)
219 // referrer_
220 // original_request_url_
221 // is_overriding_user_agent_
222 // timestamp_
223 // search_terms_
224 // http_status_code_
225
WriteToPickle(int max_size,Pickle * pickle) const226 void SerializedNavigationEntry::WriteToPickle(int max_size,
227 Pickle* pickle) const {
228 pickle->WriteInt(index_);
229
230 int bytes_written = 0;
231
232 WriteStringToPickle(pickle, &bytes_written, max_size,
233 virtual_url_.spec());
234
235 WriteString16ToPickle(pickle, &bytes_written, max_size, title_);
236
237 content::PageState page_state = page_state_;
238 if (has_post_data_)
239 page_state = page_state.RemovePasswordData();
240
241 WriteStringToPickle(pickle, &bytes_written, max_size,
242 page_state.ToEncodedData());
243
244 pickle->WriteInt(transition_type_);
245
246 const int type_mask = has_post_data_ ? HAS_POST_DATA : 0;
247 pickle->WriteInt(type_mask);
248
249 WriteStringToPickle(
250 pickle, &bytes_written, max_size,
251 referrer_.url.is_valid() ? referrer_.url.spec() : std::string());
252
253 pickle->WriteInt(referrer_.policy);
254
255 // Save info required to override the user agent.
256 WriteStringToPickle(
257 pickle, &bytes_written, max_size,
258 original_request_url_.is_valid() ?
259 original_request_url_.spec() : std::string());
260 pickle->WriteBool(is_overriding_user_agent_);
261 pickle->WriteInt64(timestamp_.ToInternalValue());
262
263 WriteString16ToPickle(pickle, &bytes_written, max_size, search_terms_);
264
265 pickle->WriteInt(http_status_code_);
266 }
267
ReadFromPickle(PickleIterator * iterator)268 bool SerializedNavigationEntry::ReadFromPickle(PickleIterator* iterator) {
269 *this = SerializedNavigationEntry();
270 std::string virtual_url_spec, page_state_data;
271 int transition_type_int = 0;
272 if (!iterator->ReadInt(&index_) ||
273 !iterator->ReadString(&virtual_url_spec) ||
274 !iterator->ReadString16(&title_) ||
275 !iterator->ReadString(&page_state_data) ||
276 !iterator->ReadInt(&transition_type_int))
277 return false;
278 virtual_url_ = GURL(virtual_url_spec);
279 page_state_ = content::PageState::CreateFromEncodedData(page_state_data);
280 transition_type_ = ui::PageTransitionFromInt(transition_type_int);
281
282 // type_mask did not always exist in the written stream. As such, we
283 // don't fail if it can't be read.
284 int type_mask = 0;
285 bool has_type_mask = iterator->ReadInt(&type_mask);
286
287 if (has_type_mask) {
288 has_post_data_ = type_mask & HAS_POST_DATA;
289 // the "referrer" property was added after type_mask to the written
290 // stream. As such, we don't fail if it can't be read.
291 std::string referrer_spec;
292 if (!iterator->ReadString(&referrer_spec))
293 referrer_spec = std::string();
294 // The "referrer policy" property was added even later, so we fall back to
295 // the default policy if the property is not present.
296 int policy_int;
297 blink::WebReferrerPolicy policy;
298 if (iterator->ReadInt(&policy_int))
299 policy = static_cast<blink::WebReferrerPolicy>(policy_int);
300 else
301 policy = blink::WebReferrerPolicyDefault;
302 referrer_ = content::Referrer(GURL(referrer_spec), policy);
303
304 // If the original URL can't be found, leave it empty.
305 std::string original_request_url_spec;
306 if (!iterator->ReadString(&original_request_url_spec))
307 original_request_url_spec = std::string();
308 original_request_url_ = GURL(original_request_url_spec);
309
310 // Default to not overriding the user agent if we don't have info.
311 if (!iterator->ReadBool(&is_overriding_user_agent_))
312 is_overriding_user_agent_ = false;
313
314 int64 timestamp_internal_value = 0;
315 if (iterator->ReadInt64(×tamp_internal_value)) {
316 timestamp_ = base::Time::FromInternalValue(timestamp_internal_value);
317 } else {
318 timestamp_ = base::Time();
319 }
320
321 // If the search terms field can't be found, leave it empty.
322 if (!iterator->ReadString16(&search_terms_))
323 search_terms_.clear();
324
325 if (!iterator->ReadInt(&http_status_code_))
326 http_status_code_ = 0;
327 }
328
329 Sanitize();
330
331 is_restored_ = true;
332
333 return true;
334 }
335
ToNavigationEntry(int page_id,content::BrowserContext * browser_context) const336 scoped_ptr<NavigationEntry> SerializedNavigationEntry::ToNavigationEntry(
337 int page_id,
338 content::BrowserContext* browser_context) const {
339 scoped_ptr<NavigationEntry> entry(
340 content::NavigationController::CreateNavigationEntry(
341 virtual_url_,
342 referrer_,
343 // Use a transition type of reload so that we don't incorrectly
344 // increase the typed count.
345 ui::PAGE_TRANSITION_RELOAD,
346 false,
347 // The extra headers are not sync'ed across sessions.
348 std::string(),
349 browser_context));
350
351 entry->SetTitle(title_);
352 entry->SetPageState(page_state_);
353 entry->SetPageID(page_id);
354 entry->SetHasPostData(has_post_data_);
355 entry->SetPostID(post_id_);
356 entry->SetOriginalRequestURL(original_request_url_);
357 entry->SetIsOverridingUserAgent(is_overriding_user_agent_);
358 entry->SetTimestamp(timestamp_);
359 entry->SetExtraData(kSearchTermsKey, search_terms_);
360 entry->SetHttpStatusCode(http_status_code_);
361 entry->SetRedirectChain(redirect_chain_);
362
363 // These fields should have default values.
364 DCHECK_EQ(STATE_INVALID, blocked_state_);
365 DCHECK_EQ(0u, content_pack_categories_.size());
366
367 return entry.Pass();
368 }
369
370 // TODO(zea): perhaps sync state (scroll position, form entries, etc.) as well?
371 // See http://crbug.com/67068.
ToSyncData() const372 sync_pb::TabNavigation SerializedNavigationEntry::ToSyncData() const {
373 sync_pb::TabNavigation sync_data;
374 sync_data.set_virtual_url(virtual_url_.spec());
375 sync_data.set_referrer(referrer_.url.spec());
376 sync_data.set_referrer_policy(referrer_.policy);
377 sync_data.set_title(base::UTF16ToUTF8(title_));
378
379 // Page transition core.
380 COMPILE_ASSERT(ui::PAGE_TRANSITION_LAST_CORE ==
381 ui::PAGE_TRANSITION_KEYWORD_GENERATED,
382 PageTransitionCoreBounds);
383 switch (ui::PageTransitionStripQualifier(transition_type_)) {
384 case ui::PAGE_TRANSITION_LINK:
385 sync_data.set_page_transition(
386 sync_pb::SyncEnums_PageTransition_LINK);
387 break;
388 case ui::PAGE_TRANSITION_TYPED:
389 sync_data.set_page_transition(
390 sync_pb::SyncEnums_PageTransition_TYPED);
391 break;
392 case ui::PAGE_TRANSITION_AUTO_BOOKMARK:
393 sync_data.set_page_transition(
394 sync_pb::SyncEnums_PageTransition_AUTO_BOOKMARK);
395 break;
396 case ui::PAGE_TRANSITION_AUTO_SUBFRAME:
397 sync_data.set_page_transition(
398 sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME);
399 break;
400 case ui::PAGE_TRANSITION_MANUAL_SUBFRAME:
401 sync_data.set_page_transition(
402 sync_pb::SyncEnums_PageTransition_MANUAL_SUBFRAME);
403 break;
404 case ui::PAGE_TRANSITION_GENERATED:
405 sync_data.set_page_transition(
406 sync_pb::SyncEnums_PageTransition_GENERATED);
407 break;
408 case ui::PAGE_TRANSITION_AUTO_TOPLEVEL:
409 sync_data.set_page_transition(
410 sync_pb::SyncEnums_PageTransition_AUTO_TOPLEVEL);
411 break;
412 case ui::PAGE_TRANSITION_FORM_SUBMIT:
413 sync_data.set_page_transition(
414 sync_pb::SyncEnums_PageTransition_FORM_SUBMIT);
415 break;
416 case ui::PAGE_TRANSITION_RELOAD:
417 sync_data.set_page_transition(
418 sync_pb::SyncEnums_PageTransition_RELOAD);
419 break;
420 case ui::PAGE_TRANSITION_KEYWORD:
421 sync_data.set_page_transition(
422 sync_pb::SyncEnums_PageTransition_KEYWORD);
423 break;
424 case ui::PAGE_TRANSITION_KEYWORD_GENERATED:
425 sync_data.set_page_transition(
426 sync_pb::SyncEnums_PageTransition_KEYWORD_GENERATED);
427 break;
428 default:
429 NOTREACHED();
430 }
431
432 // Page transition qualifiers.
433 if (ui::PageTransitionIsRedirect(transition_type_)) {
434 if (transition_type_ & ui::PAGE_TRANSITION_CLIENT_REDIRECT) {
435 sync_data.set_redirect_type(
436 sync_pb::SyncEnums_PageTransitionRedirectType_CLIENT_REDIRECT);
437 } else if (transition_type_ & ui::PAGE_TRANSITION_SERVER_REDIRECT) {
438 sync_data.set_redirect_type(
439 sync_pb::SyncEnums_PageTransitionRedirectType_SERVER_REDIRECT);
440 }
441 }
442 sync_data.set_navigation_forward_back(
443 (transition_type_ & ui::PAGE_TRANSITION_FORWARD_BACK) != 0);
444 sync_data.set_navigation_from_address_bar(
445 (transition_type_ & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) != 0);
446 sync_data.set_navigation_home_page(
447 (transition_type_ & ui::PAGE_TRANSITION_HOME_PAGE) != 0);
448 sync_data.set_navigation_chain_start(
449 (transition_type_ & ui::PAGE_TRANSITION_CHAIN_START) != 0);
450 sync_data.set_navigation_chain_end(
451 (transition_type_ & ui::PAGE_TRANSITION_CHAIN_END) != 0);
452
453 sync_data.set_unique_id(unique_id_);
454 sync_data.set_timestamp_msec(syncer::TimeToProtoTime(timestamp_));
455 // The full-resolution timestamp works as a global ID.
456 sync_data.set_global_id(timestamp_.ToInternalValue());
457
458 sync_data.set_search_terms(base::UTF16ToUTF8(search_terms_));
459
460 sync_data.set_http_status_code(http_status_code_);
461
462 if (favicon_url_.is_valid())
463 sync_data.set_favicon_url(favicon_url_.spec());
464
465 if (blocked_state_ != STATE_INVALID) {
466 sync_data.set_blocked_state(
467 static_cast<sync_pb::TabNavigation_BlockedState>(blocked_state_));
468 }
469
470 for (std::set<std::string>::const_iterator it =
471 content_pack_categories_.begin();
472 it != content_pack_categories_.end(); ++it) {
473 sync_data.add_content_pack_categories(*it);
474 }
475
476 // Copy all redirect chain entries except the last URL (which should match
477 // the virtual_url).
478 if (redirect_chain_.size() > 1) { // Single entry chains have no redirection.
479 size_t last_entry = redirect_chain_.size() - 1;
480 for (size_t i = 0; i < last_entry; i++) {
481 sync_pb::NavigationRedirect* navigation_redirect =
482 sync_data.add_navigation_redirect();
483 navigation_redirect->set_url(redirect_chain_[i].spec());
484 }
485 // If the last URL didn't match the virtual_url, record it separately.
486 if (sync_data.virtual_url() != redirect_chain_[last_entry].spec()) {
487 sync_data.set_last_navigation_redirect_url(
488 redirect_chain_[last_entry].spec());
489 }
490 }
491
492 sync_data.set_is_restored(is_restored_);
493
494 return sync_data;
495 }
496
497 // static
ToNavigationEntries(const std::vector<SerializedNavigationEntry> & navigations,content::BrowserContext * browser_context)498 std::vector<NavigationEntry*> SerializedNavigationEntry::ToNavigationEntries(
499 const std::vector<SerializedNavigationEntry>& navigations,
500 content::BrowserContext* browser_context) {
501 int page_id = 0;
502 std::vector<NavigationEntry*> entries;
503 for (std::vector<SerializedNavigationEntry>::const_iterator
504 it = navigations.begin(); it != navigations.end(); ++it) {
505 entries.push_back(
506 it->ToNavigationEntry(page_id, browser_context).release());
507 ++page_id;
508 }
509 return entries;
510 }
511
Sanitize()512 void SerializedNavigationEntry::Sanitize() {
513 content::Referrer new_referrer =
514 content::Referrer::SanitizeForRequest(virtual_url_, referrer_);
515
516 // No need to compare the policy, as it doesn't change during
517 // sanitization. If there has been a change, the referrer needs to be
518 // stripped from the page state as well.
519 if (referrer_.url != new_referrer.url) {
520 referrer_ = content::Referrer();
521 page_state_ = page_state_.RemoveReferrer();
522 }
523 }
524
525 } // namespace sessions
526