/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 |