Bug 1547623 [wpt PR 16386] - [Animation Worklet] Support effects with no target,...
[gecko.git] / parser / htmlparser / nsExpatDriver.cpp
blobdd09ba67d67eb373d3c86ceac279190c2d9d3c52
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsExpatDriver.h"
7 #include "nsCOMPtr.h"
8 #include "nsParserCIID.h"
9 #include "CParserContext.h"
10 #include "nsIExpatSink.h"
11 #include "nsIContentSink.h"
12 #include "nsParserMsgUtils.h"
13 #include "nsIURL.h"
14 #include "nsIUnicharInputStream.h"
15 #include "nsIProtocolHandler.h"
16 #include "nsNetUtil.h"
17 #include "nsTextFormatter.h"
18 #include "nsDirectoryServiceDefs.h"
19 #include "nsCRT.h"
20 #include "nsIConsoleService.h"
21 #include "nsIScriptError.h"
22 #include "nsIContentPolicy.h"
23 #include "nsContentPolicyUtils.h"
24 #include "nsError.h"
25 #include "nsXPCOMCIDInternal.h"
26 #include "nsUnicharInputStream.h"
27 #include "nsContentUtils.h"
28 #include "mozilla/NullPrincipal.h"
30 #include "mozilla/Logging.h"
32 using mozilla::fallible;
33 using mozilla::LogLevel;
34 using mozilla::MakeStringSpan;
35 using mozilla::dom::Document;
37 #define kExpatSeparatorChar 0xFFFF
39 static const char16_t kUTF16[] = {'U', 'T', 'F', '-', '1', '6', '\0'};
41 static mozilla::LazyLogModule gExpatDriverLog("expatdriver");
43 /***************************** EXPAT CALL BACKS ******************************/
44 // The callback handlers that get called from the expat parser.
46 static void Driver_HandleXMLDeclaration(void* aUserData,
47 const XML_Char* aVersion,
48 const XML_Char* aEncoding,
49 int aStandalone) {
50 NS_ASSERTION(aUserData, "expat driver should exist");
51 if (aUserData) {
52 nsExpatDriver* driver = static_cast<nsExpatDriver*>(aUserData);
53 driver->HandleXMLDeclaration(aVersion, aEncoding, aStandalone);
57 static void Driver_HandleCharacterData(void* aUserData, const XML_Char* aData,
58 int aLength) {
59 NS_ASSERTION(aUserData, "expat driver should exist");
60 if (aUserData) {
61 nsExpatDriver* driver = static_cast<nsExpatDriver*>(aUserData);
62 driver->HandleCharacterData(aData, uint32_t(aLength));
66 static void Driver_HandleComment(void* aUserData, const XML_Char* aName) {
67 NS_ASSERTION(aUserData, "expat driver should exist");
68 if (aUserData) {
69 static_cast<nsExpatDriver*>(aUserData)->HandleComment(aName);
73 static void Driver_HandleProcessingInstruction(void* aUserData,
74 const XML_Char* aTarget,
75 const XML_Char* aData) {
76 NS_ASSERTION(aUserData, "expat driver should exist");
77 if (aUserData) {
78 nsExpatDriver* driver = static_cast<nsExpatDriver*>(aUserData);
79 driver->HandleProcessingInstruction(aTarget, aData);
83 static void Driver_HandleDefault(void* aUserData, const XML_Char* aData,
84 int aLength) {
85 NS_ASSERTION(aUserData, "expat driver should exist");
86 if (aUserData) {
87 nsExpatDriver* driver = static_cast<nsExpatDriver*>(aUserData);
88 driver->HandleDefault(aData, uint32_t(aLength));
92 static void Driver_HandleStartCdataSection(void* aUserData) {
93 NS_ASSERTION(aUserData, "expat driver should exist");
94 if (aUserData) {
95 static_cast<nsExpatDriver*>(aUserData)->HandleStartCdataSection();
99 static void Driver_HandleEndCdataSection(void* aUserData) {
100 NS_ASSERTION(aUserData, "expat driver should exist");
101 if (aUserData) {
102 static_cast<nsExpatDriver*>(aUserData)->HandleEndCdataSection();
106 static void Driver_HandleStartDoctypeDecl(void* aUserData,
107 const XML_Char* aDoctypeName,
108 const XML_Char* aSysid,
109 const XML_Char* aPubid,
110 int aHasInternalSubset) {
111 NS_ASSERTION(aUserData, "expat driver should exist");
112 if (aUserData) {
113 static_cast<nsExpatDriver*>(aUserData)->HandleStartDoctypeDecl(
114 aDoctypeName, aSysid, aPubid, !!aHasInternalSubset);
118 static void Driver_HandleEndDoctypeDecl(void* aUserData) {
119 NS_ASSERTION(aUserData, "expat driver should exist");
120 if (aUserData) {
121 static_cast<nsExpatDriver*>(aUserData)->HandleEndDoctypeDecl();
125 static int Driver_HandleExternalEntityRef(void* aExternalEntityRefHandler,
126 const XML_Char* aOpenEntityNames,
127 const XML_Char* aBase,
128 const XML_Char* aSystemId,
129 const XML_Char* aPublicId) {
130 NS_ASSERTION(aExternalEntityRefHandler, "expat driver should exist");
131 if (!aExternalEntityRefHandler) {
132 return 1;
135 nsExpatDriver* driver =
136 static_cast<nsExpatDriver*>(aExternalEntityRefHandler);
138 return driver->HandleExternalEntityRef(aOpenEntityNames, aBase, aSystemId,
139 aPublicId);
142 /***************************** END CALL BACKS ********************************/
144 /***************************** CATALOG UTILS *********************************/
146 // Initially added for bug 113400 to switch from the remote "XHTML 1.0 plus
147 // MathML 2.0" DTD to the the lightweight customized version that Mozilla uses.
148 // Since Mozilla is not validating, no need to fetch a *huge* file at each
149 // click.
150 // XXX The cleanest solution here would be to fix Bug 98413: Implement XML
151 // Catalogs.
152 struct nsCatalogData {
153 const char* mPublicID;
154 const char* mLocalDTD;
155 const char* mAgentSheet;
158 // The order of this table is guestimated to be in the optimum order
159 static const nsCatalogData kCatalogTable[] = {
160 {"-//W3C//DTD XHTML 1.0 Transitional//EN", "htmlmathml-f.ent", nullptr},
161 {"-//W3C//DTD XHTML 1.1//EN", "htmlmathml-f.ent", nullptr},
162 {"-//W3C//DTD XHTML 1.0 Strict//EN", "htmlmathml-f.ent", nullptr},
163 {"-//W3C//DTD XHTML 1.0 Frameset//EN", "htmlmathml-f.ent", nullptr},
164 {"-//W3C//DTD XHTML Basic 1.0//EN", "htmlmathml-f.ent", nullptr},
165 {"-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN", "htmlmathml-f.ent", nullptr},
166 {"-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN",
167 "htmlmathml-f.ent", nullptr},
168 {"-//W3C//DTD MathML 2.0//EN", "htmlmathml-f.ent", nullptr},
169 {"-//WAPFORUM//DTD XHTML Mobile 1.0//EN", "htmlmathml-f.ent", nullptr},
170 {nullptr, nullptr, nullptr}};
172 static const nsCatalogData* LookupCatalogData(const char16_t* aPublicID) {
173 nsDependentString publicID(aPublicID);
175 // linear search for now since the number of entries is going to
176 // be negligible, and the fix for bug 98413 would get rid of this
177 // code anyway
178 const nsCatalogData* data = kCatalogTable;
179 while (data->mPublicID) {
180 if (publicID.EqualsASCII(data->mPublicID)) {
181 return data;
183 ++data;
186 return nullptr;
189 // This function provides a resource URI to a local DTD
190 // in resource://gre/res/dtd/ which may or may not exist.
191 // If aCatalogData is provided, it is used to remap the
192 // DTD instead of taking the filename from the URI.
193 static void GetLocalDTDURI(const nsCatalogData* aCatalogData, nsIURI* aDTD,
194 nsIURI** aResult) {
195 NS_ASSERTION(aDTD, "Null parameter.");
197 nsAutoCString fileName;
198 if (aCatalogData) {
199 // remap the DTD to a known local DTD
200 fileName.Assign(aCatalogData->mLocalDTD);
203 if (fileName.IsEmpty()) {
204 // Try to see if the user has installed the DTD file -- we extract the
205 // filename.ext of the DTD here. Hence, for any DTD for which we have
206 // no predefined mapping, users just have to copy the DTD file to our
207 // special DTD directory and it will be picked.
208 nsCOMPtr<nsIURL> dtdURL = do_QueryInterface(aDTD);
209 if (!dtdURL) {
210 return;
213 dtdURL->GetFileName(fileName);
214 if (fileName.IsEmpty()) {
215 return;
219 nsAutoCString respath("resource://gre/res/dtd/");
220 respath += fileName;
221 NS_NewURI(aResult, respath);
224 /***************************** END CATALOG UTILS *****************************/
226 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsExpatDriver)
227 NS_INTERFACE_MAP_ENTRY(nsITokenizer)
228 NS_INTERFACE_MAP_ENTRY(nsIDTD)
229 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDTD)
230 NS_INTERFACE_MAP_END
232 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsExpatDriver)
233 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsExpatDriver)
235 NS_IMPL_CYCLE_COLLECTION(nsExpatDriver, mSink)
237 nsExpatDriver::nsExpatDriver()
238 : mExpatParser(nullptr),
239 mInCData(false),
240 mInInternalSubset(false),
241 mInExternalDTD(false),
242 mMadeFinalCallToExpat(false),
243 mIsFinalChunk(false),
244 mInternalState(NS_OK),
245 mExpatBuffered(0),
246 mCatalogData(nullptr),
247 mInnerWindowID(0) {}
249 nsExpatDriver::~nsExpatDriver() {
250 if (mExpatParser) {
251 XML_ParserFree(mExpatParser);
255 /* static */
256 void nsExpatDriver::HandleStartElement(void* aUserData, const char16_t* aName,
257 const char16_t** aAtts) {
258 nsExpatDriver* self = static_cast<nsExpatDriver*>(aUserData);
260 NS_ASSERTION(self->mSink, "content sink not found!");
262 // Calculate the total number of elements in aAtts.
263 // XML_GetSpecifiedAttributeCount will only give us the number of specified
264 // attrs (twice that number, actually), so we have to check for default attrs
265 // ourselves.
266 uint32_t attrArrayLength;
267 for (attrArrayLength = XML_GetSpecifiedAttributeCount(self->mExpatParser);
268 aAtts[attrArrayLength]; attrArrayLength += 2) {
269 // Just looping till we find out what the length is
272 if (self->mSink) {
273 nsresult rv = self->mSink->HandleStartElement(
274 aName, aAtts, attrArrayLength,
275 XML_GetCurrentLineNumber(self->mExpatParser),
276 XML_GetCurrentColumnNumber(self->mExpatParser));
277 self->MaybeStopParser(rv);
281 /* static */
282 void nsExpatDriver::HandleStartElementForSystemPrincipal(
283 void* aUserData, const char16_t* aName, const char16_t** aAtts) {
284 nsExpatDriver* self = static_cast<nsExpatDriver*>(aUserData);
286 if (!MOZ_XML_ProcessingEntityValue(self->mExpatParser)) {
287 HandleStartElement(aUserData, aName, aAtts);
288 } else {
289 nsCOMPtr<Document> doc =
290 do_QueryInterface(self->mOriginalSink->GetTarget());
292 // Adjust the column number so that it is one based rather than zero based.
293 uint32_t colNumber = XML_GetCurrentColumnNumber(self->mExpatParser) + 1;
294 uint32_t lineNumber = XML_GetCurrentLineNumber(self->mExpatParser);
296 int32_t nameSpaceID;
297 RefPtr<nsAtom> prefix, localName;
298 nsContentUtils::SplitExpatName(aName, getter_AddRefs(prefix),
299 getter_AddRefs(localName), &nameSpaceID);
301 nsAutoString error;
302 error.AppendLiteral("Ignoring element <");
303 if (prefix) {
304 error.Append(prefix->GetUTF16String());
305 error.Append(':');
307 error.Append(localName->GetUTF16String());
308 error.AppendLiteral("> created from entity value.");
310 nsContentUtils::ReportToConsoleNonLocalized(
311 error, nsIScriptError::warningFlag, NS_LITERAL_CSTRING("XML Document"),
312 doc, nullptr, EmptyString(), lineNumber, colNumber);
316 /* static */
317 void nsExpatDriver::HandleEndElement(void* aUserData, const char16_t* aName) {
318 nsExpatDriver* self = static_cast<nsExpatDriver*>(aUserData);
320 NS_ASSERTION(self->mSink, "content sink not found!");
321 NS_ASSERTION(self->mInternalState != NS_ERROR_HTMLPARSER_BLOCK,
322 "Shouldn't block from HandleStartElement.");
324 if (self->mSink && self->mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) {
325 nsresult rv = self->mSink->HandleEndElement(aName);
326 self->MaybeStopParser(rv);
330 /* static */
331 void nsExpatDriver::HandleEndElementForSystemPrincipal(void* aUserData,
332 const char16_t* aName) {
333 nsExpatDriver* self = static_cast<nsExpatDriver*>(aUserData);
335 if (!MOZ_XML_ProcessingEntityValue(self->mExpatParser)) {
336 HandleEndElement(aUserData, aName);
340 nsresult nsExpatDriver::HandleCharacterData(const char16_t* aValue,
341 const uint32_t aLength) {
342 NS_ASSERTION(mSink, "content sink not found!");
344 if (mInCData) {
345 if (!mCDataText.Append(aValue, aLength, fallible)) {
346 MaybeStopParser(NS_ERROR_OUT_OF_MEMORY);
348 } else if (mSink) {
349 nsresult rv = mSink->HandleCharacterData(aValue, aLength);
350 MaybeStopParser(rv);
353 return NS_OK;
356 nsresult nsExpatDriver::HandleComment(const char16_t* aValue) {
357 NS_ASSERTION(mSink, "content sink not found!");
359 if (mInExternalDTD) {
360 // Ignore comments from external DTDs
361 return NS_OK;
364 if (mInInternalSubset) {
365 mInternalSubset.AppendLiteral("<!--");
366 mInternalSubset.Append(aValue);
367 mInternalSubset.AppendLiteral("-->");
368 } else if (mSink) {
369 nsresult rv = mSink->HandleComment(aValue);
370 MaybeStopParser(rv);
373 return NS_OK;
376 nsresult nsExpatDriver::HandleProcessingInstruction(const char16_t* aTarget,
377 const char16_t* aData) {
378 NS_ASSERTION(mSink, "content sink not found!");
380 if (mInExternalDTD) {
381 // Ignore PIs in external DTDs for now. Eventually we want to
382 // pass them to the sink in a way that doesn't put them in the DOM
383 return NS_OK;
386 if (mInInternalSubset) {
387 mInternalSubset.AppendLiteral("<?");
388 mInternalSubset.Append(aTarget);
389 mInternalSubset.Append(' ');
390 mInternalSubset.Append(aData);
391 mInternalSubset.AppendLiteral("?>");
392 } else if (mSink) {
393 nsresult rv = mSink->HandleProcessingInstruction(aTarget, aData);
394 MaybeStopParser(rv);
397 return NS_OK;
400 nsresult nsExpatDriver::HandleXMLDeclaration(const char16_t* aVersion,
401 const char16_t* aEncoding,
402 int32_t aStandalone) {
403 if (mSink) {
404 nsresult rv = mSink->HandleXMLDeclaration(aVersion, aEncoding, aStandalone);
405 MaybeStopParser(rv);
408 return NS_OK;
411 nsresult nsExpatDriver::HandleDefault(const char16_t* aValue,
412 const uint32_t aLength) {
413 NS_ASSERTION(mSink, "content sink not found!");
415 if (mInExternalDTD) {
416 // Ignore newlines in external DTDs
417 return NS_OK;
420 if (mInInternalSubset) {
421 mInternalSubset.Append(aValue, aLength);
422 } else if (mSink) {
423 uint32_t i;
424 nsresult rv = mInternalState;
425 for (i = 0; i < aLength && NS_SUCCEEDED(rv); ++i) {
426 if (aValue[i] == '\n' || aValue[i] == '\r') {
427 rv = mSink->HandleCharacterData(&aValue[i], 1);
430 MaybeStopParser(rv);
433 return NS_OK;
436 nsresult nsExpatDriver::HandleStartCdataSection() {
437 mInCData = true;
439 return NS_OK;
442 nsresult nsExpatDriver::HandleEndCdataSection() {
443 NS_ASSERTION(mSink, "content sink not found!");
445 mInCData = false;
446 if (mSink) {
447 nsresult rv =
448 mSink->HandleCDataSection(mCDataText.get(), mCDataText.Length());
449 MaybeStopParser(rv);
451 mCDataText.Truncate();
453 return NS_OK;
456 nsresult nsExpatDriver::HandleStartDoctypeDecl(const char16_t* aDoctypeName,
457 const char16_t* aSysid,
458 const char16_t* aPubid,
459 bool aHasInternalSubset) {
460 mDoctypeName = aDoctypeName;
461 mSystemID = aSysid;
462 mPublicID = aPubid;
464 if (aHasInternalSubset) {
465 // Consuming a huge internal subset translates to numerous
466 // allocations. In an effort to avoid too many allocations
467 // setting mInternalSubset's capacity to be 1K ( just a guesstimate! ).
468 mInInternalSubset = true;
469 mInternalSubset.SetCapacity(1024);
470 } else {
471 // Distinguish missing internal subset from an empty one
472 mInternalSubset.SetIsVoid(true);
475 return NS_OK;
478 nsresult nsExpatDriver::HandleEndDoctypeDecl() {
479 NS_ASSERTION(mSink, "content sink not found!");
481 mInInternalSubset = false;
483 if (mSink) {
484 // let the sink know any additional knowledge that we have about the
485 // document (currently, from bug 124570, we only expect to pass additional
486 // agent sheets needed to layout the XML vocabulary of the document)
487 nsCOMPtr<nsIURI> data;
488 #if 0
489 if (mCatalogData && mCatalogData->mAgentSheet) {
490 NS_NewURI(getter_AddRefs(data), mCatalogData->mAgentSheet);
492 #endif
494 // The unused support for "catalog style sheets" was removed. It doesn't
495 // look like we'll ever fix bug 98413 either.
496 MOZ_ASSERT(!mCatalogData || !mCatalogData->mAgentSheet,
497 "Need to add back support for catalog style sheets");
499 // Note: mInternalSubset already doesn't include the [] around it.
500 nsresult rv = mSink->HandleDoctypeDecl(mInternalSubset, mDoctypeName,
501 mSystemID, mPublicID, data);
502 MaybeStopParser(rv);
505 mInternalSubset.Truncate();
507 return NS_OK;
510 static nsresult ExternalDTDStreamReaderFunc(nsIUnicharInputStream* aIn,
511 void* aClosure,
512 const char16_t* aFromSegment,
513 uint32_t aToOffset, uint32_t aCount,
514 uint32_t* aWriteCount) {
515 // Pass the buffer to expat for parsing.
516 if (XML_Parse((XML_Parser)aClosure, (const char*)aFromSegment,
517 aCount * sizeof(char16_t), 0) == XML_STATUS_OK) {
518 *aWriteCount = aCount;
520 return NS_OK;
523 *aWriteCount = 0;
525 return NS_ERROR_FAILURE;
528 int nsExpatDriver::HandleExternalEntityRef(const char16_t* openEntityNames,
529 const char16_t* base,
530 const char16_t* systemId,
531 const char16_t* publicId) {
532 if (mInInternalSubset && !mInExternalDTD && openEntityNames) {
533 mInternalSubset.Append(char16_t('%'));
534 mInternalSubset.Append(nsDependentString(openEntityNames));
535 mInternalSubset.Append(char16_t(';'));
538 // Load the external entity into a buffer.
539 nsCOMPtr<nsIInputStream> in;
540 nsAutoString absURL;
541 nsresult rv = OpenInputStreamFromExternalDTD(publicId, systemId, base,
542 getter_AddRefs(in), absURL);
543 if (NS_FAILED(rv)) {
544 #ifdef DEBUG
545 nsCString message("Failed to open external DTD: publicId \"");
546 AppendUTF16toUTF8(MakeStringSpan(publicId), message);
547 message += "\" systemId \"";
548 AppendUTF16toUTF8(MakeStringSpan(systemId), message);
549 message += "\" base \"";
550 AppendUTF16toUTF8(MakeStringSpan(base), message);
551 message += "\" URL \"";
552 AppendUTF16toUTF8(absURL, message);
553 message += "\"";
554 NS_WARNING(message.get());
555 #endif
556 return 1;
559 nsCOMPtr<nsIUnicharInputStream> uniIn;
560 rv = NS_NewUnicharInputStream(in, getter_AddRefs(uniIn));
561 NS_ENSURE_SUCCESS(rv, 1);
563 int result = 1;
564 if (uniIn) {
565 XML_Parser entParser =
566 XML_ExternalEntityParserCreate(mExpatParser, 0, kUTF16);
567 if (entParser) {
568 XML_SetBase(entParser, absURL.get());
570 mInExternalDTD = true;
572 uint32_t totalRead;
573 do {
574 rv = uniIn->ReadSegments(ExternalDTDStreamReaderFunc, entParser,
575 uint32_t(-1), &totalRead);
576 } while (NS_SUCCEEDED(rv) && totalRead > 0);
578 result = XML_Parse(entParser, nullptr, 0, 1);
580 mInExternalDTD = false;
582 XML_ParserFree(entParser);
586 return result;
589 nsresult nsExpatDriver::OpenInputStreamFromExternalDTD(const char16_t* aFPIStr,
590 const char16_t* aURLStr,
591 const char16_t* aBaseURL,
592 nsIInputStream** aStream,
593 nsAString& aAbsURL) {
594 nsCOMPtr<nsIURI> baseURI;
595 nsresult rv =
596 NS_NewURI(getter_AddRefs(baseURI), NS_ConvertUTF16toUTF8(aBaseURL));
597 NS_ENSURE_SUCCESS(rv, rv);
599 nsCOMPtr<nsIURI> uri;
600 rv = NS_NewURI(getter_AddRefs(uri), NS_ConvertUTF16toUTF8(aURLStr), nullptr,
601 baseURI);
602 NS_ENSURE_SUCCESS(rv, rv);
604 // make sure the URI is allowed to be loaded in sync
605 bool isUIResource = false;
606 rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
607 &isUIResource);
608 NS_ENSURE_SUCCESS(rv, rv);
610 nsCOMPtr<nsIURI> localURI;
611 if (!isUIResource) {
612 // Check to see if we can map the DTD to a known local DTD, or if a DTD
613 // file of the same name exists in the special DTD directory
614 if (aFPIStr) {
615 // see if the Formal Public Identifier (FPI) maps to a catalog entry
616 mCatalogData = LookupCatalogData(aFPIStr);
617 GetLocalDTDURI(mCatalogData, uri, getter_AddRefs(localURI));
619 if (!localURI) {
620 return NS_ERROR_NOT_IMPLEMENTED;
624 nsCOMPtr<nsIChannel> channel;
625 if (localURI) {
626 localURI.swap(uri);
627 rv = NS_NewChannel(getter_AddRefs(channel), uri,
628 nsContentUtils::GetSystemPrincipal(),
629 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
630 nsIContentPolicy::TYPE_DTD);
631 } else {
632 NS_ASSERTION(
633 mSink == nsCOMPtr<nsIExpatSink>(do_QueryInterface(mOriginalSink)),
634 "In nsExpatDriver::OpenInputStreamFromExternalDTD: "
635 "mOriginalSink not the same object as mSink?");
636 nsCOMPtr<nsIPrincipal> loadingPrincipal;
637 if (mOriginalSink) {
638 nsCOMPtr<Document> doc;
639 doc = do_QueryInterface(mOriginalSink->GetTarget());
640 if (doc) {
641 loadingPrincipal = doc->NodePrincipal();
644 if (!loadingPrincipal) {
645 loadingPrincipal =
646 mozilla::NullPrincipal::CreateWithoutOriginAttributes();
648 rv = NS_NewChannel(getter_AddRefs(channel), uri, loadingPrincipal,
649 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
650 nsILoadInfo::SEC_ALLOW_CHROME,
651 nsIContentPolicy::TYPE_DTD);
653 NS_ENSURE_SUCCESS(rv, rv);
655 nsAutoCString absURL;
656 rv = uri->GetSpec(absURL);
657 NS_ENSURE_SUCCESS(rv, rv);
658 CopyUTF8toUTF16(absURL, aAbsURL);
660 channel->SetContentType(NS_LITERAL_CSTRING("application/xml"));
661 return channel->Open(aStream);
664 static nsresult CreateErrorText(const char16_t* aDescription,
665 const char16_t* aSourceURL,
666 const uint32_t aLineNumber,
667 const uint32_t aColNumber,
668 nsString& aErrorString) {
669 aErrorString.Truncate();
671 nsAutoString msg;
672 nsresult rv = nsParserMsgUtils::GetLocalizedStringByName(
673 XMLPARSER_PROPERTIES, "XMLParsingError", msg);
674 NS_ENSURE_SUCCESS(rv, rv);
676 // XML Parsing Error: %1$S\nLocation: %2$S\nLine Number %3$u, Column %4$u:
677 nsTextFormatter::ssprintf(aErrorString, msg.get(), aDescription, aSourceURL,
678 aLineNumber, aColNumber);
679 return NS_OK;
682 static nsresult AppendErrorPointer(const int32_t aColNumber,
683 const char16_t* aSourceLine,
684 nsString& aSourceString) {
685 aSourceString.Append(char16_t('\n'));
687 // Last character will be '^'.
688 int32_t last = aColNumber - 1;
689 int32_t i;
690 uint32_t minuses = 0;
691 for (i = 0; i < last; ++i) {
692 if (aSourceLine[i] == '\t') {
693 // Since this uses |white-space: pre;| a tab stop equals 8 spaces.
694 uint32_t add = 8 - (minuses % 8);
695 aSourceString.AppendASCII("--------", add);
696 minuses += add;
697 } else {
698 aSourceString.Append(char16_t('-'));
699 ++minuses;
702 aSourceString.Append(char16_t('^'));
704 return NS_OK;
707 nsresult nsExpatDriver::HandleError() {
708 int32_t code = XML_GetErrorCode(mExpatParser);
709 NS_ASSERTION(code > XML_ERROR_NONE, "unexpected XML error code");
711 // Map Expat error code to an error string
712 // XXX Deal with error returns.
713 nsAutoString description;
714 nsParserMsgUtils::GetLocalizedStringByID(XMLPARSER_PROPERTIES, code,
715 description);
717 if (code == XML_ERROR_TAG_MISMATCH) {
719 * Expat can send the following:
720 * localName
721 * namespaceURI<separator>localName
722 * namespaceURI<separator>localName<separator>prefix
724 * and we use 0xFFFF for the <separator>.
727 const char16_t* mismatch = MOZ_XML_GetMismatchedTag(mExpatParser);
728 const char16_t* uriEnd = nullptr;
729 const char16_t* nameEnd = nullptr;
730 const char16_t* pos;
731 for (pos = mismatch; *pos; ++pos) {
732 if (*pos == kExpatSeparatorChar) {
733 if (uriEnd) {
734 nameEnd = pos;
735 } else {
736 uriEnd = pos;
741 nsAutoString tagName;
742 if (uriEnd && nameEnd) {
743 // We have a prefix.
744 tagName.Append(nameEnd + 1, pos - nameEnd - 1);
745 tagName.Append(char16_t(':'));
747 const char16_t* nameStart = uriEnd ? uriEnd + 1 : mismatch;
748 tagName.Append(nameStart, (nameEnd ? nameEnd : pos) - nameStart);
750 nsAutoString msg;
751 nsParserMsgUtils::GetLocalizedStringByName(XMLPARSER_PROPERTIES, "Expected",
752 msg);
754 // . Expected: </%S>.
755 nsAutoString message;
756 nsTextFormatter::ssprintf(message, msg.get(), tagName.get());
757 description.Append(message);
760 // Adjust the column number so that it is one based rather than zero based.
761 uint32_t colNumber = XML_GetCurrentColumnNumber(mExpatParser) + 1;
762 uint32_t lineNumber = XML_GetCurrentLineNumber(mExpatParser);
764 nsAutoString errorText;
765 CreateErrorText(description.get(), XML_GetBase(mExpatParser), lineNumber,
766 colNumber, errorText);
768 nsAutoString sourceText(mLastLine);
769 AppendErrorPointer(colNumber, mLastLine.get(), sourceText);
771 // Try to create and initialize the script error.
772 nsCOMPtr<nsIScriptError> serr(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
773 nsresult rv = NS_ERROR_FAILURE;
774 if (serr) {
775 rv = serr->InitWithWindowID(errorText, mURISpec, mLastLine, lineNumber,
776 colNumber, nsIScriptError::errorFlag,
777 "malformed-xml", mInnerWindowID);
780 // If it didn't initialize, we can't do any logging.
781 bool shouldReportError = NS_SUCCEEDED(rv);
783 // mSink might be null here if our parser was terminated.
784 if (mSink && shouldReportError) {
785 rv = mSink->ReportError(errorText.get(), sourceText.get(), serr,
786 &shouldReportError);
787 if (NS_FAILED(rv)) {
788 shouldReportError = true;
792 // mOriginalSink might be null here if our parser was terminated.
793 if (mOriginalSink) {
794 nsCOMPtr<Document> doc = do_QueryInterface(mOriginalSink->GetTarget());
795 if (doc && doc->SuppressParserErrorConsoleMessages()) {
796 shouldReportError = false;
800 if (shouldReportError) {
801 nsCOMPtr<nsIConsoleService> cs(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
802 if (cs) {
803 cs->LogMessage(serr);
807 return NS_ERROR_HTMLPARSER_STOPPARSING;
810 void nsExpatDriver::ParseBuffer(const char16_t* aBuffer, uint32_t aLength,
811 bool aIsFinal, uint32_t* aConsumed) {
812 NS_ASSERTION((aBuffer && aLength != 0) || (!aBuffer && aLength == 0), "?");
813 NS_ASSERTION(mInternalState != NS_OK || aIsFinal || aBuffer,
814 "Useless call, we won't call Expat");
815 MOZ_ASSERT(!BlockedOrInterrupted() || !aBuffer,
816 "Non-null buffer when resuming");
817 MOZ_ASSERT(XML_GetCurrentByteIndex(mExpatParser) % sizeof(char16_t) == 0,
818 "Consumed part of a char16_t?");
820 if (mExpatParser && (mInternalState == NS_OK || BlockedOrInterrupted())) {
821 int32_t parserBytesBefore = XML_GetCurrentByteIndex(mExpatParser);
822 NS_ASSERTION(parserBytesBefore >= 0, "Unexpected value");
824 XML_Status status;
825 if (BlockedOrInterrupted()) {
826 mInternalState = NS_OK; // Resume in case we're blocked.
827 status = XML_ResumeParser(mExpatParser);
828 } else {
829 status = XML_Parse(mExpatParser, reinterpret_cast<const char*>(aBuffer),
830 aLength * sizeof(char16_t), aIsFinal);
833 int32_t parserBytesConsumed = XML_GetCurrentByteIndex(mExpatParser);
835 NS_ASSERTION(parserBytesConsumed >= 0, "Unexpected value");
836 NS_ASSERTION(parserBytesConsumed >= parserBytesBefore,
837 "How'd this happen?");
838 NS_ASSERTION(parserBytesConsumed % sizeof(char16_t) == 0,
839 "Consumed part of a char16_t?");
841 // Consumed something.
842 *aConsumed = (parserBytesConsumed - parserBytesBefore) / sizeof(char16_t);
843 NS_ASSERTION(*aConsumed <= aLength + mExpatBuffered,
844 "Too many bytes consumed?");
846 NS_ASSERTION(status != XML_STATUS_SUSPENDED || BlockedOrInterrupted(),
847 "Inconsistent expat suspension state.");
849 if (status == XML_STATUS_ERROR) {
850 mInternalState = NS_ERROR_HTMLPARSER_STOPPARSING;
852 } else {
853 *aConsumed = 0;
857 NS_IMETHODIMP
858 nsExpatDriver::ConsumeToken(nsScanner& aScanner, bool& aFlushTokens) {
859 // We keep the scanner pointing to the position where Expat will start
860 // parsing.
861 nsScannerIterator currentExpatPosition;
862 aScanner.CurrentPosition(currentExpatPosition);
864 // This is the start of the first buffer that we need to pass to Expat.
865 nsScannerIterator start = currentExpatPosition;
866 start.advance(mExpatBuffered);
868 // This is the end of the last buffer (at this point, more data could come in
869 // later).
870 nsScannerIterator end;
871 aScanner.EndReading(end);
873 MOZ_LOG(gExpatDriverLog, LogLevel::Debug,
874 ("Remaining in expat's buffer: %i, remaining in scanner: %zu.",
875 mExpatBuffered, Distance(start, end)));
877 // We want to call Expat if we have more buffers, or if we know there won't
878 // be more buffers (and so we want to flush the remaining data), or if we're
879 // currently blocked and there's data in Expat's buffer.
880 while (start != end || (mIsFinalChunk && !mMadeFinalCallToExpat) ||
881 (BlockedOrInterrupted() && mExpatBuffered > 0)) {
882 bool noMoreBuffers = start == end && mIsFinalChunk;
883 bool blocked = BlockedOrInterrupted();
885 const char16_t* buffer;
886 uint32_t length;
887 if (blocked || noMoreBuffers) {
888 // If we're blocked we just resume Expat so we don't need a buffer, if
889 // there aren't any more buffers we pass a null buffer to Expat.
890 buffer = nullptr;
891 length = 0;
893 if (blocked) {
894 MOZ_LOG(
895 gExpatDriverLog, LogLevel::Debug,
896 ("Resuming Expat, will parse data remaining in Expat's "
897 "buffer.\nContent of Expat's buffer:\n-----\n%s\n-----\n",
898 NS_ConvertUTF16toUTF8(currentExpatPosition.get(), mExpatBuffered)
899 .get()));
900 } else {
901 NS_ASSERTION(mExpatBuffered == Distance(currentExpatPosition, end),
902 "Didn't pass all the data to Expat?");
903 MOZ_LOG(
904 gExpatDriverLog, LogLevel::Debug,
905 ("Last call to Expat, will parse data remaining in Expat's "
906 "buffer.\nContent of Expat's buffer:\n-----\n%s\n-----\n",
907 NS_ConvertUTF16toUTF8(currentExpatPosition.get(), mExpatBuffered)
908 .get()));
910 } else {
911 buffer = start.get();
912 length = uint32_t(start.size_forward());
914 MOZ_LOG(gExpatDriverLog, LogLevel::Debug,
915 ("Calling Expat, will parse data remaining in Expat's buffer and "
916 "new data.\nContent of Expat's buffer:\n-----\n%s\n-----\nNew "
917 "data:\n-----\n%s\n-----\n",
918 NS_ConvertUTF16toUTF8(currentExpatPosition.get(), mExpatBuffered)
919 .get(),
920 NS_ConvertUTF16toUTF8(start.get(), length).get()));
923 uint32_t consumed;
924 ParseBuffer(buffer, length, noMoreBuffers, &consumed);
925 if (consumed > 0) {
926 nsScannerIterator oldExpatPosition = currentExpatPosition;
927 currentExpatPosition.advance(consumed);
929 // We consumed some data, we want to store the last line of data that
930 // was consumed in case we run into an error (to show the line in which
931 // the error occurred).
933 // The length of the last line that Expat has parsed.
934 XML_Size lastLineLength = XML_GetCurrentColumnNumber(mExpatParser);
936 if (lastLineLength <= consumed) {
937 // The length of the last line was less than what expat consumed, so
938 // there was at least one line break in the consumed data. Store the
939 // last line until the point where we stopped parsing.
940 nsScannerIterator startLastLine = currentExpatPosition;
941 startLastLine.advance(-((ptrdiff_t)lastLineLength));
942 if (!CopyUnicodeTo(startLastLine, currentExpatPosition, mLastLine)) {
943 return (mInternalState = NS_ERROR_OUT_OF_MEMORY);
945 } else {
946 // There was no line break in the consumed data, append the consumed
947 // data.
948 if (!AppendUnicodeTo(oldExpatPosition, currentExpatPosition,
949 mLastLine)) {
950 return (mInternalState = NS_ERROR_OUT_OF_MEMORY);
955 mExpatBuffered += length - consumed;
957 if (BlockedOrInterrupted()) {
958 MOZ_LOG(gExpatDriverLog, LogLevel::Debug,
959 ("Blocked or interrupted parser (probably for loading linked "
960 "stylesheets or scripts)."));
962 aScanner.SetPosition(currentExpatPosition, true);
963 aScanner.Mark();
965 return mInternalState;
968 if (noMoreBuffers && mExpatBuffered == 0) {
969 mMadeFinalCallToExpat = true;
972 if (NS_FAILED(mInternalState)) {
973 if (XML_GetErrorCode(mExpatParser) != XML_ERROR_NONE) {
974 NS_ASSERTION(mInternalState == NS_ERROR_HTMLPARSER_STOPPARSING,
975 "Unexpected error");
977 // Look for the next newline after the last one we consumed
978 nsScannerIterator lastLine = currentExpatPosition;
979 while (lastLine != end) {
980 length = uint32_t(lastLine.size_forward());
981 uint32_t endOffset = 0;
982 const char16_t* buffer = lastLine.get();
983 while (endOffset < length && buffer[endOffset] != '\n' &&
984 buffer[endOffset] != '\r') {
985 ++endOffset;
987 mLastLine.Append(Substring(buffer, buffer + endOffset));
988 if (endOffset < length) {
989 // We found a newline.
990 break;
993 lastLine.advance(length);
996 HandleError();
999 return mInternalState;
1002 // Either we have more buffers, or we were blocked (and we'll flush in the
1003 // next iteration), or we should have emptied Expat's buffer.
1004 NS_ASSERTION(!noMoreBuffers || blocked ||
1005 (mExpatBuffered == 0 && currentExpatPosition == end),
1006 "Unreachable data left in Expat's buffer");
1008 start.advance(length);
1010 // It's possible for start to have passed end if we received more data
1011 // (e.g. if we spun the event loop in an inline script). Reload end now
1012 // to compensate.
1013 aScanner.EndReading(end);
1016 aScanner.SetPosition(currentExpatPosition, true);
1017 aScanner.Mark();
1019 MOZ_LOG(gExpatDriverLog, LogLevel::Debug,
1020 ("Remaining in expat's buffer: %i, remaining in scanner: %zu.",
1021 mExpatBuffered, Distance(currentExpatPosition, end)));
1023 return NS_SUCCEEDED(mInternalState) ? NS_ERROR_HTMLPARSER_EOF : NS_OK;
1026 NS_IMETHODIMP
1027 nsExpatDriver::WillBuildModel(const CParserContext& aParserContext,
1028 nsITokenizer* aTokenizer, nsIContentSink* aSink) {
1029 mSink = do_QueryInterface(aSink);
1030 if (!mSink) {
1031 NS_ERROR("nsExpatDriver didn't get an nsIExpatSink");
1032 // Make sure future calls to us bail out as needed
1033 mInternalState = NS_ERROR_UNEXPECTED;
1034 return mInternalState;
1037 mOriginalSink = aSink;
1039 static const XML_Memory_Handling_Suite memsuite = {malloc, realloc, free};
1041 static const char16_t kExpatSeparator[] = {kExpatSeparatorChar, '\0'};
1043 mExpatParser = XML_ParserCreate_MM(kUTF16, &memsuite, kExpatSeparator);
1044 NS_ENSURE_TRUE(mExpatParser, NS_ERROR_FAILURE);
1046 XML_SetReturnNSTriplet(mExpatParser, XML_TRUE);
1048 #ifdef XML_DTD
1049 XML_SetParamEntityParsing(mExpatParser, XML_PARAM_ENTITY_PARSING_ALWAYS);
1050 #endif
1052 mURISpec = aParserContext.mScanner->GetFilename();
1054 XML_SetBase(mExpatParser, mURISpec.get());
1056 nsCOMPtr<Document> doc = do_QueryInterface(mOriginalSink->GetTarget());
1057 if (doc) {
1058 nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
1059 nsCOMPtr<nsPIDOMWindowInner> inner;
1060 if (win) {
1061 inner = win->GetCurrentInnerWindow();
1062 } else {
1063 bool aHasHadScriptHandlingObject;
1064 nsIScriptGlobalObject* global =
1065 doc->GetScriptHandlingObject(aHasHadScriptHandlingObject);
1066 if (global) {
1067 inner = do_QueryInterface(global);
1070 if (inner) {
1071 mInnerWindowID = inner->WindowID();
1075 // Set up the callbacks
1076 XML_SetXmlDeclHandler(mExpatParser, Driver_HandleXMLDeclaration);
1077 if (doc && doc->NodePrincipal()->IsSystemPrincipal()) {
1078 XML_SetElementHandler(mExpatParser, HandleStartElementForSystemPrincipal,
1079 HandleEndElementForSystemPrincipal);
1080 } else {
1081 XML_SetElementHandler(mExpatParser, HandleStartElement, HandleEndElement);
1083 XML_SetCharacterDataHandler(mExpatParser, Driver_HandleCharacterData);
1084 XML_SetProcessingInstructionHandler(mExpatParser,
1085 Driver_HandleProcessingInstruction);
1086 XML_SetDefaultHandlerExpand(mExpatParser, Driver_HandleDefault);
1087 XML_SetExternalEntityRefHandler(
1088 mExpatParser,
1089 (XML_ExternalEntityRefHandler)Driver_HandleExternalEntityRef);
1090 XML_SetExternalEntityRefHandlerArg(mExpatParser, this);
1091 XML_SetCommentHandler(mExpatParser, Driver_HandleComment);
1092 XML_SetCdataSectionHandler(mExpatParser, Driver_HandleStartCdataSection,
1093 Driver_HandleEndCdataSection);
1095 XML_SetParamEntityParsing(mExpatParser,
1096 XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE);
1097 XML_SetDoctypeDeclHandler(mExpatParser, Driver_HandleStartDoctypeDecl,
1098 Driver_HandleEndDoctypeDecl);
1100 // Set up the user data.
1101 XML_SetUserData(mExpatParser, this);
1103 return mInternalState;
1106 NS_IMETHODIMP
1107 nsExpatDriver::BuildModel(nsITokenizer* aTokenizer, nsIContentSink* aSink) {
1108 return mInternalState;
1111 NS_IMETHODIMP
1112 nsExpatDriver::DidBuildModel(nsresult anErrorCode) {
1113 mOriginalSink = nullptr;
1114 mSink = nullptr;
1115 return NS_OK;
1118 NS_IMETHODIMP
1119 nsExpatDriver::WillTokenize(bool aIsFinalChunk) {
1120 mIsFinalChunk = aIsFinalChunk;
1121 return NS_OK;
1124 NS_IMETHODIMP_(void)
1125 nsExpatDriver::Terminate() {
1126 // XXX - not sure what happens to the unparsed data.
1127 if (mExpatParser) {
1128 XML_StopParser(mExpatParser, XML_FALSE);
1130 mInternalState = NS_ERROR_HTMLPARSER_STOPPARSING;
1133 NS_IMETHODIMP_(int32_t)
1134 nsExpatDriver::GetType() { return NS_IPARSER_FLAG_XML; }
1136 NS_IMETHODIMP_(nsDTDMode)
1137 nsExpatDriver::GetMode() const { return eDTDMode_full_standards; }
1139 /*************************** Unused methods **********************************/
1141 NS_IMETHODIMP_(bool)
1142 nsExpatDriver::IsContainer(int32_t aTag) const { return true; }
1144 NS_IMETHODIMP_(bool)
1145 nsExpatDriver::CanContain(int32_t aParent, int32_t aChild) const {
1146 return true;
1149 void nsExpatDriver::MaybeStopParser(nsresult aState) {
1150 if (NS_FAILED(aState)) {
1151 // If we had a failure we want to override NS_ERROR_HTMLPARSER_INTERRUPTED
1152 // and we want to override NS_ERROR_HTMLPARSER_BLOCK but not with
1153 // NS_ERROR_HTMLPARSER_INTERRUPTED.
1154 if (NS_SUCCEEDED(mInternalState) ||
1155 mInternalState == NS_ERROR_HTMLPARSER_INTERRUPTED ||
1156 (mInternalState == NS_ERROR_HTMLPARSER_BLOCK &&
1157 aState != NS_ERROR_HTMLPARSER_INTERRUPTED)) {
1158 mInternalState = (aState == NS_ERROR_HTMLPARSER_INTERRUPTED ||
1159 aState == NS_ERROR_HTMLPARSER_BLOCK)
1160 ? aState
1161 : NS_ERROR_HTMLPARSER_STOPPARSING;
1164 // If we get an error then we need to stop Expat (by calling XML_StopParser
1165 // with false as the last argument). If the parser should be blocked or
1166 // interrupted we need to pause Expat (by calling XML_StopParser with
1167 // true as the last argument).
1168 XML_StopParser(mExpatParser, BlockedOrInterrupted());
1169 } else if (NS_SUCCEEDED(mInternalState)) {
1170 // Only clobber mInternalState with the success code if we didn't block or
1171 // interrupt before.
1172 mInternalState = aState;