Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / captive_portal / captive_portal_service.cc
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/captive_portal/captive_portal_service.h"
6
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/metrics/histogram.h"
12 #include "base/prefs/pref_service.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/common/pref_names.h"
16 #include "components/captive_portal/captive_portal_types.h"
17 #include "content/public/browser/notification_service.h"
18
19 #if defined(OS_MACOSX)
20 #include "base/mac/mac_util.h"
21 #endif
22
23 #if defined(OS_WIN)
24 #include "base/win/windows_version.h"
25 #endif
26
27 using captive_portal::CaptivePortalResult;
28
29 namespace {
30
31 // Make sure this enum is in sync with CaptivePortalDetectionResult enum
32 // in histograms.xml. This enum is append-only, don't modify existing values.
33 enum CaptivePortalDetectionResult {
34   // There's a confirmed connection to the Internet.
35   DETECTION_RESULT_INTERNET_CONNECTED,
36   // Received a network or HTTP error, or a non-HTTP response.
37   DETECTION_RESULT_NO_RESPONSE,
38   // Encountered a captive portal with a non-HTTPS landing URL.
39   DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL,
40   // Received a network or HTTP error with an HTTPS landing URL.
41   DETECTION_RESULT_NO_RESPONSE_HTTPS_LANDING_URL,
42   // Encountered a captive portal with an HTTPS landing URL.
43   DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL_HTTPS_LANDING_URL,
44   // Received a network or HTTP error, or a non-HTTP response with IP address.
45   DETECTION_RESULT_NO_RESPONSE_IP_ADDRESS,
46   // Encountered a captive portal with a non-HTTPS, IP address landing URL.
47   DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL_IP_ADDRESS,
48   // Received a network or HTTP error with an HTTPS, IP address landing URL.
49   DETECTION_RESULT_NO_RESPONSE_HTTPS_LANDING_URL_IP_ADDRESS,
50   // Encountered a captive portal with an HTTPS, IP address landing URL.
51   DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL_HTTPS_LANDING_URL_IP_ADDRESS,
52   DETECTION_RESULT_COUNT
53 };
54
55 // Records histograms relating to how often captive portal detection attempts
56 // ended with |result| in a row, and for how long |result| was the last result
57 // of a detection attempt.  Recorded both on quit and on a new Result.
58 //
59 // |repeat_count| may be 0 if there were no captive portal checks during
60 // a session.
61 //
62 // |result_duration| is the time between when a captive portal check first
63 // returned |result| and when a check returned a different result, or when the
64 // CaptivePortalService was shut down.
65 void RecordRepeatHistograms(CaptivePortalResult result,
66                             int repeat_count,
67                             base::TimeDelta result_duration) {
68   // Histogram macros can't be used with variable names, since they cache
69   // pointers, so have to use the histogram functions directly.
70
71   // Record number of times the last result was received in a row.
72   base::HistogramBase* result_repeated_histogram =
73       base::Histogram::FactoryGet(
74           "CaptivePortal.ResultRepeated." + CaptivePortalResultToString(result),
75           1,  // min
76           100,  // max
77           100,  // bucket_count
78           base::Histogram::kUmaTargetedHistogramFlag);
79   result_repeated_histogram->Add(repeat_count);
80
81   if (repeat_count == 0)
82     return;
83
84   // Time between first request that returned |result| and now.
85   base::HistogramBase* result_duration_histogram =
86       base::Histogram::FactoryTimeGet(
87           "CaptivePortal.ResultDuration." + CaptivePortalResultToString(result),
88           base::TimeDelta::FromSeconds(1),  // min
89           base::TimeDelta::FromHours(1),  // max
90           50,  // bucket_count
91           base::Histogram::kUmaTargetedHistogramFlag);
92   result_duration_histogram->AddTime(result_duration);
93 }
94
95 int GetHistogramEntryForDetectionResult(
96     const captive_portal::CaptivePortalDetector::Results& results) {
97   bool is_https = results.landing_url.SchemeIs("https");
98   bool is_ip = results.landing_url.HostIsIPAddress();
99   switch (results.result) {
100     case captive_portal::RESULT_INTERNET_CONNECTED:
101       return DETECTION_RESULT_INTERNET_CONNECTED;
102     case captive_portal::RESULT_NO_RESPONSE:
103       if (is_ip) {
104         return is_https ?
105             DETECTION_RESULT_NO_RESPONSE_HTTPS_LANDING_URL_IP_ADDRESS :
106             DETECTION_RESULT_NO_RESPONSE_IP_ADDRESS;
107       }
108       return is_https ?
109           DETECTION_RESULT_NO_RESPONSE_HTTPS_LANDING_URL :
110           DETECTION_RESULT_NO_RESPONSE;
111     case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
112       if (is_ip) {
113         return is_https ?
114           DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL_HTTPS_LANDING_URL_IP_ADDRESS :
115           DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL_IP_ADDRESS;
116       }
117       return is_https ?
118           DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL_HTTPS_LANDING_URL :
119           DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL;
120     default:
121       NOTREACHED();
122       return -1;
123   }
124 }
125
126 bool ShouldDeferToNativeCaptivePortalDetection() {
127   // On Windows 8, defer to the native captive portal detection.  OSX Lion and
128   // later also have captive portal detection, but experimentally, this code
129   // works in cases its does not.
130   //
131   // TODO(mmenke): Investigate how well Windows 8's captive portal detection
132   // works.
133 #if defined(OS_WIN)
134   return base::win::GetVersion() >= base::win::VERSION_WIN8;
135 #else
136   return false;
137 #endif
138 }
139
140 }  // namespace
141
142 CaptivePortalService::TestingState CaptivePortalService::testing_state_ =
143     NOT_TESTING;
144
145 class CaptivePortalService::RecheckBackoffEntry : public net::BackoffEntry {
146  public:
147   explicit RecheckBackoffEntry(CaptivePortalService* captive_portal_service)
148       : net::BackoffEntry(
149             &captive_portal_service->recheck_policy().backoff_policy),
150         captive_portal_service_(captive_portal_service) {
151   }
152
153  private:
154   virtual base::TimeTicks ImplGetTimeNow() const OVERRIDE {
155     return captive_portal_service_->GetCurrentTimeTicks();
156   }
157
158   CaptivePortalService* captive_portal_service_;
159
160   DISALLOW_COPY_AND_ASSIGN(RecheckBackoffEntry);
161 };
162
163 CaptivePortalService::RecheckPolicy::RecheckPolicy()
164     : initial_backoff_no_portal_ms(600 * 1000),
165       initial_backoff_portal_ms(20 * 1000) {
166   // Receiving a new Result is considered a success.  All subsequent requests
167   // that get the same Result are considered "failures", so a value of N
168   // means exponential backoff starts after getting a result N + 2 times:
169   // +1 for the initial success, and +1 because N failures are ignored.
170   //
171   // A value of 6 means to start backoff on the 7th failure, which is the 8th
172   // time the same result is received.
173   backoff_policy.num_errors_to_ignore = 6;
174
175   // It doesn't matter what this is initialized to.  It will be overwritten
176   // after the first captive portal detection request.
177   backoff_policy.initial_delay_ms = initial_backoff_no_portal_ms;
178
179   backoff_policy.multiply_factor = 2.0;
180   backoff_policy.jitter_factor = 0.3;
181   backoff_policy.maximum_backoff_ms = 2 * 60 * 1000;
182
183   // -1 means the entry never expires.  This doesn't really matter, as the
184   // service never checks for its expiration.
185   backoff_policy.entry_lifetime_ms = -1;
186
187   backoff_policy.always_use_initial_delay = true;
188 }
189
190 CaptivePortalService::CaptivePortalService(Profile* profile)
191     : profile_(profile),
192       state_(STATE_IDLE),
193       captive_portal_detector_(profile->GetRequestContext()),
194       enabled_(false),
195       last_detection_result_(captive_portal::RESULT_INTERNET_CONNECTED),
196       num_checks_with_same_result_(0),
197       test_url_(captive_portal::CaptivePortalDetector::kDefaultURL) {
198   // The order matters here:
199   // |resolve_errors_with_web_service_| must be initialized and |backoff_entry_|
200   // created before the call to UpdateEnabledState.
201   resolve_errors_with_web_service_.Init(
202       prefs::kAlternateErrorPagesEnabled,
203       profile_->GetPrefs(),
204       base::Bind(&CaptivePortalService::UpdateEnabledState,
205                  base::Unretained(this)));
206   ResetBackoffEntry(last_detection_result_);
207
208   UpdateEnabledState();
209 }
210
211 CaptivePortalService::~CaptivePortalService() {
212 }
213
214 void CaptivePortalService::DetectCaptivePortal() {
215   DCHECK(CalledOnValidThread());
216
217   // If a request is pending or running, do nothing.
218   if (state_ == STATE_CHECKING_FOR_PORTAL || state_ == STATE_TIMER_RUNNING)
219     return;
220
221   base::TimeDelta time_until_next_check = backoff_entry_->GetTimeUntilRelease();
222
223   // Start asynchronously.
224   state_ = STATE_TIMER_RUNNING;
225   check_captive_portal_timer_.Start(
226       FROM_HERE,
227       time_until_next_check,
228       this,
229       &CaptivePortalService::DetectCaptivePortalInternal);
230 }
231
232 void CaptivePortalService::DetectCaptivePortalInternal() {
233   DCHECK(CalledOnValidThread());
234   DCHECK(state_ == STATE_TIMER_RUNNING || state_ == STATE_IDLE);
235   DCHECK(!TimerRunning());
236
237   state_ = STATE_CHECKING_FOR_PORTAL;
238
239   // When not enabled, just claim there's an Internet connection.
240   if (!enabled_) {
241     // Count this as a success, so the backoff entry won't apply exponential
242     // backoff, but will apply the standard delay.
243     backoff_entry_->InformOfRequest(true);
244     OnResult(captive_portal::RESULT_INTERNET_CONNECTED);
245     return;
246   }
247
248   captive_portal_detector_.DetectCaptivePortal(
249       test_url_, base::Bind(
250           &CaptivePortalService::OnPortalDetectionCompleted,
251           base::Unretained(this)));
252 }
253
254 void CaptivePortalService::OnPortalDetectionCompleted(
255     const captive_portal::CaptivePortalDetector::Results& results) {
256   DCHECK(CalledOnValidThread());
257   DCHECK_EQ(STATE_CHECKING_FOR_PORTAL, state_);
258   DCHECK(!TimerRunning());
259   DCHECK(enabled_);
260
261   CaptivePortalResult result = results.result;
262   const base::TimeDelta& retry_after_delta = results.retry_after_delta;
263   base::TimeTicks now = GetCurrentTimeTicks();
264
265   // Record histograms.
266   UMA_HISTOGRAM_ENUMERATION("CaptivePortal.DetectResult",
267                             GetHistogramEntryForDetectionResult(results),
268                             DETECTION_RESULT_COUNT);
269
270   // If this isn't the first captive portal result, record stats.
271   if (!last_check_time_.is_null()) {
272     UMA_HISTOGRAM_LONG_TIMES("CaptivePortal.TimeBetweenChecks",
273                              now - last_check_time_);
274
275     if (last_detection_result_ != result) {
276       // If the last result was different from the result of the latest test,
277       // record histograms about the previous period over which the result was
278       // the same.
279       RecordRepeatHistograms(last_detection_result_,
280                              num_checks_with_same_result_,
281                              now - first_check_time_with_same_result_);
282     }
283   }
284
285   if (last_check_time_.is_null() || result != last_detection_result_) {
286     first_check_time_with_same_result_ = now;
287     num_checks_with_same_result_ = 1;
288
289     // Reset the backoff entry both to update the default time and clear
290     // previous failures.
291     ResetBackoffEntry(result);
292
293     backoff_entry_->SetCustomReleaseTime(now + retry_after_delta);
294     // The BackoffEntry is not informed of this request, so there's no delay
295     // before the next request.  This allows for faster login when a captive
296     // portal is first detected.  It can also help when moving between captive
297     // portals.
298   } else {
299     DCHECK_LE(1, num_checks_with_same_result_);
300     ++num_checks_with_same_result_;
301
302     // Requests that have the same Result as the last one are considered
303     // "failures", to trigger backoff.
304     backoff_entry_->SetCustomReleaseTime(now + retry_after_delta);
305     backoff_entry_->InformOfRequest(false);
306   }
307
308   last_check_time_ = now;
309
310   OnResult(result);
311 }
312
313 void CaptivePortalService::Shutdown() {
314   DCHECK(CalledOnValidThread());
315   if (enabled_) {
316     RecordRepeatHistograms(
317         last_detection_result_,
318         num_checks_with_same_result_,
319         GetCurrentTimeTicks() - first_check_time_with_same_result_);
320   }
321 }
322
323 void CaptivePortalService::OnResult(CaptivePortalResult result) {
324   DCHECK_EQ(STATE_CHECKING_FOR_PORTAL, state_);
325   state_ = STATE_IDLE;
326
327   Results results;
328   results.previous_result = last_detection_result_;
329   results.result = result;
330   last_detection_result_ = result;
331
332   content::NotificationService::current()->Notify(
333       chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
334       content::Source<Profile>(profile_),
335       content::Details<Results>(&results));
336 }
337
338 void CaptivePortalService::ResetBackoffEntry(CaptivePortalResult result) {
339   if (!enabled_ || result == captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL) {
340     // Use the shorter time when the captive portal service is not enabled, or
341     // behind a captive portal.
342     recheck_policy_.backoff_policy.initial_delay_ms =
343         recheck_policy_.initial_backoff_portal_ms;
344   } else {
345     recheck_policy_.backoff_policy.initial_delay_ms =
346         recheck_policy_.initial_backoff_no_portal_ms;
347   }
348
349   backoff_entry_.reset(new RecheckBackoffEntry(this));
350 }
351
352 void CaptivePortalService::UpdateEnabledState() {
353   DCHECK(CalledOnValidThread());
354   bool enabled_before = enabled_;
355   enabled_ = testing_state_ != DISABLED_FOR_TESTING &&
356              resolve_errors_with_web_service_.GetValue();
357
358   if (testing_state_ != SKIP_OS_CHECK_FOR_TESTING &&
359       ShouldDeferToNativeCaptivePortalDetection()) {
360     enabled_ = false;
361   }
362
363   if (enabled_before == enabled_)
364     return;
365
366   // Clear data used for histograms.
367   num_checks_with_same_result_ = 0;
368   first_check_time_with_same_result_ = base::TimeTicks();
369   last_check_time_ = base::TimeTicks();
370
371   ResetBackoffEntry(last_detection_result_);
372
373   if (state_ == STATE_CHECKING_FOR_PORTAL || state_ == STATE_TIMER_RUNNING) {
374     // If a captive portal check was running or pending, cancel check
375     // and the timer.
376     check_captive_portal_timer_.Stop();
377     captive_portal_detector_.Cancel();
378     state_ = STATE_IDLE;
379
380     // Since a captive portal request was queued or running, something may be
381     // expecting to receive a captive portal result.
382     DetectCaptivePortal();
383   }
384 }
385
386 base::TimeTicks CaptivePortalService::GetCurrentTimeTicks() const {
387   if (time_ticks_for_testing_.is_null())
388     return base::TimeTicks::Now();
389   else
390     return time_ticks_for_testing_;
391 }
392
393 bool CaptivePortalService::DetectionInProgress() const {
394   return state_ == STATE_CHECKING_FOR_PORTAL;
395 }
396
397 bool CaptivePortalService::TimerRunning() const {
398   return check_captive_portal_timer_.IsRunning();
399 }