OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/google_url_tracker.h" | 5 #include "chrome/browser/google_url_tracker.h" |
6 | 6 |
7 #include <vector> | 7 #include <vector> |
8 | 8 |
| 9 #include "app/l10n_util.h" |
9 #include "base/compiler_specific.h" | 10 #include "base/compiler_specific.h" |
10 #include "base/string_util.h" | 11 #include "base/string_util.h" |
11 #include "base/utf_string_conversions.h" | 12 #include "base/utf_string_conversions.h" |
12 #include "chrome/browser/browser_process.h" | 13 #include "chrome/browser/browser_process.h" |
13 #include "chrome/browser/pref_service.h" | 14 #include "chrome/browser/pref_service.h" |
14 #include "chrome/browser/profile.h" | 15 #include "chrome/browser/profile.h" |
| 16 #include "chrome/browser/search_engines/template_url.h" |
| 17 #include "chrome/browser/tab_contents/infobar_delegate.h" |
| 18 #include "chrome/browser/tab_contents/navigation_controller.h" |
| 19 #include "chrome/browser/tab_contents/tab_contents.h" |
| 20 #include "chrome/common/net/url_fetcher_protect.h" |
15 #include "chrome/common/notification_service.h" | 21 #include "chrome/common/notification_service.h" |
16 #include "chrome/common/pref_names.h" | 22 #include "chrome/common/pref_names.h" |
| 23 #include "grit/generated_resources.h" |
17 #include "net/base/load_flags.h" | 24 #include "net/base/load_flags.h" |
18 #include "net/url_request/url_request_status.h" | 25 #include "net/url_request/url_request_status.h" |
19 | 26 |
20 const char GoogleURLTracker::kDefaultGoogleHomepage[] = | 27 const char GoogleURLTracker::kDefaultGoogleHomepage[] = |
21 "http://www.google.com/"; | 28 "http://www.google.com/"; |
| 29 const char GoogleURLTracker::kSearchDomainCheckURL[] = |
| 30 "https://www.google.com/searchdomaincheck?format=domain&type=chrome"; |
| 31 |
| 32 namespace { |
| 33 |
| 34 class GoogleURLTrackerInfoBarDelegate : public ConfirmInfoBarDelegate { |
| 35 public: |
| 36 GoogleURLTrackerInfoBarDelegate(TabContents* tab_contents, |
| 37 GoogleURLTracker* google_url_tracker, |
| 38 const GURL& new_google_url) |
| 39 : ConfirmInfoBarDelegate(tab_contents), |
| 40 google_url_tracker_(google_url_tracker), |
| 41 new_google_url_(new_google_url) {} |
| 42 |
| 43 // ConfirmInfoBarDelegate |
| 44 virtual string16 GetMessageText() const { |
| 45 // TODO(ukai): change new_google_url to google_base_domain? |
| 46 return l10n_util::GetStringFUTF16(IDS_GOOGLE_URL_TRACKER_INFOBAR_MESSAGE, |
| 47 UTF8ToUTF16(new_google_url_.spec())); |
| 48 } |
| 49 |
| 50 virtual int GetButtons() const { |
| 51 return BUTTON_OK | BUTTON_CANCEL; |
| 52 } |
| 53 |
| 54 virtual string16 GetButtonLabel(InfoBarButton button) const { |
| 55 return l10n_util::GetStringUTF16((button == BUTTON_OK) ? |
| 56 IDS_CONFIRM_MESSAGEBOX_YES_BUTTON_LABEL : |
| 57 IDS_CONFIRM_MESSAGEBOX_NO_BUTTON_LABEL); |
| 58 } |
| 59 |
| 60 virtual bool Accept() { |
| 61 google_url_tracker_->AcceptGoogleURL(new_google_url_); |
| 62 google_url_tracker_->RedoSearch(); |
| 63 return true; |
| 64 } |
| 65 |
| 66 virtual void InfoBarClosed() { |
| 67 google_url_tracker_->InfoBarClosed(); |
| 68 delete this; |
| 69 } |
| 70 |
| 71 private: |
| 72 virtual ~GoogleURLTrackerInfoBarDelegate() {} |
| 73 |
| 74 GoogleURLTracker* google_url_tracker_; |
| 75 const GURL new_google_url_; |
| 76 |
| 77 DISALLOW_COPY_AND_ASSIGN(GoogleURLTrackerInfoBarDelegate); |
| 78 }; |
| 79 |
| 80 } // anonymous namespace |
| 81 |
| 82 InfoBarDelegate* GoogleURLTracker::InfoBarDelegateFactory::CreateInfoBar( |
| 83 TabContents* tab_contents, |
| 84 GoogleURLTracker* google_url_tracker, |
| 85 const GURL& new_google_url) { |
| 86 InfoBarDelegate* infobar = |
| 87 new GoogleURLTrackerInfoBarDelegate(tab_contents, |
| 88 google_url_tracker, |
| 89 new_google_url); |
| 90 tab_contents->AddInfoBar(infobar); |
| 91 return infobar; |
| 92 } |
22 | 93 |
23 GoogleURLTracker::GoogleURLTracker() | 94 GoogleURLTracker::GoogleURLTracker() |
24 : google_url_(g_browser_process->local_state()->GetString( | 95 : google_url_(g_browser_process->local_state()->GetString( |
25 prefs::kLastKnownGoogleURL)), | 96 prefs::kLastKnownGoogleURL)), |
26 ALLOW_THIS_IN_INITIALIZER_LIST(fetcher_factory_(this)), | 97 ALLOW_THIS_IN_INITIALIZER_LIST(runnable_method_factory_(this)), |
| 98 fetcher_id_(0), |
27 in_startup_sleep_(true), | 99 in_startup_sleep_(true), |
28 already_fetched_(false), | 100 already_fetched_(false), |
29 need_to_fetch_(false), | 101 need_to_fetch_(false), |
30 request_context_available_(!!Profile::GetDefaultRequestContext()) { | 102 request_context_available_(!!Profile::GetDefaultRequestContext()), |
| 103 need_to_prompt_(false), |
| 104 controller_(NULL), |
| 105 infobar_factory_(new InfoBarDelegateFactory), |
| 106 infobar_(NULL) { |
31 registrar_.Add(this, NotificationType::DEFAULT_REQUEST_CONTEXT_AVAILABLE, | 107 registrar_.Add(this, NotificationType::DEFAULT_REQUEST_CONTEXT_AVAILABLE, |
32 NotificationService::AllSources()); | 108 NotificationService::AllSources()); |
33 | 109 |
| 110 net::NetworkChangeNotifier::AddObserver(this); |
| 111 |
| 112 // Configure to max_retries at most kMaxRetries times for 5xx errors. |
| 113 URLFetcherProtectEntry* protect = |
| 114 URLFetcherProtectManager::GetInstance()->Register( |
| 115 GURL(kSearchDomainCheckURL).host()); |
| 116 static const int kMaxRetries = 5; |
| 117 protect->SetMaxRetries(kMaxRetries); |
| 118 |
34 // Because this function can be called during startup, when kicking off a URL | 119 // Because this function can be called during startup, when kicking off a URL |
35 // fetch can eat up 20 ms of time, we delay five seconds, which is hopefully | 120 // fetch can eat up 20 ms of time, we delay five seconds, which is hopefully |
36 // long enough to be after startup, but still get results back quickly. | 121 // long enough to be after startup, but still get results back quickly. |
37 // Ideally, instead of this timer, we'd do something like "check if the | 122 // Ideally, instead of this timer, we'd do something like "check if the |
38 // browser is starting up, and if so, come back later", but there is currently | 123 // browser is starting up, and if so, come back later", but there is currently |
39 // no function to do this. | 124 // no function to do this. |
40 static const int kStartFetchDelayMS = 5000; | 125 static const int kStartFetchDelayMS = 5000; |
41 MessageLoop::current()->PostDelayedTask(FROM_HERE, | 126 MessageLoop::current()->PostDelayedTask(FROM_HERE, |
42 fetcher_factory_.NewRunnableMethod(&GoogleURLTracker::FinishSleep), | 127 runnable_method_factory_.NewRunnableMethod( |
| 128 &GoogleURLTracker::FinishSleep), |
43 kStartFetchDelayMS); | 129 kStartFetchDelayMS); |
44 } | 130 } |
45 | 131 |
46 GoogleURLTracker::~GoogleURLTracker() { | 132 GoogleURLTracker::~GoogleURLTracker() { |
| 133 runnable_method_factory_.RevokeAll(); |
| 134 net::NetworkChangeNotifier::RemoveObserver(this); |
47 } | 135 } |
48 | 136 |
49 // static | 137 // static |
50 GURL GoogleURLTracker::GoogleURL() { | 138 GURL GoogleURLTracker::GoogleURL() { |
51 const GoogleURLTracker* const tracker = | 139 const GoogleURLTracker* const tracker = |
52 g_browser_process->google_url_tracker(); | 140 g_browser_process->google_url_tracker(); |
53 return tracker ? tracker->google_url_ : GURL(kDefaultGoogleHomepage); | 141 return tracker ? tracker->google_url_ : GURL(kDefaultGoogleHomepage); |
54 } | 142 } |
55 | 143 |
56 // static | 144 // static |
57 void GoogleURLTracker::RequestServerCheck() { | 145 void GoogleURLTracker::RequestServerCheck() { |
58 GoogleURLTracker* const tracker = g_browser_process->google_url_tracker(); | 146 GoogleURLTracker* const tracker = g_browser_process->google_url_tracker(); |
59 if (tracker) | 147 if (tracker) |
60 tracker->SetNeedToFetch(); | 148 tracker->SetNeedToFetch(); |
61 } | 149 } |
62 | 150 |
63 // static | 151 // static |
64 void GoogleURLTracker::RegisterPrefs(PrefService* prefs) { | 152 void GoogleURLTracker::RegisterPrefs(PrefService* prefs) { |
65 prefs->RegisterStringPref(prefs::kLastKnownGoogleURL, | 153 prefs->RegisterStringPref(prefs::kLastKnownGoogleURL, |
66 kDefaultGoogleHomepage); | 154 kDefaultGoogleHomepage); |
| 155 prefs->RegisterStringPref(prefs::kLastPromptedGoogleURL, std::string()); |
67 } | 156 } |
68 | 157 |
69 // static | 158 // static |
70 bool GoogleURLTracker::CheckAndConvertToGoogleBaseURL(const GURL& url, | 159 void GoogleURLTracker::GoogleURLSearchCommitted() { |
71 GURL* base_url) { | 160 GoogleURLTracker* tracker = g_browser_process->google_url_tracker(); |
72 // Only allow updates if the new URL appears to be on google.xx, google.co.xx, | 161 if (tracker) |
73 // or google.com.xx. Cases other than this are either malicious, or doorway | 162 tracker->SearchCommitted(); |
74 // pages for hotel WiFi connections and the like. | |
75 // NOTE: Obviously the above is not as secure as whitelisting all known Google | |
76 // frontpage domains, but for now we're trying to prevent login pages etc. | |
77 // from ruining the user experience, rather than preventing hijacking. | |
78 std::vector<std::string> host_components; | |
79 SplitStringDontTrim(url.host(), '.', &host_components); | |
80 if (host_components.size() < 2) | |
81 return false; | |
82 size_t google_component = host_components.size() - 2; | |
83 const std::string& component = host_components[google_component]; | |
84 if (component != "google") { | |
85 if ((host_components.size() < 3) || | |
86 ((component != "co") && (component != "com"))) | |
87 return false; | |
88 google_component = host_components.size() - 3; | |
89 if (host_components[google_component] != "google") | |
90 return false; | |
91 } | |
92 // For Google employees only: If the URL appears to be on | |
93 // [*.]corp.google.com, it's likely a doorway (e.g. | |
94 // wifi.corp.google.com), so ignore it. | |
95 if ((google_component > 0) && | |
96 (host_components[google_component - 1] == "corp")) | |
97 return false; | |
98 | |
99 // If the url's path does not begin "/intl/", reset it to "/". Other paths | |
100 // represent services such as iGoogle that are irrelevant to the baseURL. | |
101 *base_url = url.path().compare(0, 6, "/intl/") ? url.GetWithEmptyPath() : url; | |
102 return true; | |
103 } | 163 } |
104 | 164 |
105 void GoogleURLTracker::SetNeedToFetch() { | 165 void GoogleURLTracker::SetNeedToFetch() { |
106 need_to_fetch_ = true; | 166 need_to_fetch_ = true; |
107 StartFetchIfDesirable(); | 167 StartFetchIfDesirable(); |
108 } | 168 } |
109 | 169 |
110 void GoogleURLTracker::FinishSleep() { | 170 void GoogleURLTracker::FinishSleep() { |
111 in_startup_sleep_ = false; | 171 in_startup_sleep_ = false; |
112 StartFetchIfDesirable(); | 172 StartFetchIfDesirable(); |
113 } | 173 } |
114 | 174 |
115 void GoogleURLTracker::StartFetchIfDesirable() { | 175 void GoogleURLTracker::StartFetchIfDesirable() { |
116 // Bail if a fetch isn't appropriate right now. This function will be called | 176 // Bail if a fetch isn't appropriate right now. This function will be called |
117 // again each time one of the preconditions changes, so we'll fetch | 177 // again each time one of the preconditions changes, so we'll fetch |
118 // immediately once all of them are met. | 178 // immediately once all of them are met. |
119 // | 179 // |
120 // See comments in header on the class, on RequestServerCheck(), and on the | 180 // See comments in header on the class, on RequestServerCheck(), and on the |
121 // various members here for more detail on exactly what the conditions are. | 181 // various members here for more detail on exactly what the conditions are. |
122 if (in_startup_sleep_ || already_fetched_ || !need_to_fetch_ || | 182 if (in_startup_sleep_ || already_fetched_ || !need_to_fetch_ || |
123 !request_context_available_) | 183 !request_context_available_) |
124 return; | 184 return; |
125 | 185 |
126 need_to_fetch_ = false; | 186 already_fetched_ = true; |
127 already_fetched_ = true; // If fetching fails, we don't bother to reset this | 187 fetcher_.reset(URLFetcher::Create(fetcher_id_, GURL(kSearchDomainCheckURL), |
128 // flag; we just live with an outdated URL for this | 188 URLFetcher::GET, this)); |
129 // run of the browser. | 189 ++fetcher_id_; |
130 fetcher_.reset(new URLFetcher(GURL(kDefaultGoogleHomepage), URLFetcher::HEAD, | |
131 this)); | |
132 // We don't want this fetch to affect existing state in the profile. For | 190 // We don't want this fetch to affect existing state in the profile. For |
133 // example, if a user has no Google cookies, this automatic check should not | 191 // example, if a user has no Google cookies, this automatic check should not |
134 // cause one to be set, lest we alarm the user. | 192 // cause one to be set, lest we alarm the user. |
135 fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE | | 193 fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE | |
136 net::LOAD_DO_NOT_SAVE_COOKIES); | 194 net::LOAD_DO_NOT_SAVE_COOKIES); |
137 fetcher_->set_request_context(Profile::GetDefaultRequestContext()); | 195 fetcher_->set_request_context(Profile::GetDefaultRequestContext()); |
138 fetcher_->Start(); | 196 fetcher_->Start(); |
139 } | 197 } |
140 | 198 |
141 void GoogleURLTracker::OnURLFetchComplete(const URLFetcher* source, | 199 void GoogleURLTracker::OnURLFetchComplete(const URLFetcher* source, |
142 const GURL& url, | 200 const GURL& url, |
143 const URLRequestStatus& status, | 201 const URLRequestStatus& status, |
144 int response_code, | 202 int response_code, |
145 const ResponseCookies& cookies, | 203 const ResponseCookies& cookies, |
146 const std::string& data) { | 204 const std::string& data) { |
147 // Delete the fetcher on this function's exit. | 205 // Delete the fetcher on this function's exit. |
148 scoped_ptr<URLFetcher> clean_up_fetcher(fetcher_.release()); | 206 scoped_ptr<URLFetcher> clean_up_fetcher(fetcher_.release()); |
149 | 207 |
150 // Don't update the URL if the request didn't succeed. | 208 // Don't update the URL if the request didn't succeed. |
151 if (!status.is_success() || (response_code != 200)) | 209 if (!status.is_success() || (response_code != 200)) { |
| 210 already_fetched_ = false; |
| 211 return; |
| 212 } |
| 213 |
| 214 // See if the response data was one we want to use, and if so, convert to the |
| 215 // appropriate Google base URL. |
| 216 std::string url_str; |
| 217 TrimWhitespace(data, TRIM_ALL, &url_str); |
| 218 |
| 219 if (!StartsWithASCII(url_str, ".google.", false)) |
152 return; | 220 return; |
153 | 221 |
154 // See if the response URL was one we want to use, and if so, convert to the | 222 fetched_google_url_ = GURL("http://www" + url_str); |
155 // appropriate Google base URL. | 223 GURL last_prompted_url( |
156 GURL base_url; | 224 g_browser_process->local_state()->GetString( |
157 if (!CheckAndConvertToGoogleBaseURL(url, &base_url)) | 225 prefs::kLastPromptedGoogleURL)); |
| 226 need_to_prompt_ = false; |
| 227 // On the very first run of Chrome, when we've never looked up the URL at all, |
| 228 // we should just silently switch over to whatever we get immediately. |
| 229 if (last_prompted_url.is_empty()) { |
| 230 AcceptGoogleURL(fetched_google_url_); |
| 231 // Set fetched_google_url_ as an initial value of last prompted URL. |
| 232 g_browser_process->local_state()->SetString(prefs::kLastPromptedGoogleURL, |
| 233 fetched_google_url_.spec()); |
158 return; | 234 return; |
| 235 } |
159 | 236 |
160 // Update the saved base URL if it has changed. | 237 if (fetched_google_url_ == last_prompted_url) |
161 const std::string base_url_str(base_url.spec()); | 238 return; |
162 if (g_browser_process->local_state()->GetString(prefs::kLastKnownGoogleURL) != | 239 if (fetched_google_url_ == google_url_) { |
163 base_url_str) { | 240 // The user came back to their original location after having temporarily |
164 g_browser_process->local_state()->SetString(prefs::kLastKnownGoogleURL, | 241 // moved. Reset the prompted URL so we'll prompt again if they move again. |
165 base_url_str); | 242 g_browser_process->local_state()->SetString(prefs::kLastPromptedGoogleURL, |
166 google_url_ = base_url; | 243 fetched_google_url_.spec()); |
167 NotificationService::current()->Notify(NotificationType::GOOGLE_URL_UPDATED, | 244 return; |
168 NotificationService::AllSources(), | |
169 NotificationService::NoDetails()); | |
170 } | 245 } |
| 246 |
| 247 need_to_prompt_ = true; |
| 248 } |
| 249 |
| 250 void GoogleURLTracker::AcceptGoogleURL(const GURL& new_google_url) { |
| 251 google_url_ = new_google_url; |
| 252 g_browser_process->local_state()->SetString(prefs::kLastKnownGoogleURL, |
| 253 google_url_.spec()); |
| 254 NotificationService::current()->Notify(NotificationType::GOOGLE_URL_UPDATED, |
| 255 NotificationService::AllSources(), |
| 256 NotificationService::NoDetails()); |
| 257 need_to_prompt_ = false; |
| 258 } |
| 259 |
| 260 void GoogleURLTracker::InfoBarClosed() { |
| 261 registrar_.RemoveAll(); |
| 262 controller_ = NULL; |
| 263 infobar_ = NULL; |
| 264 search_url_ = GURL(); |
| 265 } |
| 266 |
| 267 void GoogleURLTracker::RedoSearch() { |
| 268 // re-do the user's search on the new domain. |
| 269 DCHECK(controller_); |
| 270 url_canon::Replacements<char> replacements; |
| 271 replacements.SetHost(google_url_.host().data(), |
| 272 url_parse::Component(0, google_url_.host().length())); |
| 273 search_url_ = search_url_.ReplaceComponents(replacements); |
| 274 if (search_url_.is_valid()) |
| 275 controller_->tab_contents()->OpenURL(search_url_, GURL(), CURRENT_TAB, |
| 276 PageTransition::GENERATED); |
171 } | 277 } |
172 | 278 |
173 void GoogleURLTracker::Observe(NotificationType type, | 279 void GoogleURLTracker::Observe(NotificationType type, |
174 const NotificationSource& source, | 280 const NotificationSource& source, |
175 const NotificationDetails& details) { | 281 const NotificationDetails& details) { |
176 DCHECK_EQ(NotificationType::DEFAULT_REQUEST_CONTEXT_AVAILABLE, type.value); | 282 switch (type.value) { |
177 request_context_available_ = true; | 283 case NotificationType::DEFAULT_REQUEST_CONTEXT_AVAILABLE: |
| 284 request_context_available_ = true; |
| 285 StartFetchIfDesirable(); |
| 286 break; |
| 287 |
| 288 case NotificationType::NAV_ENTRY_PENDING: |
| 289 // If we've already received a notification for the same controller, we |
| 290 // should reset infobar as that indicates that the page is being |
| 291 // re-loaded |
| 292 if (!infobar_ && |
| 293 controller_ == Source<NavigationController>(source).ptr()) { |
| 294 infobar_ = NULL; |
| 295 } else if (!controller_) { |
| 296 controller_ = Source<NavigationController>(source).ptr(); |
| 297 NavigationEntry* entry = controller_->pending_entry(); |
| 298 DCHECK(entry); |
| 299 search_url_ = entry->url(); |
| 300 |
| 301 // Start listening for the commit notification. We also need to listen |
| 302 // for the tab close command since that means the load will never |
| 303 // commit! |
| 304 registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, |
| 305 Source<NavigationController>(controller_)); |
| 306 registrar_.Add(this, NotificationType::TAB_CLOSED, |
| 307 Source<NavigationController>(controller_)); |
| 308 } |
| 309 break; |
| 310 |
| 311 case NotificationType::NAV_ENTRY_COMMITTED: |
| 312 DCHECK(controller_); |
| 313 registrar_.Remove(this, NotificationType::NAV_ENTRY_COMMITTED, |
| 314 Source<NavigationController>(controller_)); |
| 315 ShowGoogleURLInfoBarIfNecessary(controller_->tab_contents()); |
| 316 break; |
| 317 |
| 318 case NotificationType::TAB_CLOSED: |
| 319 registrar_.RemoveAll(); |
| 320 controller_ = NULL; |
| 321 infobar_ = NULL; |
| 322 break; |
| 323 |
| 324 default: |
| 325 NOTREACHED() << "Unknown notification received:" << type.value; |
| 326 } |
| 327 } |
| 328 |
| 329 void GoogleURLTracker::OnIPAddressChanged() { |
| 330 already_fetched_ = false; |
178 StartFetchIfDesirable(); | 331 StartFetchIfDesirable(); |
179 } | 332 } |
| 333 |
| 334 void GoogleURLTracker::SearchCommitted() { |
| 335 registrar_.Add(this, NotificationType::NAV_ENTRY_PENDING, |
| 336 NotificationService::AllSources()); |
| 337 } |
| 338 |
| 339 void GoogleURLTracker::ShowGoogleURLInfoBarIfNecessary( |
| 340 TabContents* tab_contents) { |
| 341 if (!need_to_prompt_) |
| 342 return; |
| 343 if (infobar_) |
| 344 return; |
| 345 DCHECK(!fetched_google_url_.is_empty()); |
| 346 DCHECK(infobar_factory_.get()); |
| 347 |
| 348 infobar_ = infobar_factory_->CreateInfoBar(tab_contents, |
| 349 this, |
| 350 fetched_google_url_); |
| 351 g_browser_process->local_state()->SetString(prefs::kLastPromptedGoogleURL, |
| 352 fetched_google_url_.spec()); |
| 353 } |
OLD | NEW |