From 01946a8fe9644d812994514be6f3e6a8c3baf8fc Mon Sep 17 00:00:00 2001 From: "bzbarsky%mit.edu" Date: Sat, 9 Sep 2006 04:54:03 +0000 Subject: [PATCH] Make it possible to give DOMParsers the right principals from C++. Bug 332840, r=sicking, sr=jst --- content/base/public/nsIDOMParser.idl | 44 ++- content/base/src/nsDOMParser.cpp | 271 ++++++++++++++---- content/base/src/nsDOMParser.h | 30 +- dom/src/base/nsDOMClassInfo.cpp | 1 + .../xforms/nsXFormsSubmissionElement.cpp | 4 +- extensions/xmlextras/tests/TestXMLExtras.cpp | 2 +- .../places/src/nsBookmarksFeedHandler.cpp | 3 +- 7 files changed, 293 insertions(+), 62 deletions(-) diff --git a/content/base/public/nsIDOMParser.idl b/content/base/public/nsIDOMParser.idl index c2b7b75f2111..5162ee44be18 100644 --- a/content/base/public/nsIDOMParser.idl +++ b/content/base/public/nsIDOMParser.idl @@ -40,6 +40,7 @@ interface nsIInputStream; interface nsIDOMDocument; interface nsIURI; +interface nsIPrincipal; /** * The nsIDOMParser interface is a non-SAX interface that can be used @@ -49,10 +50,9 @@ interface nsIURI; * parsing with the XMLHttpRequest interface, which can be used for * asynchronous (callback-based) loading. */ - -[scriptable, uuid(4f45513e-55e5-411c-a844-e899057026c1)] -interface nsIDOMParser : nsISupports { - +[scriptable, uuid(5db52912-cdee-46d2-9166-01436c3f9e73)] +interface nsIDOMParser : nsISupports +{ /** * The string passed in is parsed into a DOM document. * @@ -97,9 +97,41 @@ interface nsIDOMParser : nsISupports { in string contentType); /** - * Set/Get the baseURI, may be needed when called from native code. + * Initialize the principal and document and base URIs that the parser should + * use for documents it creates. If this is not called, then a null + * principal and its URI will be used. When creating a DOMParser via the JS + * constructor, this will be called automatically. This method may only be + * called once. If this method fails, all following parse attempts will + * fail. + * + * @param principal The principal to use for documents we create. + * If this is null, a codebase principal will be created + * based on documentURI; in that case the documentURI must + * be non-null. + * @param documentURI The documentURI to use for the documents we create. + * If null, the principal's URI will be used; + * in that case, the principal must be non-null and its + * URI must be non-null. + * @param baseURI The baseURI to use for the documents we create. + * If null, the documentURI will be used. */ - [noscript] attribute nsIURI baseURI; + [noscript] void init(in nsIPrincipal principal, + in nsIURI documentURI, + in nsIURI baseURI); +}; + +/** + * The nsIDOMParserJS interface provides a scriptable way of calling init(). + * Do NOT use this interface from languages other than JavaScript. + */ +[scriptable, uuid(dca92fe9-ae7a-44b7-80aa-d151216698ac)] +interface nsIDOMParserJS : nsISupports +{ + /** + * Just like nsIDOMParser.init, but callable from JS. It'll pick up the args + * from XPConnect. + */ + void init(); }; %{ C++ diff --git a/content/base/src/nsDOMParser.cpp b/content/base/src/nsDOMParser.cpp index eba6049f6b9a..c4a11867a884 100644 --- a/content/base/src/nsDOMParser.cpp +++ b/content/base/src/nsDOMParser.cpp @@ -57,6 +57,7 @@ #include "nsNetCID.h" #include "nsContentUtils.h" #include "nsDOMJSUtils.h" +#include "nsDOMError.h" // nsIDOMEventListener nsresult @@ -103,7 +104,8 @@ nsDOMParser::Error(nsIDOMEvent* aEvent) } nsDOMParser::nsDOMParser() - : mLoopingForSyncLoad(PR_FALSE) + : mLoopingForSyncLoad(PR_FALSE), + mAttemptedInit(PR_FALSE) { } @@ -118,8 +120,10 @@ nsDOMParser::~nsDOMParser() NS_INTERFACE_MAP_BEGIN(nsDOMParser) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMParser) NS_INTERFACE_MAP_ENTRY(nsIDOMParser) + NS_INTERFACE_MAP_ENTRY(nsIDOMParserJS) NS_INTERFACE_MAP_ENTRY(nsIDOMLoadListener) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIJSNativeInitializer) NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(DOMParser) NS_INTERFACE_MAP_END @@ -187,8 +191,23 @@ nsDOMParser::ParseFromStream(nsIInputStream *stream, (nsCRT::strcmp(contentType, "application/xhtml+xml") != 0)) return NS_ERROR_NOT_IMPLEMENTED; - // Put the nsCOMPtr out here so we hold a ref to the stream as needed nsresult rv; + if (!mPrincipal) { + NS_ENSURE_TRUE(!mAttemptedInit, NS_ERROR_NOT_INITIALIZED); + AttemptedInitMarker marker(&mAttemptedInit); + + nsCOMPtr prin = + do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Init(prin, nsnull, nsnull); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_ASSERTION(mPrincipal, "Must have principal by now"); + NS_ASSERTION(mDocumentURI, "Must have document URI by now"); + + // Put the nsCOMPtr out here so we hold a ref to the stream as needed nsCOMPtr bufferedStream; if (!NS_InputStreamIsBuffered(stream)) { rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, @@ -198,40 +217,9 @@ nsDOMParser::ParseFromStream(nsIInputStream *stream, stream = bufferedStream; } - nsCOMPtr principal; - nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager(); - if (secMan) { - secMan->GetSubjectPrincipal(getter_AddRefs(principal)); - } - - // Try to find a base URI for the document we're creating. - nsCOMPtr baseURI; - nsCOMPtr doc = - do_QueryInterface(nsContentUtils::GetDocumentFromContext()); - if (doc) { - baseURI = doc->GetBaseURI(); - } - - if (!baseURI) { - // No URI from script environment (we are running from command line, for example). - // Create a dummy one. - // XXX Is this safe? Could we get the URI from stream or something? - if (!mBaseURI) { - rv = NS_NewURI(getter_AddRefs(baseURI), - "about:blank" ); - if (NS_FAILED(rv)) return rv; - } else { - baseURI = mBaseURI; - } - } - - // XXXbz Is this really right? Why are we setting the documentURI to - // baseURI? But note that's what the StartDocumentLoad() below would do - // if we let it reset. In any case, this is odd, since the caller can - // set baseURI to anything it feels like, pretty much. nsCOMPtr domDocument; rv = nsContentUtils::CreateDocument(EmptyString(), EmptyString(), nsnull, - baseURI, baseURI, principal, + mDocumentURI, mBaseURI, mPrincipal, getter_AddRefs(domDocument)); NS_ENSURE_SUCCESS(rv, rv); @@ -251,14 +239,11 @@ nsDOMParser::ParseFromStream(nsIInputStream *stream, // Create a fake channel nsCOMPtr parserChannel; - NS_NewInputStreamChannel(getter_AddRefs(parserChannel), baseURI, nsnull, + NS_NewInputStreamChannel(getter_AddRefs(parserChannel), mDocumentURI, nsnull, nsDependentCString(contentType), nsnull); NS_ENSURE_STATE(parserChannel); - // Hold a reference to it in this method - if (principal) { - parserChannel->SetOwner(principal); - } + parserChannel->SetOwner(mPrincipal); if (charset) { parserChannel->SetContentCharset(nsDependentCString(charset)); @@ -273,16 +258,18 @@ nsDOMParser::ParseFromStream(nsIInputStream *stream, // Have to pass PR_FALSE for reset here, else the reset will remove // our event listener. Should that listener addition move to later - // than this call? + // than this call? Then we wouldn't need to mess around with + // SetPrincipal, etc, probably! rv = document->StartDocumentLoad(kLoadAsData, parserChannel, nsnull, nsnull, getter_AddRefs(listener), PR_FALSE); - if (principal) { - // Make sure to give this document the right principal - document->SetPrincipal(principal); - } + // Make sure to give this document the right principal + document->SetPrincipal(mPrincipal); + + // And the right base URI + document->SetBaseURI(mBaseURI); if (NS_FAILED(rv) || !listener) { return NS_ERROR_FAILURE; @@ -326,20 +313,200 @@ nsDOMParser::ParseFromStream(nsIInputStream *stream, return NS_OK; } -NS_IMETHODIMP -nsDOMParser::GetBaseURI(nsIURI **aBaseURI) +NS_IMETHODIMP +nsDOMParser::Init(nsIPrincipal* principal, nsIURI* documentURI, + nsIURI* baseURI) { - NS_ENSURE_ARG_POINTER(aBaseURI); + NS_ENSURE_STATE(!mAttemptedInit); + mAttemptedInit = PR_TRUE; + + NS_ENSURE_ARG(principal || documentURI); - NS_IF_ADDREF(*aBaseURI = mBaseURI); + mDocumentURI = documentURI; + if (!mDocumentURI) { + principal->GetURI(getter_AddRefs(mDocumentURI)); + if (!mDocumentURI) { + return NS_ERROR_INVALID_ARG; + } + } + mPrincipal = principal; + if (!mPrincipal) { + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); + NS_ENSURE_TRUE(secMan, NS_ERROR_NOT_AVAILABLE); + nsresult rv = + secMan->GetCodebasePrincipal(mDocumentURI, getter_AddRefs(mPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + } + + mBaseURI = baseURI; + // Note: if mBaseURI is null, fine. Leave it like that; that will use the + // documentURI as the base. Otherwise for null principals we'll get + // nsDocument::SetBaseURI giving errors. + + NS_POSTCONDITION(mPrincipal, "Must have principal"); + NS_POSTCONDITION(mDocumentURI, "Must have document URI"); + return NS_OK; +} + +static nsQueryInterface +JSvalToInterface(JSContext* cx, jsval val, nsIXPConnect* xpc, PRBool* wasNull) +{ + if (val == JSVAL_NULL) { + *wasNull = PR_TRUE; + return nsQueryInterface(nsnull); + } + + *wasNull = PR_FALSE; + if (JSVAL_IS_OBJECT(val)) { + JSObject* arg = JSVAL_TO_OBJECT(val); + + nsCOMPtr native; + xpc->GetWrappedNativeOfJSObject(cx, arg, getter_AddRefs(native)); + + // do_QueryWrappedNative is not null-safe + if (native) { + return do_QueryWrappedNative(native); + } + } + + return nsQueryInterface(nsnull); +} + +static nsresult +GetInitArgs(JSContext *cx, PRUint32 argc, jsval *argv, + nsIPrincipal** aPrincipal, nsIURI** aDocumentURI, + nsIURI** aBaseURI) +{ + // Only proceed if the caller has UniversalXPConnect. + PRBool haveUniversalXPConnect; + nsresult rv = nsContentUtils::GetSecurityManager()-> + IsCapabilityEnabled("UniversalXPConnect", &haveUniversalXPConnect); + NS_ENSURE_SUCCESS(rv, rv); + + if (!haveUniversalXPConnect) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsIXPConnect* xpc = nsContentUtils::XPConnect(); + + // First arg is our principal. If someone passes something that's + // not a principal and not null, die to prevent privilege escalation. + PRBool wasNull; + nsCOMPtr prin = JSvalToInterface(cx, argv[0], xpc, &wasNull); + if (!prin && !wasNull) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr documentURI; + nsCOMPtr baseURI; + if (argc > 1) { + // Grab our document URI too. Again, if it's something unexpected bail + // out. + documentURI = JSvalToInterface(cx, argv[1], xpc, &wasNull); + if (!documentURI && !wasNull) { + return NS_ERROR_INVALID_ARG; + } + + if (argc > 2) { + // Grab our base URI as well + baseURI = JSvalToInterface(cx, argv[2], xpc, &wasNull); + if (!baseURI && !wasNull) { + return NS_ERROR_INVALID_ARG; + } + } + } + + NS_IF_ADDREF(*aPrincipal = prin); + NS_IF_ADDREF(*aDocumentURI = documentURI); + NS_IF_ADDREF(*aBaseURI = baseURI); return NS_OK; } -NS_IMETHODIMP -nsDOMParser::SetBaseURI(nsIURI *aBaseURI) +NS_IMETHODIMP +nsDOMParser::Initialize(JSContext *cx, JSObject* obj, + PRUint32 argc, jsval *argv) { - mBaseURI = aBaseURI; + AttemptedInitMarker marker(&mAttemptedInit); + nsCOMPtr prin; + nsCOMPtr documentURI; + nsCOMPtr baseURI; + if (argc > 0) { + nsresult rv = GetInitArgs(cx, argc, argv, getter_AddRefs(prin), + getter_AddRefs(documentURI), + getter_AddRefs(baseURI)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + // No arguments; use the subject principal + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); + NS_ENSURE_TRUE(secMan, NS_ERROR_UNEXPECTED); - return NS_OK; + secMan->GetSubjectPrincipal(getter_AddRefs(prin)); + + // We're called from JS; there better be a subject principal, really. + NS_ENSURE_TRUE(prin, NS_ERROR_UNEXPECTED); + } + + NS_ASSERTION(prin, "Must have principal by now"); + + if (!documentURI) { + // No explicit documentURI; grab document and base URIs off the window our + // constructor was called on. Error out if anything untoward happens. + + // Note that this is a behavior change as far as I can tell -- we're now + // using the base URI and document URI of the window off of which the + // DOMParser is created, not the window in which parse*() is called. + // Does that matter? + + // Also note that |cx| matches what GetDocumentFromContext() would return, + // while GetDocumentFromCaller() gives us the window that the DOMParser() + // call was made on. + + nsCOMPtr doc = + do_QueryInterface(nsContentUtils::GetDocumentFromCaller()); + if (!doc) { + return NS_ERROR_UNEXPECTED; + } + + baseURI = doc->GetBaseURI(); + documentURI = doc->GetDocumentURI(); + } + + return Init(prin, documentURI, baseURI); +} + +NS_IMETHODIMP +nsDOMParser::Init() +{ + AttemptedInitMarker marker(&mAttemptedInit); + + nsCOMPtr ncc; + + nsresult rv = nsContentUtils::XPConnect()-> + GetCurrentNativeCallContext(getter_AddRefs(ncc)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(ncc, NS_ERROR_UNEXPECTED); + + JSContext *cx = nsnull; + rv = ncc->GetJSContext(&cx); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(cx, NS_ERROR_UNEXPECTED); + + PRUint32 argc; + jsval *argv = nsnull; + ncc->GetArgc(&argc); + ncc->GetArgvPtr(&argv); + + if (argc != 3) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr prin; + nsCOMPtr documentURI; + nsCOMPtr baseURI; + rv = GetInitArgs(cx, argc, argv, getter_AddRefs(prin), + getter_AddRefs(documentURI), getter_AddRefs(baseURI)); + NS_ENSURE_SUCCESS(rv, rv); + + return Init(prin, documentURI, baseURI); } diff --git a/content/base/src/nsDOMParser.h b/content/base/src/nsDOMParser.h index 6a88a8f853ac..e78d89e6e754 100644 --- a/content/base/src/nsDOMParser.h +++ b/content/base/src/nsDOMParser.h @@ -43,9 +43,12 @@ #include "nsIURI.h" #include "nsIDOMLoadListener.h" #include "nsWeakReference.h" +#include "nsIJSNativeInitializer.h" class nsDOMParser : public nsIDOMParser, + public nsIDOMParserJS, public nsIDOMLoadListener, + public nsIJSNativeInitializer, public nsSupportsWeakReference { public: @@ -57,6 +60,9 @@ public: // nsIDOMParser NS_DECL_NSIDOMPARSER + // nsIDOMParserJS + NS_DECL_NSIDOMPARSERJS + // nsIDOMEventListener NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent); @@ -67,9 +73,31 @@ public: NS_IMETHOD Abort(nsIDOMEvent* aEvent); NS_IMETHOD Error(nsIDOMEvent* aEvent); + // nsIJSNativeInitializer + NS_IMETHOD Initialize(JSContext *cx, JSObject *obj, + PRUint32 argc, jsval *argv); + private: + class AttemptedInitMarker { + public: + AttemptedInitMarker(PRPackedBool* aAttemptedInit) : + mAttemptedInit(aAttemptedInit) + {} + + ~AttemptedInitMarker() { + *mAttemptedInit = PR_TRUE; + } + + private: + PRPackedBool* mAttemptedInit; + }; + + nsCOMPtr mPrincipal; + nsCOMPtr mDocumentURI; nsCOMPtr mBaseURI; - PRBool mLoopingForSyncLoad; + + PRPackedBool mLoopingForSyncLoad; + PRPackedBool mAttemptedInit; }; #endif diff --git a/dom/src/base/nsDOMClassInfo.cpp b/dom/src/base/nsDOMClassInfo.cpp index 77cc6fecf9bf..98b61aba9dbe 100644 --- a/dom/src/base/nsDOMClassInfo.cpp +++ b/dom/src/base/nsDOMClassInfo.cpp @@ -3050,6 +3050,7 @@ nsDOMClassInfo::Init() DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(DOMParser, nsIDOMParser) DOM_CLASSINFO_MAP_ENTRY(nsIDOMParser) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMParserJS) DOM_CLASSINFO_MAP_END DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(XMLSerializer, nsIDOMSerializer) diff --git a/extensions/xforms/nsXFormsSubmissionElement.cpp b/extensions/xforms/nsXFormsSubmissionElement.cpp index 213ce3f3e60a..c1f2482ff31d 100644 --- a/extensions/xforms/nsXFormsSubmissionElement.cpp +++ b/extensions/xforms/nsXFormsSubmissionElement.cpp @@ -500,7 +500,9 @@ nsXFormsSubmissionElement::LoadReplaceInstance(nsIChannel *channel) nsCOMPtr uri; nsresult rv = channel->GetURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); - rv = parser->SetBaseURI(uri); + + // XXXbz is this the right principal? + rv = parser->Init(nsnull, uri, nsnull); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr newDoc; diff --git a/extensions/xmlextras/tests/TestXMLExtras.cpp b/extensions/xmlextras/tests/TestXMLExtras.cpp index 4f57cc5ec469..f8ec55b9298c 100644 --- a/extensions/xmlextras/tests/TestXMLExtras.cpp +++ b/extensions/xmlextras/tests/TestXMLExtras.cpp @@ -200,7 +200,7 @@ int main (int argc, char* argv[]) &rv ); if (NS_SUCCEEDED( rv )) { - pDOMParser->SetBaseURI(pURI); + pDOMParser->Init(nsnull, pURI, nsnull); rv = pDOMParser->ParseFromStream( pInputStream, "UTF-8", diff --git a/toolkit/components/places/src/nsBookmarksFeedHandler.cpp b/toolkit/components/places/src/nsBookmarksFeedHandler.cpp index a17e0b43272c..265375e28740 100644 --- a/toolkit/components/places/src/nsBookmarksFeedHandler.cpp +++ b/toolkit/components/places/src/nsBookmarksFeedHandler.cpp @@ -572,7 +572,8 @@ nsLivemarkLoadListener::TryParseAsSimpleRSS () if (NS_FAILED(rv)) return rv; nsCOMPtr xmldoc; - parser->SetBaseURI(mLivemark->feedURI); + // XXXbz is this the right principal? + parser->Init(nsnull, mLivemark->feedURI, nsnull); rv = parser->ParseFromBuffer ((const PRUint8*) mBody.get(), mBody.Length(), "text/xml", getter_AddRefs(xmldoc)); if (NS_FAILED(rv)) return rv;