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