Source code

Revision control

Other Tools

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