Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/netwerk/base/CaptivePortalService.cpp
Line
Count
Source (jump to first uncovered line)
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "mozilla/net/CaptivePortalService.h"
6
#include "mozilla/ClearOnShutdown.h"
7
#include "mozilla/Services.h"
8
#include "mozilla/Preferences.h"
9
#include "nsIObserverService.h"
10
#include "nsServiceManagerUtils.h"
11
#include "nsXULAppAPI.h"
12
#include "xpcpublic.h"
13
14
static NS_NAMED_LITERAL_STRING(kInterfaceName, u"captive-portal-inteface");
15
16
static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login";
17
static const char kAbortCaptivePortalLoginEvent[] = "captive-portal-login-abort";
18
static const char kCaptivePortalLoginSuccessEvent[] = "captive-portal-login-success";
19
20
static const uint32_t kDefaultInterval = 60*1000; // check every 60 seconds
21
22
namespace mozilla {
23
namespace net {
24
25
static LazyLogModule gCaptivePortalLog("CaptivePortalService");
26
#undef LOG
27
6
#define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args)
28
29
NS_IMPL_ISUPPORTS(CaptivePortalService, nsICaptivePortalService, nsIObserver,
30
                  nsISupportsWeakReference, nsITimerCallback,
31
                  nsICaptivePortalCallback, nsINamed)
32
33
static StaticRefPtr<CaptivePortalService> gCPService;
34
35
// static
36
already_AddRefed<nsICaptivePortalService>
37
CaptivePortalService::GetSingleton()
38
3
{
39
3
  if (gCPService) {
40
0
    return do_AddRef(gCPService);
41
0
  }
42
3
43
3
  gCPService = new CaptivePortalService();
44
3
  ClearOnShutdown(&gCPService);
45
3
  return do_AddRef(gCPService);
46
3
}
47
48
CaptivePortalService::CaptivePortalService()
49
  : mState(UNKNOWN)
50
  , mStarted(false)
51
  , mInitialized(false)
52
  , mRequestInProgress(false)
53
  , mEverBeenCaptive(false)
54
  , mDelay(kDefaultInterval)
55
  , mSlackCount(0)
56
  , mMinInterval(kDefaultInterval)
57
  , mMaxInterval(25*kDefaultInterval)
58
  , mBackoffFactor(5.0)
59
3
{
60
3
  mLastChecked = TimeStamp::Now();
61
3
}
62
63
CaptivePortalService::~CaptivePortalService()
64
0
{
65
0
  LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n",
66
0
       XRE_GetProcessType() == GeckoProcessType_Default));
67
0
}
68
69
nsresult
70
CaptivePortalService::PerformCheck()
71
0
{
72
0
  LOG(("CaptivePortalService::PerformCheck mRequestInProgress:%d mInitialized:%d mStarted:%d\n",
73
0
        mRequestInProgress, mInitialized, mStarted));
74
0
  // Don't issue another request if last one didn't complete
75
0
  if (mRequestInProgress || !mInitialized || !mStarted) {
76
0
    return NS_OK;
77
0
  }
78
0
  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
79
0
  nsresult rv;
80
0
  if (!mCaptivePortalDetector) {
81
0
    mCaptivePortalDetector =
82
0
      do_GetService("@mozilla.org/toolkit/captive-detector;1", &rv);
83
0
    if (NS_FAILED(rv)) {
84
0
        LOG(("Unable to get a captive portal detector\n"));
85
0
        return rv;
86
0
    }
87
0
  }
88
0
89
0
  LOG(("CaptivePortalService::PerformCheck - Calling CheckCaptivePortal\n"));
90
0
  mRequestInProgress = true;
91
0
  mCaptivePortalDetector->CheckCaptivePortal(kInterfaceName, this);
92
0
  return NS_OK;
93
0
}
94
95
nsresult
96
CaptivePortalService::RearmTimer()
97
0
{
98
0
  LOG(("CaptivePortalService::RearmTimer\n"));
99
0
  // Start a timer to recheck
100
0
  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
101
0
  if (mTimer) {
102
0
    mTimer->Cancel();
103
0
  }
104
0
105
0
  // If we have successfully determined the state, and we have never detected
106
0
  // a captive portal, we don't need to keep polling, but will rely on events
107
0
  // to trigger detection.
108
0
  if (mState == NOT_CAPTIVE) {
109
0
    return NS_OK;
110
0
  }
111
0
112
0
  if (!mTimer) {
113
0
    mTimer = NS_NewTimer();
114
0
  }
115
0
116
0
  if (mTimer && mDelay > 0) {
117
0
    LOG(("CaptivePortalService - Reloading timer with delay %u\n", mDelay));
118
0
    return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
119
0
  }
120
0
121
0
  return NS_OK;
122
0
}
123
124
nsresult
125
CaptivePortalService::Initialize()
126
3
{
127
3
  if (mInitialized) {
128
0
    return NS_OK;
129
0
  }
130
3
  mInitialized = true;
131
3
132
3
  // Only the main process service should actually do anything. The service in
133
3
  // the content process only mirrors the CP state in the main process.
134
3
  if (XRE_GetProcessType() != GeckoProcessType_Default) {
135
0
    return NS_OK;
136
0
  }
137
3
138
3
  nsCOMPtr<nsIObserverService> observerService =
139
3
    mozilla::services::GetObserverService();
140
3
  if (observerService) {
141
3
    observerService->AddObserver(this, kOpenCaptivePortalLoginEvent, true);
142
3
    observerService->AddObserver(this, kAbortCaptivePortalLoginEvent, true);
143
3
    observerService->AddObserver(this, kCaptivePortalLoginSuccessEvent, true);
144
3
  }
145
3
146
3
  LOG(("Initialized CaptivePortalService\n"));
147
3
  return NS_OK;
148
3
}
149
150
nsresult
151
CaptivePortalService::Start()
152
0
{
153
0
  if (!mInitialized) {
154
0
    return NS_ERROR_NOT_INITIALIZED;
155
0
  }
156
0
157
0
  if (xpc::AreNonLocalConnectionsDisabled()
158
0
      && !Preferences::GetBool("network.captive-portal-service.testMode", false)) {
159
0
    return NS_ERROR_NOT_AVAILABLE;
160
0
  }
161
0
162
0
  if (XRE_GetProcessType() != GeckoProcessType_Default) {
163
0
    // Doesn't do anything if called in the content process.
164
0
    return NS_OK;
165
0
  }
166
0
167
0
  if (mStarted) {
168
0
    return NS_OK;
169
0
  }
170
0
171
0
  MOZ_ASSERT(mState == UNKNOWN, "Initial state should be UNKNOWN");
172
0
  mStarted = true;
173
0
  mEverBeenCaptive = false;
174
0
175
0
  // Get the delay prefs
176
0
  Preferences::GetUint("network.captive-portal-service.minInterval", &mMinInterval);
177
0
  Preferences::GetUint("network.captive-portal-service.maxInterval", &mMaxInterval);
178
0
  Preferences::GetFloat("network.captive-portal-service.backoffFactor", &mBackoffFactor);
179
0
180
0
  LOG(("CaptivePortalService::Start min:%u max:%u backoff:%.2f\n",
181
0
       mMinInterval, mMaxInterval, mBackoffFactor));
182
0
183
0
  mSlackCount = 0;
184
0
  mDelay = mMinInterval;
185
0
186
0
  // When Start is called, perform a check immediately
187
0
  PerformCheck();
188
0
  RearmTimer();
189
0
  return NS_OK;
190
0
}
191
192
nsresult
193
CaptivePortalService::Stop()
194
3
{
195
3
  LOG(("CaptivePortalService::Stop\n"));
196
3
197
3
  if (XRE_GetProcessType() != GeckoProcessType_Default) {
198
0
    // Doesn't do anything when called in the content process.
199
0
    return NS_OK;
200
0
  }
201
3
202
3
  if (!mStarted) {
203
3
    return NS_OK;
204
3
  }
205
0
206
0
  if (mTimer) {
207
0
    mTimer->Cancel();
208
0
  }
209
0
  mTimer = nullptr;
210
0
  mRequestInProgress = false;
211
0
  mStarted = false;
212
0
  if (mCaptivePortalDetector) {
213
0
    mCaptivePortalDetector->Abort(kInterfaceName);
214
0
  }
215
0
  mCaptivePortalDetector = nullptr;
216
0
217
0
  // Clear the state in case anyone queries the state while detection is off.
218
0
  mState = UNKNOWN;
219
0
  return NS_OK;
220
0
}
221
222
void
223
CaptivePortalService::SetStateInChild(int32_t aState)
224
0
{
225
0
  // This should only be called in the content process, from ContentChild.cpp
226
0
  // in order to mirror the captive portal state set in the chrome process.
227
0
  MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default);
228
0
229
0
  mState = aState;
230
0
  mLastChecked = TimeStamp::Now();
231
0
}
232
233
//-----------------------------------------------------------------------------
234
// CaptivePortalService::nsICaptivePortalService
235
//-----------------------------------------------------------------------------
236
237
NS_IMETHODIMP
238
CaptivePortalService::GetState(int32_t *aState)
239
3
{
240
3
  *aState = mState;
241
3
  return NS_OK;
242
3
}
243
244
NS_IMETHODIMP
245
CaptivePortalService::RecheckCaptivePortal()
246
0
{
247
0
  LOG(("CaptivePortalService::RecheckCaptivePortal\n"));
248
0
249
0
  if (XRE_GetProcessType() != GeckoProcessType_Default) {
250
0
    // Doesn't do anything if called in the content process.
251
0
    return NS_OK;
252
0
  }
253
0
254
0
  // This is called for user activity. We need to reset the slack count,
255
0
  // so the checks continue to be quite frequent.
256
0
  mSlackCount = 0;
257
0
  mDelay = mMinInterval;
258
0
259
0
  PerformCheck();
260
0
  RearmTimer();
261
0
  return NS_OK;
262
0
}
263
264
NS_IMETHODIMP
265
CaptivePortalService::GetLastChecked(uint64_t *aLastChecked)
266
0
{
267
0
  double duration = (TimeStamp::Now() - mLastChecked).ToMilliseconds();
268
0
  *aLastChecked = static_cast<uint64_t>(duration);
269
0
  return NS_OK;
270
0
}
271
272
//-----------------------------------------------------------------------------
273
// CaptivePortalService::nsITimer
274
// This callback gets called every mDelay miliseconds
275
// It issues a checkCaptivePortal operation if one isn't already in progress
276
//-----------------------------------------------------------------------------
277
NS_IMETHODIMP
278
CaptivePortalService::Notify(nsITimer *aTimer)
279
0
{
280
0
  LOG(("CaptivePortalService::Notify\n"));
281
0
  MOZ_ASSERT(aTimer == mTimer);
282
0
  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
283
0
284
0
  PerformCheck();
285
0
286
0
  // This is needed because we don't want to always make requests very often.
287
0
  // Every 10 checks, we the delay is increased mBackoffFactor times
288
0
  // to a maximum delay of mMaxInterval
289
0
  mSlackCount++;
290
0
  if (mSlackCount % 10 == 0) {
291
0
    mDelay = mDelay * mBackoffFactor;
292
0
  }
293
0
  if (mDelay > mMaxInterval) {
294
0
    mDelay = mMaxInterval;
295
0
  }
296
0
297
0
  // Note - if mDelay is 0, the timer will not be rearmed.
298
0
  RearmTimer();
299
0
300
0
  return NS_OK;
301
0
}
302
303
//-----------------------------------------------------------------------------
304
// CaptivePortalService::nsINamed
305
//-----------------------------------------------------------------------------
306
307
NS_IMETHODIMP
308
CaptivePortalService::GetName(nsACString& aName)
309
0
{
310
0
  aName.AssignLiteral("CaptivePortalService");
311
0
  return NS_OK;
312
0
}
313
314
//-----------------------------------------------------------------------------
315
// CaptivePortalService::nsIObserver
316
//-----------------------------------------------------------------------------
317
NS_IMETHODIMP
318
CaptivePortalService::Observe(nsISupports *aSubject,
319
                              const char * aTopic,
320
                              const char16_t * aData)
321
0
{
322
0
  if (XRE_GetProcessType() != GeckoProcessType_Default) {
323
0
    // Doesn't do anything if called in the content process.
324
0
    return NS_OK;
325
0
  }
326
0
327
0
  LOG(("CaptivePortalService::Observe() topic=%s\n", aTopic));
328
0
  if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) {
329
0
    // A redirect or altered content has been detected.
330
0
    // The user needs to log in. We are in a captive portal.
331
0
    mState = LOCKED_PORTAL;
332
0
    mLastChecked = TimeStamp::Now();
333
0
    mEverBeenCaptive = true;
334
0
  } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) {
335
0
    // The user has successfully logged in. We have connectivity.
336
0
    mState = UNLOCKED_PORTAL;
337
0
    mLastChecked = TimeStamp::Now();
338
0
    mSlackCount = 0;
339
0
    mDelay = mMinInterval;
340
0
341
0
    RearmTimer();
342
0
  } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) {
343
0
    // The login has been aborted
344
0
    mState = UNKNOWN;
345
0
    mLastChecked = TimeStamp::Now();
346
0
    mSlackCount = 0;
347
0
  }
348
0
349
0
  // Send notification so that the captive portal state is mirrored in the
350
0
  // content process.
351
0
  nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
352
0
  if (observerService) {
353
0
    nsCOMPtr<nsICaptivePortalService> cps(this);
354
0
    observerService->NotifyObservers(cps, NS_IPC_CAPTIVE_PORTAL_SET_STATE, nullptr);
355
0
  }
356
0
357
0
  return NS_OK;
358
0
}
359
360
void
361
CaptivePortalService::NotifyConnectivityAvailable(bool aCaptive)
362
0
{
363
0
  nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
364
0
  if (observerService) {
365
0
    nsCOMPtr<nsICaptivePortalService> cps(this);
366
0
    observerService->NotifyObservers(cps, NS_CAPTIVE_PORTAL_CONNECTIVITY,
367
0
                                     aCaptive ? u"captive" : u"clear");
368
0
  }
369
0
}
370
371
//-----------------------------------------------------------------------------
372
// CaptivePortalService::nsICaptivePortalCallback
373
//-----------------------------------------------------------------------------
374
NS_IMETHODIMP
375
CaptivePortalService::Prepare()
376
0
{
377
0
  LOG(("CaptivePortalService::Prepare\n"));
378
0
  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
379
0
  // XXX: Finish preparation shouldn't be called until dns and routing is available.
380
0
  if (mCaptivePortalDetector) {
381
0
    mCaptivePortalDetector->FinishPreparation(kInterfaceName);
382
0
  }
383
0
  return NS_OK;
384
0
}
385
386
NS_IMETHODIMP
387
CaptivePortalService::Complete(bool success)
388
0
{
389
0
  LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success, mState));
390
0
  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
391
0
  mLastChecked = TimeStamp::Now();
392
0
393
0
  // Note: this callback gets called when:
394
0
  // 1. the request is completed, and content is valid (success == true)
395
0
  // 2. when the request is aborted or times out (success == false)
396
0
397
0
  if (success) {
398
0
    if (mEverBeenCaptive) {
399
0
      mState = UNLOCKED_PORTAL;
400
0
      NotifyConnectivityAvailable(true);
401
0
    } else {
402
0
      mState = NOT_CAPTIVE;
403
0
      NotifyConnectivityAvailable(false);
404
0
    }
405
0
  }
406
0
407
0
  mRequestInProgress = false;
408
0
  return NS_OK;
409
0
}
410
411
} // namespace net
412
} // namespace mozilla