diff options
Diffstat (limited to 'dom/base/DOMParser.cpp')
-rw-r--r-- | dom/base/DOMParser.cpp | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/dom/base/DOMParser.cpp b/dom/base/DOMParser.cpp new file mode 100644 index 000000000..55911d477 --- /dev/null +++ b/dom/base/DOMParser.cpp @@ -0,0 +1,495 @@ +/* -*- 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 "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 "nsNullPrincipal.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/ScriptSettings.h" + +using namespace mozilla; +using namespace mozilla::dom; + +DOMParser::DOMParser() + : mAttemptedInit(false) +{ +} + +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<int>(aType)].value; +} + +already_AddRefed<nsIDocument> +DOMParser::ParseFromString(const nsAString& aStr, SupportedType aType, + ErrorResult& rv) +{ + nsCOMPtr<nsIDOMDocument> domDocument; + rv = ParseFromString(aStr, + StringFromSupportedType(aType), + getter_AddRefs(domDocument)); + nsCOMPtr<nsIDocument> document(do_QueryInterface(domDocument)); + return document.forget(); +} + +NS_IMETHODIMP +DOMParser::ParseFromString(const char16_t *str, + const char *contentType, + nsIDOMDocument **aResult) +{ + NS_ENSURE_ARG(str); + // Converting a string to an enum value manually is a bit of a pain, + // so let's just use a helper that takes a content-type string. + return ParseFromString(nsDependentString(str), contentType, aResult); +} + +nsresult +DOMParser::ParseFromString(const nsAString& str, + const char *contentType, + nsIDOMDocument **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsresult rv; + + if (!nsCRT::strcmp(contentType, "text/html")) { + nsCOMPtr<nsIDOMDocument> domDocument; + rv = SetUpDocument(DocumentFlavorHTML, getter_AddRefs(domDocument)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIDocument> document = do_QueryInterface(domDocument); + + // Keep the XULXBL state, base URL and principal setting in sync with the + // XML case + + if (nsContentUtils::IsSystemPrincipal(mOriginalPrincipal)) { + document->ForceEnableXULXBL(); + } + + // Make sure to give this document the right base URI + document->SetBaseURI(mBaseURI); + // And the right principal + document->SetPrincipal(mPrincipal); + + rv = nsContentUtils::ParseDocumentHTML(str, document, false); + NS_ENSURE_SUCCESS(rv, rv); + + domDocument.forget(aResult); + return rv; + } + + nsAutoCString utf8str; + // Convert from UTF16 to UTF8 using fallible allocations + if (!AppendUTF16toUTF8(str, utf8str, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // The new stream holds a reference to the buffer + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewByteInputStream(getter_AddRefs(stream), + utf8str.get(), utf8str.Length(), + NS_ASSIGNMENT_DEPEND); + if (NS_FAILED(rv)) + return rv; + + return ParseFromStream(stream, "UTF-8", utf8str.Length(), contentType, aResult); +} + +already_AddRefed<nsIDocument> +DOMParser::ParseFromBuffer(const Sequence<uint8_t>& aBuf, uint32_t aBufLen, + SupportedType aType, ErrorResult& rv) +{ + if (aBufLen > aBuf.Length()) { + rv.Throw(NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY); + return nullptr; + } + nsCOMPtr<nsIDOMDocument> domDocument; + rv = DOMParser::ParseFromBuffer(aBuf.Elements(), aBufLen, + StringFromSupportedType(aType), + getter_AddRefs(domDocument)); + nsCOMPtr<nsIDocument> document(do_QueryInterface(domDocument)); + return document.forget(); +} + +already_AddRefed<nsIDocument> +DOMParser::ParseFromBuffer(const Uint8Array& aBuf, uint32_t aBufLen, + SupportedType aType, ErrorResult& rv) +{ + aBuf.ComputeLengthAndData(); + + if (aBufLen > aBuf.Length()) { + rv.Throw(NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY); + return nullptr; + } + nsCOMPtr<nsIDOMDocument> domDocument; + rv = DOMParser::ParseFromBuffer(aBuf.Data(), aBufLen, + StringFromSupportedType(aType), + getter_AddRefs(domDocument)); + nsCOMPtr<nsIDocument> document(do_QueryInterface(domDocument)); + return document.forget(); +} + +NS_IMETHODIMP +DOMParser::ParseFromBuffer(const uint8_t *buf, + uint32_t 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<nsIInputStream> stream; + nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), + reinterpret_cast<const char *>(buf), + bufLen, NS_ASSIGNMENT_DEPEND); + if (NS_FAILED(rv)) + return rv; + + return ParseFromStream(stream, nullptr, bufLen, contentType, aResult); +} + + +already_AddRefed<nsIDocument> +DOMParser::ParseFromStream(nsIInputStream* aStream, + const nsAString& aCharset, + int32_t aContentLength, + SupportedType aType, + ErrorResult& rv) +{ + nsCOMPtr<nsIDOMDocument> domDocument; + rv = DOMParser::ParseFromStream(aStream, + NS_ConvertUTF16toUTF8(aCharset).get(), + aContentLength, + StringFromSupportedType(aType), + getter_AddRefs(domDocument)); + nsCOMPtr<nsIDocument> document(do_QueryInterface(domDocument)); + return document.forget(); +} + +NS_IMETHODIMP +DOMParser::ParseFromStream(nsIInputStream *stream, + const char *charset, + int32_t contentLength, + const char *contentType, + nsIDOMDocument **aResult) +{ + NS_ENSURE_ARG(stream); + NS_ENSURE_ARG(contentType); + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + bool svg = nsCRT::strcmp(contentType, "image/svg+xml") == 0; + + // For now, we can only create XML documents. + //XXXsmaug Should we create an HTMLDocument (in XHTML mode) + // for "application/xhtml+xml"? + if ((nsCRT::strcmp(contentType, "text/xml") != 0) && + (nsCRT::strcmp(contentType, "application/xml") != 0) && + (nsCRT::strcmp(contentType, "application/xhtml+xml") != 0) && + !svg) + return NS_ERROR_NOT_IMPLEMENTED; + + nsresult rv; + + // Put the nsCOMPtr out here so we hold a ref to the stream as needed + nsCOMPtr<nsIInputStream> bufferedStream; + if (!NS_InputStreamIsBuffered(stream)) { + rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, + 4096); + NS_ENSURE_SUCCESS(rv, rv); + + stream = bufferedStream; + } + + nsCOMPtr<nsIDOMDocument> domDocument; + rv = SetUpDocument(svg ? DocumentFlavorSVG : DocumentFlavorLegacyGuess, + getter_AddRefs(domDocument)); + NS_ENSURE_SUCCESS(rv, rv); + + // Create a fake channel + nsCOMPtr<nsIChannel> parserChannel; + NS_NewInputStreamChannel(getter_AddRefs(parserChannel), + mDocumentURI, + nullptr, // aStream + mOriginalPrincipal, + nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, + nsIContentPolicy::TYPE_OTHER, + nsDependentCString(contentType)); + NS_ENSURE_STATE(parserChannel); + + if (charset) { + parserChannel->SetContentCharset(nsDependentCString(charset)); + } + + // Tell the document to start loading + nsCOMPtr<nsIStreamListener> listener; + + // 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? Then we wouldn't need to mess around with + // SetPrincipal, etc, probably! + nsCOMPtr<nsIDocument> document(do_QueryInterface(domDocument)); + if (!document) return NS_ERROR_FAILURE; + + // Keep the XULXBL state, base URL and principal setting in sync with the + // HTML case + + if (nsContentUtils::IsSystemPrincipal(mOriginalPrincipal)) { + document->ForceEnableXULXBL(); + } + + rv = document->StartDocumentLoad(kLoadAsData, parserChannel, + nullptr, nullptr, + getter_AddRefs(listener), + false); + + // Make sure to give this document the right base URI + document->SetBaseURI(mBaseURI); + + // And the right principal + document->SetPrincipal(mPrincipal); + + if (NS_FAILED(rv) || !listener) { + return NS_ERROR_FAILURE; + } + + // 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, + contentLength); + 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)) { + return NS_ERROR_FAILURE; + } + + domDocument.swap(*aResult); + + return NS_OK; +} + +NS_IMETHODIMP +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); + + PrincipalOriginAttributes attrs; + mPrincipal = BasePrincipal::CreateCodebasePrincipal(mDocumentURI, attrs); + NS_ENSURE_TRUE(mPrincipal, NS_ERROR_FAILURE); + mOriginalPrincipal = mPrincipal; + } else { + mOriginalPrincipal = mPrincipal; + if (nsContentUtils::IsSystemPrincipal(mPrincipal)) { + // Don't give DOMParsers the system principal. Use a null + // principal instead. + mPrincipal = nsNullPrincipal::Create(); + + if (!mDocumentURI) { + rv = mPrincipal->GetURI(getter_AddRefs(mDocumentURI)); + 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(mOriginalPrincipal, "Must have original principal"); + NS_POSTCONDITION(mDocumentURI, "Must have document URI"); + return NS_OK; +} + +/*static */already_AddRefed<DOMParser> +DOMParser::Constructor(const GlobalObject& aOwner, + nsIPrincipal* aPrincipal, nsIURI* aDocumentURI, + nsIURI* aBaseURI, ErrorResult& rv) +{ + if (!nsContentUtils::IsCallerChrome()) { + rv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + RefPtr<DOMParser> domParser = new DOMParser(aOwner.GetAsSupports()); + rv = domParser->InitInternal(aOwner.GetAsSupports(), aPrincipal, aDocumentURI, + aBaseURI); + if (rv.Failed()) { + return nullptr; + } + return domParser.forget(); +} + +/*static */already_AddRefed<DOMParser> +DOMParser::Constructor(const GlobalObject& aOwner, + ErrorResult& rv) +{ + RefPtr<DOMParser> domParser = new DOMParser(aOwner.GetAsSupports()); + rv = domParser->InitInternal(aOwner.GetAsSupports(), + nsContentUtils::SubjectPrincipal(), + nullptr, nullptr); + if (rv.Failed()) { + return nullptr; + } + return domParser.forget(); +} + +nsresult +DOMParser::InitInternal(nsISupports* aOwner, nsIPrincipal* prin, + nsIURI* documentURI, nsIURI* baseURI) +{ + AttemptedInitMarker marker(&mAttemptedInit); + 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<nsPIDOMWindowInner> window = do_QueryInterface(aOwner); + if (!window) { + return NS_ERROR_UNEXPECTED; + } + + baseURI = window->GetDocBaseURI(); + documentURI = window->GetDocumentURI(); + if (!documentURI) { + return NS_ERROR_UNEXPECTED; + } + } + + nsCOMPtr<nsIGlobalObject> scriptglobal = do_QueryInterface(aOwner); + return Init(prin, documentURI, baseURI, scriptglobal); +} + +void +DOMParser::Init(nsIPrincipal* aPrincipal, nsIURI* aDocumentURI, + nsIURI* aBaseURI, mozilla::ErrorResult& rv) +{ + AttemptedInitMarker marker(&mAttemptedInit); + + nsCOMPtr<nsIPrincipal> principal = aPrincipal; + if (!principal && !aDocumentURI) { + principal = nsContentUtils::SubjectPrincipal(); + } + + rv = Init(principal, aDocumentURI, aBaseURI, GetEntryGlobal()); +} + +nsresult +DOMParser::SetUpDocument(DocumentFlavor aFlavor, nsIDOMDocument** aResult) +{ + // 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<nsIScriptGlobalObject> scriptHandlingObject = + do_QueryReferent(mScriptHandlingObject); + nsresult rv; + if (!mPrincipal) { + NS_ENSURE_TRUE(!mAttemptedInit, NS_ERROR_NOT_INITIALIZED); + AttemptedInitMarker marker(&mAttemptedInit); + + nsCOMPtr<nsIPrincipal> prin = nsNullPrincipal::Create(); + rv = Init(prin, nullptr, nullptr, scriptHandlingObject); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_ASSERTION(mPrincipal, "Must have principal by now"); + NS_ASSERTION(mDocumentURI, "Must have document URI by now"); + + // Here we have to cheat a little bit... Setting the base URI won't + // work if the document has a null principal, so use + // mOriginalPrincipal when creating the document, then reset the + // principal. + return NS_NewDOMDocument(aResult, EmptyString(), EmptyString(), nullptr, + mDocumentURI, mBaseURI, + mOriginalPrincipal, + true, + scriptHandlingObject, + aFlavor); +} |