DOMParser.cpp (11584B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/DOMParser.h" 8 9 #include "MainThreadUtils.h" 10 #include "SystemPrincipal.h" 11 #include "mozilla/BasePrincipal.h" 12 #include "mozilla/LoadInfo.h" 13 #include "mozilla/NullPrincipal.h" 14 #include "mozilla/dom/BindingUtils.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/ScriptSettings.h" 17 #include "mozilla/dom/TrustedTypeUtils.h" 18 #include "mozilla/dom/TrustedTypesConstants.h" 19 #include "nsCRT.h" 20 #include "nsContentUtils.h" 21 #include "nsDOMJSUtils.h" 22 #include "nsDOMString.h" 23 #include "nsError.h" 24 #include "nsIScriptGlobalObject.h" 25 #include "nsIStreamListener.h" 26 #include "nsNetUtil.h" 27 #include "nsPIDOMWindow.h" 28 #include "nsStreamUtils.h" 29 #include "nsStringStream.h" 30 31 using namespace mozilla; 32 using namespace mozilla::dom; 33 34 DOMParser::DOMParser(nsIGlobalObject* aOwner, nsIPrincipal* aDocPrincipal, 35 nsIURI* aDocumentURI) 36 : mOwner(aOwner), 37 mPrincipal(aDocPrincipal), 38 mDocumentURI(aDocumentURI), 39 mForceEnableXULXBL(false), 40 mForceEnableDTD(false) { 41 MOZ_ASSERT(aDocPrincipal); 42 MOZ_ASSERT(aDocumentURI); 43 } 44 45 DOMParser::~DOMParser() = default; 46 47 // QueryInterface implementation for DOMParser 48 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMParser) 49 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 50 NS_INTERFACE_MAP_ENTRY(nsISupports) 51 NS_INTERFACE_MAP_END 52 53 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMParser, mOwner) 54 55 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMParser) 56 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMParser) 57 58 already_AddRefed<Document> DOMParser::ParseFromStringInternal( 59 const nsAString& aStr, SupportedType aType, ErrorResult& aRv) { 60 if (aType == SupportedType::Text_html) { 61 nsCOMPtr<Document> document = SetUpDocument(DocumentFlavor::HTML, aRv); 62 if (NS_WARN_IF(aRv.Failed())) { 63 return nullptr; 64 } 65 66 // Keep the XULXBL state in sync with the XML case. 67 if (mForceEnableXULXBL) { 68 document->ForceEnableXULXBL(); 69 } 70 71 if (mForceEnableDTD) { 72 document->ForceSkipDTDSecurityChecks(); 73 } 74 75 nsresult rv = nsContentUtils::ParseDocumentHTML(aStr, document, false); 76 if (NS_WARN_IF(NS_FAILED(rv))) { 77 aRv.Throw(rv); 78 return nullptr; 79 } 80 81 return document.forget(); 82 } 83 84 nsAutoCString utf8str; 85 // Convert from UTF16 to UTF8 using fallible allocations 86 if (!AppendUTF16toUTF8(aStr, utf8str, mozilla::fallible)) { 87 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 88 return nullptr; 89 } 90 91 // The new stream holds a reference to the buffer 92 nsCOMPtr<nsIInputStream> stream; 93 nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), utf8str, 94 NS_ASSIGNMENT_DEPEND); 95 if (NS_WARN_IF(NS_FAILED(rv))) { 96 aRv.Throw(rv); 97 return nullptr; 98 } 99 100 return ParseFromStream(stream, u"UTF-8"_ns, utf8str.Length(), aType, aRv); 101 } 102 103 already_AddRefed<Document> DOMParser::ParseFromString( 104 const TrustedHTMLOrString& aStr, SupportedType aType, 105 nsIPrincipal* aSubjectPrincipal, ErrorResult& aRv) { 106 constexpr nsLiteralString sink = u"DOMParser parseFromString"_ns; 107 108 MOZ_ASSERT(mOwner); 109 nsCOMPtr<nsIGlobalObject> pinnedOwner = mOwner; 110 Maybe<nsAutoString> compliantStringHolder; 111 const nsAString* compliantString = 112 TrustedTypeUtils::GetTrustedTypesCompliantString( 113 aStr, sink, kTrustedTypesOnlySinkGroup, *pinnedOwner, 114 aSubjectPrincipal, compliantStringHolder, aRv); 115 if (aRv.Failed()) { 116 return nullptr; 117 } 118 119 return ParseFromStringInternal(*compliantString, aType, aRv); 120 } 121 122 already_AddRefed<Document> DOMParser::ParseFromSafeString(const nsAString& aStr, 123 SupportedType aType, 124 ErrorResult& aRv) { 125 // Create the new document with the same principal as `mOwner`, even if it is 126 // the system principal. This will ensure that nodes from the returned 127 // document are in the same DocGroup as the owner global's document, allowing 128 // nodes to be adopted. 129 nsCOMPtr<nsIPrincipal> docPrincipal = mPrincipal; 130 if (mOwner && mOwner->PrincipalOrNull()) { 131 mPrincipal = mOwner->PrincipalOrNull(); 132 } 133 134 RefPtr<Document> ret = ParseFromStringInternal(aStr, aType, aRv); 135 mPrincipal = docPrincipal; 136 return ret.forget(); 137 } 138 139 already_AddRefed<Document> DOMParser::ParseFromBuffer(const Uint8Array& aBuf, 140 SupportedType aType, 141 ErrorResult& aRv) { 142 return aBuf.ProcessFixedData([&](const Span<uint8_t>& aData) { 143 return ParseFromBuffer(aData, aType, aRv); 144 }); 145 } 146 147 already_AddRefed<Document> DOMParser::ParseFromBuffer(Span<const uint8_t> aBuf, 148 SupportedType aType, 149 ErrorResult& aRv) { 150 // The new stream holds a reference to the buffer 151 nsCOMPtr<nsIInputStream> stream; 152 nsresult rv = NS_NewByteInputStream( 153 getter_AddRefs(stream), 154 Span(reinterpret_cast<const char*>(aBuf.Elements()), aBuf.Length()), 155 NS_ASSIGNMENT_DEPEND); 156 if (NS_FAILED(rv)) { 157 aRv.Throw(rv); 158 return nullptr; 159 } 160 161 return ParseFromStream(stream, VoidString(), aBuf.Length(), aType, aRv); 162 } 163 164 already_AddRefed<Document> DOMParser::ParseFromStream(nsIInputStream* aStream, 165 const nsAString& aCharset, 166 int32_t aContentLength, 167 SupportedType aType, 168 ErrorResult& aRv) { 169 bool svg = (aType == SupportedType::Image_svg_xml); 170 171 // For now, we can only create XML documents. 172 // XXXsmaug Should we create an HTMLDocument (in XHTML mode) 173 // for "application/xhtml+xml"? 174 if (aType != SupportedType::Text_xml && 175 aType != SupportedType::Application_xml && 176 aType != SupportedType::Application_xhtml_xml && !svg) { 177 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); 178 return nullptr; 179 } 180 181 // Put the nsCOMPtr out here so we hold a ref to the stream as needed 182 nsCOMPtr<nsIInputStream> stream = aStream; 183 if (!NS_InputStreamIsBuffered(stream)) { 184 nsCOMPtr<nsIInputStream> bufferedStream; 185 nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), 186 stream.forget(), 4096); 187 if (NS_WARN_IF(NS_FAILED(rv))) { 188 aRv.Throw(rv); 189 return nullptr; 190 } 191 192 stream = bufferedStream; 193 } 194 195 nsCOMPtr<Document> document = SetUpDocument( 196 svg ? DocumentFlavor::SVG : DocumentFlavor::LegacyGuess, aRv); 197 if (NS_WARN_IF(aRv.Failed())) { 198 return nullptr; 199 } 200 201 // Create a fake channel 202 nsCOMPtr<nsIChannel> parserChannel; 203 NS_NewInputStreamChannel(getter_AddRefs(parserChannel), mDocumentURI, 204 nullptr, // aStream 205 mPrincipal, nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, 206 nsIContentPolicy::TYPE_OTHER, GetEnumString(aType)); 207 if (NS_WARN_IF(!parserChannel)) { 208 aRv.Throw(NS_ERROR_UNEXPECTED); 209 return nullptr; 210 } 211 212 if (!DOMStringIsNull(aCharset)) { 213 parserChannel->SetContentCharset(NS_ConvertUTF16toUTF8(aCharset)); 214 } 215 216 // Tell the document to start loading 217 nsCOMPtr<nsIStreamListener> listener; 218 219 // Keep the XULXBL state in sync with the HTML case 220 if (mForceEnableXULXBL) { 221 document->ForceEnableXULXBL(); 222 } 223 224 if (mForceEnableDTD) { 225 document->ForceSkipDTDSecurityChecks(); 226 } 227 228 // Have to pass false for reset here, else the reset will remove 229 // our event listener. Should that listener addition move to later 230 // than this call? 231 nsresult rv = 232 document->StartDocumentLoad(kLoadAsData, parserChannel, nullptr, nullptr, 233 getter_AddRefs(listener), false); 234 235 if (NS_FAILED(rv) || !listener) { 236 aRv.Throw(NS_ERROR_FAILURE); 237 return nullptr; 238 } 239 240 // Now start pumping data to the listener 241 nsresult status; 242 243 rv = listener->OnStartRequest(parserChannel); 244 if (NS_FAILED(rv)) parserChannel->Cancel(rv); 245 parserChannel->GetStatus(&status); 246 247 if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) { 248 rv = listener->OnDataAvailable(parserChannel, stream, 0, aContentLength); 249 if (NS_FAILED(rv)) parserChannel->Cancel(rv); 250 parserChannel->GetStatus(&status); 251 } 252 253 rv = listener->OnStopRequest(parserChannel, status); 254 // Failure returned from OnStopRequest does not affect the final status of 255 // the channel, so we do not need to call Cancel(rv) as we do above. 256 257 if (NS_FAILED(rv)) { 258 aRv.Throw(NS_ERROR_FAILURE); 259 return nullptr; 260 } 261 262 return document.forget(); 263 } 264 265 /*static */ 266 already_AddRefed<DOMParser> DOMParser::Constructor(const GlobalObject& aOwner, 267 ErrorResult& rv) { 268 MOZ_ASSERT(NS_IsMainThread()); 269 nsCOMPtr<nsIPrincipal> docPrincipal = aOwner.GetSubjectPrincipal(); 270 nsCOMPtr<nsIURI> documentURI; 271 if (docPrincipal->IsSystemPrincipal()) { 272 docPrincipal = NullPrincipal::Create(OriginAttributes()); 273 documentURI = docPrincipal->GetURI(); 274 } else { 275 // Grab document URI off the window our constructor was called on. 276 // Error out if anything untoward happens. 277 nsCOMPtr<nsPIDOMWindowInner> window = 278 do_QueryInterface(aOwner.GetAsSupports()); 279 if (!window) { 280 rv.Throw(NS_ERROR_UNEXPECTED); 281 return nullptr; 282 } 283 documentURI = window->GetDocumentURI(); 284 } 285 286 if (!documentURI) { 287 rv.Throw(NS_ERROR_UNEXPECTED); 288 return nullptr; 289 } 290 291 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aOwner.GetAsSupports()); 292 MOZ_ASSERT(global); 293 RefPtr<DOMParser> domParser = 294 new DOMParser(global, docPrincipal, documentURI); 295 return domParser.forget(); 296 } 297 298 // static 299 already_AddRefed<DOMParser> DOMParser::CreateWithoutGlobal(ErrorResult& aRv) { 300 nsCOMPtr<nsIPrincipal> docPrincipal = 301 NullPrincipal::Create(OriginAttributes()); 302 303 nsCOMPtr<nsIURI> documentURI = docPrincipal->GetURI(); 304 if (!documentURI) { 305 aRv.Throw(NS_ERROR_UNEXPECTED); 306 return nullptr; 307 } 308 309 RefPtr<DOMParser> domParser = 310 new DOMParser(nullptr, docPrincipal, documentURI); 311 return domParser.forget(); 312 } 313 314 already_AddRefed<Document> DOMParser::SetUpDocument(DocumentFlavor aFlavor, 315 ErrorResult& aRv) { 316 // We should really just use mOwner here, but Document gets confused 317 // if we pass it a scriptHandlingObject that doesn't QI to 318 // nsIScriptGlobalObject, and test_isequalnode.js (an xpcshell test without 319 // a window global) breaks. The correct solution is just to wean Document off 320 // of nsIScriptGlobalObject, but that's a yak to shave another day. 321 nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject = 322 do_QueryInterface(mOwner); 323 324 // Try to inherit a style backend. 325 NS_ASSERTION(mPrincipal, "Must have principal by now"); 326 NS_ASSERTION(mDocumentURI, "Must have document URI by now"); 327 328 nsCOMPtr<Document> doc; 329 nsresult rv = NS_NewDOMDocument( 330 getter_AddRefs(doc), u""_ns, u""_ns, nullptr, mDocumentURI, mDocumentURI, 331 mPrincipal, LoadedAsData::AsData, scriptHandlingObject, aFlavor); 332 if (NS_WARN_IF(NS_FAILED(rv))) { 333 aRv.Throw(rv); 334 return nullptr; 335 } 336 337 return doc.forget(); 338 }