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/omnibox_navigation_observer.h"
6
7 #include "chrome/browser/history/shortcuts_backend.h"
8 #include "chrome/browser/history/shortcuts_backend_factory.h"
9 #include "chrome/browser/infobars/infobar_service.h"
10 #include "chrome/browser/intranet_redirect_detector.h"
11 #include "chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.h"
12 #include "content/public/browser/browser_context.h"
13 #include "content/public/browser/navigation_controller.h"
14 #include "content/public/browser/navigation_details.h"
15 #include "content/public/browser/navigation_entry.h"
16 #include "content/public/browser/notification_service.h"
17 #include "content/public/browser/notification_types.h"
18 #include "content/public/browser/web_contents.h"
19 #include "net/base/load_flags.h"
20 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
21 #include "net/url_request/url_fetcher.h"
22 #include "net/url_request/url_request.h"
23
24
25 // Helpers --------------------------------------------------------------------
26
27 namespace {
28
29 // HTTP 2xx, 401, and 407 all indicate that the target address exists.
ResponseCodeIndicatesSuccess(int response_code)30 bool ResponseCodeIndicatesSuccess(int response_code) {
31 return ((response_code / 100) == 2) || (response_code == 401) ||
32 (response_code == 407);
33 }
34
35 // Returns true if |final_url| doesn't represent an ISP hijack of
36 // |original_url|, based on the IntranetRedirectDetector's RedirectOrigin().
IsValidNavigation(const GURL & original_url,const GURL & final_url)37 bool IsValidNavigation(const GURL& original_url, const GURL& final_url) {
38 const GURL& redirect_url(IntranetRedirectDetector::RedirectOrigin());
39 return !redirect_url.is_valid() ||
40 net::registry_controlled_domains::SameDomainOrHost(
41 original_url, final_url,
42 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES) ||
43 !net::registry_controlled_domains::SameDomainOrHost(
44 final_url, redirect_url,
45 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
46 }
47
48 } // namespace
49
50
51 // OmniboxNavigationObserver --------------------------------------------------
52
OmniboxNavigationObserver(Profile * profile,const base::string16 & text,const AutocompleteMatch & match,const AutocompleteMatch & alternate_nav_match)53 OmniboxNavigationObserver::OmniboxNavigationObserver(
54 Profile* profile,
55 const base::string16& text,
56 const AutocompleteMatch& match,
57 const AutocompleteMatch& alternate_nav_match)
58 : text_(text),
59 match_(match),
60 alternate_nav_match_(alternate_nav_match),
61 shortcuts_backend_(ShortcutsBackendFactory::GetForProfile(profile)),
62 load_state_(LOAD_NOT_SEEN),
63 fetch_state_(FETCH_NOT_COMPLETE) {
64 if (alternate_nav_match_.destination_url.is_valid()) {
65 fetcher_.reset(net::URLFetcher::Create(alternate_nav_match_.destination_url,
66 net::URLFetcher::HEAD, this));
67 fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES);
68 fetcher_->SetStopOnRedirect(true);
69 }
70 // We need to start by listening to AllSources, since we don't know which tab
71 // the navigation might occur in.
72 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
73 content::NotificationService::AllSources());
74 }
75
~OmniboxNavigationObserver()76 OmniboxNavigationObserver::~OmniboxNavigationObserver() {
77 }
78
OnSuccessfulNavigation()79 void OmniboxNavigationObserver::OnSuccessfulNavigation() {
80 if (shortcuts_backend_)
81 shortcuts_backend_->AddOrUpdateShortcut(text_, match_);
82 }
83
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)84 void OmniboxNavigationObserver::Observe(
85 int type,
86 const content::NotificationSource& source,
87 const content::NotificationDetails& details) {
88 DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_PENDING, type);
89 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
90 content::NotificationService::AllSources());
91 content::NavigationController* controller =
92 content::Source<content::NavigationController>(source).ptr();
93 if (fetcher_) {
94 fetcher_->SetRequestContext(
95 controller->GetBrowserContext()->GetRequestContext());
96 }
97 WebContentsObserver::Observe(controller->GetWebContents());
98 // DidStartNavigationToPendingEntry() will be called for this load as well.
99 }
100
NavigationEntryCommitted(const content::LoadCommittedDetails & load_details)101 void OmniboxNavigationObserver::NavigationEntryCommitted(
102 const content::LoadCommittedDetails& load_details) {
103 load_state_ = LOAD_COMMITTED;
104 if (ResponseCodeIndicatesSuccess(load_details.http_status_code) &&
105 IsValidNavigation(match_.destination_url, load_details.entry->GetURL()))
106 OnSuccessfulNavigation();
107 if (!fetcher_ || (fetch_state_ != FETCH_NOT_COMPLETE))
108 OnAllLoadingFinished(); // deletes |this|!
109 }
110
WebContentsDestroyed(content::WebContents * web_contents)111 void OmniboxNavigationObserver::WebContentsDestroyed(
112 content::WebContents* web_contents) {
113 delete this;
114 }
115
DidStartNavigationToPendingEntry(const GURL & url,content::NavigationController::ReloadType reload_type)116 void OmniboxNavigationObserver::DidStartNavigationToPendingEntry(
117 const GURL& url,
118 content::NavigationController::ReloadType reload_type) {
119 if (load_state_ == LOAD_NOT_SEEN) {
120 load_state_ = LOAD_PENDING;
121 if (fetcher_)
122 fetcher_->Start();
123 } else {
124 delete this;
125 }
126 }
127
OnURLFetchComplete(const net::URLFetcher * source)128 void OmniboxNavigationObserver::OnURLFetchComplete(
129 const net::URLFetcher* source) {
130 DCHECK_EQ(fetcher_.get(), source);
131 const net::URLRequestStatus& status = source->GetStatus();
132 int response_code = source->GetResponseCode();
133 fetch_state_ =
134 (status.is_success() && ResponseCodeIndicatesSuccess(response_code)) ||
135 ((status.status() == net::URLRequestStatus::CANCELED) &&
136 ((response_code / 100) == 3) &&
137 IsValidNavigation(alternate_nav_match_.destination_url,
138 source->GetURL())) ?
139 FETCH_SUCCEEDED : FETCH_FAILED;
140 if (load_state_ == LOAD_COMMITTED)
141 OnAllLoadingFinished(); // deletes |this|!
142 }
143
OnAllLoadingFinished()144 void OmniboxNavigationObserver::OnAllLoadingFinished() {
145 if (fetch_state_ == FETCH_SUCCEEDED) {
146 AlternateNavInfoBarDelegate::Create(
147 web_contents(), text_, alternate_nav_match_, match_.destination_url);
148 }
149 delete this;
150 }
151