Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
6 #ifdef MOZ_LOGGING
7 #define FORCE_PR_LOG
8 #endif
10 #include "nspr.h"
11 #include "prlog.h"
13 #include "nsISecureBrowserUI.h"
14 #include "nsSecureBrowserUIImpl.h"
15 #include "nsCOMPtr.h"
16 #include "nsIInterfaceRequestor.h"
17 #include "nsIInterfaceRequestorUtils.h"
18 #include "nsIServiceManager.h"
19 #include "nsCURILoader.h"
20 #include "nsIDocShell.h"
21 #include "nsIDocShellTreeItem.h"
22 #include "nsIDocument.h"
23 #include "nsIPrincipal.h"
24 #include "nsIDOMElement.h"
25 #include "nsPIDOMWindow.h"
26 #include "nsIContent.h"
27 #include "nsIWebProgress.h"
28 #include "nsIWebProgressListener.h"
29 #include "nsIChannel.h"
30 #include "nsIHttpChannel.h"
31 #include "nsIFileChannel.h"
32 #include "nsIWyciwygChannel.h"
33 #include "nsIFTPChannel.h"
34 #include "nsITransportSecurityInfo.h"
35 #include "nsISSLStatus.h"
36 #include "nsIURI.h"
37 #include "nsISecurityEventSink.h"
38 #include "nsIPrompt.h"
39 #include "nsIFormSubmitObserver.h"
40 #include "nsISecurityWarningDialogs.h"
41 #include "nsISecurityInfoProvider.h"
42 #include "imgIRequest.h"
43 #include "nsThreadUtils.h"
44 #include "nsNetUtil.h"
45 #include "nsNetCID.h"
46 #include "nsCRT.h"
48 using namespace mozilla;
50 #define IS_SECURE(state) ((state & 0xFFFF) == STATE_IS_SECURE)
52 #if defined(PR_LOGGING)
53 //
54 // Log module for nsSecureBrowserUI logging...
55 //
56 // To enable logging (see prlog.h for full details):
57 //
58 // set NSPR_LOG_MODULES=nsSecureBrowserUI:5
59 // set NSPR_LOG_FILE=nspr.log
60 //
61 // this enables PR_LOG_DEBUG level information and places all output in
62 // the file nspr.log
63 //
64 PRLogModuleInfo* gSecureDocLog = nullptr;
65 #endif /* PR_LOGGING */
67 struct RequestHashEntry : PLDHashEntryHdr {
68 void *r;
69 };
71 static bool
72 RequestMapMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr,
73 const void *key)
74 {
75 const RequestHashEntry *entry = static_cast<const RequestHashEntry*>(hdr);
76 return entry->r == key;
77 }
79 static bool
80 RequestMapInitEntry(PLDHashTable *table, PLDHashEntryHdr *hdr,
81 const void *key)
82 {
83 RequestHashEntry *entry = static_cast<RequestHashEntry*>(hdr);
84 entry->r = (void*)key;
85 return true;
86 }
88 static const PLDHashTableOps gMapOps = {
89 PL_DHashAllocTable,
90 PL_DHashFreeTable,
91 PL_DHashVoidPtrKeyStub,
92 RequestMapMatchEntry,
93 PL_DHashMoveEntryStub,
94 PL_DHashClearEntryStub,
95 PL_DHashFinalizeStub,
96 RequestMapInitEntry
97 };
99 #ifdef DEBUG
100 class nsAutoAtomic {
101 public:
102 nsAutoAtomic(Atomic<int32_t> &i)
103 :mI(i) {
104 mI++;
105 }
107 ~nsAutoAtomic() {
108 mI--;
109 }
111 protected:
112 Atomic<int32_t> &mI;
114 private:
115 nsAutoAtomic(); // not accessible
116 };
117 #endif
119 nsSecureBrowserUIImpl::nsSecureBrowserUIImpl()
120 : mReentrantMonitor("nsSecureBrowserUIImpl.mReentrantMonitor")
121 , mNotifiedSecurityState(lis_no_security)
122 , mNotifiedToplevelIsEV(false)
123 , mNewToplevelSecurityState(STATE_IS_INSECURE)
124 , mNewToplevelIsEV(false)
125 , mNewToplevelSecurityStateKnown(true)
126 , mIsViewSource(false)
127 , mSubRequestsBrokenSecurity(0)
128 , mSubRequestsNoSecurity(0)
129 , mRestoreSubrequests(false)
130 , mOnLocationChangeSeen(false)
131 #ifdef DEBUG
132 , mOnStateLocationChangeReentranceDetection(0)
133 #endif
134 {
135 mTransferringRequests.ops = nullptr;
136 ResetStateTracking();
138 #if defined(PR_LOGGING)
139 if (!gSecureDocLog)
140 gSecureDocLog = PR_NewLogModule("nsSecureBrowserUI");
141 #endif /* PR_LOGGING */
142 }
144 nsSecureBrowserUIImpl::~nsSecureBrowserUIImpl()
145 {
146 if (mTransferringRequests.ops) {
147 PL_DHashTableFinish(&mTransferringRequests);
148 mTransferringRequests.ops = nullptr;
149 }
150 }
152 NS_IMPL_ISUPPORTS(nsSecureBrowserUIImpl,
153 nsISecureBrowserUI,
154 nsIWebProgressListener,
155 nsIFormSubmitObserver,
156 nsISupportsWeakReference,
157 nsISSLStatusProvider)
159 NS_IMETHODIMP
160 nsSecureBrowserUIImpl::Init(nsIDOMWindow *aWindow)
161 {
163 #ifdef PR_LOGGING
164 nsCOMPtr<nsIDOMWindow> window(do_QueryReferent(mWindow));
166 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
167 ("SecureUI:%p: Init: mWindow: %p, aWindow: %p\n", this,
168 window.get(), aWindow));
169 #endif
171 if (!aWindow) {
172 NS_WARNING("Null window passed to nsSecureBrowserUIImpl::Init()");
173 return NS_ERROR_INVALID_ARG;
174 }
176 if (mWindow) {
177 NS_WARNING("Trying to init an nsSecureBrowserUIImpl twice");
178 return NS_ERROR_ALREADY_INITIALIZED;
179 }
181 nsCOMPtr<nsPIDOMWindow> pwin(do_QueryInterface(aWindow));
182 if (pwin->IsInnerWindow()) {
183 pwin = pwin->GetOuterWindow();
184 }
186 nsresult rv;
187 mWindow = do_GetWeakReference(pwin, &rv);
188 NS_ENSURE_SUCCESS(rv, rv);
190 nsCOMPtr<nsPIDOMWindow> piwindow(do_QueryInterface(aWindow));
191 if (!piwindow) return NS_ERROR_FAILURE;
193 nsIDocShell *docShell = piwindow->GetDocShell();
195 // The Docshell will own the SecureBrowserUI object
196 if (!docShell)
197 return NS_ERROR_FAILURE;
199 docShell->SetSecurityUI(this);
201 /* GetWebProgress(mWindow) */
202 // hook up to the webprogress notifications.
203 nsCOMPtr<nsIWebProgress> wp(do_GetInterface(docShell));
204 if (!wp) return NS_ERROR_FAILURE;
205 /* end GetWebProgress */
207 wp->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
208 nsIWebProgress::NOTIFY_STATE_ALL |
209 nsIWebProgress::NOTIFY_LOCATION |
210 nsIWebProgress::NOTIFY_SECURITY);
213 return NS_OK;
214 }
216 NS_IMETHODIMP
217 nsSecureBrowserUIImpl::GetState(uint32_t* aState)
218 {
219 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
220 return MapInternalToExternalState(aState, mNotifiedSecurityState, mNotifiedToplevelIsEV);
221 }
223 // static
224 already_AddRefed<nsISupports>
225 nsSecureBrowserUIImpl::ExtractSecurityInfo(nsIRequest* aRequest)
226 {
227 nsCOMPtr<nsISupports> retval;
228 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
229 if (channel)
230 channel->GetSecurityInfo(getter_AddRefs(retval));
232 if (!retval) {
233 nsCOMPtr<nsISecurityInfoProvider> provider(do_QueryInterface(aRequest));
234 if (provider)
235 provider->GetSecurityInfo(getter_AddRefs(retval));
236 }
238 return retval.forget();
239 }
241 nsresult
242 nsSecureBrowserUIImpl::MapInternalToExternalState(uint32_t* aState, lockIconState lock, bool ev)
243 {
244 NS_ENSURE_ARG(aState);
246 switch (lock)
247 {
248 case lis_broken_security:
249 *aState = STATE_IS_BROKEN;
250 break;
252 case lis_mixed_security:
253 *aState = STATE_IS_BROKEN;
254 break;
256 case lis_high_security:
257 *aState = STATE_IS_SECURE | STATE_SECURE_HIGH;
258 break;
260 default:
261 case lis_no_security:
262 *aState = STATE_IS_INSECURE;
263 break;
264 }
266 if (ev && (*aState & STATE_IS_SECURE))
267 *aState |= nsIWebProgressListener::STATE_IDENTITY_EV_TOPLEVEL;
269 nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
270 if (!docShell)
271 return NS_OK;
273 // For content docShell's, the mixed content security state is set on the root docShell.
274 if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
275 nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(do_QueryInterface(docShell));
276 nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
277 docShellTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
278 NS_ASSERTION(sameTypeRoot, "No document shell root tree item from document shell tree item!");
279 docShell = do_QueryInterface(sameTypeRoot);
280 if (!docShell)
281 return NS_OK;
282 }
284 // Has a Mixed Content Load initiated in nsMixedContentBlocker?
285 // If so, the state should be broken; overriding the previous state
286 // set by the lock parameter.
287 if (docShell->GetHasMixedActiveContentLoaded() &&
288 docShell->GetHasMixedDisplayContentLoaded()) {
289 *aState = STATE_IS_BROKEN |
290 nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
291 nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
292 } else if (docShell->GetHasMixedActiveContentLoaded()) {
293 *aState = STATE_IS_BROKEN |
294 nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
295 } else if (docShell->GetHasMixedDisplayContentLoaded()) {
296 *aState = STATE_IS_BROKEN |
297 nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
298 }
300 // Has Mixed Content Been Blocked in nsMixedContentBlocker?
301 if (docShell->GetHasMixedActiveContentBlocked())
302 *aState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
304 if (docShell->GetHasMixedDisplayContentBlocked())
305 *aState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT;
307 return NS_OK;
308 }
310 NS_IMETHODIMP
311 nsSecureBrowserUIImpl::SetDocShell(nsIDocShell *aDocShell)
312 {
313 nsresult rv;
314 mDocShell = do_GetWeakReference(aDocShell, &rv);
315 return rv;
316 }
318 static nsresult IsChildOfDomWindow(nsIDOMWindow *parent, nsIDOMWindow *child,
319 bool* value)
320 {
321 *value = false;
323 if (parent == child) {
324 *value = true;
325 return NS_OK;
326 }
328 nsCOMPtr<nsIDOMWindow> childsParent;
329 child->GetParent(getter_AddRefs(childsParent));
331 if (childsParent && childsParent.get() != child)
332 IsChildOfDomWindow(parent, childsParent, value);
334 return NS_OK;
335 }
337 static uint32_t GetSecurityStateFromSecurityInfo(nsISupports *info)
338 {
339 nsresult res;
340 uint32_t securityState;
342 nsCOMPtr<nsITransportSecurityInfo> psmInfo(do_QueryInterface(info));
343 if (!psmInfo) {
344 PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI: GetSecurityState: - no nsITransportSecurityInfo for %p\n",
345 (nsISupports *)info));
346 return nsIWebProgressListener::STATE_IS_INSECURE;
347 }
348 PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI: GetSecurityState: - info is %p\n",
349 (nsISupports *)info));
351 res = psmInfo->GetSecurityState(&securityState);
352 if (NS_FAILED(res)) {
353 PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI: GetSecurityState: - GetSecurityState failed: %d\n",
354 res));
355 securityState = nsIWebProgressListener::STATE_IS_BROKEN;
356 }
358 PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI: GetSecurityState: - Returning %d\n",
359 securityState));
360 return securityState;
361 }
364 NS_IMETHODIMP
365 nsSecureBrowserUIImpl::Notify(nsIDOMHTMLFormElement* aDOMForm,
366 nsIDOMWindow* aWindow, nsIURI* actionURL,
367 bool* cancelSubmit)
368 {
369 // Return NS_OK unless we want to prevent this form from submitting.
370 *cancelSubmit = false;
371 if (!aWindow || !actionURL || !aDOMForm)
372 return NS_OK;
374 nsCOMPtr<nsIContent> formNode = do_QueryInterface(aDOMForm);
376 nsCOMPtr<nsIDocument> document = formNode->GetDocument();
377 if (!document) return NS_OK;
379 nsIPrincipal *principal = formNode->NodePrincipal();
381 if (!principal)
382 {
383 *cancelSubmit = true;
384 return NS_OK;
385 }
387 nsCOMPtr<nsIURI> formURL;
388 if (NS_FAILED(principal->GetURI(getter_AddRefs(formURL))) ||
389 !formURL)
390 {
391 formURL = document->GetDocumentURI();
392 }
394 nsCOMPtr<nsIDOMWindow> postingWindow =
395 do_QueryInterface(document->GetWindow());
396 // We can't find this document's window, cancel it.
397 if (!postingWindow)
398 {
399 NS_WARNING("If you see this and can explain why it should be allowed, note in Bug 332324");
400 *cancelSubmit = true;
401 return NS_OK;
402 }
404 nsCOMPtr<nsIDOMWindow> window;
405 {
406 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
407 window = do_QueryReferent(mWindow);
409 // The window was destroyed, so we assume no form was submitted within it.
410 if (!window)
411 return NS_OK;
412 }
414 bool isChild;
415 IsChildOfDomWindow(window, postingWindow, &isChild);
417 // This notify call is not for our window, ignore it.
418 if (!isChild)
419 return NS_OK;
421 bool okayToPost;
422 nsresult res = CheckPost(formURL, actionURL, &okayToPost);
424 if (NS_SUCCEEDED(res) && !okayToPost)
425 *cancelSubmit = true;
427 return res;
428 }
430 // nsIWebProgressListener
431 NS_IMETHODIMP
432 nsSecureBrowserUIImpl::OnProgressChange(nsIWebProgress* aWebProgress,
433 nsIRequest* aRequest,
434 int32_t aCurSelfProgress,
435 int32_t aMaxSelfProgress,
436 int32_t aCurTotalProgress,
437 int32_t aMaxTotalProgress)
438 {
439 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
440 return NS_OK;
441 }
443 void nsSecureBrowserUIImpl::ResetStateTracking()
444 {
445 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
447 mDocumentRequestsInProgress = 0;
448 if (mTransferringRequests.ops) {
449 PL_DHashTableFinish(&mTransferringRequests);
450 mTransferringRequests.ops = nullptr;
451 }
452 PL_DHashTableInit(&mTransferringRequests, &gMapOps, nullptr,
453 sizeof(RequestHashEntry), 16);
454 }
456 nsresult
457 nsSecureBrowserUIImpl::EvaluateAndUpdateSecurityState(nsIRequest* aRequest, nsISupports *info,
458 bool withNewLocation)
459 {
460 /* I explicitly ignore the camelCase variable naming style here,
461 I want to make it clear these are temp variables that relate to the
462 member variables with the same suffix.*/
464 uint32_t temp_NewToplevelSecurityState = nsIWebProgressListener::STATE_IS_INSECURE;
465 bool temp_NewToplevelIsEV = false;
467 bool updateStatus = false;
468 nsCOMPtr<nsISSLStatus> temp_SSLStatus;
470 temp_NewToplevelSecurityState = GetSecurityStateFromSecurityInfo(info);
472 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
473 ("SecureUI:%p: OnStateChange: remember mNewToplevelSecurityState => %x\n", this,
474 temp_NewToplevelSecurityState));
476 nsCOMPtr<nsISSLStatusProvider> sp = do_QueryInterface(info);
477 if (sp) {
478 // Ignore result
479 updateStatus = true;
480 (void) sp->GetSSLStatus(getter_AddRefs(temp_SSLStatus));
481 if (temp_SSLStatus) {
482 bool aTemp;
483 if (NS_SUCCEEDED(temp_SSLStatus->GetIsExtendedValidation(&aTemp))) {
484 temp_NewToplevelIsEV = aTemp;
485 }
486 }
487 }
489 // assume temp_NewToplevelSecurityState was set in this scope!
490 // see code that is directly above
492 {
493 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
494 mNewToplevelSecurityStateKnown = true;
495 mNewToplevelSecurityState = temp_NewToplevelSecurityState;
496 mNewToplevelIsEV = temp_NewToplevelIsEV;
497 if (updateStatus) {
498 mSSLStatus = temp_SSLStatus;
499 }
500 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
501 ("SecureUI:%p: remember securityInfo %p\n", this,
502 info));
503 nsCOMPtr<nsIAssociatedContentSecurity> associatedContentSecurityFromRequest =
504 do_QueryInterface(aRequest);
505 if (associatedContentSecurityFromRequest)
506 mCurrentToplevelSecurityInfo = aRequest;
507 else
508 mCurrentToplevelSecurityInfo = info;
510 // The subrequest counters are now in sync with
511 // mCurrentToplevelSecurityInfo, don't restore after top level
512 // document load finishes.
513 mRestoreSubrequests = false;
514 }
516 return UpdateSecurityState(aRequest, withNewLocation, updateStatus);
517 }
519 void
520 nsSecureBrowserUIImpl::UpdateSubrequestMembers(nsISupports *securityInfo)
521 {
522 // For wyciwyg channels in subdocuments we only update our
523 // subrequest state members.
524 uint32_t reqState = GetSecurityStateFromSecurityInfo(securityInfo);
526 // the code above this line should run without a lock
527 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
529 if (reqState & STATE_IS_SECURE) {
530 // do nothing
531 } else if (reqState & STATE_IS_BROKEN) {
532 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
533 ("SecureUI:%p: OnStateChange: subreq BROKEN\n", this));
534 ++mSubRequestsBrokenSecurity;
535 } else {
536 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
537 ("SecureUI:%p: OnStateChange: subreq INSECURE\n", this));
538 ++mSubRequestsNoSecurity;
539 }
540 }
542 NS_IMETHODIMP
543 nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress,
544 nsIRequest* aRequest,
545 uint32_t aProgressStateFlags,
546 nsresult aStatus)
547 {
548 #ifdef DEBUG
549 nsAutoAtomic atomic(mOnStateLocationChangeReentranceDetection);
550 NS_ASSERTION(mOnStateLocationChangeReentranceDetection == 1,
551 "unexpected parallel nsIWebProgress OnStateChange and/or OnLocationChange notification");
552 #endif
553 /*
554 All discussion, unless otherwise mentioned, only refers to
555 http, https, file or wyciwig requests.
558 Redirects are evil, well, some of them.
559 There are multiple forms of redirects.
561 Redirects caused by http refresh content are ok, because experiments show,
562 with those redirects, the old page contents and their requests will come to STOP
563 completely, before any progress from new refreshed page content is reported.
564 So we can safely treat them as separate page loading transactions.
566 Evil are redirects at the http protocol level, like code 302.
568 If the toplevel documents gets replaced, i.e. redirected with 302, we do not care for the
569 security state of the initial transaction, which has now been redirected,
570 we only care for the new page load.
572 For the implementation of the security UI, we make an assumption, that is hopefully true.
574 Imagine, the received page that was delivered with the 302 redirection answer,
575 also delivered html content.
577 What happens if the parser starts to analyze the content and tries to load contained sub objects?
579 In that case we would see start and stop requests for subdocuments, some for the previous document,
580 some for the new target document. And only those for the new toplevel document may be
581 taken into consideration, when deciding about the security state of the next toplevel document.
583 Because security state is being looked at, when loading stops for (sub)documents, this
584 could cause real confusion, because we have to decide, whether an incoming progress
585 belongs to the new toplevel page, or the previous, already redirected page.
587 Can we simplify here?
589 If a redirect at the http protocol level is seen, can we safely assume, its html content
590 will not be parsed, anylzed, and no embedded objects will get loaded (css, js, images),
591 because the redirect is already happening?
593 If we can assume that, this really simplify things. Because we will never see notification
594 for sub requests that need to get ignored.
596 I would like to make this assumption for now, but please let me (kaie) know if I'm wrong.
598 Excurse:
599 If my assumption is wrong, then we would require more tracking information.
600 We need to keep lists of all pointers to request object that had been seen since the
601 last toplevel start event.
602 If the start for a redirected page is seen, the list of releveant object must be cleared,
603 and only progress for requests which start after it must be analyzed.
604 All other events must be ignored, as they belong to now irrelevant previous top level documents.
607 Frames are also evil.
609 First we need a decision.
610 kaie thinks:
611 Only if the toplevel frame is secure, we should try to display secure lock icons.
612 If some of the inner contents are insecure, we display mixed mode.
614 But if the top level frame is not secure, why indicate a mixed lock icon at all?
615 I think we should always display an open lock icon, if the top level frameset is insecure.
617 That's the way Netscape Communicator behaves, and I think we should do the same.
619 The user will not know which parts are secure and which are not,
620 and any certificate information, displayed in the tooltip or in the "page info"
621 will only be relevant for some subframe(s), and the user will not know which ones,
622 so we shouldn't display it as a general attribute of the displayed page.
624 Why are frames evil?
626 Because the progress for the toplevel frame document is not easily distinguishable
627 from subframes. The same STATE bits are reported.
629 While at first sight, when a new page load happens,
630 the toplevel frameset document has also the STATE_IS_NETWORK bit in it.
631 But this can't really be used. Because in case that document causes a http 302 redirect,
632 the real top level frameset will no longer have that bit.
634 But we need some way to distinguish top level frames from inner frames.
636 I saw that the web progress we get delivered has a reference to the toplevel DOM window.
638 I suggest, we look at all incoming requests.
639 If a request is NOT for the toplevel DOM window, we will always treat it as a subdocument request,
640 regardless of whether the load flags indicate a top level document.
641 */
643 nsCOMPtr<nsIDOMWindow> windowForProgress;
644 aWebProgress->GetDOMWindow(getter_AddRefs(windowForProgress));
646 nsCOMPtr<nsIDOMWindow> window;
647 bool isViewSource;
649 nsCOMPtr<nsINetUtil> ioService;
651 {
652 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
653 window = do_QueryReferent(mWindow);
654 NS_ASSERTION(window, "Window has gone away?!");
655 isViewSource = mIsViewSource;
656 ioService = mIOService;
657 }
659 if (!ioService)
660 {
661 ioService = do_GetService(NS_IOSERVICE_CONTRACTID);
662 if (ioService)
663 {
664 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
665 mIOService = ioService;
666 }
667 }
669 bool isNoContentResponse = false;
670 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
671 if (httpChannel)
672 {
673 uint32_t response;
674 isNoContentResponse = NS_SUCCEEDED(httpChannel->GetResponseStatus(&response)) &&
675 (response == 204 || response == 205);
676 }
677 const bool isToplevelProgress = (windowForProgress.get() == window.get()) && !isNoContentResponse;
679 #ifdef PR_LOGGING
680 if (windowForProgress)
681 {
682 if (isToplevelProgress)
683 {
684 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
685 ("SecureUI:%p: OnStateChange: progress: for toplevel\n", this));
686 }
687 else
688 {
689 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
690 ("SecureUI:%p: OnStateChange: progress: for something else\n", this));
691 }
692 }
693 else
694 {
695 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
696 ("SecureUI:%p: OnStateChange: progress: no window known\n", this));
697 }
698 #endif
700 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
701 ("SecureUI:%p: OnStateChange\n", this));
703 if (isViewSource)
704 return NS_OK;
706 if (!aRequest)
707 {
708 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
709 ("SecureUI:%p: OnStateChange with null request\n", this));
710 return NS_ERROR_NULL_POINTER;
711 }
713 #ifdef PR_LOGGING
714 if (PR_LOG_TEST(gSecureDocLog, PR_LOG_DEBUG)) {
715 nsXPIDLCString reqname;
716 aRequest->GetName(reqname);
717 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
718 ("SecureUI:%p: %p %p OnStateChange %x %s\n", this, aWebProgress,
719 aRequest, aProgressStateFlags, reqname.get()));
720 }
721 #endif
723 nsCOMPtr<nsISupports> securityInfo(ExtractSecurityInfo(aRequest));
725 nsCOMPtr<nsIURI> uri;
726 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
727 if (channel) {
728 channel->GetURI(getter_AddRefs(uri));
729 }
731 nsCOMPtr<imgIRequest> imgRequest(do_QueryInterface(aRequest));
732 if (imgRequest) {
733 NS_ASSERTION(!channel, "How did that happen, exactly?");
734 // for image requests, we get the URI from here
735 imgRequest->GetURI(getter_AddRefs(uri));
736 }
738 if (uri) {
739 bool vs;
740 if (NS_SUCCEEDED(uri->SchemeIs("javascript", &vs)) && vs) {
741 // We ignore the progress events for javascript URLs.
742 // If a document loading gets triggered, we will see more events.
743 return NS_OK;
744 }
745 }
747 uint32_t loadFlags = 0;
748 aRequest->GetLoadFlags(&loadFlags);
750 #ifdef PR_LOGGING
751 if (aProgressStateFlags & STATE_START
752 &&
753 aProgressStateFlags & STATE_IS_REQUEST
754 &&
755 isToplevelProgress
756 &&
757 loadFlags & nsIChannel::LOAD_DOCUMENT_URI)
758 {
759 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
760 ("SecureUI:%p: OnStateChange: SOMETHING STARTS FOR TOPMOST DOCUMENT\n", this));
761 }
763 if (aProgressStateFlags & STATE_STOP
764 &&
765 aProgressStateFlags & STATE_IS_REQUEST
766 &&
767 isToplevelProgress
768 &&
769 loadFlags & nsIChannel::LOAD_DOCUMENT_URI)
770 {
771 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
772 ("SecureUI:%p: OnStateChange: SOMETHING STOPS FOR TOPMOST DOCUMENT\n", this));
773 }
774 #endif
776 bool isSubDocumentRelevant = true;
778 // We are only interested in requests that load in the browser window...
779 if (!imgRequest) { // is not imgRequest
780 nsCOMPtr<nsIHttpChannel> httpRequest(do_QueryInterface(aRequest));
781 if (!httpRequest) {
782 nsCOMPtr<nsIFileChannel> fileRequest(do_QueryInterface(aRequest));
783 if (!fileRequest) {
784 nsCOMPtr<nsIWyciwygChannel> wyciwygRequest(do_QueryInterface(aRequest));
785 if (!wyciwygRequest) {
786 nsCOMPtr<nsIFTPChannel> ftpRequest(do_QueryInterface(aRequest));
787 if (!ftpRequest) {
788 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
789 ("SecureUI:%p: OnStateChange: not relevant for sub content\n", this));
790 isSubDocumentRelevant = false;
791 }
792 }
793 }
794 }
795 }
797 // This will ignore all resource, chrome, data, file, moz-icon, and anno
798 // protocols. Local resources are treated as trusted.
799 if (uri && ioService) {
800 bool hasFlag;
801 nsresult rv =
802 ioService->URIChainHasFlags(uri,
803 nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
804 &hasFlag);
805 if (NS_SUCCEEDED(rv) && hasFlag) {
806 isSubDocumentRelevant = false;
807 }
808 }
810 #if defined(DEBUG)
811 nsCString info2;
812 uint32_t testFlags = loadFlags;
814 if (testFlags & nsIChannel::LOAD_DOCUMENT_URI)
815 {
816 testFlags -= nsIChannel::LOAD_DOCUMENT_URI;
817 info2.Append("LOAD_DOCUMENT_URI ");
818 }
819 if (testFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI)
820 {
821 testFlags -= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI;
822 info2.Append("LOAD_RETARGETED_DOCUMENT_URI ");
823 }
824 if (testFlags & nsIChannel::LOAD_REPLACE)
825 {
826 testFlags -= nsIChannel::LOAD_REPLACE;
827 info2.Append("LOAD_REPLACE ");
828 }
830 const char *_status = NS_SUCCEEDED(aStatus) ? "1" : "0";
832 nsCString info;
833 uint32_t f = aProgressStateFlags;
834 if (f & nsIWebProgressListener::STATE_START)
835 {
836 f -= nsIWebProgressListener::STATE_START;
837 info.Append("START ");
838 }
839 if (f & nsIWebProgressListener::STATE_REDIRECTING)
840 {
841 f -= nsIWebProgressListener::STATE_REDIRECTING;
842 info.Append("REDIRECTING ");
843 }
844 if (f & nsIWebProgressListener::STATE_TRANSFERRING)
845 {
846 f -= nsIWebProgressListener::STATE_TRANSFERRING;
847 info.Append("TRANSFERRING ");
848 }
849 if (f & nsIWebProgressListener::STATE_NEGOTIATING)
850 {
851 f -= nsIWebProgressListener::STATE_NEGOTIATING;
852 info.Append("NEGOTIATING ");
853 }
854 if (f & nsIWebProgressListener::STATE_STOP)
855 {
856 f -= nsIWebProgressListener::STATE_STOP;
857 info.Append("STOP ");
858 }
859 if (f & nsIWebProgressListener::STATE_IS_REQUEST)
860 {
861 f -= nsIWebProgressListener::STATE_IS_REQUEST;
862 info.Append("IS_REQUEST ");
863 }
864 if (f & nsIWebProgressListener::STATE_IS_DOCUMENT)
865 {
866 f -= nsIWebProgressListener::STATE_IS_DOCUMENT;
867 info.Append("IS_DOCUMENT ");
868 }
869 if (f & nsIWebProgressListener::STATE_IS_NETWORK)
870 {
871 f -= nsIWebProgressListener::STATE_IS_NETWORK;
872 info.Append("IS_NETWORK ");
873 }
874 if (f & nsIWebProgressListener::STATE_IS_WINDOW)
875 {
876 f -= nsIWebProgressListener::STATE_IS_WINDOW;
877 info.Append("IS_WINDOW ");
878 }
879 if (f & nsIWebProgressListener::STATE_IS_INSECURE)
880 {
881 f -= nsIWebProgressListener::STATE_IS_INSECURE;
882 info.Append("IS_INSECURE ");
883 }
884 if (f & nsIWebProgressListener::STATE_IS_BROKEN)
885 {
886 f -= nsIWebProgressListener::STATE_IS_BROKEN;
887 info.Append("IS_BROKEN ");
888 }
889 if (f & nsIWebProgressListener::STATE_IS_SECURE)
890 {
891 f -= nsIWebProgressListener::STATE_IS_SECURE;
892 info.Append("IS_SECURE ");
893 }
894 if (f & nsIWebProgressListener::STATE_SECURE_HIGH)
895 {
896 f -= nsIWebProgressListener::STATE_SECURE_HIGH;
897 info.Append("SECURE_HIGH ");
898 }
899 if (f & nsIWebProgressListener::STATE_RESTORING)
900 {
901 f -= nsIWebProgressListener::STATE_RESTORING;
902 info.Append("STATE_RESTORING ");
903 }
905 if (f > 0)
906 {
907 info.Append("f contains unknown flag!");
908 }
910 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
911 ("SecureUI:%p: OnStateChange: %s %s -- %s\n", this, _status,
912 info.get(), info2.get()));
914 if (aProgressStateFlags & STATE_STOP
915 &&
916 channel)
917 {
918 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
919 ("SecureUI:%p: OnStateChange: seeing STOP with security state: %d\n", this,
920 GetSecurityStateFromSecurityInfo(securityInfo)
921 ));
922 }
923 #endif
925 if (aProgressStateFlags & STATE_TRANSFERRING
926 &&
927 aProgressStateFlags & STATE_IS_REQUEST)
928 {
929 // The listing of a request in mTransferringRequests
930 // means, there has already been data transfered.
932 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
933 PL_DHashTableOperate(&mTransferringRequests, aRequest, PL_DHASH_ADD);
935 return NS_OK;
936 }
938 bool requestHasTransferedData = false;
940 if (aProgressStateFlags & STATE_STOP
941 &&
942 aProgressStateFlags & STATE_IS_REQUEST)
943 {
944 { /* scope for the ReentrantMonitorAutoEnter */
945 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
946 PLDHashEntryHdr *entry = PL_DHashTableOperate(&mTransferringRequests, aRequest, PL_DHASH_LOOKUP);
947 if (PL_DHASH_ENTRY_IS_BUSY(entry))
948 {
949 PL_DHashTableOperate(&mTransferringRequests, aRequest, PL_DHASH_REMOVE);
951 requestHasTransferedData = true;
952 }
953 }
955 if (!requestHasTransferedData) {
956 // Because image loads doesn't support any TRANSFERRING notifications but
957 // only START and STOP we must ask them directly whether content was
958 // transferred. See bug 432685 for details.
959 nsCOMPtr<nsISecurityInfoProvider> securityInfoProvider =
960 do_QueryInterface(aRequest);
961 // Guess true in all failure cases to be safe. But if we're not
962 // an nsISecurityInfoProvider, then we just haven't transferred
963 // any data.
964 bool hasTransferred;
965 requestHasTransferedData =
966 securityInfoProvider &&
967 (NS_FAILED(securityInfoProvider->GetHasTransferredData(&hasTransferred)) ||
968 hasTransferred);
969 }
970 }
972 bool allowSecurityStateChange = true;
973 if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI)
974 {
975 // The original consumer (this) is no longer the target of the load.
976 // Ignore any events with this flag, do not allow them to update
977 // our secure UI state.
978 allowSecurityStateChange = false;
979 }
981 if (aProgressStateFlags & STATE_START
982 &&
983 aProgressStateFlags & STATE_IS_REQUEST
984 &&
985 isToplevelProgress
986 &&
987 loadFlags & nsIChannel::LOAD_DOCUMENT_URI)
988 {
989 bool inProgress;
991 int32_t saveSubBroken;
992 int32_t saveSubNo;
993 nsCOMPtr<nsIAssociatedContentSecurity> prevContentSecurity;
995 int32_t newSubBroken = 0;
996 int32_t newSubNo = 0;
998 {
999 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1000 inProgress = (mDocumentRequestsInProgress!=0);
1002 if (allowSecurityStateChange && !inProgress)
1003 {
1004 saveSubBroken = mSubRequestsBrokenSecurity;
1005 saveSubNo = mSubRequestsNoSecurity;
1006 prevContentSecurity = do_QueryInterface(mCurrentToplevelSecurityInfo);
1007 }
1008 }
1010 if (allowSecurityStateChange && !inProgress)
1011 {
1012 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
1013 ("SecureUI:%p: OnStateChange: start for toplevel document\n", this
1014 ));
1016 if (prevContentSecurity)
1017 {
1018 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
1019 ("SecureUI:%p: OnStateChange: start, saving current sub state\n", this
1020 ));
1022 // before resetting our state, let's save information about
1023 // sub element loads, so we can restore it later
1024 prevContentSecurity->SetCountSubRequestsBrokenSecurity(saveSubBroken);
1025 prevContentSecurity->SetCountSubRequestsNoSecurity(saveSubNo);
1026 prevContentSecurity->Flush();
1027 PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI:%p: Saving subs in START to %p as %d,%d\n",
1028 this, prevContentSecurity.get(), saveSubBroken, saveSubNo));
1029 }
1031 bool retrieveAssociatedState = false;
1033 if (securityInfo &&
1034 (aProgressStateFlags & nsIWebProgressListener::STATE_RESTORING) != 0) {
1035 retrieveAssociatedState = true;
1036 } else {
1037 nsCOMPtr<nsIWyciwygChannel> wyciwygRequest(do_QueryInterface(aRequest));
1038 if (wyciwygRequest) {
1039 retrieveAssociatedState = true;
1040 }
1041 }
1043 if (retrieveAssociatedState)
1044 {
1045 // When restoring from bfcache, we will not get events for the
1046 // page's sub elements, so let's load the state of sub elements
1047 // from the cache.
1049 nsCOMPtr<nsIAssociatedContentSecurity>
1050 newContentSecurity(do_QueryInterface(securityInfo));
1052 if (newContentSecurity)
1053 {
1054 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
1055 ("SecureUI:%p: OnStateChange: start, loading old sub state\n", this
1056 ));
1058 newContentSecurity->GetCountSubRequestsBrokenSecurity(&newSubBroken);
1059 newContentSecurity->GetCountSubRequestsNoSecurity(&newSubNo);
1060 PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI:%p: Restoring subs in START from %p to %d,%d\n",
1061 this, newContentSecurity.get(), newSubBroken, newSubNo));
1062 }
1063 }
1064 else
1065 {
1066 // If we don't get OnLocationChange for this top level load later,
1067 // it didn't get rendered. But we reset the state to unknown and
1068 // mSubRequests* to zeros. If we would have left these values after
1069 // this top level load stoped, we would override the original top level
1070 // load with all zeros and break mixed content state on back and forward.
1071 mRestoreSubrequests = true;
1072 }
1073 }
1075 {
1076 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1078 if (allowSecurityStateChange && !inProgress)
1079 {
1080 ResetStateTracking();
1081 mSubRequestsBrokenSecurity = newSubBroken;
1082 mSubRequestsNoSecurity = newSubNo;
1083 mNewToplevelSecurityStateKnown = false;
1084 }
1086 // By using a counter, this code also works when the toplevel
1087 // document get's redirected, but the STOP request for the
1088 // previous toplevel document has not yet have been received.
1089 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
1090 ("SecureUI:%p: OnStateChange: ++mDocumentRequestsInProgress\n", this
1091 ));
1092 ++mDocumentRequestsInProgress;
1093 }
1095 return NS_OK;
1096 }
1098 if (aProgressStateFlags & STATE_STOP
1099 &&
1100 aProgressStateFlags & STATE_IS_REQUEST
1101 &&
1102 isToplevelProgress
1103 &&
1104 loadFlags & nsIChannel::LOAD_DOCUMENT_URI)
1105 {
1106 int32_t temp_DocumentRequestsInProgress;
1107 nsCOMPtr<nsISecurityEventSink> temp_ToplevelEventSink;
1109 {
1110 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1111 temp_DocumentRequestsInProgress = mDocumentRequestsInProgress;
1112 if (allowSecurityStateChange)
1113 {
1114 temp_ToplevelEventSink = mToplevelEventSink;
1115 }
1116 }
1118 if (temp_DocumentRequestsInProgress <= 0)
1119 {
1120 // Ignore stop requests unless a document load is in progress
1121 // Unfortunately on application start, see some stops without having seen any starts...
1122 return NS_OK;
1123 }
1125 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
1126 ("SecureUI:%p: OnStateChange: --mDocumentRequestsInProgress\n", this
1127 ));
1129 if (!temp_ToplevelEventSink && channel)
1130 {
1131 if (allowSecurityStateChange)
1132 {
1133 ObtainEventSink(channel, temp_ToplevelEventSink);
1134 }
1135 }
1137 bool sinkChanged = false;
1138 bool inProgress;
1139 {
1140 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1141 if (allowSecurityStateChange)
1142 {
1143 sinkChanged = (mToplevelEventSink != temp_ToplevelEventSink);
1144 mToplevelEventSink = temp_ToplevelEventSink;
1145 }
1146 --mDocumentRequestsInProgress;
1147 inProgress = mDocumentRequestsInProgress > 0;
1148 }
1150 if (allowSecurityStateChange && requestHasTransferedData) {
1151 // Data has been transferred for the single toplevel
1152 // request. Evaluate the security state.
1154 // Do this only when the sink has changed. We update and notify
1155 // the state from OnLacationChange, this is actually redundant.
1156 // But when the target sink changes between OnLocationChange and
1157 // OnStateChange, we have to fire the notification here (again).
1159 if (sinkChanged || mOnLocationChangeSeen)
1160 return EvaluateAndUpdateSecurityState(aRequest, securityInfo, false);
1161 }
1162 mOnLocationChangeSeen = false;
1164 if (mRestoreSubrequests && !inProgress)
1165 {
1166 // We get here when there were no OnLocationChange between
1167 // OnStateChange(START) and OnStateChange(STOP). Then the load has not
1168 // been rendered but has been retargeted in some other way then by external
1169 // app handler. Restore mSubRequests* members to what the current security
1170 // state info holds (it was reset to all zero in OnStateChange(START)
1171 // before).
1172 nsCOMPtr<nsIAssociatedContentSecurity> currentContentSecurity;
1173 {
1174 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1175 currentContentSecurity = do_QueryInterface(mCurrentToplevelSecurityInfo);
1177 // Drop this indication flag, the restore opration is just being
1178 // done.
1179 mRestoreSubrequests = false;
1181 // We can do this since the state didn't actually change.
1182 mNewToplevelSecurityStateKnown = true;
1183 }
1185 int32_t subBroken = 0;
1186 int32_t subNo = 0;
1188 if (currentContentSecurity)
1189 {
1190 currentContentSecurity->GetCountSubRequestsBrokenSecurity(&subBroken);
1191 currentContentSecurity->GetCountSubRequestsNoSecurity(&subNo);
1192 PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI:%p: Restoring subs in STOP from %p to %d,%d\n",
1193 this, currentContentSecurity.get(), subBroken, subNo));
1194 }
1196 {
1197 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1198 mSubRequestsBrokenSecurity = subBroken;
1199 mSubRequestsNoSecurity = subNo;
1200 }
1201 }
1203 return NS_OK;
1204 }
1206 if (aProgressStateFlags & STATE_STOP
1207 &&
1208 aProgressStateFlags & STATE_IS_REQUEST)
1209 {
1210 if (!isSubDocumentRelevant)
1211 return NS_OK;
1213 // if we arrive here, LOAD_DOCUMENT_URI is not set
1215 // We only care for the security state of sub requests which have actually transfered data.
1217 if (allowSecurityStateChange && requestHasTransferedData)
1218 {
1219 UpdateSubrequestMembers(securityInfo);
1221 // Care for the following scenario:
1222 // A new top level document load might have already started,
1223 // but the security state of the new top level document might not yet been known.
1224 //
1225 // At this point, we are learning about the security state of a sub-document.
1226 // We must not update the security state based on the sub content,
1227 // if the new top level state is not yet known.
1228 //
1229 // We skip updating the security state in this case.
1231 bool temp_NewToplevelSecurityStateKnown;
1232 {
1233 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1234 temp_NewToplevelSecurityStateKnown = mNewToplevelSecurityStateKnown;
1235 }
1237 if (temp_NewToplevelSecurityStateKnown)
1238 return UpdateSecurityState(aRequest, false, false);
1239 }
1241 return NS_OK;
1242 }
1244 return NS_OK;
1245 }
1247 // I'm keeping this as a separate function, in order to simplify the review
1248 // for bug 412456. We should inline this in a follow up patch.
1249 void nsSecureBrowserUIImpl::ObtainEventSink(nsIChannel *channel,
1250 nsCOMPtr<nsISecurityEventSink> &sink)
1251 {
1252 if (!sink)
1253 NS_QueryNotificationCallbacks(channel, sink);
1254 }
1256 nsresult nsSecureBrowserUIImpl::UpdateSecurityState(nsIRequest* aRequest,
1257 bool withNewLocation,
1258 bool withUpdateStatus)
1259 {
1260 lockIconState warnSecurityState = lis_no_security;
1261 nsresult rv = NS_OK;
1263 // both parameters are both input and outout
1264 bool flagsChanged = UpdateMyFlags(warnSecurityState);
1266 if (flagsChanged || withNewLocation || withUpdateStatus)
1267 rv = TellTheWorld(warnSecurityState, aRequest);
1269 return rv;
1270 }
1272 // must not fail, by definition, only trivial assignments
1273 // or string operations are allowed
1274 // returns true if our overall state has changed and we must send out notifications
1275 bool nsSecureBrowserUIImpl::UpdateMyFlags(lockIconState &warnSecurityState)
1276 {
1277 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1278 bool mustTellTheWorld = false;
1280 lockIconState newSecurityState;
1282 if (mNewToplevelSecurityState & STATE_IS_SECURE)
1283 {
1284 if (mSubRequestsBrokenSecurity
1285 ||
1286 mSubRequestsNoSecurity)
1287 {
1288 newSecurityState = lis_mixed_security;
1289 }
1290 else
1291 {
1292 newSecurityState = lis_high_security;
1293 }
1294 }
1295 else
1296 if (mNewToplevelSecurityState & STATE_IS_BROKEN)
1297 {
1298 // indicating BROKEN is more important than MIXED.
1300 newSecurityState = lis_broken_security;
1301 }
1302 else
1303 {
1304 newSecurityState = lis_no_security;
1305 }
1307 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
1308 ("SecureUI:%p: UpdateSecurityState: old-new %d - %d\n", this,
1309 mNotifiedSecurityState, newSecurityState
1310 ));
1312 if (mNotifiedSecurityState != newSecurityState)
1313 {
1314 mustTellTheWorld = true;
1316 // we'll treat "broken" exactly like "insecure",
1318 /*
1319 security icon
1320 ----------------
1322 no open
1323 mixed broken
1324 broken broken
1325 high high
1326 */
1328 mNotifiedSecurityState = newSecurityState;
1330 if (lis_no_security == newSecurityState)
1331 {
1332 mSSLStatus = nullptr;
1333 }
1334 }
1336 if (mNotifiedToplevelIsEV != mNewToplevelIsEV) {
1337 mustTellTheWorld = true;
1338 mNotifiedToplevelIsEV = mNewToplevelIsEV;
1339 }
1341 return mustTellTheWorld;
1342 }
1344 nsresult nsSecureBrowserUIImpl::TellTheWorld(lockIconState warnSecurityState,
1345 nsIRequest* aRequest)
1346 {
1347 nsCOMPtr<nsISecurityEventSink> temp_ToplevelEventSink;
1348 lockIconState temp_NotifiedSecurityState;
1349 bool temp_NotifiedToplevelIsEV;
1351 {
1352 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1353 temp_ToplevelEventSink = mToplevelEventSink;
1354 temp_NotifiedSecurityState = mNotifiedSecurityState;
1355 temp_NotifiedToplevelIsEV = mNotifiedToplevelIsEV;
1356 }
1358 if (temp_ToplevelEventSink)
1359 {
1360 uint32_t newState = STATE_IS_INSECURE;
1361 MapInternalToExternalState(&newState,
1362 temp_NotifiedSecurityState,
1363 temp_NotifiedToplevelIsEV);
1365 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
1366 ("SecureUI:%p: UpdateSecurityState: calling OnSecurityChange\n", this
1367 ));
1369 temp_ToplevelEventSink->OnSecurityChange(aRequest, newState);
1370 }
1371 else
1372 {
1373 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
1374 ("SecureUI:%p: UpdateSecurityState: NO mToplevelEventSink!\n", this
1375 ));
1377 }
1379 return NS_OK;
1380 }
1382 NS_IMETHODIMP
1383 nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress,
1384 nsIRequest* aRequest,
1385 nsIURI* aLocation,
1386 uint32_t aFlags)
1387 {
1388 #ifdef DEBUG
1389 nsAutoAtomic atomic(mOnStateLocationChangeReentranceDetection);
1390 NS_ASSERTION(mOnStateLocationChangeReentranceDetection == 1,
1391 "unexpected parallel nsIWebProgress OnStateChange and/or OnLocationChange notification");
1392 #endif
1393 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
1394 ("SecureUI:%p: OnLocationChange\n", this));
1396 bool updateIsViewSource = false;
1397 bool temp_IsViewSource = false;
1398 nsCOMPtr<nsIDOMWindow> window;
1400 if (aLocation)
1401 {
1402 bool vs;
1404 nsresult rv = aLocation->SchemeIs("view-source", &vs);
1405 NS_ENSURE_SUCCESS(rv, rv);
1407 if (vs) {
1408 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
1409 ("SecureUI:%p: OnLocationChange: view-source\n", this));
1410 }
1412 updateIsViewSource = true;
1413 temp_IsViewSource = vs;
1414 }
1416 {
1417 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1418 if (updateIsViewSource) {
1419 mIsViewSource = temp_IsViewSource;
1420 }
1421 mCurrentURI = aLocation;
1422 window = do_QueryReferent(mWindow);
1423 NS_ASSERTION(window, "Window has gone away?!");
1424 }
1426 // When |aRequest| is null, basically we don't trust that document. But if
1427 // docshell insists that the document has not changed at all, we will reuse
1428 // the previous security state, no matter what |aRequest| may be.
1429 if (aFlags & LOCATION_CHANGE_SAME_DOCUMENT)
1430 return NS_OK;
1432 // The location bar has changed, so we must update the security state. The
1433 // only concern with doing this here is that a page may transition from being
1434 // reported as completely secure to being reported as partially secure
1435 // (mixed). This may be confusing for users, and it may bother users who
1436 // like seeing security dialogs. However, it seems prudent given that page
1437 // loading may never end in some edge cases (perhaps by a site with malicious
1438 // intent).
1440 nsCOMPtr<nsIDOMWindow> windowForProgress;
1441 aWebProgress->GetDOMWindow(getter_AddRefs(windowForProgress));
1443 nsCOMPtr<nsISupports> securityInfo(ExtractSecurityInfo(aRequest));
1445 if (windowForProgress.get() == window.get()) {
1446 // For toplevel channels, update the security state right away.
1447 mOnLocationChangeSeen = true;
1448 return EvaluateAndUpdateSecurityState(aRequest, securityInfo, true);
1449 }
1451 // For channels in subdocuments we only update our subrequest state members.
1452 UpdateSubrequestMembers(securityInfo);
1454 // Care for the following scenario:
1456 // A new toplevel document load might have already started, but the security
1457 // state of the new toplevel document might not yet be known.
1458 //
1459 // At this point, we are learning about the security state of a sub-document.
1460 // We must not update the security state based on the sub content, if the new
1461 // top level state is not yet known.
1462 //
1463 // We skip updating the security state in this case.
1465 bool temp_NewToplevelSecurityStateKnown;
1466 {
1467 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1468 temp_NewToplevelSecurityStateKnown = mNewToplevelSecurityStateKnown;
1469 }
1471 if (temp_NewToplevelSecurityStateKnown)
1472 return UpdateSecurityState(aRequest, true, false);
1474 return NS_OK;
1475 }
1477 NS_IMETHODIMP
1478 nsSecureBrowserUIImpl::OnStatusChange(nsIWebProgress* aWebProgress,
1479 nsIRequest* aRequest,
1480 nsresult aStatus,
1481 const char16_t* aMessage)
1482 {
1483 NS_NOTREACHED("notification excluded in AddProgressListener(...)");
1484 return NS_OK;
1485 }
1487 nsresult
1488 nsSecureBrowserUIImpl::OnSecurityChange(nsIWebProgress *aWebProgress,
1489 nsIRequest *aRequest,
1490 uint32_t state)
1491 {
1492 #if defined(DEBUG)
1493 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
1494 if (!channel)
1495 return NS_OK;
1497 nsCOMPtr<nsIURI> aURI;
1498 channel->GetURI(getter_AddRefs(aURI));
1500 if (aURI) {
1501 nsAutoCString temp;
1502 aURI->GetSpec(temp);
1503 PR_LOG(gSecureDocLog, PR_LOG_DEBUG,
1504 ("SecureUI:%p: OnSecurityChange: (%x) %s\n", this,
1505 state, temp.get()));
1506 }
1507 #endif
1509 return NS_OK;
1510 }
1512 // nsISSLStatusProvider methods
1513 NS_IMETHODIMP
1514 nsSecureBrowserUIImpl::GetSSLStatus(nsISSLStatus** _result)
1515 {
1516 NS_ENSURE_ARG_POINTER(_result);
1518 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1520 switch (mNotifiedSecurityState)
1521 {
1522 case lis_mixed_security:
1523 case lis_high_security:
1524 break;
1526 default:
1527 NS_NOTREACHED("if this is reached you must add more entries to the switch");
1528 case lis_no_security:
1529 case lis_broken_security:
1530 *_result = nullptr;
1531 return NS_OK;
1532 }
1534 *_result = mSSLStatus;
1535 NS_IF_ADDREF(*_result);
1537 return NS_OK;
1538 }
1540 nsresult
1541 nsSecureBrowserUIImpl::IsURLHTTPS(nsIURI* aURL, bool* value)
1542 {
1543 *value = false;
1545 if (!aURL)
1546 return NS_OK;
1548 return aURL->SchemeIs("https", value);
1549 }
1551 nsresult
1552 nsSecureBrowserUIImpl::IsURLJavaScript(nsIURI* aURL, bool* value)
1553 {
1554 *value = false;
1556 if (!aURL)
1557 return NS_OK;
1559 return aURL->SchemeIs("javascript", value);
1560 }
1562 nsresult
1563 nsSecureBrowserUIImpl::CheckPost(nsIURI *formURL, nsIURI *actionURL, bool *okayToPost)
1564 {
1565 bool formSecure, actionSecure, actionJavaScript;
1566 *okayToPost = true;
1568 nsresult rv = IsURLHTTPS(formURL, &formSecure);
1569 if (NS_FAILED(rv))
1570 return rv;
1572 rv = IsURLHTTPS(actionURL, &actionSecure);
1573 if (NS_FAILED(rv))
1574 return rv;
1576 rv = IsURLJavaScript(actionURL, &actionJavaScript);
1577 if (NS_FAILED(rv))
1578 return rv;
1580 // If we are posting to a secure link, all is okay.
1581 // It doesn't matter whether the currently viewed page is secure or not,
1582 // because the data will be sent to a secure URL.
1583 if (actionSecure) {
1584 return NS_OK;
1585 }
1587 // Action is a JavaScript call, not an actual post. That's okay too.
1588 if (actionJavaScript) {
1589 return NS_OK;
1590 }
1592 // posting to insecure webpage from a secure webpage.
1593 if (formSecure) {
1594 *okayToPost = ConfirmPostToInsecureFromSecure();
1595 }
1597 return NS_OK;
1598 }
1600 //
1601 // Implementation of an nsIInterfaceRequestor for use
1602 // as context for NSS calls
1603 //
1604 class nsUIContext : public nsIInterfaceRequestor
1605 {
1606 public:
1607 NS_DECL_ISUPPORTS
1608 NS_DECL_NSIINTERFACEREQUESTOR
1610 nsUIContext(nsIDOMWindow *window);
1611 virtual ~nsUIContext();
1613 private:
1614 nsCOMPtr<nsIDOMWindow> mWindow;
1615 };
1617 NS_IMPL_ISUPPORTS(nsUIContext, nsIInterfaceRequestor)
1619 nsUIContext::nsUIContext(nsIDOMWindow *aWindow)
1620 : mWindow(aWindow)
1621 {
1622 }
1624 nsUIContext::~nsUIContext()
1625 {
1626 }
1628 /* void getInterface (in nsIIDRef uuid, [iid_is (uuid), retval] out nsQIResult result); */
1629 NS_IMETHODIMP nsUIContext::GetInterface(const nsIID & uuid, void * *result)
1630 {
1631 NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
1632 nsresult rv;
1634 if (uuid.Equals(NS_GET_IID(nsIPrompt))) {
1635 nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(mWindow, &rv);
1636 if (NS_FAILED(rv)) return rv;
1638 nsIPrompt *prompt;
1640 rv = window->GetPrompter(&prompt);
1641 *result = prompt;
1642 } else if (uuid.Equals(NS_GET_IID(nsIDOMWindow))) {
1643 *result = mWindow;
1644 NS_ADDREF ((nsISupports*) *result);
1645 rv = NS_OK;
1646 } else {
1647 rv = NS_ERROR_NO_INTERFACE;
1648 }
1650 return rv;
1651 }
1653 bool
1654 nsSecureBrowserUIImpl::GetNSSDialogs(nsCOMPtr<nsISecurityWarningDialogs> & dialogs,
1655 nsCOMPtr<nsIInterfaceRequestor> & ctx)
1656 {
1657 if (!NS_IsMainThread()) {
1658 NS_ERROR("nsSecureBrowserUIImpl::GetNSSDialogs called off the main thread");
1659 return false;
1660 }
1662 dialogs = do_GetService(NS_SECURITYWARNINGDIALOGS_CONTRACTID);
1663 if (!dialogs)
1664 return false;
1666 nsCOMPtr<nsIDOMWindow> window;
1667 {
1668 ReentrantMonitorAutoEnter lock(mReentrantMonitor);
1669 window = do_QueryReferent(mWindow);
1670 NS_ASSERTION(window, "Window has gone away?!");
1671 }
1672 ctx = new nsUIContext(window);
1674 return true;
1675 }
1677 /**
1678 * ConfirmPostToInsecureFromSecure - returns true if
1679 * the user approves the submit (or doesn't care).
1680 * returns false on errors.
1681 */
1682 bool nsSecureBrowserUIImpl::
1683 ConfirmPostToInsecureFromSecure()
1684 {
1685 nsCOMPtr<nsISecurityWarningDialogs> dialogs;
1686 nsCOMPtr<nsIInterfaceRequestor> ctx;
1688 if (!GetNSSDialogs(dialogs, ctx)) {
1689 return false; // Should this allow true for unimplemented?
1690 }
1692 bool result;
1694 nsresult rv = dialogs->ConfirmPostToInsecureFromSecure(ctx, &result);
1695 if (NS_FAILED(rv)) return false;
1697 return result;
1698 }