michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/dom/DOMParser.h" michael@0: michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsStringStream.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsCRT.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsDOMJSUtils.h" michael@0: #include "nsError.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: DOMParser::DOMParser() michael@0: : mAttemptedInit(false) michael@0: { michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: DOMParser::~DOMParser() michael@0: { michael@0: } michael@0: michael@0: // QueryInterface implementation for DOMParser michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMParser) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMParser) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMParser) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(DOMParser, mOwner) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMParser) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMParser) michael@0: michael@0: static const char* michael@0: StringFromSupportedType(SupportedType aType) michael@0: { michael@0: return SupportedTypeValues::strings[static_cast(aType)].value; michael@0: } michael@0: michael@0: already_AddRefed michael@0: DOMParser::ParseFromString(const nsAString& aStr, SupportedType aType, michael@0: ErrorResult& rv) michael@0: { michael@0: nsCOMPtr domDocument; michael@0: rv = ParseFromString(aStr, michael@0: StringFromSupportedType(aType), michael@0: getter_AddRefs(domDocument)); michael@0: nsCOMPtr document(do_QueryInterface(domDocument)); michael@0: return document.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DOMParser::ParseFromString(const char16_t *str, michael@0: const char *contentType, michael@0: nsIDOMDocument **aResult) michael@0: { michael@0: NS_ENSURE_ARG(str); michael@0: // Converting a string to an enum value manually is a bit of a pain, michael@0: // so let's just use a helper that takes a content-type string. michael@0: return ParseFromString(nsDependentString(str), contentType, aResult); michael@0: } michael@0: michael@0: nsresult michael@0: DOMParser::ParseFromString(const nsAString& str, michael@0: const char *contentType, michael@0: nsIDOMDocument **aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!nsCRT::strcmp(contentType, "text/html")) { michael@0: nsCOMPtr domDocument; michael@0: rv = SetUpDocument(DocumentFlavorHTML, getter_AddRefs(domDocument)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr document = do_QueryInterface(domDocument); michael@0: michael@0: // Keep the XULXBL state, base URL and principal setting in sync with the michael@0: // XML case michael@0: michael@0: if (nsContentUtils::IsSystemPrincipal(mOriginalPrincipal)) { michael@0: document->ForceEnableXULXBL(); michael@0: } michael@0: michael@0: // Make sure to give this document the right base URI michael@0: document->SetBaseURI(mBaseURI); michael@0: // And the right principal michael@0: document->SetPrincipal(mPrincipal); michael@0: michael@0: rv = nsContentUtils::ParseDocumentHTML(str, document, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: domDocument.forget(aResult); michael@0: return rv; michael@0: } michael@0: michael@0: nsAutoCString utf8str; michael@0: // Convert from UTF16 to UTF8 using fallible allocations michael@0: if (!AppendUTF16toUTF8(str, utf8str, mozilla::fallible_t())) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: // The new stream holds a reference to the buffer michael@0: nsCOMPtr stream; michael@0: rv = NS_NewByteInputStream(getter_AddRefs(stream), michael@0: utf8str.get(), utf8str.Length(), michael@0: NS_ASSIGNMENT_DEPEND); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: return ParseFromStream(stream, "UTF-8", utf8str.Length(), contentType, aResult); michael@0: } michael@0: michael@0: already_AddRefed michael@0: DOMParser::ParseFromBuffer(const Sequence& aBuf, uint32_t aBufLen, michael@0: SupportedType aType, ErrorResult& rv) michael@0: { michael@0: if (aBufLen > aBuf.Length()) { michael@0: rv.Throw(NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY); michael@0: return nullptr; michael@0: } michael@0: nsCOMPtr domDocument; michael@0: rv = DOMParser::ParseFromBuffer(aBuf.Elements(), aBufLen, michael@0: StringFromSupportedType(aType), michael@0: getter_AddRefs(domDocument)); michael@0: nsCOMPtr document(do_QueryInterface(domDocument)); michael@0: return document.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: DOMParser::ParseFromBuffer(const Uint8Array& aBuf, uint32_t aBufLen, michael@0: SupportedType aType, ErrorResult& rv) michael@0: { michael@0: aBuf.ComputeLengthAndData(); michael@0: michael@0: if (aBufLen > aBuf.Length()) { michael@0: rv.Throw(NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY); michael@0: return nullptr; michael@0: } michael@0: nsCOMPtr domDocument; michael@0: rv = DOMParser::ParseFromBuffer(aBuf.Data(), aBufLen, michael@0: StringFromSupportedType(aType), michael@0: getter_AddRefs(domDocument)); michael@0: nsCOMPtr document(do_QueryInterface(domDocument)); michael@0: return document.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DOMParser::ParseFromBuffer(const uint8_t *buf, michael@0: uint32_t bufLen, michael@0: const char *contentType, michael@0: nsIDOMDocument **aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(buf); michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: michael@0: // The new stream holds a reference to the buffer michael@0: nsCOMPtr stream; michael@0: nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), michael@0: reinterpret_cast(buf), michael@0: bufLen, NS_ASSIGNMENT_DEPEND); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: return ParseFromStream(stream, nullptr, bufLen, contentType, aResult); michael@0: } michael@0: michael@0: michael@0: already_AddRefed michael@0: DOMParser::ParseFromStream(nsIInputStream* aStream, michael@0: const nsAString& aCharset, michael@0: int32_t aContentLength, michael@0: SupportedType aType, michael@0: ErrorResult& rv) michael@0: { michael@0: nsCOMPtr domDocument; michael@0: rv = DOMParser::ParseFromStream(aStream, michael@0: NS_ConvertUTF16toUTF8(aCharset).get(), michael@0: aContentLength, michael@0: StringFromSupportedType(aType), michael@0: getter_AddRefs(domDocument)); michael@0: nsCOMPtr document(do_QueryInterface(domDocument)); michael@0: return document.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DOMParser::ParseFromStream(nsIInputStream *stream, michael@0: const char *charset, michael@0: int32_t contentLength, michael@0: const char *contentType, michael@0: nsIDOMDocument **aResult) michael@0: { michael@0: NS_ENSURE_ARG(stream); michael@0: NS_ENSURE_ARG(contentType); michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: *aResult = nullptr; michael@0: michael@0: bool svg = nsCRT::strcmp(contentType, "image/svg+xml") == 0; michael@0: michael@0: // For now, we can only create XML documents. michael@0: //XXXsmaug Should we create an HTMLDocument (in XHTML mode) michael@0: // for "application/xhtml+xml"? michael@0: if ((nsCRT::strcmp(contentType, "text/xml") != 0) && michael@0: (nsCRT::strcmp(contentType, "application/xml") != 0) && michael@0: (nsCRT::strcmp(contentType, "application/xhtml+xml") != 0) && michael@0: !svg) michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: michael@0: nsresult rv; michael@0: michael@0: // Put the nsCOMPtr out here so we hold a ref to the stream as needed michael@0: nsCOMPtr bufferedStream; michael@0: if (!NS_InputStreamIsBuffered(stream)) { michael@0: rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, michael@0: 4096); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: stream = bufferedStream; michael@0: } michael@0: michael@0: nsCOMPtr domDocument; michael@0: rv = SetUpDocument(svg ? DocumentFlavorSVG : DocumentFlavorLegacyGuess, michael@0: getter_AddRefs(domDocument)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Create a fake channel michael@0: nsCOMPtr parserChannel; michael@0: NS_NewInputStreamChannel(getter_AddRefs(parserChannel), mDocumentURI, nullptr, michael@0: nsDependentCString(contentType), nullptr); michael@0: NS_ENSURE_STATE(parserChannel); michael@0: michael@0: // More principal-faking here michael@0: parserChannel->SetOwner(mOriginalPrincipal); michael@0: michael@0: if (charset) { michael@0: parserChannel->SetContentCharset(nsDependentCString(charset)); michael@0: } michael@0: michael@0: // Tell the document to start loading michael@0: nsCOMPtr listener; michael@0: michael@0: // Have to pass false for reset here, else the reset will remove michael@0: // our event listener. Should that listener addition move to later michael@0: // than this call? Then we wouldn't need to mess around with michael@0: // SetPrincipal, etc, probably! michael@0: nsCOMPtr document(do_QueryInterface(domDocument)); michael@0: if (!document) return NS_ERROR_FAILURE; michael@0: michael@0: // Keep the XULXBL state, base URL and principal setting in sync with the michael@0: // HTML case michael@0: michael@0: if (nsContentUtils::IsSystemPrincipal(mOriginalPrincipal)) { michael@0: document->ForceEnableXULXBL(); michael@0: } michael@0: michael@0: rv = document->StartDocumentLoad(kLoadAsData, parserChannel, michael@0: nullptr, nullptr, michael@0: getter_AddRefs(listener), michael@0: false); michael@0: michael@0: // Make sure to give this document the right base URI michael@0: document->SetBaseURI(mBaseURI); michael@0: michael@0: // And the right principal michael@0: document->SetPrincipal(mPrincipal); michael@0: michael@0: if (NS_FAILED(rv) || !listener) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Now start pumping data to the listener michael@0: nsresult status; michael@0: michael@0: rv = listener->OnStartRequest(parserChannel, nullptr); michael@0: if (NS_FAILED(rv)) michael@0: parserChannel->Cancel(rv); michael@0: parserChannel->GetStatus(&status); michael@0: michael@0: if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) { michael@0: rv = listener->OnDataAvailable(parserChannel, nullptr, stream, 0, michael@0: contentLength); michael@0: if (NS_FAILED(rv)) michael@0: parserChannel->Cancel(rv); michael@0: parserChannel->GetStatus(&status); michael@0: } michael@0: michael@0: rv = listener->OnStopRequest(parserChannel, nullptr, status); michael@0: // Failure returned from OnStopRequest does not affect the final status of michael@0: // the channel, so we do not need to call Cancel(rv) as we do above. michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: domDocument.swap(*aResult); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DOMParser::Init(nsIPrincipal* principal, nsIURI* documentURI, michael@0: nsIURI* baseURI, nsIScriptGlobalObject* aScriptObject) michael@0: { michael@0: NS_ENSURE_STATE(!mAttemptedInit); michael@0: mAttemptedInit = true; michael@0: michael@0: NS_ENSURE_ARG(principal || documentURI); michael@0: michael@0: mDocumentURI = documentURI; michael@0: michael@0: if (!mDocumentURI) { michael@0: principal->GetURI(getter_AddRefs(mDocumentURI)); michael@0: // If we have the system principal, then we'll just use the null principals michael@0: // uri. michael@0: if (!mDocumentURI && !nsContentUtils::IsSystemPrincipal(principal)) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: } michael@0: michael@0: mScriptHandlingObject = do_GetWeakReference(aScriptObject); michael@0: mPrincipal = principal; michael@0: nsresult rv; michael@0: if (!mPrincipal) { michael@0: nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); michael@0: NS_ENSURE_TRUE(secMan, NS_ERROR_NOT_AVAILABLE); michael@0: rv = michael@0: secMan->GetSimpleCodebasePrincipal(mDocumentURI, michael@0: getter_AddRefs(mPrincipal)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: mOriginalPrincipal = mPrincipal; michael@0: } else { michael@0: mOriginalPrincipal = mPrincipal; michael@0: if (nsContentUtils::IsSystemPrincipal(mPrincipal)) { michael@0: // Don't give DOMParsers the system principal. Use a null michael@0: // principal instead. michael@0: mPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!mDocumentURI) { michael@0: rv = mPrincipal->GetURI(getter_AddRefs(mDocumentURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: } michael@0: michael@0: mBaseURI = baseURI; michael@0: // Note: if mBaseURI is null, fine. Leave it like that; that will use the michael@0: // documentURI as the base. Otherwise for null principals we'll get michael@0: // nsDocument::SetBaseURI giving errors. michael@0: michael@0: NS_POSTCONDITION(mPrincipal, "Must have principal"); michael@0: NS_POSTCONDITION(mOriginalPrincipal, "Must have original principal"); michael@0: NS_POSTCONDITION(mDocumentURI, "Must have document URI"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /*static */already_AddRefed michael@0: DOMParser::Constructor(const GlobalObject& aOwner, michael@0: nsIPrincipal* aPrincipal, nsIURI* aDocumentURI, michael@0: nsIURI* aBaseURI, ErrorResult& rv) michael@0: { michael@0: if (!nsContentUtils::IsCallerChrome()) { michael@0: rv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return nullptr; michael@0: } michael@0: nsRefPtr domParser = new DOMParser(aOwner.GetAsSupports()); michael@0: rv = domParser->InitInternal(aOwner.GetAsSupports(), aPrincipal, aDocumentURI, michael@0: aBaseURI); michael@0: if (rv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: return domParser.forget(); michael@0: } michael@0: michael@0: /*static */already_AddRefed michael@0: DOMParser::Constructor(const GlobalObject& aOwner, michael@0: ErrorResult& rv) michael@0: { michael@0: nsCOMPtr prin; michael@0: nsCOMPtr documentURI; michael@0: nsCOMPtr baseURI; michael@0: // No arguments; use the subject principal michael@0: nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); michael@0: if (!secMan) { michael@0: rv.Throw(NS_ERROR_UNEXPECTED); michael@0: return nullptr; michael@0: } michael@0: michael@0: rv = secMan->GetSubjectPrincipal(getter_AddRefs(prin)); michael@0: if (rv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // We're called from JS; there better be a subject principal, really. michael@0: if (!prin) { michael@0: rv.Throw(NS_ERROR_UNEXPECTED); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr domParser = new DOMParser(aOwner.GetAsSupports()); michael@0: rv = domParser->InitInternal(aOwner.GetAsSupports(), prin, documentURI, baseURI); michael@0: if (rv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: return domParser.forget(); michael@0: } michael@0: michael@0: nsresult michael@0: DOMParser::InitInternal(nsISupports* aOwner, nsIPrincipal* prin, michael@0: nsIURI* documentURI, nsIURI* baseURI) michael@0: { michael@0: AttemptedInitMarker marker(&mAttemptedInit); michael@0: if (!documentURI) { michael@0: // No explicit documentURI; grab document and base URIs off the window our michael@0: // constructor was called on. Error out if anything untoward happens. michael@0: michael@0: // Note that this is a behavior change as far as I can tell -- we're now michael@0: // using the base URI and document URI of the window off of which the michael@0: // DOMParser is created, not the window in which parse*() is called. michael@0: // Does that matter? michael@0: michael@0: // Also note that |cx| matches what GetDocumentFromContext() would return, michael@0: // while GetDocumentFromCaller() gives us the window that the DOMParser() michael@0: // call was made on. michael@0: michael@0: nsCOMPtr window = do_QueryInterface(aOwner); michael@0: if (!window) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: baseURI = window->GetDocBaseURI(); michael@0: documentURI = window->GetDocumentURI(); michael@0: if (!documentURI) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr scriptglobal = do_QueryInterface(aOwner); michael@0: return Init(prin, documentURI, baseURI, scriptglobal); michael@0: } michael@0: michael@0: void michael@0: DOMParser::Init(nsIPrincipal* aPrincipal, nsIURI* aDocumentURI, michael@0: nsIURI* aBaseURI, mozilla::ErrorResult& rv) michael@0: { michael@0: AttemptedInitMarker marker(&mAttemptedInit); michael@0: michael@0: JSContext *cx = nsContentUtils::GetCurrentJSContext(); michael@0: if (!cx) { michael@0: rv.Throw(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: michael@0: nsIScriptContext* scriptContext = GetScriptContextFromJSContext(cx); michael@0: michael@0: nsCOMPtr principal = aPrincipal; michael@0: michael@0: if (!principal && !aDocumentURI) { michael@0: nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); michael@0: if (!secMan) { michael@0: rv.Throw(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: michael@0: rv = secMan->GetSubjectPrincipal(getter_AddRefs(principal)); michael@0: if (rv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: // We're called from JS; there better be a subject principal, really. michael@0: if (!principal) { michael@0: rv.Throw(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: rv = Init(principal, aDocumentURI, aBaseURI, michael@0: scriptContext ? scriptContext->GetGlobalObject() : nullptr); michael@0: } michael@0: michael@0: nsresult michael@0: DOMParser::SetUpDocument(DocumentFlavor aFlavor, nsIDOMDocument** aResult) michael@0: { michael@0: nsCOMPtr scriptHandlingObject = michael@0: do_QueryReferent(mScriptHandlingObject); michael@0: nsresult rv; michael@0: if (!mPrincipal) { michael@0: NS_ENSURE_TRUE(!mAttemptedInit, NS_ERROR_NOT_INITIALIZED); michael@0: AttemptedInitMarker marker(&mAttemptedInit); michael@0: michael@0: nsCOMPtr prin = michael@0: do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = Init(prin, nullptr, nullptr, scriptHandlingObject); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: NS_ASSERTION(mPrincipal, "Must have principal by now"); michael@0: NS_ASSERTION(mDocumentURI, "Must have document URI by now"); michael@0: michael@0: // Here we have to cheat a little bit... Setting the base URI won't michael@0: // work if the document has a null principal, so use michael@0: // mOriginalPrincipal when creating the document, then reset the michael@0: // principal. michael@0: return NS_NewDOMDocument(aResult, EmptyString(), EmptyString(), nullptr, michael@0: mDocumentURI, mBaseURI, michael@0: mOriginalPrincipal, michael@0: true, michael@0: scriptHandlingObject, michael@0: aFlavor); michael@0: }