From b278293c7d971fb115013f5ecc8ee47ed296da81 Mon Sep 17 00:00:00 2001 From: Arthur Edelstein Date: Tue, 30 May 2017 10:25:34 -0700 Subject: [PATCH] Bug 22343: Make 'Save Page As' obey first-party isolation Fixes * Save Page As on the File menu * Save Page As, Sage Image As, and Save Frame As on the context menu on content pages * Save As in Page Info. --- browser/base/content/browser.js | 2 +- browser/base/content/nsContextMenu.js | 7 +++-- browser/base/content/pageinfo/pageInfo.js | 5 ++-- embedding/browser/nsWebBrowser.cpp | 12 ++++++++ .../nsIWebBrowserPersist.idl | 5 +++- .../webbrowserpersist/nsWebBrowserPersist.cpp | 18 ++++++++++-- .../webbrowserpersist/nsWebBrowserPersist.h | 2 ++ netwerk/base/LoadContextInfo.cpp | 18 ++++++++++-- toolkit/content/contentAreaUtils.js | 29 +++++++++++++------ 9 files changed, 77 insertions(+), 21 deletions(-) diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 83aca0788d32..cfbf69f50e10 100755 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -5601,7 +5601,7 @@ function handleLinkClick(event, href, linkNode) { if (where == "save") { saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true, - true, doc.documentURIObject, doc); + true, doc.documentURIObject, doc, doc.nodePrincipal); event.preventDefault(); return true; } diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index 8eb9b034f648..0ba54584c8b5 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -1187,7 +1187,7 @@ nsContextMenu.prototype = { let dataURL = message.data.dataURL; saveImageURL(dataURL, name, "SaveImageTitle", true, false, document.documentURIObject, null, null, null, - isPrivate); + isPrivate, this.principal); }; mm.addMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage); }, @@ -1429,14 +1429,15 @@ nsContextMenu.prototype = { this._canvasToBlobURL(this.target).then(function(blobURL) { saveImageURL(blobURL, "canvas.png", "SaveImageTitle", true, false, referrerURI, null, null, null, - isPrivate); + isPrivate, this.principal); }, Cu.reportError); } else if (this.onImage) { urlSecurityCheck(this.mediaURL, this.principal); saveImageURL(this.mediaURL, null, "SaveImageTitle", false, false, referrerURI, null, gContextMenuContentData.contentType, - gContextMenuContentData.contentDisposition, isPrivate); + gContextMenuContentData.contentDisposition, isPrivate, + this.principal); } else if (this.onVideo || this.onAudio) { urlSecurityCheck(this.mediaURL, this.principal); diff --git a/browser/base/content/pageinfo/pageInfo.js b/browser/base/content/pageinfo/pageInfo.js index fc3e3bbf8379..9e8248542706 100644 --- a/browser/base/content/pageinfo/pageInfo.js +++ b/browser/base/content/pageinfo/pageInfo.js @@ -743,7 +743,7 @@ function saveMedia() titleKey = "SaveAudioTitle"; saveURL(url, null, titleKey, false, false, makeURI(item.baseURI), - null, gDocInfo.isContentWindowPrivate); + null, gDocInfo.isContentWindowPrivate, gDocInfo.principal); } } else { selectSaveFolder(function(aDirectory) { @@ -751,7 +751,8 @@ function saveMedia() var saveAnImage = function(aURIString, aChosenData, aBaseURI) { uniqueFile(aChosenData.file); internalSave(aURIString, null, null, null, null, false, "SaveImageTitle", - aChosenData, aBaseURI, null, false, null, gDocInfo.isContentWindowPrivate); + aChosenData, aBaseURI, null, false, null, gDocInfo.isContentWindowPrivate, + gDocInfo.principal); }; for (var i = 0; i < rowArray.length; i++) { diff --git a/embedding/browser/nsWebBrowser.cpp b/embedding/browser/nsWebBrowser.cpp index a833d1efe053..f7856b98f150 100644 --- a/embedding/browser/nsWebBrowser.cpp +++ b/embedding/browser/nsWebBrowser.cpp @@ -985,6 +985,18 @@ nsWebBrowser::SetProgressListener(nsIWebProgressListener* aProgressListener) return NS_OK; } +NS_IMETHODIMP +nsWebBrowser::GetLoadingPrincipal(nsIPrincipal** loadingPrincipal) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsWebBrowser::SetLoadingPrincipal(nsIPrincipal* loadingPrincipal) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + NS_IMETHODIMP nsWebBrowser::SaveURI(nsIURI* aURI, nsISupports* aCacheKey, diff --git a/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl b/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl index cd4bd8b69c18..6a554385f57b 100644 --- a/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl +++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl @@ -13,11 +13,12 @@ interface nsIWebProgressListener; interface nsIFile; interface nsIChannel; interface nsILoadContext; +interface nsIPrincipal; /** * Interface for persisting DOM documents and URIs to local or remote storage. */ -[scriptable, uuid(8cd752a4-60b1-42c3-a819-65c7a1138a28)] +[scriptable, uuid(ccdbc750-be09-4f11-bb01-4e0a4db76c41)] interface nsIWebBrowserPersist : nsICancelable { /** No special persistence behaviour. */ @@ -111,6 +112,8 @@ interface nsIWebBrowserPersist : nsICancelable */ attribute nsIWebProgressListener progressListener; + attribute nsIPrincipal loadingPrincipal; + /** * Save the specified URI to file. * diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp index de85a59bb1c3..abd0551ccb6e 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp @@ -274,6 +274,7 @@ const char *kWebBrowserPersistStringBundle = nsWebBrowserPersist::nsWebBrowserPersist() : mCurrentDataPathIsRelative(false), mCurrentThingsToPersist(0), + mLoadingPrincipal(nsContentUtils::GetSystemPrincipal()), mFirstAndOnlyUse(true), mSavingDocument(false), mCancel(false), @@ -410,6 +411,19 @@ NS_IMETHODIMP nsWebBrowserPersist::SetProgressListener( return NS_OK; } +NS_IMETHODIMP nsWebBrowserPersist::GetLoadingPrincipal(nsIPrincipal** loadingPrincipal) +{ + *loadingPrincipal = mLoadingPrincipal; + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::SetLoadingPrincipal(nsIPrincipal* loadingPrincipal) +{ + mLoadingPrincipal = loadingPrincipal ? loadingPrincipal : + nsContentUtils::GetSystemPrincipal(); + return NS_OK; +} + NS_IMETHODIMP nsWebBrowserPersist::SaveURI( nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer, uint32_t aReferrerPolicy, @@ -1352,7 +1366,7 @@ nsresult nsWebBrowserPersist::SaveURIInternal( nsCOMPtr inputChannel; rv = NS_NewChannel(getter_AddRefs(inputChannel), aURI, - nsContentUtils::GetSystemPrincipal(), + mLoadingPrincipal, nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsIContentPolicy::TYPE_OTHER, nullptr, // aLoadGroup @@ -2696,7 +2710,7 @@ nsWebBrowserPersist::CreateChannelFromURI(nsIURI *aURI, nsIChannel **aChannel) rv = NS_NewChannel(aChannel, aURI, - nsContentUtils::GetSystemPrincipal(), + mLoadingPrincipal, nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsIContentPolicy::TYPE_OTHER); NS_ENSURE_SUCCESS(rv, rv); diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h index 1816af0a698b..9d24d6c2ca85 100644 --- a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h @@ -146,6 +146,8 @@ class nsWebBrowserPersist final : public nsIInterfaceRequestor, nsCOMPtr mMIMEService; nsCOMPtr mURI; nsCOMPtr mProgressListener; + nsCOMPtr mLoadingPrincipal; + /** * Progress listener for 64-bit values; this is the same object as * mProgressListener, but is a member to avoid having to qi it for each diff --git a/netwerk/base/LoadContextInfo.cpp b/netwerk/base/LoadContextInfo.cpp index 77eeb79ec641..ce8ad812e443 100644 --- a/netwerk/base/LoadContextInfo.cpp +++ b/netwerk/base/LoadContextInfo.cpp @@ -118,8 +118,6 @@ GetLoadContextInfo(nsIChannel * aChannel) { nsresult rv; - DebugOnly pb = NS_UsePrivateBrowsing(aChannel); - bool anon = false; nsLoadFlags loadFlags; rv = aChannel->GetLoadFlags(&loadFlags); @@ -129,7 +127,21 @@ GetLoadContextInfo(nsIChannel * aChannel) NeckoOriginAttributes oa; NS_GetOriginAttributes(aChannel, oa); - MOZ_ASSERT(pb == (oa.mPrivateBrowsingId > 0)); + +#ifdef DEBUG + nsCOMPtr loadInfo = aChannel->GetLoadInfo(); + if (loadInfo) { + nsCOMPtr principal = loadInfo->LoadingPrincipal(); + if (principal) { + bool chrome; + principal->GetIsSystemPrincipal(&chrome); + if (!chrome) { + bool pb = NS_UsePrivateBrowsing(aChannel); + MOZ_ASSERT(pb == (oa.mPrivateBrowsingId > 0)); + } + } + } +#endif return new LoadContextInfo(anon, oa); } diff --git a/toolkit/content/contentAreaUtils.js b/toolkit/content/contentAreaUtils.js index 2b7af30dec39..ed52c9841a9f 100644 --- a/toolkit/content/contentAreaUtils.js +++ b/toolkit/content/contentAreaUtils.js @@ -86,15 +86,15 @@ function forbidCPOW(arg, func, argname) // - A linked document using Alt-click Save Link As... // function saveURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache, - aSkipPrompt, aReferrer, aSourceDocument, aIsContentWindowPrivate) + aSkipPrompt, aReferrer, aSourceDocument, aIsContentWindowPrivate, + aContentPrincipal) { forbidCPOW(aURL, "saveURL", "aURL"); forbidCPOW(aReferrer, "saveURL", "aReferrer"); // Allow aSourceDocument to be a CPOW. - internalSave(aURL, null, aFileName, null, null, aShouldBypassCache, aFilePickerTitleKey, null, aReferrer, aSourceDocument, - aSkipPrompt, null, aIsContentWindowPrivate); + aSkipPrompt, null, aIsContentWindowPrivate, aContentPrincipal); } // Just like saveURL, but will get some info off the image before @@ -137,7 +137,7 @@ const nsISupportsCString = Components.interfaces.nsISupportsCString; */ function saveImageURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache, aSkipPrompt, aReferrer, aDoc, aContentType, aContentDisp, - aIsContentWindowPrivate) + aIsContentWindowPrivate, aContentPrincipal) { forbidCPOW(aURL, "saveImageURL", "aURL"); forbidCPOW(aReferrer, "saveImageURL", "aReferrer"); @@ -182,7 +182,8 @@ function saveImageURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache, internalSave(aURL, null, aFileName, aContentDisp, aContentType, aShouldBypassCache, aFilePickerTitleKey, null, aReferrer, - null, aSkipPrompt, null, aIsContentWindowPrivate); + null, aSkipPrompt, null, aIsContentWindowPrivate, + aContentPrincipal); } // This is like saveDocument, but takes any browser/frame-like element @@ -199,7 +200,7 @@ function saveBrowser(aBrowser, aSkipPrompt, aOuterWindowID=0) let stack = Components.stack.caller; persistable.startPersistence(aOuterWindowID, { onDocumentReady: function (document) { - saveDocument(document, aSkipPrompt); + saveDocument(document, aSkipPrompt, aBrowser.contentPrincipal); }, onError: function (status) { throw new Components.Exception("saveBrowser failed asynchronously in startPersistence", @@ -215,7 +216,9 @@ function saveBrowser(aBrowser, aSkipPrompt, aOuterWindowID=0) // case "save as" modes that serialize the document's DOM are // unavailable. This is a temporary measure for the "Save Frame As" // command (bug 1141337) and pre-e10s add-ons. -function saveDocument(aDocument, aSkipPrompt) +// +// aContentPrincipal is the principal for downloading and saving the document. +function saveDocument(aDocument, aSkipPrompt, aContentPrincipal) { const Ci = Components.interfaces; @@ -273,7 +276,7 @@ function saveDocument(aDocument, aSkipPrompt) internalSave(aDocument.documentURI, aDocument, null, contentDisposition, aDocument.contentType, false, null, null, aDocument.referrer ? makeURI(aDocument.referrer) : null, - aDocument, aSkipPrompt, cacheKey); + aDocument, aSkipPrompt, cacheKey, undefined, aContentPrincipal); } function DownloadListener(win, transfer) { @@ -384,11 +387,13 @@ XPCOMUtils.defineConstant(this, "kSaveAsType_Text", kSaveAsType_Text); * This parameter is provided when the aInitiatingDocument is not a * real document object. Stores whether aInitiatingDocument.defaultView * was private or not. + * @param aContentPrincipal [optional] + * The principal to be used for loading and saving the target URL. */ function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition, aContentType, aShouldBypassCache, aFilePickerTitleKey, aChosenData, aReferrer, aInitiatingDocument, aSkipPrompt, - aCacheKey, aIsContentWindowPrivate) + aCacheKey, aIsContentWindowPrivate, aContentPrincipal) { forbidCPOW(aURL, "internalSave", "aURL"); forbidCPOW(aReferrer, "internalSave", "aReferrer"); @@ -477,6 +482,7 @@ function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition, sourcePostData : nonCPOWDocument ? getPostData(aDocument) : null, bypassCache : aShouldBypassCache, isPrivate : isPrivate, + loadingPrincipal : aContentPrincipal, }; // Start the actual save process @@ -513,11 +519,16 @@ function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition, * If true, the document will always be refetched from the server * @param persistArgs.isPrivate * Indicates whether this is taking place in a private browsing context. + * @param persistArgs.loadingPrincipal + * The principal assigned to the document being saved. */ function internalPersist(persistArgs) { var persist = makeWebBrowserPersist(); + if (persistArgs.sourceURI.scheme !== "file") { + persist.loadingPrincipal = persistArgs.loadingPrincipal; + } // Calculate persist flags. const nsIWBP = Components.interfaces.nsIWebBrowserPersist; const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES |