/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "jsapi.h" #include "nsDOMParser.h" #include "nsIURI.h" #include "nsIChannel.h" #include "nsILoadGroup.h" #include "nsIInputStream.h" #include "nsNetUtil.h" #include "nsStringStream.h" #include "nsIDocument.h" #include "nsIDOMDocument.h" #include "nsIScriptSecurityManager.h" #include "nsIPrincipal.h" #include "nsIDOMClassInfo.h" #include "nsReadableUtils.h" #include "nsCRT.h" #include "nsIDOMEventReceiver.h" #include "nsLoadListenerProxy.h" #include "nsStreamUtils.h" #include "nsThreadUtils.h" #include "nsNetCID.h" #include "nsContentUtils.h" #include "nsDOMJSUtils.h" #include "nsDOMError.h" // nsIDOMEventListener nsresult nsDOMParser::HandleEvent(nsIDOMEvent* aEvent) { return NS_OK; } // nsIDOMLoadListener nsresult nsDOMParser::Load(nsIDOMEvent* aEvent) { mLoopingForSyncLoad = PR_FALSE; return NS_OK; } nsresult nsDOMParser::BeforeUnload(nsIDOMEvent* aEvent) { return NS_OK; } nsresult nsDOMParser::Unload(nsIDOMEvent* aEvent) { return NS_OK; } nsresult nsDOMParser::Abort(nsIDOMEvent* aEvent) { mLoopingForSyncLoad = PR_FALSE; return NS_OK; } nsresult nsDOMParser::Error(nsIDOMEvent* aEvent) { mLoopingForSyncLoad = PR_FALSE; return NS_OK; } nsDOMParser::nsDOMParser() : mLoopingForSyncLoad(PR_FALSE), mAttemptedInit(PR_FALSE) { } nsDOMParser::~nsDOMParser() { NS_ABORT_IF_FALSE(!mLoopingForSyncLoad, "we rather crash than hang"); mLoopingForSyncLoad = PR_FALSE; } // QueryInterface implementation for 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 NS_IMPL_ADDREF(nsDOMParser) NS_IMPL_RELEASE(nsDOMParser) NS_IMETHODIMP nsDOMParser::ParseFromString(const PRUnichar *str, const char *contentType, nsIDOMDocument **aResult) { NS_ENSURE_ARG(str); NS_ENSURE_ARG_POINTER(aResult); NS_ConvertUTF16toUTF8 data(str); // The new stream holds a reference to the buffer nsCOMPtr stream; nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data.get(), data.Length(), NS_ASSIGNMENT_DEPEND); if (NS_FAILED(rv)) return rv; return ParseFromStream(stream, "UTF-8", data.Length(), contentType, aResult); } NS_IMETHODIMP nsDOMParser::ParseFromBuffer(const PRUint8 *buf, PRUint32 bufLen, const char *contentType, nsIDOMDocument **aResult) { NS_ENSURE_ARG_POINTER(buf); NS_ENSURE_ARG_POINTER(aResult); // The new stream holds a reference to the buffer nsCOMPtr stream; nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), NS_REINTERPRET_CAST(const char *, buf), bufLen, NS_ASSIGNMENT_DEPEND); if (NS_FAILED(rv)) return rv; return ParseFromStream(stream, nsnull, bufLen, contentType, aResult); } NS_IMETHODIMP nsDOMParser::ParseFromStream(nsIInputStream *stream, const char *charset, PRInt32 contentLength, const char *contentType, nsIDOMDocument **aResult) { NS_ENSURE_ARG(stream); NS_ENSURE_ARG(contentType); NS_ENSURE_ARG_POINTER(aResult); *aResult = nsnull; // For now, we can only create XML documents. if ((nsCRT::strcmp(contentType, "text/xml") != 0) && (nsCRT::strcmp(contentType, "application/xml") != 0) && (nsCRT::strcmp(contentType, "application/xhtml+xml") != 0)) return NS_ERROR_NOT_IMPLEMENTED; 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, 4096); NS_ENSURE_SUCCESS(rv, rv); stream = bufferedStream; } nsCOMPtr domDocument; rv = nsContentUtils::CreateDocument(EmptyString(), EmptyString(), nsnull, mDocumentURI, mBaseURI, mPrincipal, getter_AddRefs(domDocument)); NS_ENSURE_SUCCESS(rv, rv); // Register as a load listener on the document nsCOMPtr target(do_QueryInterface(domDocument)); if (target) { nsWeakPtr requestWeak(do_GetWeakReference(NS_STATIC_CAST(nsIDOMParser*, this))); nsLoadListenerProxy* proxy = new nsLoadListenerProxy(requestWeak); if (!proxy) return NS_ERROR_OUT_OF_MEMORY; // This will addref the proxy rv = target->AddEventListenerByIID(NS_STATIC_CAST(nsIDOMEventListener*, proxy), NS_GET_IID(nsIDOMLoadListener)); NS_ENSURE_SUCCESS(rv, rv); } // Create a fake channel nsCOMPtr parserChannel; NS_NewInputStreamChannel(getter_AddRefs(parserChannel), mDocumentURI, nsnull, nsDependentCString(contentType), nsnull); NS_ENSURE_STATE(parserChannel); parserChannel->SetOwner(mPrincipal); if (charset) { parserChannel->SetContentCharset(nsDependentCString(charset)); } // Tell the document to start loading nsCOMPtr listener; nsCOMPtr document(do_QueryInterface(domDocument)); if (!document) return NS_ERROR_FAILURE; mLoopingForSyncLoad = PR_TRUE; // 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? Then we wouldn't need to mess around with // SetPrincipal, etc, probably! rv = document->StartDocumentLoad(kLoadAsData, parserChannel, nsnull, nsnull, getter_AddRefs(listener), PR_FALSE); // 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; } // Now start pumping data to the listener nsresult status; rv = listener->OnStartRequest(parserChannel, nsnull); if (NS_FAILED(rv)) parserChannel->Cancel(rv); parserChannel->GetStatus(&status); if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) { rv = listener->OnDataAvailable(parserChannel, nsnull, stream, 0, contentLength); if (NS_FAILED(rv)) parserChannel->Cancel(rv); parserChannel->GetStatus(&status); } rv = listener->OnStopRequest(parserChannel, nsnull, status); // Failure returned from OnStopRequest does not affect the final status of // the channel, so we do not need to call Cancel(rv) as we do above. if (NS_FAILED(rv)) { return NS_ERROR_FAILURE; } // Process events until we receive a load, abort, or error event for the // document object. That event may have already fired. nsIThread *thread = NS_GetCurrentThread(); while (mLoopingForSyncLoad) { if (!NS_ProcessNextEvent(thread)) break; } domDocument.swap(*aResult); return NS_OK; } NS_IMETHODIMP nsDOMParser::Init(nsIPrincipal* principal, nsIURI* documentURI, nsIURI* baseURI) { NS_ENSURE_STATE(!mAttemptedInit); mAttemptedInit = PR_TRUE; NS_ENSURE_ARG(principal || documentURI); 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::Initialize(JSContext *cx, JSObject* obj, PRUint32 argc, jsval *argv) { 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); 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); }