1// Copyright (c) 2011 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/alternate_nav_url_fetcher.h"
6
7#include "base/utf_string_conversions.h"
8#include "chrome/browser/intranet_redirect_detector.h"
9#include "chrome/browser/profiles/profile.h"
10#include "content/browser/tab_contents/navigation_controller.h"
11#include "content/browser/tab_contents/navigation_entry.h"
12#include "content/browser/tab_contents/tab_contents.h"
13#include "content/common/notification_service.h"
14#include "grit/generated_resources.h"
15#include "grit/theme_resources.h"
16#include "net/base/registry_controlled_domain.h"
17#include "net/url_request/url_request.h"
18#include "ui/base/l10n/l10n_util.h"
19#include "ui/base/resource/resource_bundle.h"
20
21AlternateNavURLFetcher::AlternateNavURLFetcher(
22    const GURL& alternate_nav_url)
23    : LinkInfoBarDelegate(NULL),
24      alternate_nav_url_(alternate_nav_url),
25      controller_(NULL),
26      state_(NOT_STARTED),
27      navigated_to_entry_(false),
28      infobar_contents_(NULL) {
29  registrar_.Add(this, NotificationType::NAV_ENTRY_PENDING,
30                 NotificationService::AllSources());
31}
32
33AlternateNavURLFetcher::~AlternateNavURLFetcher() {}
34
35void AlternateNavURLFetcher::Observe(NotificationType type,
36                                     const NotificationSource& source,
37                                     const NotificationDetails& details) {
38  switch (type.value) {
39    case NotificationType::NAV_ENTRY_PENDING:
40      // If we've already received a notification for the same controller, we
41      // should delete ourselves as that indicates that the page is being
42      // re-loaded so this instance is now stale.
43      // http://crbug.com/43378
44      if (!infobar_contents_ &&
45          controller_ == Source<NavigationController>(source).ptr()) {
46        delete this;
47      } else if (!controller_) {
48        controller_ = Source<NavigationController>(source).ptr();
49        DCHECK(controller_->pending_entry());
50
51        // Start listening for the commit notification. We also need to listen
52        // for the tab close command since that means the load will never
53        // commit!
54        registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
55                       Source<NavigationController>(controller_));
56        registrar_.Add(this, NotificationType::TAB_CLOSED,
57                       Source<NavigationController>(controller_));
58
59        DCHECK_EQ(NOT_STARTED, state_);
60        state_ = IN_PROGRESS;
61        fetcher_.reset(new URLFetcher(GURL(alternate_nav_url_),
62                                      URLFetcher::HEAD, this));
63        fetcher_->set_request_context(
64            controller_->profile()->GetRequestContext());
65        fetcher_->Start();
66      }
67      break;
68
69    case NotificationType::NAV_ENTRY_COMMITTED:
70      // The page was navigated, we can show the infobar now if necessary.
71      registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED,
72                        Source<NavigationController>(controller_));
73      navigated_to_entry_ = true;
74      ShowInfobarIfPossible();
75      break;
76
77    case NotificationType::TAB_CLOSED:
78      // We have been closed. In order to prevent the URLFetcher from trying to
79      // access the controller that will be invalid, we delete ourselves.
80      // This deletes the URLFetcher and insures its callback won't be called.
81      delete this;
82      break;
83
84    default:
85      NOTREACHED();
86  }
87}
88
89void AlternateNavURLFetcher::OnURLFetchComplete(
90    const URLFetcher* source,
91    const GURL& url,
92    const net::URLRequestStatus& status,
93    int response_code,
94    const ResponseCookies& cookies,
95    const std::string& data) {
96  DCHECK_EQ(fetcher_.get(), source);
97  SetStatusFromURLFetch(url, status, response_code);
98  ShowInfobarIfPossible();
99}
100
101SkBitmap* AlternateNavURLFetcher::GetIcon() const {
102  return ResourceBundle::GetSharedInstance().GetBitmapNamed(
103      IDR_INFOBAR_ALT_NAV_URL);
104}
105
106InfoBarDelegate::Type AlternateNavURLFetcher::GetInfoBarType() const {
107  return PAGE_ACTION_TYPE;
108}
109
110string16 AlternateNavURLFetcher::GetMessageTextWithOffset(
111    size_t* link_offset) const {
112  const string16 label = l10n_util::GetStringFUTF16(
113      IDS_ALTERNATE_NAV_URL_VIEW_LABEL, string16(), link_offset);
114  return label;
115}
116
117string16 AlternateNavURLFetcher::GetLinkText() const {
118  return UTF8ToUTF16(alternate_nav_url_.spec());
119}
120
121bool AlternateNavURLFetcher::LinkClicked(WindowOpenDisposition disposition) {
122  infobar_contents_->OpenURL(
123      alternate_nav_url_, GURL(), disposition,
124      // Pretend the user typed this URL, so that navigating to
125      // it will be the default action when it's typed again in
126      // the future.
127      PageTransition::TYPED);
128
129  // We should always close, even if the navigation did not occur within this
130  // TabContents.
131  return true;
132}
133
134void AlternateNavURLFetcher::InfoBarClosed() {
135  delete this;
136}
137
138void AlternateNavURLFetcher::SetStatusFromURLFetch(
139    const GURL& url,
140    const net::URLRequestStatus& status,
141    int response_code) {
142  if (!status.is_success() ||
143      // HTTP 2xx, 401, and 407 all indicate that the target address exists.
144      (((response_code / 100) != 2) &&
145       (response_code != 401) && (response_code != 407)) ||
146      // Fail if we're redirected to a common location.
147      // This happens for ISPs/DNS providers/etc. who return
148      // provider-controlled pages to arbitrary user navigation attempts.
149      // Because this can result in infobars on large fractions of user
150      // searches, we don't show automatic infobars for these.  Note that users
151      // can still choose to explicitly navigate to or search for pages in
152      // these domains, and can still get infobars for cases that wind up on
153      // other domains (e.g. legit intranet sites), we're just trying to avoid
154      // erroneously harassing the user with our own UI prompts.
155      net::RegistryControlledDomainService::SameDomainOrHost(url,
156          IntranetRedirectDetector::RedirectOrigin())) {
157    state_ = FAILED;
158    return;
159  }
160  state_ = SUCCEEDED;
161}
162
163void AlternateNavURLFetcher::ShowInfobarIfPossible() {
164  if (!navigated_to_entry_ || state_ != SUCCEEDED) {
165    if (state_ == FAILED)
166      delete this;
167    return;
168  }
169
170  infobar_contents_ = controller_->tab_contents();
171  StoreActiveEntryUniqueID(infobar_contents_);
172  infobar_contents_->AddInfoBar(this);
173}
174