// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/alternate_nav_url_fetcher.h" #include "app/l10n_util.h" #include "app/resource_bundle.h" #include "base/utf_string_conversions.h" #include "chrome/browser/intranet_redirect_detector.h" #include "chrome/browser/profile.h" #include "chrome/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/navigation_entry.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/notification_service.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "net/base/registry_controlled_domain.h" #include "net/url_request/url_request.h" AlternateNavURLFetcher::AlternateNavURLFetcher( const GURL& alternate_nav_url) : LinkInfoBarDelegate(NULL), alternate_nav_url_(alternate_nav_url), controller_(NULL), state_(NOT_STARTED), navigated_to_entry_(false), infobar_contents_(NULL) { registrar_.Add(this, NotificationType::NAV_ENTRY_PENDING, NotificationService::AllSources()); } void AlternateNavURLFetcher::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::NAV_ENTRY_PENDING: controller_ = Source(source).ptr(); DCHECK(controller_->pending_entry()); // Unregister for this notification now that we're pending, and start // listening for the corresponding commit. We also need to listen for the // tab close command since that means the load will never commit! registrar_.Remove(this, NotificationType::NAV_ENTRY_PENDING, NotificationService::AllSources()); registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, Source(controller_)); registrar_.Add(this, NotificationType::TAB_CLOSED, Source(controller_)); DCHECK_EQ(NOT_STARTED, state_); state_ = IN_PROGRESS; fetcher_.reset(new URLFetcher(GURL(alternate_nav_url_), URLFetcher::HEAD, this)); fetcher_->set_request_context( controller_->profile()->GetRequestContext()); fetcher_->Start(); break; case NotificationType::NAV_ENTRY_COMMITTED: // The page was navigated, we can show the infobar now if necessary. registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED, Source(controller_)); navigated_to_entry_ = true; ShowInfobarIfPossible(); break; case NotificationType::TAB_CLOSED: // We have been closed. In order to prevent the URLFetcher from trying to // access the controller that will be invalid, we delete ourselves. // This deletes the URLFetcher and insures its callback won't be called. delete this; break; default: NOTREACHED(); } } void AlternateNavURLFetcher::OnURLFetchComplete(const URLFetcher* source, const GURL& url, const URLRequestStatus& status, int response_code, const ResponseCookies& cookies, const std::string& data) { DCHECK(fetcher_.get() == source); SetStatusFromURLFetch(url, status, response_code); ShowInfobarIfPossible(); } std::wstring AlternateNavURLFetcher::GetMessageTextWithOffset( size_t* link_offset) const { const std::wstring label = l10n_util::GetStringF( IDS_ALTERNATE_NAV_URL_VIEW_LABEL, std::wstring(), link_offset); DCHECK(*link_offset != std::wstring::npos); return label; } std::wstring AlternateNavURLFetcher::GetLinkText() const { return UTF8ToWide(alternate_nav_url_.spec()); } SkBitmap* AlternateNavURLFetcher::GetIcon() const { return ResourceBundle::GetSharedInstance().GetBitmapNamed( IDR_INFOBAR_ALT_NAV_URL); } bool AlternateNavURLFetcher::LinkClicked(WindowOpenDisposition disposition) { infobar_contents_->OpenURL( alternate_nav_url_, GURL(), disposition, // Pretend the user typed this URL, so that navigating to // it will be the default action when it's typed again in // the future. PageTransition::TYPED); // We should always close, even if the navigation did not occur within this // TabContents. return true; } void AlternateNavURLFetcher::InfoBarClosed() { delete this; } void AlternateNavURLFetcher::SetStatusFromURLFetch( const GURL& url, const URLRequestStatus& status, int response_code) { if (!status.is_success() || // HTTP 2xx, 401, and 407 all indicate that the target address exists. (((response_code / 100) != 2) && (response_code != 401) && (response_code != 407)) || // Fail if we're redirected to a common location. This is the "automatic // heuristic" version of the explicit blacklist below; see comments there. net::RegistryControlledDomainService::SameDomainOrHost(url, IntranetRedirectDetector::RedirectOrigin())) { state_ = FAILED; return; } // The following TLD+1s are used as destinations by ISPs/DNS providers/etc. // who return provider-controlled pages to arbitrary user navigation attempts. // Because this can result in infobars on large fractions of user searches, we // don't show automatic infobars for these. Note that users can still choose // to explicitly navigate to or search for pages in these domains, and can // still get infobars for cases that wind up on other domains (e.g. legit // intranet sites), we're just trying to avoid erroneously harassing the user // with our own UI prompts. const char* kBlacklistedSites[] = { // NOTE: Use complete URLs, because GURL() doesn't do fixup! "http://comcast.com/", "http://opendns.com/", "http://verizon.net/", }; for (size_t i = 0; i < arraysize(kBlacklistedSites); ++i) { if (net::RegistryControlledDomainService::SameDomainOrHost(url, GURL(kBlacklistedSites[i]))) { state_ = FAILED; return; } } state_ = SUCCEEDED; } void AlternateNavURLFetcher::ShowInfobarIfPossible() { if (!navigated_to_entry_ || state_ != SUCCEEDED) { if (state_ == FAILED) delete this; return; } infobar_contents_ = controller_->tab_contents(); StoreActiveEntryUniqueID(infobar_contents_); // We will be deleted when the InfoBar is destroyed. (See InfoBarClosed). infobar_contents_->AddInfoBar(this); }