1 // Copyright (c) 2012 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 "chrome/browser/ui/omnibox/alternate_nav_url_fetcher.h" 6 7 #include "chrome/browser/chrome_notification_types.h" 8 #include "chrome/browser/infobars/infobar_service.h" 9 #include "chrome/browser/intranet_redirect_detector.h" 10 #include "chrome/browser/profiles/profile.h" 11 #include "chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.h" 12 #include "content/public/browser/navigation_controller.h" 13 #include "content/public/browser/notification_service.h" 14 #include "content/public/browser/render_process_host.h" 15 #include "content/public/browser/render_view_host.h" 16 #include "content/public/browser/web_contents.h" 17 #include "net/base/load_flags.h" 18 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" 19 #include "net/url_request/url_fetcher.h" 20 #include "net/url_request/url_request.h" 21 22 using content::NavigationController; 23 24 AlternateNavURLFetcher::AlternateNavURLFetcher( 25 const GURL& alternate_nav_url) 26 : alternate_nav_url_(alternate_nav_url), 27 controller_(NULL), 28 state_(NOT_STARTED), 29 navigated_to_entry_(false) { 30 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING, 31 content::NotificationService::AllSources()); 32 } 33 34 AlternateNavURLFetcher::~AlternateNavURLFetcher() { 35 } 36 37 void AlternateNavURLFetcher::Observe( 38 int type, 39 const content::NotificationSource& source, 40 const content::NotificationDetails& details) { 41 switch (type) { 42 case content::NOTIFICATION_NAV_ENTRY_PENDING: { 43 // If we've already received a notification for the same controller, we 44 // should delete ourselves as that indicates that the page is being 45 // re-loaded so this instance is now stale. 46 NavigationController* controller = 47 content::Source<NavigationController>(source).ptr(); 48 if (controller_ == controller) { 49 delete this; 50 } else if (!controller_) { 51 // Start listening for the commit notification. 52 DCHECK(controller->GetPendingEntry()); 53 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 54 content::Source<NavigationController>( 55 controller)); 56 StartFetch(controller); 57 } 58 break; 59 } 60 61 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: 62 // The page was navigated, we can show the infobar now if necessary. 63 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 64 content::Source<NavigationController>(controller_)); 65 navigated_to_entry_ = true; 66 ShowInfobarIfPossible(); 67 // WARNING: |this| may be deleted! 68 break; 69 70 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: 71 // We have been closed. In order to prevent the URLFetcher from trying to 72 // access the controller that will be invalid, we delete ourselves. 73 // This deletes the URLFetcher and insures its callback won't be called. 74 delete this; 75 break; 76 77 default: 78 NOTREACHED(); 79 } 80 } 81 82 void AlternateNavURLFetcher::OnURLFetchComplete( 83 const net::URLFetcher* source) { 84 DCHECK_EQ(fetcher_.get(), source); 85 SetStatusFromURLFetch( 86 source->GetURL(), source->GetStatus(), source->GetResponseCode()); 87 ShowInfobarIfPossible(); 88 // WARNING: |this| may be deleted! 89 } 90 91 void AlternateNavURLFetcher::StartFetch(NavigationController* controller) { 92 controller_ = controller; 93 content::WebContents* web_contents = controller_->GetWebContents(); 94 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 95 content::Source<content::WebContents>(web_contents)); 96 97 DCHECK_EQ(NOT_STARTED, state_); 98 state_ = IN_PROGRESS; 99 fetcher_.reset(net::URLFetcher::Create(GURL(alternate_nav_url_), 100 net::URLFetcher::HEAD, this)); 101 fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES); 102 fetcher_->SetRequestContext( 103 controller_->GetBrowserContext()->GetRequestContext()); 104 fetcher_->SetStopOnRedirect(true); 105 fetcher_->Start(); 106 } 107 108 void AlternateNavURLFetcher::SetStatusFromURLFetch( 109 const GURL& url, 110 const net::URLRequestStatus& status, 111 int response_code) { 112 if (status.is_success()) { 113 // HTTP 2xx, 401, and 407 all indicate that the target address exists. 114 state_ = (((response_code / 100) == 2) || (response_code == 401) || 115 (response_code == 407)) ? SUCCEEDED : FAILED; 116 } else { 117 // If we got HTTP 3xx, we'll have auto-canceled; treat this as evidence the 118 // target address exists as long as we're not redirected to a common 119 // location every time, lest we annoy the user with infobars on everything 120 // they type. See comments on IntranetRedirectDetector. 121 if (status.status() == net::URLRequestStatus::CANCELED && 122 (response_code / 100) == 3) { 123 const bool same_domain_or_host = 124 net::registry_controlled_domains::SameDomainOrHost( 125 url, 126 IntranetRedirectDetector::RedirectOrigin(), 127 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); 128 state_ = same_domain_or_host ? FAILED : SUCCEEDED; 129 } else { 130 state_ = FAILED; 131 } 132 } 133 } 134 135 void AlternateNavURLFetcher::ShowInfobarIfPossible() { 136 if (navigated_to_entry_ && (state_ == SUCCEEDED)) { 137 AlternateNavInfoBarDelegate::Create( 138 InfoBarService::FromWebContents(controller_->GetWebContents()), 139 alternate_nav_url_); 140 } else if (state_ != FAILED) { 141 return; 142 } 143 delete this; 144 } 145