/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/DOMParser.h" #include "nsIDOMDocument.h" #include "nsNetUtil.h" #include "nsDOMString.h" #include "MainThreadUtils.h" #include "nsIStreamListener.h" #include "nsStringStream.h" #include "nsIScriptError.h" #include "nsIScriptSecurityManager.h" #include "nsCRT.h" #include "nsStreamUtils.h" #include "nsContentUtils.h" #include "nsDOMJSUtils.h" #include "nsError.h" #include "nsPIDOMWindow.h" #include "NullPrincipal.h" #include "mozilla/LoadInfo.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/ScriptSettings.h" using namespace mozilla; using namespace mozilla::dom; DOMParser::~DOMParser() { } // QueryInterface implementation for DOMParser NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMParser) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMParser) NS_INTERFACE_MAP_ENTRY(nsIDOMParser) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMParser, mOwner) NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMParser) NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMParser) static const char* StringFromSupportedType(SupportedType aType) { return SupportedTypeValues::strings[static_cast(aType)].value; } already_AddRefed DOMParser::ParseFromString(const nsAString& aStr, SupportedType aType, ErrorResult& aRv) { if (aType == SupportedType::Text_html) { nsCOMPtr document = SetUpDocument(DocumentFlavorHTML, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Keep the XULXBL state in sync with the XML case. if (mForceEnableXULXBL) { document->ForceEnableXULXBL(); } nsresult rv = nsContentUtils::ParseDocumentHTML(aStr, document, false); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return nullptr; } return document.forget(); } nsAutoCString utf8str; // Convert from UTF16 to UTF8 using fallible allocations if (!AppendUTF16toUTF8(aStr, utf8str, mozilla::fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return nullptr; } // The new stream holds a reference to the buffer nsCOMPtr stream; nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), utf8str.get(), utf8str.Length(), NS_ASSIGNMENT_DEPEND); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return nullptr; } return ParseFromStream(stream, NS_LITERAL_STRING("UTF-8"), utf8str.Length(), aType, aRv); } already_AddRefed DOMParser::ParseFromBuffer(const Uint8Array& aBuf, SupportedType aType, ErrorResult& aRv) { aBuf.ComputeLengthAndData(); return ParseFromBuffer(MakeSpan(aBuf.Data(), aBuf.Length()), aType, aRv); } already_AddRefed DOMParser::ParseFromBuffer(Span aBuf, SupportedType aType, ErrorResult& aRv) { // The new stream holds a reference to the buffer nsCOMPtr stream; nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), reinterpret_cast(aBuf.Elements()), aBuf.Length(), NS_ASSIGNMENT_DEPEND); if (NS_FAILED(rv)) { aRv.Throw(rv); return nullptr; } return ParseFromStream(stream, VoidString(), aBuf.Length(), aType, aRv); } already_AddRefed DOMParser::ParseFromStream(nsIInputStream* aStream, const nsAString& aCharset, int32_t aContentLength, SupportedType aType, ErrorResult& aRv) { bool svg = (aType == SupportedType::Image_svg_xml); // For now, we can only create XML documents. //XXXsmaug Should we create an HTMLDocument (in XHTML mode) // for "application/xhtml+xml"? if (aType != SupportedType::Text_xml && aType != SupportedType::Application_xml && aType != SupportedType::Application_xhtml_xml && !svg) { aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); return nullptr; } // Put the nsCOMPtr out here so we hold a ref to the stream as needed nsCOMPtr stream = aStream; if (!NS_InputStreamIsBuffered(stream)) { nsCOMPtr bufferedStream; nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream.forget(), 4096); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return nullptr; } stream = bufferedStream; } nsCOMPtr document = SetUpDocument(svg ? DocumentFlavorSVG : DocumentFlavorLegacyGuess, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Create a fake channel nsCOMPtr parserChannel; NS_NewInputStreamChannel(getter_AddRefs(parserChannel), mDocumentURI, nullptr, // aStream mPrincipal, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, nsIContentPolicy::TYPE_OTHER, nsDependentCString(StringFromSupportedType(aType))); if (NS_WARN_IF(!parserChannel)) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } if (!DOMStringIsNull(aCharset)) { parserChannel->SetContentCharset(NS_ConvertUTF16toUTF8(aCharset)); } // Tell the document to start loading nsCOMPtr listener; // Keep the XULXBL state in sync with the HTML case if (mForceEnableXULXBL) { document->ForceEnableXULXBL(); } // Have to pass false for reset here, else the reset will remove // our event listener. Should that listener addition move to later // than this call? nsresult rv = document->StartDocumentLoad(kLoadAsData, parserChannel, nullptr, nullptr, getter_AddRefs(listener), false); if (NS_FAILED(rv) || !listener) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } // Now start pumping data to the listener nsresult status; rv = listener->OnStartRequest(parserChannel, nullptr); if (NS_FAILED(rv)) parserChannel->Cancel(rv); parserChannel->GetStatus(&status); if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) { rv = listener->OnDataAvailable(parserChannel, nullptr, stream, 0, aContentLength); if (NS_FAILED(rv)) parserChannel->Cancel(rv); parserChannel->GetStatus(&status); } rv = listener->OnStopRequest(parserChannel, nullptr, 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)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } return document.forget(); } nsresult DOMParser::Init(nsIPrincipal* principal, nsIURI* documentURI, nsIURI* baseURI, nsIGlobalObject* aScriptObject) { NS_ENSURE_STATE(!mAttemptedInit); mAttemptedInit = true; NS_ENSURE_ARG(principal || documentURI); mDocumentURI = documentURI; if (!mDocumentURI) { principal->GetURI(getter_AddRefs(mDocumentURI)); // If we have the system principal, then we'll just use the null principals // uri. if (!mDocumentURI && !nsContentUtils::IsSystemPrincipal(principal)) { return NS_ERROR_INVALID_ARG; } } mScriptHandlingObject = do_GetWeakReference(aScriptObject); mPrincipal = principal; nsresult rv; if (!mPrincipal) { // BUG 1237080 -- in this case we're getting a chrome privilege scripted // DOMParser object creation without an explicit principal set. This is // now deprecated. nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), nullptr, nsContentUtils::eDOM_PROPERTIES, "ChromeScriptedDOMParserWithoutPrincipal", nullptr, 0, documentURI); OriginAttributes attrs; mPrincipal = BasePrincipal::CreateCodebasePrincipal(mDocumentURI, attrs); NS_ENSURE_TRUE(mPrincipal, NS_ERROR_FAILURE); } else { if (nsContentUtils::IsSystemPrincipal(mPrincipal)) { // Don't give DOMParsers the system principal. Use a null // principal instead. mForceEnableXULXBL = true; mPrincipal = NullPrincipal::CreateWithoutOriginAttributes(); if (!mDocumentURI) { rv = mPrincipal->GetURI(getter_AddRefs(mDocumentURI)); NS_ENSURE_SUCCESS(rv, rv); } } } mBaseURI = baseURI; MOZ_ASSERT(mPrincipal, "Must have principal"); MOZ_ASSERT(mDocumentURI, "Must have document URI"); return NS_OK; } /*static */already_AddRefed DOMParser::Constructor(const GlobalObject& aOwner, ErrorResult& rv) { MOZ_ASSERT(NS_IsMainThread()); RefPtr domParser = new DOMParser(aOwner.GetAsSupports()); nsCOMPtr docPrincipal = aOwner.GetSubjectPrincipal(); nsIURI* documentURI = nullptr; nsIURI* baseURI = nullptr; if (nsContentUtils::IsSystemPrincipal(docPrincipal)) { docPrincipal = NullPrincipal::CreateWithoutOriginAttributes(); } else { AttemptedInitMarker marker(&domParser->mAttemptedInit); // Grab document and base URIs off the window our constructor was // called on. Error out if anything untoward happens. nsCOMPtr window = do_QueryInterface(aOwner.GetAsSupports()); if (!window) { rv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } baseURI = window->GetDocBaseURI(); documentURI = window->GetDocumentURI(); if (!documentURI) { rv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } } nsCOMPtr global = do_QueryInterface(aOwner.GetAsSupports()); rv = domParser->Init(docPrincipal, documentURI, baseURI, global); if (rv.Failed()) { return nullptr; } return domParser.forget(); } already_AddRefed DOMParser::SetUpDocument(DocumentFlavor aFlavor, ErrorResult& aRv) { // We should really QI to nsIGlobalObject here, but nsDocument gets confused // if we pass it a scriptHandlingObject that doesn't QI to // nsIScriptGlobalObject, and test_isequalnode.js (an xpcshell test without // a window global) breaks. The correct solution is just to wean nsDocument // off of nsIScriptGlobalObject, but that's a yak to shave another day. nsCOMPtr scriptHandlingObject = do_QueryReferent(mScriptHandlingObject); nsresult rv; if (!mPrincipal) { if (NS_WARN_IF(mAttemptedInit)) { aRv.Throw(NS_ERROR_NOT_INITIALIZED); return nullptr; } AttemptedInitMarker marker(&mAttemptedInit); nsCOMPtr prin = NullPrincipal::CreateWithoutOriginAttributes(); rv = Init(prin, nullptr, nullptr, scriptHandlingObject); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return nullptr; } } // Try to inherit a style backend. NS_ASSERTION(mPrincipal, "Must have principal by now"); NS_ASSERTION(mDocumentURI, "Must have document URI by now"); nsCOMPtr domDoc; rv = NS_NewDOMDocument(getter_AddRefs(domDoc), EmptyString(), EmptyString(), nullptr, mDocumentURI, mBaseURI, mPrincipal, true, scriptHandlingObject, aFlavor); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return nullptr; } nsCOMPtr doc = do_QueryInterface(domDoc); return doc.forget(); }