Home | History | Annotate | Download | only in sessions
      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(&timestamp_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