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