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/autocomplete/shortcuts_backend.h"
8#include "chrome/browser/autocomplete/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
27namespace {
28
29// HTTP 2xx, 401, and 407 all indicate that the target address exists.
30bool 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().
37bool 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
53OmniboxNavigationObserver::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
76OmniboxNavigationObserver::~OmniboxNavigationObserver() {
77}
78
79void OmniboxNavigationObserver::OnSuccessfulNavigation() {
80  if (shortcuts_backend_.get())
81    shortcuts_backend_->AddOrUpdateShortcut(text_, match_);
82}
83
84void 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
101void 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
111void OmniboxNavigationObserver::WebContentsDestroyed() {
112  delete this;
113}
114
115void OmniboxNavigationObserver::DidStartNavigationToPendingEntry(
116    const GURL& url,
117    content::NavigationController::ReloadType reload_type) {
118  if (load_state_ == LOAD_NOT_SEEN) {
119    load_state_ = LOAD_PENDING;
120    if (fetcher_)
121      fetcher_->Start();
122  } else {
123    delete this;
124  }
125}
126
127void OmniboxNavigationObserver::OnURLFetchComplete(
128    const net::URLFetcher* source) {
129  DCHECK_EQ(fetcher_.get(), source);
130  const net::URLRequestStatus& status = source->GetStatus();
131  int response_code = source->GetResponseCode();
132  fetch_state_ =
133      (status.is_success() && ResponseCodeIndicatesSuccess(response_code)) ||
134      ((status.status() == net::URLRequestStatus::CANCELED) &&
135       ((response_code / 100) == 3) &&
136       IsValidNavigation(alternate_nav_match_.destination_url,
137                         source->GetURL())) ?
138          FETCH_SUCCEEDED : FETCH_FAILED;
139  if (load_state_ == LOAD_COMMITTED)
140    OnAllLoadingFinished();  // deletes |this|!
141}
142
143void OmniboxNavigationObserver::OnAllLoadingFinished() {
144  if (fetch_state_ == FETCH_SUCCEEDED) {
145    AlternateNavInfoBarDelegate::Create(
146        web_contents(), text_, alternate_nav_match_, match_.destination_url);
147  }
148  delete this;
149}
150