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