diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index b03fdd5fbf..7e555df9f6 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -4053,9 +4053,16 @@ Element::GetReferrerPolicyAsEnum() if (Preferences::GetBool("network.http.enablePerElementReferrer", true) && IsHTMLElement()) { const nsAttrValue* referrerValue = GetParsedAttr(nsGkAtoms::referrerpolicy); - if (referrerValue && referrerValue->Type() == nsAttrValue::eEnum) { - return net::ReferrerPolicy(referrerValue->GetEnumValue()); - } + return ReferrerPolicyFromAttr(referrerValue); + } + return net::RP_Unset; +} + +net::ReferrerPolicy +Element::ReferrerPolicyFromAttr(const nsAttrValue* aValue) +{ + if (aValue && aValue->Type() == nsAttrValue::eEnum) { + return net::ReferrerPolicy(aValue->GetEnumValue()); } return net::RP_Unset; } diff --git a/dom/base/Element.h b/dom/base/Element.h index 22ec00418b..ff7aef6177 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -1265,6 +1265,7 @@ public: float FontSizeInflation(); net::ReferrerPolicy GetReferrerPolicyAsEnum(); + net::ReferrerPolicy ReferrerPolicyFromAttr(const nsAttrValue* aValue); /* * Helpers for .dataset. This is implemented on Element, though only some diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 1d3086c512..4d6f7852a7 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -199,6 +199,7 @@ #include "nsThreadUtils.h" #include "nsUnicharUtilCIID.h" #include "nsUnicodeProperties.h" +#include "nsURLHelper.h" #include "nsViewManager.h" #include "nsViewportInfo.h" #include "nsWidgetsCID.h" @@ -2118,6 +2119,54 @@ nsContentUtils::CanCallerAccess(nsPIDOMWindowInner* aWindow) return CanCallerAccess(SubjectPrincipal(), scriptObject->GetPrincipal()); } +// static +nsIPrincipal* +nsContentUtils::GetAttrTriggeringPrincipal(nsIContent* aContent, const nsAString& aAttrValue, + nsIPrincipal* aSubjectPrincipal) +{ + nsIPrincipal* contentPrin = aContent ? aContent->NodePrincipal() : nullptr; + + // If the subject principal is the same as the content principal, or no + // explicit subject principal was provided, we don't need to do any further + // checks. Just return the content principal. + if (contentPrin == aSubjectPrincipal || !aSubjectPrincipal) { + return contentPrin; + } + + // If the attribute value is empty, it's not an absolute URL, so don't bother + // with more expensive checks. + if (!aAttrValue.IsEmpty() && + IsAbsoluteURL(NS_ConvertUTF16toUTF8(aAttrValue))) { + return aSubjectPrincipal; + } + + return contentPrin; +} + +// static +bool +nsContentUtils::IsAbsoluteURL(const nsACString& aURL) +{ + nsAutoCString scheme; + if (NS_FAILED(net_ExtractURLScheme(aURL, scheme))) { + // If we can't extract a scheme, it's not an absolute URL. + return false; + } + + // If it parses as an absolute StandardURL, it's definitely an absolute URL, + // so no need to check with the IO service. + if (net_IsAbsoluteURL(aURL)) { + return true; + } + + uint32_t flags; + if (NS_SUCCEEDED(sIOService->GetProtocolFlags(scheme.get(), &flags))) { + return flags & nsIProtocolHandler::URI_NORELATIVE; + } + + return false; +} + //static bool nsContentUtils::InProlog(nsINode *aNode) diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 37866aaf64..dd0968c9c0 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -526,6 +526,40 @@ public: // aWindow can be either outer or inner window. static bool CanCallerAccess(nsPIDOMWindowInner* aWindow); + /** + * Returns the triggering principal which should be used for the given URL + * attribute value with the given subject principal. + * + * If the attribute value is not an absolute URL, the subject principal will + * be ignored, and the node principal of aContent will be used instead. + * If aContent is non-null, this function will always return a principal. + * Otherewise, it may return null if aSubjectPrincipal is null or is rejected + * based on the attribute value. + * + * @param aContent The content on which the attribute is being set. + * @param aAttrValue The URL value of the attribute. For parsed attribute + * values, such as `srcset`, this function should be called separately + * for each URL value it contains. + * @param aSubjectPrincipal The subject principal of the scripted caller + * responsible for setting the attribute, or null if no scripted caller + * can be determined. + */ + static nsIPrincipal* GetAttrTriggeringPrincipal(nsIContent* aContent, + const nsAString& aAttrValue, + nsIPrincipal* aSubjectPrincipal); + + /** + * Returns true if the given string is guaranteed to be treated as an absolute + * URL, rather than a relative URL. In practice, this means any complete URL + * as supported by nsStandardURL, or any string beginning with a valid scheme + * which is known to the IO service, and has the URI_NORELATIVE flag. + * + * If the URL may be treated as absolute in some cases, but relative in others + * (for instance, "http:foo", which can be either an absolute or relative URL, + * depending on the context), this function returns false. + */ + static bool IsAbsoluteURL(const nsACString& aURL); + /** * GetDocumentFromCaller gets its document by looking at the last called * function and finding the document that the function itself relates to. diff --git a/dom/base/nsImageLoadingContent.cpp b/dom/base/nsImageLoadingContent.cpp index 26fa18df99..082522ccbf 100644 --- a/dom/base/nsImageLoadingContent.cpp +++ b/dom/base/nsImageLoadingContent.cpp @@ -734,7 +734,8 @@ nsresult nsImageLoadingContent::LoadImage(const nsAString& aNewURI, bool aForce, bool aNotify, - ImageLoadType aImageLoadType) + ImageLoadType aImageLoadType, + nsIPrincipal* aTriggeringPrincipal) { // First, get a document (needed for security checks and the like) nsIDocument* doc = GetOurOwnerDoc(); @@ -768,7 +769,8 @@ nsImageLoadingContent::LoadImage(const nsAString& aNewURI, NS_TryToSetImmutable(imageURI); - return LoadImage(imageURI, aForce, aNotify, aImageLoadType, false, doc); + return LoadImage(imageURI, aForce, aNotify, aImageLoadType, false, doc, + nsIRequest::LOAD_NORMAL, aTriggeringPrincipal); } nsresult @@ -778,7 +780,8 @@ nsImageLoadingContent::LoadImage(nsIURI* aNewURI, ImageLoadType aImageLoadType, bool aLoadStart, nsIDocument* aDocument, - nsLoadFlags aLoadFlags) + nsLoadFlags aLoadFlags, + nsIPrincipal* aTriggeringPrincipal) { MOZ_ASSERT(!mIsStartingImageLoad, "some evil code is reentering LoadImage."); if (mIsStartingImageLoad) { @@ -886,7 +889,7 @@ nsImageLoadingContent::LoadImage(nsIURI* aNewURI, nsresult rv = nsContentUtils::LoadImage(aNewURI, thisNode, aDocument, - aDocument->NodePrincipal(), + aTriggeringPrincipal ? aTriggeringPrincipal : aDocument->NodePrincipal(), aDocument->GetDocumentURI(), referrerPolicy, this, loadFlags, diff --git a/dom/base/nsImageLoadingContent.h b/dom/base/nsImageLoadingContent.h index b97bede0d0..85240db788 100644 --- a/dom/base/nsImageLoadingContent.h +++ b/dom/base/nsImageLoadingContent.h @@ -102,9 +102,12 @@ protected: * @param aNotify If true, nsIDocumentObserver state change notifications * will be sent as needed. * @param aImageLoadType The ImageLoadType for this request + * @param aTriggeringPrincipal Optional parameter specifying the triggering + * principal to use for the image load */ nsresult LoadImage(const nsAString& aNewURI, bool aForce, - bool aNotify, ImageLoadType aImageLoadType); + bool aNotify, ImageLoadType aImageLoadType, + nsIPrincipal* aTriggeringPrincipal = nullptr); /** * ImageState is called by subclasses that are computing their content state. @@ -134,11 +137,23 @@ protected: * This is purely a performance optimization. * @param aLoadFlags Optional parameter specifying load flags to use for * the image load + * @param aTriggeringPrincipal Optional parameter specifying the triggering + * principal to use for the image load */ nsresult LoadImage(nsIURI* aNewURI, bool aForce, bool aNotify, ImageLoadType aImageLoadType, bool aLoadStart = true, nsIDocument* aDocument = nullptr, - nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL); + nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL, + nsIPrincipal* aTriggeringPrincipal = nullptr); + // Simplified version to pass triggering principal with defaults otherwise. + nsresult LoadImage(nsIURI* aNewURI, bool aForce, bool aNotify, + ImageLoadType aImageLoadType, + nsIPrincipal* aTriggeringPrincipal) + { + return LoadImage(aNewURI, aForce, aNotify, aImageLoadType, + true, nullptr, nsIRequest::LOAD_NORMAL, + aTriggeringPrincipal); + } /** * helpers to get the document for this content (from the nodeinfo diff --git a/dom/html/HTMLImageElement.cpp b/dom/html/HTMLImageElement.cpp index 885e653fa4..2124aab8c1 100644 --- a/dom/html/HTMLImageElement.cpp +++ b/dom/html/HTMLImageElement.cpp @@ -109,7 +109,6 @@ private: HTMLImageElement::HTMLImageElement(already_AddRefed& aNodeInfo) : nsGenericHTMLElement(aNodeInfo) , mForm(nullptr) - , mForceReload(false) , mInDocResponsiveContent(false) , mCurrentDensity(1.0) { @@ -370,10 +369,6 @@ HTMLImageElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName, const nsAttrValueOrString* aValue, bool aNotify) { - if (aValue) { - BeforeMaybeChangeAttr(aNameSpaceID, aName, *aValue, aNotify); - } - if (aNameSpaceID == kNameSpaceID_None && mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) { // remove the image from the hashtable as needed @@ -397,8 +392,11 @@ HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, nsIPrincipal* aMaybeScriptedPrincipal, bool aNotify) { + nsAttrValueOrString attrVal(aValue); + if (aValue) { - AfterMaybeChangeAttr(aNameSpaceID, aName, aMaybeScriptedPrincipal, aNotify); + AfterMaybeChangeAttr(aNameSpaceID, aName, attrVal, aOldValue, true, + aMaybeScriptedPrincipal, aNotify); } if (aNameSpaceID == kNameSpaceID_None && mForm && @@ -415,8 +413,6 @@ HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, // parser or some such place; we'll get bound after all the attributes have // been set, so we'll do the image load from BindToTree. - nsAttrValueOrString attrVal(aValue); - if (aName == nsGkAtoms::src && aNameSpaceID == kNameSpaceID_None && !aValue) { @@ -451,18 +447,21 @@ HTMLImageElement::OnAttrSetButNotChanged(int32_t aNamespaceID, nsIAtom* aName, const nsAttrValueOrString& aValue, bool aNotify) { - BeforeMaybeChangeAttr(aNamespaceID, aName, aValue, aNotify); - AfterMaybeChangeAttr(aNamespaceID, aName, nullptr, aNotify); + AfterMaybeChangeAttr(aNamespaceID, aName, aValue, nullptr, false, nullptr, aNotify); return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID, aName, aValue, aNotify); } void -HTMLImageElement::BeforeMaybeChangeAttr(int32_t aNamespaceID, nsIAtom* aName, - const nsAttrValueOrString& aValue, - bool aNotify) +HTMLImageElement::AfterMaybeChangeAttr(int32_t aNamespaceID, nsIAtom* aName, + const nsAttrValueOrString& aValue, + const nsAttrValue* aOldValue, + bool aValueMaybeChanged, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) { + bool forceReload = false; // We need to force our image to reload. This must be done here, not in // AfterSetAttr or BeforeSetAttr, because we want to do it even if the attr is // being set to its existing value, which is normally optimized away as a @@ -473,12 +472,12 @@ HTMLImageElement::BeforeMaybeChangeAttr(int32_t aNamespaceID, nsIAtom* aName, // spec. // // Both cases handle unsetting src in AfterSetAttr - // - // Much of this should probably happen in AfterMaybeChangeAttr. - // See Bug 1370705 if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::src) { + mSrcTriggeringPrincipal = + nsContentUtils::GetAttrTriggeringPrincipal(this, aValue.String(), aMaybeScriptedPrincipal); + if (InResponsiveMode()) { if (mResponsiveSelector && mResponsiveSelector->Content() == this) { @@ -496,53 +495,46 @@ HTMLImageElement::BeforeMaybeChangeAttr(int32_t aNamespaceID, nsIAtom* aName, mNewRequestsWillNeedAnimationReset = true; // Force image loading here, so that we'll try to load the image from - // network if it's set to be not cacheable... If we change things so that - // the state gets in Element's attr-setting happen around this - // LoadImage call, we could start passing false instead of aNotify - // here. - LoadImage(aValue.String(), true, aNotify, eImageLoadType_Normal); + // network if it's set to be not cacheable. + // Potentially, false could be passed here rather than aNotify since + // UpdateState will be called by SetAttrAndNotify, but there are two + // obstacles to this: 1) LoadImage will end up calling + // UpdateState(aNotify), and we do not want it to call UpdateState(false) + // when aNotify is true, and 2) When this function is called by + // OnAttrSetButNotChanged, SetAttrAndNotify will not subsequently call + // UpdateState. + LoadImage(aValue.String(), true, aNotify, eImageLoadType_Normal, mSrcTriggeringPrincipal); mNewRequestsWillNeedAnimationReset = false; } } else if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::crossorigin && aNotify) { - nsAttrValue attrValue; - ParseCORSValue(aValue.String(), attrValue); - if (GetCORSMode() != AttrValueToCORSMode(&attrValue)) { + if (aValueMaybeChanged && GetCORSMode() != AttrValueToCORSMode(aOldValue)) { // Force a new load of the image with the new cross origin policy. - mForceReload = true; + forceReload = true; } } else if (aName == nsGkAtoms::referrerpolicy && aNamespaceID == kNameSpaceID_None && aNotify) { - ReferrerPolicy referrerPolicy = AttributeReferrerPolicyFromString(aValue.String()); + ReferrerPolicy referrerPolicy = GetImageReferrerPolicy(); if (!InResponsiveMode() && referrerPolicy != RP_Unset && - referrerPolicy != GetImageReferrerPolicy()) { + aValueMaybeChanged && + referrerPolicy != ReferrerPolicyFromAttr(aOldValue)) { // XXX: Bug 1076583 - We still use the older synchronous algorithm // Because referrerPolicy is not treated as relevant mutations, setting // the attribute will neither trigger a reload nor update the referrer // policy of the loading channel (whether it has previously completed or // not). Force a new load of the image with the new referrerpolicy. - mForceReload = true; + forceReload = true; } } - return; -} - -void -HTMLImageElement::AfterMaybeChangeAttr(int32_t aNamespaceID, nsIAtom* aName, - nsIPrincipal* aMaybeScriptedPrincipal, - bool aNotify) -{ // Because we load image synchronously in non-responsive-mode, we need to do // reload after the attribute has been set if the reload is triggerred by // cross origin changing. - if (mForceReload) { - mForceReload = false; - + if (forceReload) { if (InResponsiveMode()) { // per spec, full selection runs when this changes, even though // it doesn't directly affect the source selection @@ -554,8 +546,6 @@ HTMLImageElement::AfterMaybeChangeAttr(int32_t aNamespaceID, nsIAtom* aName, ForceReload(aNotify); } } - - return; } nsresult @@ -1037,7 +1027,8 @@ HTMLImageElement::LoadSelectedImage(bool aForce, bool aNotify, bool aAlwaysLoad) // valid responsive sources from either, per spec. rv = LoadImage(src, aForce, aNotify, HaveSrcsetOrInPicture() ? eImageLoadType_Imageset - : eImageLoadType_Normal); + : eImageLoadType_Normal, + mSrcTriggeringPrincipal); } } mLastSelectedSource = selectedSource; diff --git a/dom/html/HTMLImageElement.h b/dom/html/HTMLImageElement.h index 748b922499..ad32dcc1bf 100644 --- a/dom/html/HTMLImageElement.h +++ b/dom/html/HTMLImageElement.h @@ -137,9 +137,13 @@ public: { SetHTMLAttr(nsGkAtoms::alt, aAlt, aError); } - void SetSrc(const nsAString& aSrc, ErrorResult& aError) + void GetSrc(nsAString& aSrc, nsIPrincipal&) { - SetHTMLAttr(nsGkAtoms::src, aSrc, aError); + GetURIAttr(nsGkAtoms::src, nullptr, aSrc); + } + void SetSrc(const nsAString& aSrc, nsIPrincipal& aTriggeringPrincipal, ErrorResult& aError) + { + SetHTMLAttr(nsGkAtoms::src, aSrc, aTriggeringPrincipal, aError); } void SetSrcset(const nsAString& aSrcset, ErrorResult& aError) { @@ -365,31 +369,24 @@ private: * @param aName the localname of the attribute being set * @param aValue the value it's being set to represented as either a string or * a parsed nsAttrValue. - * @param aNotify Whether we plan to notify document observers. - */ - void BeforeMaybeChangeAttr(int32_t aNamespaceID, nsIAtom* aName, - const nsAttrValueOrString& aValue, - bool aNotify); - /** - * This function is called by AfterSetAttr and OnAttrSetButNotChanged. - * It will not be called if the value is being unset. - * - * @param aNamespaceID the namespace of the attr being set - * @param aName the localname of the attribute being set + * @param aOldValue the value previously set. Will be null if no value was + * previously set. This value should only be used when + * aValueMaybeChanged is true; when aValueMaybeChanged is false, + * aOldValue should be considered unreliable. + * @param aValueMaybeChanged will be false when this function is called from + * OnAttrSetButNotChanged to indicate that the value was not changed. * @param aNotify Whether we plan to notify document observers. */ void AfterMaybeChangeAttr(int32_t aNamespaceID, nsIAtom* aName, + const nsAttrValueOrString& aValue, + const nsAttrValue* aOldValue, + bool aValueMaybeChanged, nsIPrincipal* aMaybeScriptedPrincipal, bool aNotify); - /** - * Used by BeforeMaybeChangeAttr and AfterMaybeChangeAttr to keep track of - * whether a reload needs to be forced after an attribute change that is - * currently in progress. - */ - bool mForceReload; bool mInDocResponsiveContent; RefPtr mPendingImageLoadTask; + nsCOMPtr mSrcTriggeringPrincipal; // Last URL that was attempted to load by this element. nsCOMPtr mLastSelectedSource; diff --git a/dom/webidl/HTMLImageElement.webidl b/dom/webidl/HTMLImageElement.webidl index e50a9ef8e3..4df2f1fac3 100644 --- a/dom/webidl/HTMLImageElement.webidl +++ b/dom/webidl/HTMLImageElement.webidl @@ -21,7 +21,7 @@ interface nsIStreamListener; interface HTMLImageElement : HTMLElement { [CEReactions, SetterThrows] attribute DOMString alt; - [CEReactions, SetterThrows] + [CEReactions, NeedsSubjectPrincipal, SetterThrows] attribute DOMString src; [CEReactions, SetterThrows] attribute DOMString srcset;