michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG michael@0: #endif michael@0: michael@0: #include "nspr.h" michael@0: #include "prlog.h" michael@0: michael@0: #include "nsISecureBrowserUI.h" michael@0: #include "nsSecureBrowserUIImpl.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsCURILoader.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIWebProgress.h" michael@0: #include "nsIWebProgressListener.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIFileChannel.h" michael@0: #include "nsIWyciwygChannel.h" michael@0: #include "nsIFTPChannel.h" michael@0: #include "nsITransportSecurityInfo.h" michael@0: #include "nsISSLStatus.h" michael@0: #include "nsIURI.h" michael@0: #include "nsISecurityEventSink.h" michael@0: #include "nsIPrompt.h" michael@0: #include "nsIFormSubmitObserver.h" michael@0: #include "nsISecurityWarningDialogs.h" michael@0: #include "nsISecurityInfoProvider.h" michael@0: #include "imgIRequest.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsCRT.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #define IS_SECURE(state) ((state & 0xFFFF) == STATE_IS_SECURE) michael@0: michael@0: #if defined(PR_LOGGING) michael@0: // michael@0: // Log module for nsSecureBrowserUI logging... michael@0: // michael@0: // To enable logging (see prlog.h for full details): michael@0: // michael@0: // set NSPR_LOG_MODULES=nsSecureBrowserUI:5 michael@0: // set NSPR_LOG_FILE=nspr.log michael@0: // michael@0: // this enables PR_LOG_DEBUG level information and places all output in michael@0: // the file nspr.log michael@0: // michael@0: PRLogModuleInfo* gSecureDocLog = nullptr; michael@0: #endif /* PR_LOGGING */ michael@0: michael@0: struct RequestHashEntry : PLDHashEntryHdr { michael@0: void *r; michael@0: }; michael@0: michael@0: static bool michael@0: RequestMapMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, michael@0: const void *key) michael@0: { michael@0: const RequestHashEntry *entry = static_cast(hdr); michael@0: return entry->r == key; michael@0: } michael@0: michael@0: static bool michael@0: RequestMapInitEntry(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: const void *key) michael@0: { michael@0: RequestHashEntry *entry = static_cast(hdr); michael@0: entry->r = (void*)key; michael@0: return true; michael@0: } michael@0: michael@0: static const PLDHashTableOps gMapOps = { michael@0: PL_DHashAllocTable, michael@0: PL_DHashFreeTable, michael@0: PL_DHashVoidPtrKeyStub, michael@0: RequestMapMatchEntry, michael@0: PL_DHashMoveEntryStub, michael@0: PL_DHashClearEntryStub, michael@0: PL_DHashFinalizeStub, michael@0: RequestMapInitEntry michael@0: }; michael@0: michael@0: #ifdef DEBUG michael@0: class nsAutoAtomic { michael@0: public: michael@0: nsAutoAtomic(Atomic &i) michael@0: :mI(i) { michael@0: mI++; michael@0: } michael@0: michael@0: ~nsAutoAtomic() { michael@0: mI--; michael@0: } michael@0: michael@0: protected: michael@0: Atomic &mI; michael@0: michael@0: private: michael@0: nsAutoAtomic(); // not accessible michael@0: }; michael@0: #endif michael@0: michael@0: nsSecureBrowserUIImpl::nsSecureBrowserUIImpl() michael@0: : mReentrantMonitor("nsSecureBrowserUIImpl.mReentrantMonitor") michael@0: , mNotifiedSecurityState(lis_no_security) michael@0: , mNotifiedToplevelIsEV(false) michael@0: , mNewToplevelSecurityState(STATE_IS_INSECURE) michael@0: , mNewToplevelIsEV(false) michael@0: , mNewToplevelSecurityStateKnown(true) michael@0: , mIsViewSource(false) michael@0: , mSubRequestsBrokenSecurity(0) michael@0: , mSubRequestsNoSecurity(0) michael@0: , mRestoreSubrequests(false) michael@0: , mOnLocationChangeSeen(false) michael@0: #ifdef DEBUG michael@0: , mOnStateLocationChangeReentranceDetection(0) michael@0: #endif michael@0: { michael@0: mTransferringRequests.ops = nullptr; michael@0: ResetStateTracking(); michael@0: michael@0: #if defined(PR_LOGGING) michael@0: if (!gSecureDocLog) michael@0: gSecureDocLog = PR_NewLogModule("nsSecureBrowserUI"); michael@0: #endif /* PR_LOGGING */ michael@0: } michael@0: michael@0: nsSecureBrowserUIImpl::~nsSecureBrowserUIImpl() michael@0: { michael@0: if (mTransferringRequests.ops) { michael@0: PL_DHashTableFinish(&mTransferringRequests); michael@0: mTransferringRequests.ops = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsSecureBrowserUIImpl, michael@0: nsISecureBrowserUI, michael@0: nsIWebProgressListener, michael@0: nsIFormSubmitObserver, michael@0: nsISupportsWeakReference, michael@0: nsISSLStatusProvider) michael@0: michael@0: NS_IMETHODIMP michael@0: nsSecureBrowserUIImpl::Init(nsIDOMWindow *aWindow) michael@0: { michael@0: michael@0: #ifdef PR_LOGGING michael@0: nsCOMPtr window(do_QueryReferent(mWindow)); michael@0: michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: Init: mWindow: %p, aWindow: %p\n", this, michael@0: window.get(), aWindow)); michael@0: #endif michael@0: michael@0: if (!aWindow) { michael@0: NS_WARNING("Null window passed to nsSecureBrowserUIImpl::Init()"); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: if (mWindow) { michael@0: NS_WARNING("Trying to init an nsSecureBrowserUIImpl twice"); michael@0: return NS_ERROR_ALREADY_INITIALIZED; michael@0: } michael@0: michael@0: nsCOMPtr pwin(do_QueryInterface(aWindow)); michael@0: if (pwin->IsInnerWindow()) { michael@0: pwin = pwin->GetOuterWindow(); michael@0: } michael@0: michael@0: nsresult rv; michael@0: mWindow = do_GetWeakReference(pwin, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr piwindow(do_QueryInterface(aWindow)); michael@0: if (!piwindow) return NS_ERROR_FAILURE; michael@0: michael@0: nsIDocShell *docShell = piwindow->GetDocShell(); michael@0: michael@0: // The Docshell will own the SecureBrowserUI object michael@0: if (!docShell) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: docShell->SetSecurityUI(this); michael@0: michael@0: /* GetWebProgress(mWindow) */ michael@0: // hook up to the webprogress notifications. michael@0: nsCOMPtr wp(do_GetInterface(docShell)); michael@0: if (!wp) return NS_ERROR_FAILURE; michael@0: /* end GetWebProgress */ michael@0: michael@0: wp->AddProgressListener(static_cast(this), michael@0: nsIWebProgress::NOTIFY_STATE_ALL | michael@0: nsIWebProgress::NOTIFY_LOCATION | michael@0: nsIWebProgress::NOTIFY_SECURITY); michael@0: michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSecureBrowserUIImpl::GetState(uint32_t* aState) michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: return MapInternalToExternalState(aState, mNotifiedSecurityState, mNotifiedToplevelIsEV); michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: nsSecureBrowserUIImpl::ExtractSecurityInfo(nsIRequest* aRequest) michael@0: { michael@0: nsCOMPtr retval; michael@0: nsCOMPtr channel(do_QueryInterface(aRequest)); michael@0: if (channel) michael@0: channel->GetSecurityInfo(getter_AddRefs(retval)); michael@0: michael@0: if (!retval) { michael@0: nsCOMPtr provider(do_QueryInterface(aRequest)); michael@0: if (provider) michael@0: provider->GetSecurityInfo(getter_AddRefs(retval)); michael@0: } michael@0: michael@0: return retval.forget(); michael@0: } michael@0: michael@0: nsresult michael@0: nsSecureBrowserUIImpl::MapInternalToExternalState(uint32_t* aState, lockIconState lock, bool ev) michael@0: { michael@0: NS_ENSURE_ARG(aState); michael@0: michael@0: switch (lock) michael@0: { michael@0: case lis_broken_security: michael@0: *aState = STATE_IS_BROKEN; michael@0: break; michael@0: michael@0: case lis_mixed_security: michael@0: *aState = STATE_IS_BROKEN; michael@0: break; michael@0: michael@0: case lis_high_security: michael@0: *aState = STATE_IS_SECURE | STATE_SECURE_HIGH; michael@0: break; michael@0: michael@0: default: michael@0: case lis_no_security: michael@0: *aState = STATE_IS_INSECURE; michael@0: break; michael@0: } michael@0: michael@0: if (ev && (*aState & STATE_IS_SECURE)) michael@0: *aState |= nsIWebProgressListener::STATE_IDENTITY_EV_TOPLEVEL; michael@0: michael@0: nsCOMPtr docShell = do_QueryReferent(mDocShell); michael@0: if (!docShell) michael@0: return NS_OK; michael@0: michael@0: // For content docShell's, the mixed content security state is set on the root docShell. michael@0: if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) { michael@0: nsCOMPtr docShellTreeItem(do_QueryInterface(docShell)); michael@0: nsCOMPtr sameTypeRoot; michael@0: docShellTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); michael@0: NS_ASSERTION(sameTypeRoot, "No document shell root tree item from document shell tree item!"); michael@0: docShell = do_QueryInterface(sameTypeRoot); michael@0: if (!docShell) michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Has a Mixed Content Load initiated in nsMixedContentBlocker? michael@0: // If so, the state should be broken; overriding the previous state michael@0: // set by the lock parameter. michael@0: if (docShell->GetHasMixedActiveContentLoaded() && michael@0: docShell->GetHasMixedDisplayContentLoaded()) { michael@0: *aState = STATE_IS_BROKEN | michael@0: nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | michael@0: nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT; michael@0: } else if (docShell->GetHasMixedActiveContentLoaded()) { michael@0: *aState = STATE_IS_BROKEN | michael@0: nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT; michael@0: } else if (docShell->GetHasMixedDisplayContentLoaded()) { michael@0: *aState = STATE_IS_BROKEN | michael@0: nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT; michael@0: } michael@0: michael@0: // Has Mixed Content Been Blocked in nsMixedContentBlocker? michael@0: if (docShell->GetHasMixedActiveContentBlocked()) michael@0: *aState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT; michael@0: michael@0: if (docShell->GetHasMixedDisplayContentBlocked()) michael@0: *aState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSecureBrowserUIImpl::SetDocShell(nsIDocShell *aDocShell) michael@0: { michael@0: nsresult rv; michael@0: mDocShell = do_GetWeakReference(aDocShell, &rv); michael@0: return rv; michael@0: } michael@0: michael@0: static nsresult IsChildOfDomWindow(nsIDOMWindow *parent, nsIDOMWindow *child, michael@0: bool* value) michael@0: { michael@0: *value = false; michael@0: michael@0: if (parent == child) { michael@0: *value = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr childsParent; michael@0: child->GetParent(getter_AddRefs(childsParent)); michael@0: michael@0: if (childsParent && childsParent.get() != child) michael@0: IsChildOfDomWindow(parent, childsParent, value); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static uint32_t GetSecurityStateFromSecurityInfo(nsISupports *info) michael@0: { michael@0: nsresult res; michael@0: uint32_t securityState; michael@0: michael@0: nsCOMPtr psmInfo(do_QueryInterface(info)); michael@0: if (!psmInfo) { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI: GetSecurityState: - no nsITransportSecurityInfo for %p\n", michael@0: (nsISupports *)info)); michael@0: return nsIWebProgressListener::STATE_IS_INSECURE; michael@0: } michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI: GetSecurityState: - info is %p\n", michael@0: (nsISupports *)info)); michael@0: michael@0: res = psmInfo->GetSecurityState(&securityState); michael@0: if (NS_FAILED(res)) { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI: GetSecurityState: - GetSecurityState failed: %d\n", michael@0: res)); michael@0: securityState = nsIWebProgressListener::STATE_IS_BROKEN; michael@0: } michael@0: michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI: GetSecurityState: - Returning %d\n", michael@0: securityState)); michael@0: return securityState; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSecureBrowserUIImpl::Notify(nsIDOMHTMLFormElement* aDOMForm, michael@0: nsIDOMWindow* aWindow, nsIURI* actionURL, michael@0: bool* cancelSubmit) michael@0: { michael@0: // Return NS_OK unless we want to prevent this form from submitting. michael@0: *cancelSubmit = false; michael@0: if (!aWindow || !actionURL || !aDOMForm) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr formNode = do_QueryInterface(aDOMForm); michael@0: michael@0: nsCOMPtr document = formNode->GetDocument(); michael@0: if (!document) return NS_OK; michael@0: michael@0: nsIPrincipal *principal = formNode->NodePrincipal(); michael@0: michael@0: if (!principal) michael@0: { michael@0: *cancelSubmit = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr formURL; michael@0: if (NS_FAILED(principal->GetURI(getter_AddRefs(formURL))) || michael@0: !formURL) michael@0: { michael@0: formURL = document->GetDocumentURI(); michael@0: } michael@0: michael@0: nsCOMPtr postingWindow = michael@0: do_QueryInterface(document->GetWindow()); michael@0: // We can't find this document's window, cancel it. michael@0: if (!postingWindow) michael@0: { michael@0: NS_WARNING("If you see this and can explain why it should be allowed, note in Bug 332324"); michael@0: *cancelSubmit = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr window; michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: window = do_QueryReferent(mWindow); michael@0: michael@0: // The window was destroyed, so we assume no form was submitted within it. michael@0: if (!window) michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool isChild; michael@0: IsChildOfDomWindow(window, postingWindow, &isChild); michael@0: michael@0: // This notify call is not for our window, ignore it. michael@0: if (!isChild) michael@0: return NS_OK; michael@0: michael@0: bool okayToPost; michael@0: nsresult res = CheckPost(formURL, actionURL, &okayToPost); michael@0: michael@0: if (NS_SUCCEEDED(res) && !okayToPost) michael@0: *cancelSubmit = true; michael@0: michael@0: return res; michael@0: } michael@0: michael@0: // nsIWebProgressListener michael@0: NS_IMETHODIMP michael@0: nsSecureBrowserUIImpl::OnProgressChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, michael@0: int32_t aCurSelfProgress, michael@0: int32_t aMaxSelfProgress, michael@0: int32_t aCurTotalProgress, michael@0: int32_t aMaxTotalProgress) michael@0: { michael@0: NS_NOTREACHED("notification excluded in AddProgressListener(...)"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsSecureBrowserUIImpl::ResetStateTracking() michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: michael@0: mDocumentRequestsInProgress = 0; michael@0: if (mTransferringRequests.ops) { michael@0: PL_DHashTableFinish(&mTransferringRequests); michael@0: mTransferringRequests.ops = nullptr; michael@0: } michael@0: PL_DHashTableInit(&mTransferringRequests, &gMapOps, nullptr, michael@0: sizeof(RequestHashEntry), 16); michael@0: } michael@0: michael@0: nsresult michael@0: nsSecureBrowserUIImpl::EvaluateAndUpdateSecurityState(nsIRequest* aRequest, nsISupports *info, michael@0: bool withNewLocation) michael@0: { michael@0: /* I explicitly ignore the camelCase variable naming style here, michael@0: I want to make it clear these are temp variables that relate to the michael@0: member variables with the same suffix.*/ michael@0: michael@0: uint32_t temp_NewToplevelSecurityState = nsIWebProgressListener::STATE_IS_INSECURE; michael@0: bool temp_NewToplevelIsEV = false; michael@0: michael@0: bool updateStatus = false; michael@0: nsCOMPtr temp_SSLStatus; michael@0: michael@0: temp_NewToplevelSecurityState = GetSecurityStateFromSecurityInfo(info); michael@0: michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: remember mNewToplevelSecurityState => %x\n", this, michael@0: temp_NewToplevelSecurityState)); michael@0: michael@0: nsCOMPtr sp = do_QueryInterface(info); michael@0: if (sp) { michael@0: // Ignore result michael@0: updateStatus = true; michael@0: (void) sp->GetSSLStatus(getter_AddRefs(temp_SSLStatus)); michael@0: if (temp_SSLStatus) { michael@0: bool aTemp; michael@0: if (NS_SUCCEEDED(temp_SSLStatus->GetIsExtendedValidation(&aTemp))) { michael@0: temp_NewToplevelIsEV = aTemp; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // assume temp_NewToplevelSecurityState was set in this scope! michael@0: // see code that is directly above michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: mNewToplevelSecurityStateKnown = true; michael@0: mNewToplevelSecurityState = temp_NewToplevelSecurityState; michael@0: mNewToplevelIsEV = temp_NewToplevelIsEV; michael@0: if (updateStatus) { michael@0: mSSLStatus = temp_SSLStatus; michael@0: } michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: remember securityInfo %p\n", this, michael@0: info)); michael@0: nsCOMPtr associatedContentSecurityFromRequest = michael@0: do_QueryInterface(aRequest); michael@0: if (associatedContentSecurityFromRequest) michael@0: mCurrentToplevelSecurityInfo = aRequest; michael@0: else michael@0: mCurrentToplevelSecurityInfo = info; michael@0: michael@0: // The subrequest counters are now in sync with michael@0: // mCurrentToplevelSecurityInfo, don't restore after top level michael@0: // document load finishes. michael@0: mRestoreSubrequests = false; michael@0: } michael@0: michael@0: return UpdateSecurityState(aRequest, withNewLocation, updateStatus); michael@0: } michael@0: michael@0: void michael@0: nsSecureBrowserUIImpl::UpdateSubrequestMembers(nsISupports *securityInfo) michael@0: { michael@0: // For wyciwyg channels in subdocuments we only update our michael@0: // subrequest state members. michael@0: uint32_t reqState = GetSecurityStateFromSecurityInfo(securityInfo); michael@0: michael@0: // the code above this line should run without a lock michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: michael@0: if (reqState & STATE_IS_SECURE) { michael@0: // do nothing michael@0: } else if (reqState & STATE_IS_BROKEN) { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: subreq BROKEN\n", this)); michael@0: ++mSubRequestsBrokenSecurity; michael@0: } else { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: subreq INSECURE\n", this)); michael@0: ++mSubRequestsNoSecurity; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, michael@0: uint32_t aProgressStateFlags, michael@0: nsresult aStatus) michael@0: { michael@0: #ifdef DEBUG michael@0: nsAutoAtomic atomic(mOnStateLocationChangeReentranceDetection); michael@0: NS_ASSERTION(mOnStateLocationChangeReentranceDetection == 1, michael@0: "unexpected parallel nsIWebProgress OnStateChange and/or OnLocationChange notification"); michael@0: #endif michael@0: /* michael@0: All discussion, unless otherwise mentioned, only refers to michael@0: http, https, file or wyciwig requests. michael@0: michael@0: michael@0: Redirects are evil, well, some of them. michael@0: There are multiple forms of redirects. michael@0: michael@0: Redirects caused by http refresh content are ok, because experiments show, michael@0: with those redirects, the old page contents and their requests will come to STOP michael@0: completely, before any progress from new refreshed page content is reported. michael@0: So we can safely treat them as separate page loading transactions. michael@0: michael@0: Evil are redirects at the http protocol level, like code 302. michael@0: michael@0: If the toplevel documents gets replaced, i.e. redirected with 302, we do not care for the michael@0: security state of the initial transaction, which has now been redirected, michael@0: we only care for the new page load. michael@0: michael@0: For the implementation of the security UI, we make an assumption, that is hopefully true. michael@0: michael@0: Imagine, the received page that was delivered with the 302 redirection answer, michael@0: also delivered html content. michael@0: michael@0: What happens if the parser starts to analyze the content and tries to load contained sub objects? michael@0: michael@0: In that case we would see start and stop requests for subdocuments, some for the previous document, michael@0: some for the new target document. And only those for the new toplevel document may be michael@0: taken into consideration, when deciding about the security state of the next toplevel document. michael@0: michael@0: Because security state is being looked at, when loading stops for (sub)documents, this michael@0: could cause real confusion, because we have to decide, whether an incoming progress michael@0: belongs to the new toplevel page, or the previous, already redirected page. michael@0: michael@0: Can we simplify here? michael@0: michael@0: If a redirect at the http protocol level is seen, can we safely assume, its html content michael@0: will not be parsed, anylzed, and no embedded objects will get loaded (css, js, images), michael@0: because the redirect is already happening? michael@0: michael@0: If we can assume that, this really simplify things. Because we will never see notification michael@0: for sub requests that need to get ignored. michael@0: michael@0: I would like to make this assumption for now, but please let me (kaie) know if I'm wrong. michael@0: michael@0: Excurse: michael@0: If my assumption is wrong, then we would require more tracking information. michael@0: We need to keep lists of all pointers to request object that had been seen since the michael@0: last toplevel start event. michael@0: If the start for a redirected page is seen, the list of releveant object must be cleared, michael@0: and only progress for requests which start after it must be analyzed. michael@0: All other events must be ignored, as they belong to now irrelevant previous top level documents. michael@0: michael@0: michael@0: Frames are also evil. michael@0: michael@0: First we need a decision. michael@0: kaie thinks: michael@0: Only if the toplevel frame is secure, we should try to display secure lock icons. michael@0: If some of the inner contents are insecure, we display mixed mode. michael@0: michael@0: But if the top level frame is not secure, why indicate a mixed lock icon at all? michael@0: I think we should always display an open lock icon, if the top level frameset is insecure. michael@0: michael@0: That's the way Netscape Communicator behaves, and I think we should do the same. michael@0: michael@0: The user will not know which parts are secure and which are not, michael@0: and any certificate information, displayed in the tooltip or in the "page info" michael@0: will only be relevant for some subframe(s), and the user will not know which ones, michael@0: so we shouldn't display it as a general attribute of the displayed page. michael@0: michael@0: Why are frames evil? michael@0: michael@0: Because the progress for the toplevel frame document is not easily distinguishable michael@0: from subframes. The same STATE bits are reported. michael@0: michael@0: While at first sight, when a new page load happens, michael@0: the toplevel frameset document has also the STATE_IS_NETWORK bit in it. michael@0: But this can't really be used. Because in case that document causes a http 302 redirect, michael@0: the real top level frameset will no longer have that bit. michael@0: michael@0: But we need some way to distinguish top level frames from inner frames. michael@0: michael@0: I saw that the web progress we get delivered has a reference to the toplevel DOM window. michael@0: michael@0: I suggest, we look at all incoming requests. michael@0: If a request is NOT for the toplevel DOM window, we will always treat it as a subdocument request, michael@0: regardless of whether the load flags indicate a top level document. michael@0: */ michael@0: michael@0: nsCOMPtr windowForProgress; michael@0: aWebProgress->GetDOMWindow(getter_AddRefs(windowForProgress)); michael@0: michael@0: nsCOMPtr window; michael@0: bool isViewSource; michael@0: michael@0: nsCOMPtr ioService; michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: window = do_QueryReferent(mWindow); michael@0: NS_ASSERTION(window, "Window has gone away?!"); michael@0: isViewSource = mIsViewSource; michael@0: ioService = mIOService; michael@0: } michael@0: michael@0: if (!ioService) michael@0: { michael@0: ioService = do_GetService(NS_IOSERVICE_CONTRACTID); michael@0: if (ioService) michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: mIOService = ioService; michael@0: } michael@0: } michael@0: michael@0: bool isNoContentResponse = false; michael@0: nsCOMPtr httpChannel = do_QueryInterface(aRequest); michael@0: if (httpChannel) michael@0: { michael@0: uint32_t response; michael@0: isNoContentResponse = NS_SUCCEEDED(httpChannel->GetResponseStatus(&response)) && michael@0: (response == 204 || response == 205); michael@0: } michael@0: const bool isToplevelProgress = (windowForProgress.get() == window.get()) && !isNoContentResponse; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (windowForProgress) michael@0: { michael@0: if (isToplevelProgress) michael@0: { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: progress: for toplevel\n", this)); michael@0: } michael@0: else michael@0: { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: progress: for something else\n", this)); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: progress: no window known\n", this)); michael@0: } michael@0: #endif michael@0: michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange\n", this)); michael@0: michael@0: if (isViewSource) michael@0: return NS_OK; michael@0: michael@0: if (!aRequest) michael@0: { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange with null request\n", this)); michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gSecureDocLog, PR_LOG_DEBUG)) { michael@0: nsXPIDLCString reqname; michael@0: aRequest->GetName(reqname); michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: %p %p OnStateChange %x %s\n", this, aWebProgress, michael@0: aRequest, aProgressStateFlags, reqname.get())); michael@0: } michael@0: #endif michael@0: michael@0: nsCOMPtr securityInfo(ExtractSecurityInfo(aRequest)); michael@0: michael@0: nsCOMPtr uri; michael@0: nsCOMPtr channel(do_QueryInterface(aRequest)); michael@0: if (channel) { michael@0: channel->GetURI(getter_AddRefs(uri)); michael@0: } michael@0: michael@0: nsCOMPtr imgRequest(do_QueryInterface(aRequest)); michael@0: if (imgRequest) { michael@0: NS_ASSERTION(!channel, "How did that happen, exactly?"); michael@0: // for image requests, we get the URI from here michael@0: imgRequest->GetURI(getter_AddRefs(uri)); michael@0: } michael@0: michael@0: if (uri) { michael@0: bool vs; michael@0: if (NS_SUCCEEDED(uri->SchemeIs("javascript", &vs)) && vs) { michael@0: // We ignore the progress events for javascript URLs. michael@0: // If a document loading gets triggered, we will see more events. michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: uint32_t loadFlags = 0; michael@0: aRequest->GetLoadFlags(&loadFlags); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (aProgressStateFlags & STATE_START michael@0: && michael@0: aProgressStateFlags & STATE_IS_REQUEST michael@0: && michael@0: isToplevelProgress michael@0: && michael@0: loadFlags & nsIChannel::LOAD_DOCUMENT_URI) michael@0: { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: SOMETHING STARTS FOR TOPMOST DOCUMENT\n", this)); michael@0: } michael@0: michael@0: if (aProgressStateFlags & STATE_STOP michael@0: && michael@0: aProgressStateFlags & STATE_IS_REQUEST michael@0: && michael@0: isToplevelProgress michael@0: && michael@0: loadFlags & nsIChannel::LOAD_DOCUMENT_URI) michael@0: { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: SOMETHING STOPS FOR TOPMOST DOCUMENT\n", this)); michael@0: } michael@0: #endif michael@0: michael@0: bool isSubDocumentRelevant = true; michael@0: michael@0: // We are only interested in requests that load in the browser window... michael@0: if (!imgRequest) { // is not imgRequest michael@0: nsCOMPtr httpRequest(do_QueryInterface(aRequest)); michael@0: if (!httpRequest) { michael@0: nsCOMPtr fileRequest(do_QueryInterface(aRequest)); michael@0: if (!fileRequest) { michael@0: nsCOMPtr wyciwygRequest(do_QueryInterface(aRequest)); michael@0: if (!wyciwygRequest) { michael@0: nsCOMPtr ftpRequest(do_QueryInterface(aRequest)); michael@0: if (!ftpRequest) { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: not relevant for sub content\n", this)); michael@0: isSubDocumentRelevant = false; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // This will ignore all resource, chrome, data, file, moz-icon, and anno michael@0: // protocols. Local resources are treated as trusted. michael@0: if (uri && ioService) { michael@0: bool hasFlag; michael@0: nsresult rv = michael@0: ioService->URIChainHasFlags(uri, michael@0: nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, michael@0: &hasFlag); michael@0: if (NS_SUCCEEDED(rv) && hasFlag) { michael@0: isSubDocumentRelevant = false; michael@0: } michael@0: } michael@0: michael@0: #if defined(DEBUG) michael@0: nsCString info2; michael@0: uint32_t testFlags = loadFlags; michael@0: michael@0: if (testFlags & nsIChannel::LOAD_DOCUMENT_URI) michael@0: { michael@0: testFlags -= nsIChannel::LOAD_DOCUMENT_URI; michael@0: info2.Append("LOAD_DOCUMENT_URI "); michael@0: } michael@0: if (testFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) michael@0: { michael@0: testFlags -= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI; michael@0: info2.Append("LOAD_RETARGETED_DOCUMENT_URI "); michael@0: } michael@0: if (testFlags & nsIChannel::LOAD_REPLACE) michael@0: { michael@0: testFlags -= nsIChannel::LOAD_REPLACE; michael@0: info2.Append("LOAD_REPLACE "); michael@0: } michael@0: michael@0: const char *_status = NS_SUCCEEDED(aStatus) ? "1" : "0"; michael@0: michael@0: nsCString info; michael@0: uint32_t f = aProgressStateFlags; michael@0: if (f & nsIWebProgressListener::STATE_START) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_START; michael@0: info.Append("START "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_REDIRECTING) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_REDIRECTING; michael@0: info.Append("REDIRECTING "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_TRANSFERRING) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_TRANSFERRING; michael@0: info.Append("TRANSFERRING "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_NEGOTIATING) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_NEGOTIATING; michael@0: info.Append("NEGOTIATING "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_STOP) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_STOP; michael@0: info.Append("STOP "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_IS_REQUEST) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_IS_REQUEST; michael@0: info.Append("IS_REQUEST "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_IS_DOCUMENT) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_IS_DOCUMENT; michael@0: info.Append("IS_DOCUMENT "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_IS_NETWORK) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_IS_NETWORK; michael@0: info.Append("IS_NETWORK "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_IS_WINDOW) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_IS_WINDOW; michael@0: info.Append("IS_WINDOW "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_IS_INSECURE) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_IS_INSECURE; michael@0: info.Append("IS_INSECURE "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_IS_BROKEN) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_IS_BROKEN; michael@0: info.Append("IS_BROKEN "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_IS_SECURE) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_IS_SECURE; michael@0: info.Append("IS_SECURE "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_SECURE_HIGH) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_SECURE_HIGH; michael@0: info.Append("SECURE_HIGH "); michael@0: } michael@0: if (f & nsIWebProgressListener::STATE_RESTORING) michael@0: { michael@0: f -= nsIWebProgressListener::STATE_RESTORING; michael@0: info.Append("STATE_RESTORING "); michael@0: } michael@0: michael@0: if (f > 0) michael@0: { michael@0: info.Append("f contains unknown flag!"); michael@0: } michael@0: michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: %s %s -- %s\n", this, _status, michael@0: info.get(), info2.get())); michael@0: michael@0: if (aProgressStateFlags & STATE_STOP michael@0: && michael@0: channel) michael@0: { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: seeing STOP with security state: %d\n", this, michael@0: GetSecurityStateFromSecurityInfo(securityInfo) michael@0: )); michael@0: } michael@0: #endif michael@0: michael@0: if (aProgressStateFlags & STATE_TRANSFERRING michael@0: && michael@0: aProgressStateFlags & STATE_IS_REQUEST) michael@0: { michael@0: // The listing of a request in mTransferringRequests michael@0: // means, there has already been data transfered. michael@0: michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: PL_DHashTableOperate(&mTransferringRequests, aRequest, PL_DHASH_ADD); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool requestHasTransferedData = false; michael@0: michael@0: if (aProgressStateFlags & STATE_STOP michael@0: && michael@0: aProgressStateFlags & STATE_IS_REQUEST) michael@0: { michael@0: { /* scope for the ReentrantMonitorAutoEnter */ michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: PLDHashEntryHdr *entry = PL_DHashTableOperate(&mTransferringRequests, aRequest, PL_DHASH_LOOKUP); michael@0: if (PL_DHASH_ENTRY_IS_BUSY(entry)) michael@0: { michael@0: PL_DHashTableOperate(&mTransferringRequests, aRequest, PL_DHASH_REMOVE); michael@0: michael@0: requestHasTransferedData = true; michael@0: } michael@0: } michael@0: michael@0: if (!requestHasTransferedData) { michael@0: // Because image loads doesn't support any TRANSFERRING notifications but michael@0: // only START and STOP we must ask them directly whether content was michael@0: // transferred. See bug 432685 for details. michael@0: nsCOMPtr securityInfoProvider = michael@0: do_QueryInterface(aRequest); michael@0: // Guess true in all failure cases to be safe. But if we're not michael@0: // an nsISecurityInfoProvider, then we just haven't transferred michael@0: // any data. michael@0: bool hasTransferred; michael@0: requestHasTransferedData = michael@0: securityInfoProvider && michael@0: (NS_FAILED(securityInfoProvider->GetHasTransferredData(&hasTransferred)) || michael@0: hasTransferred); michael@0: } michael@0: } michael@0: michael@0: bool allowSecurityStateChange = true; michael@0: if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) michael@0: { michael@0: // The original consumer (this) is no longer the target of the load. michael@0: // Ignore any events with this flag, do not allow them to update michael@0: // our secure UI state. michael@0: allowSecurityStateChange = false; michael@0: } michael@0: michael@0: if (aProgressStateFlags & STATE_START michael@0: && michael@0: aProgressStateFlags & STATE_IS_REQUEST michael@0: && michael@0: isToplevelProgress michael@0: && michael@0: loadFlags & nsIChannel::LOAD_DOCUMENT_URI) michael@0: { michael@0: bool inProgress; michael@0: michael@0: int32_t saveSubBroken; michael@0: int32_t saveSubNo; michael@0: nsCOMPtr prevContentSecurity; michael@0: michael@0: int32_t newSubBroken = 0; michael@0: int32_t newSubNo = 0; michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: inProgress = (mDocumentRequestsInProgress!=0); michael@0: michael@0: if (allowSecurityStateChange && !inProgress) michael@0: { michael@0: saveSubBroken = mSubRequestsBrokenSecurity; michael@0: saveSubNo = mSubRequestsNoSecurity; michael@0: prevContentSecurity = do_QueryInterface(mCurrentToplevelSecurityInfo); michael@0: } michael@0: } michael@0: michael@0: if (allowSecurityStateChange && !inProgress) michael@0: { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: start for toplevel document\n", this michael@0: )); michael@0: michael@0: if (prevContentSecurity) michael@0: { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: start, saving current sub state\n", this michael@0: )); michael@0: michael@0: // before resetting our state, let's save information about michael@0: // sub element loads, so we can restore it later michael@0: prevContentSecurity->SetCountSubRequestsBrokenSecurity(saveSubBroken); michael@0: prevContentSecurity->SetCountSubRequestsNoSecurity(saveSubNo); michael@0: prevContentSecurity->Flush(); michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI:%p: Saving subs in START to %p as %d,%d\n", michael@0: this, prevContentSecurity.get(), saveSubBroken, saveSubNo)); michael@0: } michael@0: michael@0: bool retrieveAssociatedState = false; michael@0: michael@0: if (securityInfo && michael@0: (aProgressStateFlags & nsIWebProgressListener::STATE_RESTORING) != 0) { michael@0: retrieveAssociatedState = true; michael@0: } else { michael@0: nsCOMPtr wyciwygRequest(do_QueryInterface(aRequest)); michael@0: if (wyciwygRequest) { michael@0: retrieveAssociatedState = true; michael@0: } michael@0: } michael@0: michael@0: if (retrieveAssociatedState) michael@0: { michael@0: // When restoring from bfcache, we will not get events for the michael@0: // page's sub elements, so let's load the state of sub elements michael@0: // from the cache. michael@0: michael@0: nsCOMPtr michael@0: newContentSecurity(do_QueryInterface(securityInfo)); michael@0: michael@0: if (newContentSecurity) michael@0: { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: start, loading old sub state\n", this michael@0: )); michael@0: michael@0: newContentSecurity->GetCountSubRequestsBrokenSecurity(&newSubBroken); michael@0: newContentSecurity->GetCountSubRequestsNoSecurity(&newSubNo); michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI:%p: Restoring subs in START from %p to %d,%d\n", michael@0: this, newContentSecurity.get(), newSubBroken, newSubNo)); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // If we don't get OnLocationChange for this top level load later, michael@0: // it didn't get rendered. But we reset the state to unknown and michael@0: // mSubRequests* to zeros. If we would have left these values after michael@0: // this top level load stoped, we would override the original top level michael@0: // load with all zeros and break mixed content state on back and forward. michael@0: mRestoreSubrequests = true; michael@0: } michael@0: } michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: michael@0: if (allowSecurityStateChange && !inProgress) michael@0: { michael@0: ResetStateTracking(); michael@0: mSubRequestsBrokenSecurity = newSubBroken; michael@0: mSubRequestsNoSecurity = newSubNo; michael@0: mNewToplevelSecurityStateKnown = false; michael@0: } michael@0: michael@0: // By using a counter, this code also works when the toplevel michael@0: // document get's redirected, but the STOP request for the michael@0: // previous toplevel document has not yet have been received. michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: ++mDocumentRequestsInProgress\n", this michael@0: )); michael@0: ++mDocumentRequestsInProgress; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aProgressStateFlags & STATE_STOP michael@0: && michael@0: aProgressStateFlags & STATE_IS_REQUEST michael@0: && michael@0: isToplevelProgress michael@0: && michael@0: loadFlags & nsIChannel::LOAD_DOCUMENT_URI) michael@0: { michael@0: int32_t temp_DocumentRequestsInProgress; michael@0: nsCOMPtr temp_ToplevelEventSink; michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: temp_DocumentRequestsInProgress = mDocumentRequestsInProgress; michael@0: if (allowSecurityStateChange) michael@0: { michael@0: temp_ToplevelEventSink = mToplevelEventSink; michael@0: } michael@0: } michael@0: michael@0: if (temp_DocumentRequestsInProgress <= 0) michael@0: { michael@0: // Ignore stop requests unless a document load is in progress michael@0: // Unfortunately on application start, see some stops without having seen any starts... michael@0: return NS_OK; michael@0: } michael@0: michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnStateChange: --mDocumentRequestsInProgress\n", this michael@0: )); michael@0: michael@0: if (!temp_ToplevelEventSink && channel) michael@0: { michael@0: if (allowSecurityStateChange) michael@0: { michael@0: ObtainEventSink(channel, temp_ToplevelEventSink); michael@0: } michael@0: } michael@0: michael@0: bool sinkChanged = false; michael@0: bool inProgress; michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: if (allowSecurityStateChange) michael@0: { michael@0: sinkChanged = (mToplevelEventSink != temp_ToplevelEventSink); michael@0: mToplevelEventSink = temp_ToplevelEventSink; michael@0: } michael@0: --mDocumentRequestsInProgress; michael@0: inProgress = mDocumentRequestsInProgress > 0; michael@0: } michael@0: michael@0: if (allowSecurityStateChange && requestHasTransferedData) { michael@0: // Data has been transferred for the single toplevel michael@0: // request. Evaluate the security state. michael@0: michael@0: // Do this only when the sink has changed. We update and notify michael@0: // the state from OnLacationChange, this is actually redundant. michael@0: // But when the target sink changes between OnLocationChange and michael@0: // OnStateChange, we have to fire the notification here (again). michael@0: michael@0: if (sinkChanged || mOnLocationChangeSeen) michael@0: return EvaluateAndUpdateSecurityState(aRequest, securityInfo, false); michael@0: } michael@0: mOnLocationChangeSeen = false; michael@0: michael@0: if (mRestoreSubrequests && !inProgress) michael@0: { michael@0: // We get here when there were no OnLocationChange between michael@0: // OnStateChange(START) and OnStateChange(STOP). Then the load has not michael@0: // been rendered but has been retargeted in some other way then by external michael@0: // app handler. Restore mSubRequests* members to what the current security michael@0: // state info holds (it was reset to all zero in OnStateChange(START) michael@0: // before). michael@0: nsCOMPtr currentContentSecurity; michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: currentContentSecurity = do_QueryInterface(mCurrentToplevelSecurityInfo); michael@0: michael@0: // Drop this indication flag, the restore opration is just being michael@0: // done. michael@0: mRestoreSubrequests = false; michael@0: michael@0: // We can do this since the state didn't actually change. michael@0: mNewToplevelSecurityStateKnown = true; michael@0: } michael@0: michael@0: int32_t subBroken = 0; michael@0: int32_t subNo = 0; michael@0: michael@0: if (currentContentSecurity) michael@0: { michael@0: currentContentSecurity->GetCountSubRequestsBrokenSecurity(&subBroken); michael@0: currentContentSecurity->GetCountSubRequestsNoSecurity(&subNo); michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, ("SecureUI:%p: Restoring subs in STOP from %p to %d,%d\n", michael@0: this, currentContentSecurity.get(), subBroken, subNo)); michael@0: } michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: mSubRequestsBrokenSecurity = subBroken; michael@0: mSubRequestsNoSecurity = subNo; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aProgressStateFlags & STATE_STOP michael@0: && michael@0: aProgressStateFlags & STATE_IS_REQUEST) michael@0: { michael@0: if (!isSubDocumentRelevant) michael@0: return NS_OK; michael@0: michael@0: // if we arrive here, LOAD_DOCUMENT_URI is not set michael@0: michael@0: // We only care for the security state of sub requests which have actually transfered data. michael@0: michael@0: if (allowSecurityStateChange && requestHasTransferedData) michael@0: { michael@0: UpdateSubrequestMembers(securityInfo); michael@0: michael@0: // Care for the following scenario: michael@0: // A new top level document load might have already started, michael@0: // but the security state of the new top level document might not yet been known. michael@0: // michael@0: // At this point, we are learning about the security state of a sub-document. michael@0: // We must not update the security state based on the sub content, michael@0: // if the new top level state is not yet known. michael@0: // michael@0: // We skip updating the security state in this case. michael@0: michael@0: bool temp_NewToplevelSecurityStateKnown; michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: temp_NewToplevelSecurityStateKnown = mNewToplevelSecurityStateKnown; michael@0: } michael@0: michael@0: if (temp_NewToplevelSecurityStateKnown) michael@0: return UpdateSecurityState(aRequest, false, false); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // I'm keeping this as a separate function, in order to simplify the review michael@0: // for bug 412456. We should inline this in a follow up patch. michael@0: void nsSecureBrowserUIImpl::ObtainEventSink(nsIChannel *channel, michael@0: nsCOMPtr &sink) michael@0: { michael@0: if (!sink) michael@0: NS_QueryNotificationCallbacks(channel, sink); michael@0: } michael@0: michael@0: nsresult nsSecureBrowserUIImpl::UpdateSecurityState(nsIRequest* aRequest, michael@0: bool withNewLocation, michael@0: bool withUpdateStatus) michael@0: { michael@0: lockIconState warnSecurityState = lis_no_security; michael@0: nsresult rv = NS_OK; michael@0: michael@0: // both parameters are both input and outout michael@0: bool flagsChanged = UpdateMyFlags(warnSecurityState); michael@0: michael@0: if (flagsChanged || withNewLocation || withUpdateStatus) michael@0: rv = TellTheWorld(warnSecurityState, aRequest); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // must not fail, by definition, only trivial assignments michael@0: // or string operations are allowed michael@0: // returns true if our overall state has changed and we must send out notifications michael@0: bool nsSecureBrowserUIImpl::UpdateMyFlags(lockIconState &warnSecurityState) michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: bool mustTellTheWorld = false; michael@0: michael@0: lockIconState newSecurityState; michael@0: michael@0: if (mNewToplevelSecurityState & STATE_IS_SECURE) michael@0: { michael@0: if (mSubRequestsBrokenSecurity michael@0: || michael@0: mSubRequestsNoSecurity) michael@0: { michael@0: newSecurityState = lis_mixed_security; michael@0: } michael@0: else michael@0: { michael@0: newSecurityState = lis_high_security; michael@0: } michael@0: } michael@0: else michael@0: if (mNewToplevelSecurityState & STATE_IS_BROKEN) michael@0: { michael@0: // indicating BROKEN is more important than MIXED. michael@0: michael@0: newSecurityState = lis_broken_security; michael@0: } michael@0: else michael@0: { michael@0: newSecurityState = lis_no_security; michael@0: } michael@0: michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: UpdateSecurityState: old-new %d - %d\n", this, michael@0: mNotifiedSecurityState, newSecurityState michael@0: )); michael@0: michael@0: if (mNotifiedSecurityState != newSecurityState) michael@0: { michael@0: mustTellTheWorld = true; michael@0: michael@0: // we'll treat "broken" exactly like "insecure", michael@0: michael@0: /* michael@0: security icon michael@0: ---------------- michael@0: michael@0: no open michael@0: mixed broken michael@0: broken broken michael@0: high high michael@0: */ michael@0: michael@0: mNotifiedSecurityState = newSecurityState; michael@0: michael@0: if (lis_no_security == newSecurityState) michael@0: { michael@0: mSSLStatus = nullptr; michael@0: } michael@0: } michael@0: michael@0: if (mNotifiedToplevelIsEV != mNewToplevelIsEV) { michael@0: mustTellTheWorld = true; michael@0: mNotifiedToplevelIsEV = mNewToplevelIsEV; michael@0: } michael@0: michael@0: return mustTellTheWorld; michael@0: } michael@0: michael@0: nsresult nsSecureBrowserUIImpl::TellTheWorld(lockIconState warnSecurityState, michael@0: nsIRequest* aRequest) michael@0: { michael@0: nsCOMPtr temp_ToplevelEventSink; michael@0: lockIconState temp_NotifiedSecurityState; michael@0: bool temp_NotifiedToplevelIsEV; michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: temp_ToplevelEventSink = mToplevelEventSink; michael@0: temp_NotifiedSecurityState = mNotifiedSecurityState; michael@0: temp_NotifiedToplevelIsEV = mNotifiedToplevelIsEV; michael@0: } michael@0: michael@0: if (temp_ToplevelEventSink) michael@0: { michael@0: uint32_t newState = STATE_IS_INSECURE; michael@0: MapInternalToExternalState(&newState, michael@0: temp_NotifiedSecurityState, michael@0: temp_NotifiedToplevelIsEV); michael@0: michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: UpdateSecurityState: calling OnSecurityChange\n", this michael@0: )); michael@0: michael@0: temp_ToplevelEventSink->OnSecurityChange(aRequest, newState); michael@0: } michael@0: else michael@0: { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: UpdateSecurityState: NO mToplevelEventSink!\n", this michael@0: )); michael@0: michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, michael@0: nsIURI* aLocation, michael@0: uint32_t aFlags) michael@0: { michael@0: #ifdef DEBUG michael@0: nsAutoAtomic atomic(mOnStateLocationChangeReentranceDetection); michael@0: NS_ASSERTION(mOnStateLocationChangeReentranceDetection == 1, michael@0: "unexpected parallel nsIWebProgress OnStateChange and/or OnLocationChange notification"); michael@0: #endif michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnLocationChange\n", this)); michael@0: michael@0: bool updateIsViewSource = false; michael@0: bool temp_IsViewSource = false; michael@0: nsCOMPtr window; michael@0: michael@0: if (aLocation) michael@0: { michael@0: bool vs; michael@0: michael@0: nsresult rv = aLocation->SchemeIs("view-source", &vs); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (vs) { michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnLocationChange: view-source\n", this)); michael@0: } michael@0: michael@0: updateIsViewSource = true; michael@0: temp_IsViewSource = vs; michael@0: } michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: if (updateIsViewSource) { michael@0: mIsViewSource = temp_IsViewSource; michael@0: } michael@0: mCurrentURI = aLocation; michael@0: window = do_QueryReferent(mWindow); michael@0: NS_ASSERTION(window, "Window has gone away?!"); michael@0: } michael@0: michael@0: // When |aRequest| is null, basically we don't trust that document. But if michael@0: // docshell insists that the document has not changed at all, we will reuse michael@0: // the previous security state, no matter what |aRequest| may be. michael@0: if (aFlags & LOCATION_CHANGE_SAME_DOCUMENT) michael@0: return NS_OK; michael@0: michael@0: // The location bar has changed, so we must update the security state. The michael@0: // only concern with doing this here is that a page may transition from being michael@0: // reported as completely secure to being reported as partially secure michael@0: // (mixed). This may be confusing for users, and it may bother users who michael@0: // like seeing security dialogs. However, it seems prudent given that page michael@0: // loading may never end in some edge cases (perhaps by a site with malicious michael@0: // intent). michael@0: michael@0: nsCOMPtr windowForProgress; michael@0: aWebProgress->GetDOMWindow(getter_AddRefs(windowForProgress)); michael@0: michael@0: nsCOMPtr securityInfo(ExtractSecurityInfo(aRequest)); michael@0: michael@0: if (windowForProgress.get() == window.get()) { michael@0: // For toplevel channels, update the security state right away. michael@0: mOnLocationChangeSeen = true; michael@0: return EvaluateAndUpdateSecurityState(aRequest, securityInfo, true); michael@0: } michael@0: michael@0: // For channels in subdocuments we only update our subrequest state members. michael@0: UpdateSubrequestMembers(securityInfo); michael@0: michael@0: // Care for the following scenario: michael@0: michael@0: // A new toplevel document load might have already started, but the security michael@0: // state of the new toplevel document might not yet be known. michael@0: // michael@0: // At this point, we are learning about the security state of a sub-document. michael@0: // We must not update the security state based on the sub content, if the new michael@0: // top level state is not yet known. michael@0: // michael@0: // We skip updating the security state in this case. michael@0: michael@0: bool temp_NewToplevelSecurityStateKnown; michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: temp_NewToplevelSecurityStateKnown = mNewToplevelSecurityStateKnown; michael@0: } michael@0: michael@0: if (temp_NewToplevelSecurityStateKnown) michael@0: return UpdateSecurityState(aRequest, true, false); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSecureBrowserUIImpl::OnStatusChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, michael@0: nsresult aStatus, michael@0: const char16_t* aMessage) michael@0: { michael@0: NS_NOTREACHED("notification excluded in AddProgressListener(...)"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSecureBrowserUIImpl::OnSecurityChange(nsIWebProgress *aWebProgress, michael@0: nsIRequest *aRequest, michael@0: uint32_t state) michael@0: { michael@0: #if defined(DEBUG) michael@0: nsCOMPtr channel(do_QueryInterface(aRequest)); michael@0: if (!channel) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr aURI; michael@0: channel->GetURI(getter_AddRefs(aURI)); michael@0: michael@0: if (aURI) { michael@0: nsAutoCString temp; michael@0: aURI->GetSpec(temp); michael@0: PR_LOG(gSecureDocLog, PR_LOG_DEBUG, michael@0: ("SecureUI:%p: OnSecurityChange: (%x) %s\n", this, michael@0: state, temp.get())); michael@0: } michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsISSLStatusProvider methods michael@0: NS_IMETHODIMP michael@0: nsSecureBrowserUIImpl::GetSSLStatus(nsISSLStatus** _result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(_result); michael@0: michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: michael@0: switch (mNotifiedSecurityState) michael@0: { michael@0: case lis_mixed_security: michael@0: case lis_high_security: michael@0: break; michael@0: michael@0: default: michael@0: NS_NOTREACHED("if this is reached you must add more entries to the switch"); michael@0: case lis_no_security: michael@0: case lis_broken_security: michael@0: *_result = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: *_result = mSSLStatus; michael@0: NS_IF_ADDREF(*_result); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSecureBrowserUIImpl::IsURLHTTPS(nsIURI* aURL, bool* value) michael@0: { michael@0: *value = false; michael@0: michael@0: if (!aURL) michael@0: return NS_OK; michael@0: michael@0: return aURL->SchemeIs("https", value); michael@0: } michael@0: michael@0: nsresult michael@0: nsSecureBrowserUIImpl::IsURLJavaScript(nsIURI* aURL, bool* value) michael@0: { michael@0: *value = false; michael@0: michael@0: if (!aURL) michael@0: return NS_OK; michael@0: michael@0: return aURL->SchemeIs("javascript", value); michael@0: } michael@0: michael@0: nsresult michael@0: nsSecureBrowserUIImpl::CheckPost(nsIURI *formURL, nsIURI *actionURL, bool *okayToPost) michael@0: { michael@0: bool formSecure, actionSecure, actionJavaScript; michael@0: *okayToPost = true; michael@0: michael@0: nsresult rv = IsURLHTTPS(formURL, &formSecure); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = IsURLHTTPS(actionURL, &actionSecure); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = IsURLJavaScript(actionURL, &actionJavaScript); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // If we are posting to a secure link, all is okay. michael@0: // It doesn't matter whether the currently viewed page is secure or not, michael@0: // because the data will be sent to a secure URL. michael@0: if (actionSecure) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Action is a JavaScript call, not an actual post. That's okay too. michael@0: if (actionJavaScript) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // posting to insecure webpage from a secure webpage. michael@0: if (formSecure) { michael@0: *okayToPost = ConfirmPostToInsecureFromSecure(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // Implementation of an nsIInterfaceRequestor for use michael@0: // as context for NSS calls michael@0: // michael@0: class nsUIContext : public nsIInterfaceRequestor michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIINTERFACEREQUESTOR michael@0: michael@0: nsUIContext(nsIDOMWindow *window); michael@0: virtual ~nsUIContext(); michael@0: michael@0: private: michael@0: nsCOMPtr mWindow; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsUIContext, nsIInterfaceRequestor) michael@0: michael@0: nsUIContext::nsUIContext(nsIDOMWindow *aWindow) michael@0: : mWindow(aWindow) michael@0: { michael@0: } michael@0: michael@0: nsUIContext::~nsUIContext() michael@0: { michael@0: } michael@0: michael@0: /* void getInterface (in nsIIDRef uuid, [iid_is (uuid), retval] out nsQIResult result); */ michael@0: NS_IMETHODIMP nsUIContext::GetInterface(const nsIID & uuid, void * *result) michael@0: { michael@0: NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE); michael@0: nsresult rv; michael@0: michael@0: if (uuid.Equals(NS_GET_IID(nsIPrompt))) { michael@0: nsCOMPtr window = do_QueryInterface(mWindow, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsIPrompt *prompt; michael@0: michael@0: rv = window->GetPrompter(&prompt); michael@0: *result = prompt; michael@0: } else if (uuid.Equals(NS_GET_IID(nsIDOMWindow))) { michael@0: *result = mWindow; michael@0: NS_ADDREF ((nsISupports*) *result); michael@0: rv = NS_OK; michael@0: } else { michael@0: rv = NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: nsSecureBrowserUIImpl::GetNSSDialogs(nsCOMPtr & dialogs, michael@0: nsCOMPtr & ctx) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: NS_ERROR("nsSecureBrowserUIImpl::GetNSSDialogs called off the main thread"); michael@0: return false; michael@0: } michael@0: michael@0: dialogs = do_GetService(NS_SECURITYWARNINGDIALOGS_CONTRACTID); michael@0: if (!dialogs) michael@0: return false; michael@0: michael@0: nsCOMPtr window; michael@0: { michael@0: ReentrantMonitorAutoEnter lock(mReentrantMonitor); michael@0: window = do_QueryReferent(mWindow); michael@0: NS_ASSERTION(window, "Window has gone away?!"); michael@0: } michael@0: ctx = new nsUIContext(window); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * ConfirmPostToInsecureFromSecure - returns true if michael@0: * the user approves the submit (or doesn't care). michael@0: * returns false on errors. michael@0: */ michael@0: bool nsSecureBrowserUIImpl:: michael@0: ConfirmPostToInsecureFromSecure() michael@0: { michael@0: nsCOMPtr dialogs; michael@0: nsCOMPtr ctx; michael@0: michael@0: if (!GetNSSDialogs(dialogs, ctx)) { michael@0: return false; // Should this allow true for unimplemented? michael@0: } michael@0: michael@0: bool result; michael@0: michael@0: nsresult rv = dialogs->ConfirmPostToInsecureFromSecure(ctx, &result); michael@0: if (NS_FAILED(rv)) return false; michael@0: michael@0: return result; michael@0: }