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_(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 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 = 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. 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. 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 226 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 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 336 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. 372 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 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 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