1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "nsSecureBrowserUIImpl.h"
7
8 #include "imgIRequest.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/IntegerPrintfMacros.h"
11 #include "mozilla/Logging.h"
12 #include "nsCURILoader.h"
13 #include "nsIAssociatedContentSecurity.h"
14 #include "nsIChannel.h"
15 #include "nsIDOMWindow.h"
16 #include "nsIDocShell.h"
17 #include "nsIDocShellTreeItem.h"
18 #include "nsIDocument.h"
19 #include "nsIFTPChannel.h"
20 #include "nsIFileChannel.h"
21 #include "nsIHttpChannel.h"
22 #include "nsIInterfaceRequestorUtils.h"
23 #include "nsIProtocolHandler.h"
24 #include "nsISSLStatus.h"
25 #include "nsISecurityInfoProvider.h"
26 #include "nsIServiceManager.h"
27 #include "nsITransportSecurityInfo.h"
28 #include "nsIWebProgress.h"
29 #include "nsIWyciwygChannel.h"
30 #include "nsNetCID.h"
31 #include "nsNetUtil.h"
32 #include "nsPIDOMWindow.h"
33 #include "nsThreadUtils.h"
34 #include "nspr.h"
35 #include "nsString.h"
36
37 using namespace mozilla;
38
39 LazyLogModule gSecureDocLog("nsSecureBrowserUI");
40
41 struct RequestHashEntry : PLDHashEntryHdr {
42 void* r;
43 };
44
RequestMapMatchEntry(const PLDHashEntryHdr * hdr,const void * key)45 static bool RequestMapMatchEntry(const PLDHashEntryHdr* hdr, const void* key) {
46 const RequestHashEntry* entry = static_cast<const RequestHashEntry*>(hdr);
47 return entry->r == key;
48 }
49
RequestMapInitEntry(PLDHashEntryHdr * hdr,const void * key)50 static void RequestMapInitEntry(PLDHashEntryHdr* hdr, const void* key) {
51 RequestHashEntry* entry = static_cast<RequestHashEntry*>(hdr);
52 entry->r = (void*)key;
53 }
54
55 static const PLDHashTableOps gMapOps = {
56 PLDHashTable::HashVoidPtrKeyStub, RequestMapMatchEntry,
57 PLDHashTable::MoveEntryStub, PLDHashTable::ClearEntryStub,
58 RequestMapInitEntry};
59
nsSecureBrowserUIImpl()60 nsSecureBrowserUIImpl::nsSecureBrowserUIImpl()
61 : mNotifiedSecurityState(lis_no_security),
62 mNotifiedToplevelIsEV(false),
63 mNewToplevelSecurityState(STATE_IS_INSECURE),
64 mNewToplevelIsEV(false),
65 mNewToplevelSecurityStateKnown(true),
66 mIsViewSource(false),
67 mSubRequestsBrokenSecurity(0),
68 mSubRequestsNoSecurity(0),
69 mCertUserOverridden(false),
70 mRestoreSubrequests(false),
71 mOnLocationChangeSeen(false)
72 #ifdef DEBUG
73 ,
74 mEntered(false)
75 #endif
76 ,
77 mTransferringRequests(&gMapOps, sizeof(RequestHashEntry)) {
78 MOZ_ASSERT(NS_IsMainThread());
79
80 ResetStateTracking();
81 }
82
NS_IMPL_ISUPPORTS(nsSecureBrowserUIImpl,nsISecureBrowserUI,nsIWebProgressListener,nsISupportsWeakReference,nsISSLStatusProvider)83 NS_IMPL_ISUPPORTS(nsSecureBrowserUIImpl, nsISecureBrowserUI,
84 nsIWebProgressListener, nsISupportsWeakReference,
85 nsISSLStatusProvider)
86
87 NS_IMETHODIMP
88 nsSecureBrowserUIImpl::Init(mozIDOMWindowProxy* aWindow) {
89 MOZ_ASSERT(NS_IsMainThread());
90
91 if (MOZ_LOG_TEST(gSecureDocLog, LogLevel::Debug)) {
92 nsCOMPtr<nsIDOMWindow> window(do_QueryReferent(mWindow));
93
94 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
95 ("SecureUI:%p: Init: mWindow: %p, aWindow: %p\n", this,
96 window.get(), aWindow));
97 }
98
99 if (!aWindow) {
100 NS_WARNING("Null window passed to nsSecureBrowserUIImpl::Init()");
101 return NS_ERROR_INVALID_ARG;
102 }
103
104 if (mWindow) {
105 NS_WARNING("Trying to init an nsSecureBrowserUIImpl twice");
106 return NS_ERROR_ALREADY_INITIALIZED;
107 }
108
109 nsresult rv;
110 mWindow = do_GetWeakReference(aWindow, &rv);
111 NS_ENSURE_SUCCESS(rv, rv);
112
113 auto* piwindow = nsPIDOMWindowOuter::From(aWindow);
114 nsIDocShell* docShell = piwindow->GetDocShell();
115
116 // The Docshell will own the SecureBrowserUI object
117 if (!docShell) return NS_ERROR_FAILURE;
118
119 docShell->SetSecurityUI(this);
120
121 /* GetWebProgress(mWindow) */
122 // hook up to the webprogress notifications.
123 nsCOMPtr<nsIWebProgress> wp(do_GetInterface(docShell));
124 if (!wp) return NS_ERROR_FAILURE;
125 /* end GetWebProgress */
126
127 wp->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
128 nsIWebProgress::NOTIFY_STATE_ALL |
129 nsIWebProgress::NOTIFY_LOCATION |
130 nsIWebProgress::NOTIFY_SECURITY);
131
132 return NS_OK;
133 }
134
135 NS_IMETHODIMP
GetState(uint32_t * aState)136 nsSecureBrowserUIImpl::GetState(uint32_t* aState) {
137 MOZ_ASSERT(NS_IsMainThread());
138 return MapInternalToExternalState(aState, mNotifiedSecurityState,
139 mNotifiedToplevelIsEV);
140 }
141
142 // static
ExtractSecurityInfo(nsIRequest * aRequest)143 already_AddRefed<nsISupports> nsSecureBrowserUIImpl::ExtractSecurityInfo(
144 nsIRequest* aRequest) {
145 nsCOMPtr<nsISupports> retval;
146 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
147 if (channel) channel->GetSecurityInfo(getter_AddRefs(retval));
148
149 if (!retval) {
150 nsCOMPtr<nsISecurityInfoProvider> provider(do_QueryInterface(aRequest));
151 if (provider) provider->GetSecurityInfo(getter_AddRefs(retval));
152 }
153
154 return retval.forget();
155 }
156
MapInternalToExternalState(uint32_t * aState,lockIconState lock,bool ev)157 nsresult nsSecureBrowserUIImpl::MapInternalToExternalState(uint32_t* aState,
158 lockIconState lock,
159 bool ev) {
160 NS_ENSURE_ARG(aState);
161
162 switch (lock) {
163 case lis_broken_security:
164 *aState = STATE_IS_BROKEN;
165 break;
166
167 case lis_mixed_security:
168 *aState = STATE_IS_BROKEN;
169 break;
170
171 case lis_high_security:
172 *aState = STATE_IS_SECURE | STATE_SECURE_HIGH;
173 break;
174
175 default:
176 case lis_no_security:
177 *aState = STATE_IS_INSECURE;
178 break;
179 }
180
181 if (ev && (*aState & STATE_IS_SECURE))
182 *aState |= nsIWebProgressListener::STATE_IDENTITY_EV_TOPLEVEL;
183
184 if (mCertUserOverridden && (*aState & STATE_IS_SECURE)) {
185 *aState |= nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN;
186 }
187
188 nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
189 if (!docShell) return NS_OK;
190
191 // For content docShell's, the mixed content security state is set on the root
192 // docShell.
193 if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
194 nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(do_QueryInterface(docShell));
195 nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
196 docShellTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
197 MOZ_ASSERT(
198 sameTypeRoot,
199 "No document shell root tree item from document shell tree item!");
200 docShell = do_QueryInterface(sameTypeRoot);
201 if (!docShell) return NS_OK;
202 }
203
204 // Has a Mixed Content Load initiated in nsMixedContentBlocker?
205 // * If not, the state should not be broken because of mixed content;
206 // overriding the previous state if it is inaccurately flagged as mixed.
207 if (lock == lis_mixed_security &&
208 !docShell->GetHasMixedActiveContentLoaded() &&
209 !docShell->GetHasMixedDisplayContentLoaded() &&
210 !docShell->GetHasMixedActiveContentBlocked() &&
211 !docShell->GetHasMixedDisplayContentBlocked()) {
212 *aState = STATE_IS_SECURE;
213 if (ev) {
214 *aState |= nsIWebProgressListener::STATE_IDENTITY_EV_TOPLEVEL;
215 }
216 }
217 // * If so, the state should be broken or insecure; overriding the previous
218 // state set by the lock parameter.
219 uint32_t tempState = STATE_IS_BROKEN;
220 if (lock == lis_no_security) {
221 // this is to ensure that http: pages with mixed content in nested
222 // iframes don't get marked as broken instead of insecure
223 tempState = STATE_IS_INSECURE;
224 }
225 if (docShell->GetHasMixedActiveContentLoaded() &&
226 docShell->GetHasMixedDisplayContentLoaded()) {
227 *aState = tempState |
228 nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
229 nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
230 } else if (docShell->GetHasMixedActiveContentLoaded()) {
231 *aState =
232 tempState | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
233 } else if (docShell->GetHasMixedDisplayContentLoaded()) {
234 *aState =
235 tempState | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
236 }
237
238 if (mCertUserOverridden) {
239 *aState |= nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN;
240 }
241
242 // Has Mixed Content Been Blocked in nsMixedContentBlocker?
243 if (docShell->GetHasMixedActiveContentBlocked())
244 *aState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
245
246 if (docShell->GetHasMixedDisplayContentBlocked())
247 *aState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT;
248
249 // Has Tracking Content been Blocked?
250 if (docShell->GetHasTrackingContentBlocked())
251 *aState |= nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT;
252
253 if (docShell->GetHasTrackingContentLoaded())
254 *aState |= nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT;
255
256 // Copy forward any diagnostic flags for downstream use (e.g., warnings)
257 if (mNewToplevelSecurityStateKnown &&
258 mNewToplevelSecurityState & STATE_CERT_DISTRUST_IMMINENT) {
259 *aState |= nsIWebProgressListener::STATE_CERT_DISTRUST_IMMINENT;
260 }
261
262 return NS_OK;
263 }
264
265 NS_IMETHODIMP
SetDocShell(nsIDocShell * aDocShell)266 nsSecureBrowserUIImpl::SetDocShell(nsIDocShell* aDocShell) {
267 MOZ_ASSERT(NS_IsMainThread());
268 nsresult rv;
269 mDocShell = do_GetWeakReference(aDocShell, &rv);
270 return rv;
271 }
272
GetSecurityStateFromSecurityInfoAndRequest(nsISupports * info,nsIRequest * request)273 static uint32_t GetSecurityStateFromSecurityInfoAndRequest(
274 nsISupports* info, nsIRequest* request) {
275 nsresult res;
276 uint32_t securityState;
277
278 nsCOMPtr<nsITransportSecurityInfo> psmInfo(do_QueryInterface(info));
279 if (!psmInfo) {
280 MOZ_LOG(
281 gSecureDocLog, LogLevel::Debug,
282 ("SecureUI: GetSecurityState: - no nsITransportSecurityInfo for %p\n",
283 (nsISupports*)info));
284 return nsIWebProgressListener::STATE_IS_INSECURE;
285 }
286 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
287 ("SecureUI: GetSecurityState: - info is %p\n", (nsISupports*)info));
288
289 res = psmInfo->GetSecurityState(&securityState);
290 if (NS_FAILED(res)) {
291 MOZ_LOG(
292 gSecureDocLog, LogLevel::Debug,
293 ("SecureUI: GetSecurityState: - GetSecurityState failed: %" PRIu32 "\n",
294 static_cast<uint32_t>(res)));
295 securityState = nsIWebProgressListener::STATE_IS_BROKEN;
296 }
297
298 if (securityState != nsIWebProgressListener::STATE_IS_INSECURE) {
299 // A secure connection does not yield a secure per-uri channel if the
300 // scheme is plain http.
301
302 nsCOMPtr<nsIURI> uri;
303 nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
304 if (channel) {
305 channel->GetURI(getter_AddRefs(uri));
306 } else {
307 nsCOMPtr<imgIRequest> imgRequest(do_QueryInterface(request));
308 if (imgRequest) {
309 imgRequest->GetURI(getter_AddRefs(uri));
310 }
311 }
312 if (uri) {
313 bool isHttp, isFtp;
314 if ((NS_SUCCEEDED(uri->SchemeIs("http", &isHttp)) && isHttp) ||
315 (NS_SUCCEEDED(uri->SchemeIs("ftp", &isFtp)) && isFtp)) {
316 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
317 ("SecureUI: GetSecurityState: - "
318 "channel scheme is insecure.\n"));
319 securityState = nsIWebProgressListener::STATE_IS_INSECURE;
320 }
321 }
322 }
323
324 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
325 ("SecureUI: GetSecurityState: - Returning %d\n", securityState));
326 return securityState;
327 }
328
329 // nsIWebProgressListener
330 NS_IMETHODIMP
OnProgressChange(nsIWebProgress *,nsIRequest *,int32_t,int32_t,int32_t,int32_t)331 nsSecureBrowserUIImpl::OnProgressChange(nsIWebProgress*, nsIRequest*, int32_t,
332 int32_t, int32_t, int32_t) {
333 MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
334 return NS_OK;
335 }
336
ResetStateTracking()337 void nsSecureBrowserUIImpl::ResetStateTracking() {
338 mDocumentRequestsInProgress = 0;
339 mTransferringRequests.Clear();
340 }
341
EvaluateAndUpdateSecurityState(nsIRequest * aRequest,nsISupports * info,bool withNewLocation,bool withNewSink)342 void nsSecureBrowserUIImpl::EvaluateAndUpdateSecurityState(nsIRequest* aRequest,
343 nsISupports* info,
344 bool withNewLocation,
345 bool withNewSink) {
346 mNewToplevelIsEV = false;
347
348 bool updateStatus = false;
349 nsCOMPtr<nsISSLStatus> temp_SSLStatus;
350
351 mNewToplevelSecurityState =
352 GetSecurityStateFromSecurityInfoAndRequest(info, aRequest);
353
354 MOZ_LOG(
355 gSecureDocLog, LogLevel::Debug,
356 ("SecureUI:%p: OnStateChange: remember mNewToplevelSecurityState => %x\n",
357 this, mNewToplevelSecurityState));
358
359 nsCOMPtr<nsISSLStatusProvider> sp(do_QueryInterface(info));
360 if (sp) {
361 // Ignore result
362 updateStatus = true;
363 (void)sp->GetSSLStatus(getter_AddRefs(temp_SSLStatus));
364 if (temp_SSLStatus) {
365 bool aTemp;
366 if (NS_SUCCEEDED(temp_SSLStatus->GetIsExtendedValidation(&aTemp))) {
367 mNewToplevelIsEV = aTemp;
368 }
369 }
370 }
371
372 mNewToplevelSecurityStateKnown = true;
373 if (updateStatus) {
374 mSSLStatus = temp_SSLStatus;
375 }
376 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
377 ("SecureUI:%p: remember securityInfo %p\n", this, info));
378 nsCOMPtr<nsIAssociatedContentSecurity> associatedContentSecurityFromRequest(
379 do_QueryInterface(aRequest));
380 if (associatedContentSecurityFromRequest) {
381 mCurrentToplevelSecurityInfo = aRequest;
382 } else {
383 mCurrentToplevelSecurityInfo = info;
384 }
385
386 // The subrequest counters are now in sync with mCurrentToplevelSecurityInfo,
387 // don't restore after top level document load finishes.
388 mRestoreSubrequests = false;
389
390 UpdateSecurityState(aRequest, withNewLocation, withNewSink || updateStatus);
391 }
392
UpdateSubrequestMembers(nsISupports * securityInfo,nsIRequest * request)393 void nsSecureBrowserUIImpl::UpdateSubrequestMembers(nsISupports* securityInfo,
394 nsIRequest* request) {
395 // For wyciwyg channels in subdocuments we only update our
396 // subrequest state members.
397 uint32_t reqState =
398 GetSecurityStateFromSecurityInfoAndRequest(securityInfo, request);
399
400 if (reqState & STATE_IS_SECURE) {
401 // do nothing
402 } else if (reqState & STATE_IS_BROKEN) {
403 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
404 ("SecureUI:%p: OnStateChange: subreq BROKEN\n", this));
405 ++mSubRequestsBrokenSecurity;
406 } else {
407 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
408 ("SecureUI:%p: OnStateChange: subreq INSECURE\n", this));
409 ++mSubRequestsNoSecurity;
410 }
411 }
412
413 NS_IMETHODIMP
OnStateChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t aProgressStateFlags,nsresult aStatus)414 nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress,
415 nsIRequest* aRequest,
416 uint32_t aProgressStateFlags,
417 nsresult aStatus) {
418 MOZ_ASSERT(NS_IsMainThread());
419 ReentrancyGuard guard(*this);
420 /*
421 All discussion, unless otherwise mentioned, only refers to
422 http, https, file or wyciwig requests.
423
424
425 Redirects are evil, well, some of them.
426 There are multiple forms of redirects.
427
428 Redirects caused by http refresh content are ok, because experiments show,
429 with those redirects, the old page contents and their requests will come to
430 STOP completely, before any progress from new refreshed page content is
431 reported. So we can safely treat them as separate page loading transactions.
432
433 Evil are redirects at the http protocol level, like code 302.
434
435 If the toplevel documents gets replaced, i.e. redirected with 302, we do not
436 care for the security state of the initial transaction, which has now been
437 redirected, we only care for the new page load.
438
439 For the implementation of the security UI, we make an assumption, that is
440 hopefully true.
441
442 Imagine, the received page that was delivered with the 302 redirection
443 answer, also delivered html content.
444
445 What happens if the parser starts to analyze the content and tries to load
446 contained sub objects?
447
448 In that case we would see start and stop requests for subdocuments, some for
449 the previous document, some for the new target document. And only those for
450 the new toplevel document may be taken into consideration, when deciding
451 about the security state of the next toplevel document.
452
453 Because security state is being looked at, when loading stops for
454 (sub)documents, this could cause real confusion, because we have to decide,
455 whether an incoming progress belongs to the new toplevel page, or the
456 previous, already redirected page.
457
458 Can we simplify here?
459
460 If a redirect at the http protocol level is seen, can we safely assume, its
461 html content will not be parsed, anylzed, and no embedded objects will get
462 loaded (css, js, images), because the redirect is already happening?
463
464 If we can assume that, this really simplify things. Because we will never
465 see notification for sub requests that need to get ignored.
466
467 I would like to make this assumption for now, but please let me (kaie) know
468 if I'm wrong.
469
470 Excurse:
471 If my assumption is wrong, then we would require more tracking
472 information. We need to keep lists of all pointers to request object that
473 had been seen since the last toplevel start event. If the start for a
474 redirected page is seen, the list of releveant object must be cleared, and
475 only progress for requests which start after it must be analyzed. All other
476 events must be ignored, as they belong to now irrelevant previous top level
477 documents.
478
479
480 Frames are also evil.
481
482 First we need a decision.
483 kaie thinks:
484 Only if the toplevel frame is secure, we should try to display secure lock
485 icons. If some of the inner contents are insecure, we display mixed mode.
486
487 But if the top level frame is not secure, why indicate a mixed lock icon
488 at all? I think we should always display an open lock icon, if the top level
489 frameset is insecure.
490
491 That's the way Netscape Communicator behaves, and I think we should do the
492 same.
493
494 The user will not know which parts are secure and which are not,
495 and any certificate information, displayed in the tooltip or in the "page
496 info" will only be relevant for some subframe(s), and the user will not know
497 which ones, so we shouldn't display it as a general attribute of the
498 displayed page.
499
500 Why are frames evil?
501
502 Because the progress for the toplevel frame document is not easily
503 distinguishable from subframes. The same STATE bits are reported.
504
505 While at first sight, when a new page load happens,
506 the toplevel frameset document has also the STATE_IS_NETWORK bit in it.
507 But this can't really be used. Because in case that document causes a http
508 302 redirect, the real top level frameset will no longer have that bit.
509
510 But we need some way to distinguish top level frames from inner frames.
511
512 I saw that the web progress we get delivered has a reference to the toplevel
513 DOM window.
514
515 I suggest, we look at all incoming requests.
516 If a request is NOT for the toplevel DOM window, we will always treat it as
517 a subdocument request, regardless of whether the load flags indicate a top
518 level document.
519 */
520
521 nsCOMPtr<mozIDOMWindowProxy> windowForProgress;
522 aWebProgress->GetDOMWindow(getter_AddRefs(windowForProgress));
523
524 nsCOMPtr<mozIDOMWindowProxy> window(do_QueryReferent(mWindow));
525 MOZ_ASSERT(window, "Window has gone away?!");
526
527 if (!mIOService) {
528 mIOService = do_GetService(NS_IOSERVICE_CONTRACTID);
529 }
530
531 bool isNoContentResponse = false;
532 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
533 if (httpChannel) {
534 uint32_t response;
535 isNoContentResponse =
536 NS_SUCCEEDED(httpChannel->GetResponseStatus(&response)) &&
537 (response == 204 || response == 205);
538 }
539 const bool isToplevelProgress =
540 (windowForProgress.get() == window.get()) && !isNoContentResponse;
541
542 if (windowForProgress) {
543 if (isToplevelProgress) {
544 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
545 ("SecureUI:%p: OnStateChange: progress: for toplevel\n", this));
546 } else {
547 MOZ_LOG(
548 gSecureDocLog, LogLevel::Debug,
549 ("SecureUI:%p: OnStateChange: progress: for something else\n", this));
550 }
551 } else {
552 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
553 ("SecureUI:%p: OnStateChange: progress: no window known\n", this));
554 }
555
556 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
557 ("SecureUI:%p: OnStateChange\n", this));
558
559 if (mIsViewSource) {
560 return NS_OK;
561 }
562
563 if (!aRequest) {
564 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
565 ("SecureUI:%p: OnStateChange with null request\n", this));
566 return NS_ERROR_NULL_POINTER;
567 }
568
569 if (MOZ_LOG_TEST(gSecureDocLog, LogLevel::Debug)) {
570 nsCString reqname;
571 aRequest->GetName(reqname);
572 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
573 ("SecureUI:%p: %p %p OnStateChange %x %s\n", this, aWebProgress,
574 aRequest, aProgressStateFlags, reqname.get()));
575 }
576
577 nsCOMPtr<nsISupports> securityInfo(ExtractSecurityInfo(aRequest));
578
579 nsCOMPtr<nsIURI> uri;
580 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
581 if (channel) {
582 channel->GetURI(getter_AddRefs(uri));
583 }
584
585 nsCOMPtr<imgIRequest> imgRequest(do_QueryInterface(aRequest));
586 if (imgRequest) {
587 MOZ_ASSERT(!channel, "Request channel somehow not available");
588 // for image requests, we get the URI from here
589 imgRequest->GetURI(getter_AddRefs(uri));
590 }
591
592 if (uri) {
593 bool vs;
594 if (NS_SUCCEEDED(uri->SchemeIs("javascript", &vs)) && vs) {
595 // We ignore the progress events for javascript URLs.
596 // If a document loading gets triggered, we will see more events.
597 return NS_OK;
598 }
599 }
600
601 uint32_t loadFlags = 0;
602 aRequest->GetLoadFlags(&loadFlags);
603
604 if (aProgressStateFlags & STATE_START &&
605 aProgressStateFlags & STATE_IS_REQUEST && isToplevelProgress &&
606 loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
607 MOZ_LOG(
608 gSecureDocLog, LogLevel::Debug,
609 ("SecureUI:%p: OnStateChange: SOMETHING STARTS FOR TOPMOST DOCUMENT\n",
610 this));
611 }
612
613 if (aProgressStateFlags & STATE_STOP &&
614 aProgressStateFlags & STATE_IS_REQUEST && isToplevelProgress &&
615 loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
616 MOZ_LOG(
617 gSecureDocLog, LogLevel::Debug,
618 ("SecureUI:%p: OnStateChange: SOMETHING STOPS FOR TOPMOST DOCUMENT\n",
619 this));
620 }
621
622 bool isSubDocumentRelevant = true;
623
624 // We are only interested in requests that load in the browser window...
625 if (!imgRequest) { // is not imgRequest
626 nsCOMPtr<nsIHttpChannel> httpRequest(do_QueryInterface(aRequest));
627 if (!httpRequest) {
628 nsCOMPtr<nsIFileChannel> fileRequest(do_QueryInterface(aRequest));
629 if (!fileRequest) {
630 nsCOMPtr<nsIWyciwygChannel> wyciwygRequest(do_QueryInterface(aRequest));
631 if (!wyciwygRequest) {
632 nsCOMPtr<nsIFTPChannel> ftpRequest(do_QueryInterface(aRequest));
633 if (!ftpRequest) {
634 MOZ_LOG(
635 gSecureDocLog, LogLevel::Debug,
636 ("SecureUI:%p: OnStateChange: not relevant for sub content\n",
637 this));
638 isSubDocumentRelevant = false;
639 }
640 }
641 }
642 }
643 }
644
645 // This will ignore all resource, chrome, data, file, moz-icon, and anno
646 // protocols. Local resources are treated as trusted.
647 if (uri && mIOService) {
648 bool hasFlag;
649 nsresult rv = mIOService->URIChainHasFlags(
650 uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &hasFlag);
651 if (NS_SUCCEEDED(rv) && hasFlag) {
652 isSubDocumentRelevant = false;
653 }
654 }
655
656 #if defined(DEBUG)
657 if (aProgressStateFlags & STATE_STOP && channel) {
658 MOZ_LOG(
659 gSecureDocLog, LogLevel::Debug,
660 ("SecureUI:%p: OnStateChange: seeing STOP with security state: %d\n",
661 this,
662 GetSecurityStateFromSecurityInfoAndRequest(securityInfo, aRequest)));
663 }
664 #endif
665
666 if (aProgressStateFlags & STATE_TRANSFERRING &&
667 aProgressStateFlags & STATE_IS_REQUEST) {
668 // The listing of a request in mTransferringRequests
669 // means, there has already been data transfered.
670 mTransferringRequests.Add(aRequest, fallible);
671
672 return NS_OK;
673 }
674
675 bool requestHasTransferedData = false;
676
677 if (aProgressStateFlags & STATE_STOP &&
678 aProgressStateFlags & STATE_IS_REQUEST) {
679 PLDHashEntryHdr* entry = mTransferringRequests.Search(aRequest);
680 if (entry) {
681 mTransferringRequests.RemoveEntry(entry);
682 requestHasTransferedData = true;
683 }
684
685 if (!requestHasTransferedData) {
686 // Because image loads doesn't support any TRANSFERRING notifications but
687 // only START and STOP we must ask them directly whether content was
688 // transferred. See bug 432685 for details.
689 nsCOMPtr<nsISecurityInfoProvider> securityInfoProvider =
690 do_QueryInterface(aRequest);
691 // Guess true in all failure cases to be safe. But if we're not
692 // an nsISecurityInfoProvider, then we just haven't transferred
693 // any data.
694 bool hasTransferred;
695 requestHasTransferedData =
696 securityInfoProvider &&
697 (NS_FAILED(
698 securityInfoProvider->GetHasTransferredData(&hasTransferred)) ||
699 hasTransferred);
700 }
701 }
702
703 bool allowSecurityStateChange = true;
704 if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) {
705 // The original consumer (this) is no longer the target of the load.
706 // Ignore any events with this flag, do not allow them to update
707 // our secure UI state.
708 allowSecurityStateChange = false;
709 }
710
711 if (aProgressStateFlags & STATE_START &&
712 aProgressStateFlags & STATE_IS_REQUEST && isToplevelProgress &&
713 loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
714 int32_t saveSubBroken;
715 int32_t saveSubNo;
716 nsCOMPtr<nsIAssociatedContentSecurity> prevContentSecurity;
717
718 int32_t newSubBroken = 0;
719 int32_t newSubNo = 0;
720
721 bool inProgress = (mDocumentRequestsInProgress != 0);
722
723 if (allowSecurityStateChange && !inProgress) {
724 saveSubBroken = mSubRequestsBrokenSecurity;
725 saveSubNo = mSubRequestsNoSecurity;
726 prevContentSecurity = do_QueryInterface(mCurrentToplevelSecurityInfo);
727 }
728
729 if (allowSecurityStateChange && !inProgress) {
730 MOZ_LOG(
731 gSecureDocLog, LogLevel::Debug,
732 ("SecureUI:%p: OnStateChange: start for toplevel document\n", this));
733
734 if (prevContentSecurity) {
735 MOZ_LOG(
736 gSecureDocLog, LogLevel::Debug,
737 ("SecureUI:%p: OnStateChange: start, saving current sub state\n",
738 this));
739
740 // before resetting our state, let's save information about
741 // sub element loads, so we can restore it later
742 prevContentSecurity->SetCountSubRequestsBrokenSecurity(saveSubBroken);
743 prevContentSecurity->SetCountSubRequestsNoSecurity(saveSubNo);
744 prevContentSecurity->Flush();
745 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
746 ("SecureUI:%p: Saving subs in START to %p as %d,%d\n", this,
747 prevContentSecurity.get(), saveSubBroken, saveSubNo));
748 }
749
750 bool retrieveAssociatedState = false;
751
752 if (securityInfo && (aProgressStateFlags &
753 nsIWebProgressListener::STATE_RESTORING) != 0) {
754 retrieveAssociatedState = true;
755 } else {
756 nsCOMPtr<nsIWyciwygChannel> wyciwygRequest(do_QueryInterface(aRequest));
757 if (wyciwygRequest) {
758 retrieveAssociatedState = true;
759 }
760 }
761
762 if (retrieveAssociatedState) {
763 // When restoring from bfcache, we will not get events for the
764 // page's sub elements, so let's load the state of sub elements
765 // from the cache.
766
767 nsCOMPtr<nsIAssociatedContentSecurity> newContentSecurity(
768 do_QueryInterface(securityInfo));
769
770 if (newContentSecurity) {
771 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
772 ("SecureUI:%p: OnStateChange: start, loading old sub state\n",
773 this));
774
775 newContentSecurity->GetCountSubRequestsBrokenSecurity(&newSubBroken);
776 newContentSecurity->GetCountSubRequestsNoSecurity(&newSubNo);
777 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
778 ("SecureUI:%p: Restoring subs in START from %p to %d,%d\n",
779 this, newContentSecurity.get(), newSubBroken, newSubNo));
780 }
781 } else {
782 // If we don't get OnLocationChange for this top level load later,
783 // it didn't get rendered. But we reset the state to unknown and
784 // mSubRequests* to zeros. If we would have left these values after
785 // this top level load stoped, we would override the original top level
786 // load with all zeros and break mixed content state on back and
787 // forward.
788 mRestoreSubrequests = true;
789 }
790 }
791
792 if (allowSecurityStateChange && !inProgress) {
793 ResetStateTracking();
794 mSubRequestsBrokenSecurity = newSubBroken;
795 mSubRequestsNoSecurity = newSubNo;
796 mNewToplevelSecurityStateKnown = false;
797 }
798
799 // By using a counter, this code also works when the toplevel
800 // document get's redirected, but the STOP request for the
801 // previous toplevel document has not yet have been received.
802 MOZ_LOG(
803 gSecureDocLog, LogLevel::Debug,
804 ("SecureUI:%p: OnStateChange: ++mDocumentRequestsInProgress\n", this));
805 ++mDocumentRequestsInProgress;
806
807 return NS_OK;
808 }
809
810 if (aProgressStateFlags & STATE_STOP &&
811 aProgressStateFlags & STATE_IS_REQUEST && isToplevelProgress &&
812 loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
813 nsCOMPtr<nsISecurityEventSink> temp_ToplevelEventSink;
814
815 if (allowSecurityStateChange) {
816 temp_ToplevelEventSink = mToplevelEventSink;
817 }
818
819 if (mDocumentRequestsInProgress <= 0) {
820 // Ignore stop requests unless a document load is in progress
821 // Unfortunately on application start, see some stops without having seen
822 // any starts...
823 return NS_OK;
824 }
825
826 MOZ_LOG(
827 gSecureDocLog, LogLevel::Debug,
828 ("SecureUI:%p: OnStateChange: --mDocumentRequestsInProgress\n", this));
829
830 if (!temp_ToplevelEventSink && channel) {
831 if (allowSecurityStateChange) {
832 ObtainEventSink(channel, temp_ToplevelEventSink);
833 }
834 }
835
836 bool sinkChanged = false;
837 bool inProgress;
838 if (allowSecurityStateChange) {
839 sinkChanged = (mToplevelEventSink != temp_ToplevelEventSink);
840 mToplevelEventSink = temp_ToplevelEventSink;
841 }
842 --mDocumentRequestsInProgress;
843 inProgress = mDocumentRequestsInProgress > 0;
844
845 if (allowSecurityStateChange && requestHasTransferedData) {
846 // Data has been transferred for the single toplevel
847 // request. Evaluate the security state.
848
849 // Do this only when the sink has changed. We update and notify
850 // the state from OnLacationChange, this is actually redundant.
851 // But when the target sink changes between OnLocationChange and
852 // OnStateChange, we have to fire the notification here (again).
853
854 if (sinkChanged || mOnLocationChangeSeen) {
855 EvaluateAndUpdateSecurityState(aRequest, securityInfo, false,
856 sinkChanged);
857 return NS_OK;
858 }
859 }
860 mOnLocationChangeSeen = false;
861
862 if (mRestoreSubrequests && !inProgress) {
863 // We get here when there were no OnLocationChange between
864 // OnStateChange(START) and OnStateChange(STOP). Then the load has not
865 // been rendered but has been retargeted in some other way then by
866 // external app handler. Restore mSubRequests* members to what the
867 // current security state info holds (it was reset to all zero in
868 // OnStateChange(START) before).
869 nsCOMPtr<nsIAssociatedContentSecurity> currentContentSecurity(
870 do_QueryInterface(mCurrentToplevelSecurityInfo));
871
872 // Drop this indication flag, the restore operation is just being done.
873 mRestoreSubrequests = false;
874
875 // We can do this since the state didn't actually change.
876 mNewToplevelSecurityStateKnown = true;
877
878 int32_t subBroken = 0;
879 int32_t subNo = 0;
880
881 if (currentContentSecurity) {
882 currentContentSecurity->GetCountSubRequestsBrokenSecurity(&subBroken);
883 currentContentSecurity->GetCountSubRequestsNoSecurity(&subNo);
884 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
885 ("SecureUI:%p: Restoring subs in STOP from %p to %d,%d\n", this,
886 currentContentSecurity.get(), subBroken, subNo));
887 }
888
889 mSubRequestsBrokenSecurity = subBroken;
890 mSubRequestsNoSecurity = subNo;
891 }
892
893 return NS_OK;
894 }
895
896 if (aProgressStateFlags & STATE_STOP &&
897 aProgressStateFlags & STATE_IS_REQUEST) {
898 if (!isSubDocumentRelevant) return NS_OK;
899
900 // if we arrive here, LOAD_DOCUMENT_URI is not set
901
902 // We only care for the security state of sub requests which have actually
903 // transfered data.
904
905 if (allowSecurityStateChange && requestHasTransferedData) {
906 UpdateSubrequestMembers(securityInfo, aRequest);
907
908 // Care for the following scenario:
909 // A new top level document load might have already started,
910 // but the security state of the new top level document might not yet been
911 // known.
912 //
913 // At this point, we are learning about the security state of a
914 // sub-document. We must not update the security state based on the sub
915 // content, if the new top level state is not yet known.
916 //
917 // We skip updating the security state in this case.
918
919 if (mNewToplevelSecurityStateKnown) {
920 UpdateSecurityState(aRequest, false, false);
921 }
922 }
923
924 return NS_OK;
925 }
926
927 return NS_OK;
928 }
929
930 // I'm keeping this as a separate function, in order to simplify the review
931 // for bug 412456. We should inline this in a follow up patch.
ObtainEventSink(nsIChannel * channel,nsCOMPtr<nsISecurityEventSink> & sink)932 void nsSecureBrowserUIImpl::ObtainEventSink(
933 nsIChannel* channel, nsCOMPtr<nsISecurityEventSink>& sink) {
934 if (!sink) NS_QueryNotificationCallbacks(channel, sink);
935 }
936
UpdateSecurityState(nsIRequest * aRequest,bool withNewLocation,bool withUpdateStatus)937 void nsSecureBrowserUIImpl::UpdateSecurityState(nsIRequest* aRequest,
938 bool withNewLocation,
939 bool withUpdateStatus) {
940 lockIconState newSecurityState = lis_no_security;
941 if (mNewToplevelSecurityState & STATE_IS_SECURE) {
942 // If a subresoure/request was insecure, then we have mixed security.
943 if (mSubRequestsBrokenSecurity || mSubRequestsNoSecurity) {
944 newSecurityState = lis_mixed_security;
945 } else {
946 newSecurityState = lis_high_security;
947 }
948 }
949
950 if (mNewToplevelSecurityState & STATE_IS_BROKEN) {
951 newSecurityState = lis_broken_security;
952 }
953
954 mCertUserOverridden = mNewToplevelSecurityState & STATE_CERT_USER_OVERRIDDEN;
955
956 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
957 ("SecureUI:%p: UpdateSecurityState: old-new %d - %d\n", this,
958 mNotifiedSecurityState, newSecurityState));
959
960 bool flagsChanged = false;
961 if (mNotifiedSecurityState != newSecurityState) {
962 // Something changed since the last time.
963 flagsChanged = true;
964 mNotifiedSecurityState = newSecurityState;
965
966 // If we have no security, we also shouldn't have any SSL status.
967 if (newSecurityState == lis_no_security) {
968 mSSLStatus = nullptr;
969 }
970 }
971
972 if (mNotifiedToplevelIsEV != mNewToplevelIsEV) {
973 flagsChanged = true;
974 mNotifiedToplevelIsEV = mNewToplevelIsEV;
975 }
976
977 if (flagsChanged || withNewLocation || withUpdateStatus) {
978 TellTheWorld(aRequest);
979 }
980 }
981
TellTheWorld(nsIRequest * aRequest)982 void nsSecureBrowserUIImpl::TellTheWorld(nsIRequest* aRequest) {
983 uint32_t state = STATE_IS_INSECURE;
984 GetState(&state);
985
986 if (mToplevelEventSink) {
987 MOZ_LOG(
988 gSecureDocLog, LogLevel::Debug,
989 ("SecureUI:%p: UpdateSecurityState: calling OnSecurityChange\n", this));
990
991 mToplevelEventSink->OnSecurityChange(aRequest, state);
992 } else {
993 MOZ_LOG(
994 gSecureDocLog, LogLevel::Debug,
995 ("SecureUI:%p: UpdateSecurityState: NO mToplevelEventSink!\n", this));
996 }
997 }
998
999 NS_IMETHODIMP
OnLocationChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,nsIURI * aLocation,uint32_t aFlags)1000 nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress,
1001 nsIRequest* aRequest, nsIURI* aLocation,
1002 uint32_t aFlags) {
1003 MOZ_ASSERT(NS_IsMainThread());
1004 ReentrancyGuard guard(*this);
1005
1006 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
1007 ("SecureUI:%p: OnLocationChange\n", this));
1008
1009 bool updateIsViewSource = false;
1010 bool temp_IsViewSource = false;
1011 nsCOMPtr<mozIDOMWindowProxy> window;
1012
1013 if (aLocation) {
1014 bool vs;
1015
1016 nsresult rv = aLocation->SchemeIs("view-source", &vs);
1017 NS_ENSURE_SUCCESS(rv, rv);
1018
1019 if (vs) {
1020 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
1021 ("SecureUI:%p: OnLocationChange: view-source\n", this));
1022 }
1023
1024 updateIsViewSource = true;
1025 temp_IsViewSource = vs;
1026 }
1027
1028 if (updateIsViewSource) {
1029 mIsViewSource = temp_IsViewSource;
1030 }
1031 mCurrentURI = aLocation;
1032 window = do_QueryReferent(mWindow);
1033 MOZ_ASSERT(window, "Window has gone away?!");
1034
1035 // When |aRequest| is null, basically we don't trust that document. But if
1036 // docshell insists that the document has not changed at all, we will reuse
1037 // the previous security state, no matter what |aRequest| may be.
1038 if (aFlags & LOCATION_CHANGE_SAME_DOCUMENT) return NS_OK;
1039
1040 // The location bar has changed, so we must update the security state. The
1041 // only concern with doing this here is that a page may transition from being
1042 // reported as completely secure to being reported as partially secure
1043 // (mixed). This may be confusing for users, and it may bother users who
1044 // like seeing security dialogs. However, it seems prudent given that page
1045 // loading may never end in some edge cases (perhaps by a site with malicious
1046 // intent).
1047
1048 nsCOMPtr<mozIDOMWindowProxy> windowForProgress;
1049 aWebProgress->GetDOMWindow(getter_AddRefs(windowForProgress));
1050
1051 nsCOMPtr<nsISupports> securityInfo(ExtractSecurityInfo(aRequest));
1052
1053 if (windowForProgress.get() == window.get()) {
1054 // For toplevel channels, update the security state right away.
1055 mOnLocationChangeSeen = true;
1056 EvaluateAndUpdateSecurityState(aRequest, securityInfo, true, false);
1057 return NS_OK;
1058 }
1059
1060 // For channels in subdocuments we only update our subrequest state members.
1061 UpdateSubrequestMembers(securityInfo, aRequest);
1062
1063 // Care for the following scenario:
1064
1065 // A new toplevel document load might have already started, but the security
1066 // state of the new toplevel document might not yet be known.
1067 //
1068 // At this point, we are learning about the security state of a sub-document.
1069 // We must not update the security state based on the sub content, if the new
1070 // top level state is not yet known.
1071 //
1072 // We skip updating the security state in this case.
1073
1074 if (mNewToplevelSecurityStateKnown) {
1075 UpdateSecurityState(aRequest, true, false);
1076 }
1077
1078 return NS_OK;
1079 }
1080
1081 NS_IMETHODIMP
OnStatusChange(nsIWebProgress *,nsIRequest *,nsresult,const char16_t *)1082 nsSecureBrowserUIImpl::OnStatusChange(nsIWebProgress*, nsIRequest*, nsresult,
1083 const char16_t*) {
1084 MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
1085 return NS_OK;
1086 }
1087
OnSecurityChange(nsIWebProgress * aWebProgress,nsIRequest * aRequest,uint32_t state)1088 nsresult nsSecureBrowserUIImpl::OnSecurityChange(nsIWebProgress* aWebProgress,
1089 nsIRequest* aRequest,
1090 uint32_t state) {
1091 MOZ_ASSERT(NS_IsMainThread());
1092 #if defined(DEBUG)
1093 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
1094 if (!channel) return NS_OK;
1095
1096 nsCOMPtr<nsIURI> aURI;
1097 channel->GetURI(getter_AddRefs(aURI));
1098
1099 if (aURI) {
1100 MOZ_LOG(gSecureDocLog, LogLevel::Debug,
1101 ("SecureUI:%p: OnSecurityChange: (%x) %s\n", this, state,
1102 aURI->GetSpecOrDefault().get()));
1103 }
1104 #endif
1105
1106 return NS_OK;
1107 }
1108
1109 // nsISSLStatusProvider methods
1110 NS_IMETHODIMP
GetSSLStatus(nsISSLStatus ** _result)1111 nsSecureBrowserUIImpl::GetSSLStatus(nsISSLStatus** _result) {
1112 NS_ENSURE_ARG_POINTER(_result);
1113 MOZ_ASSERT(NS_IsMainThread());
1114
1115 switch (mNotifiedSecurityState) {
1116 case lis_broken_security:
1117 case lis_mixed_security:
1118 case lis_high_security:
1119 break;
1120
1121 default:
1122 MOZ_FALLTHROUGH_ASSERT(
1123 "if this is reached you must add more entries to the switch");
1124 case lis_no_security:
1125 *_result = nullptr;
1126 return NS_OK;
1127 }
1128
1129 *_result = mSSLStatus;
1130 NS_IF_ADDREF(*_result);
1131
1132 return NS_OK;
1133 }
1134