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