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"
8 #include "nsParserCIID.h"
9 #include "CParserContext.h"
10 #include "nsIExpatSink.h"
11 #include "nsIContentSink.h"
12 #include "nsIDocShell.h"
13 #include "nsParserMsgUtils.h"
15 #include "nsIUnicharInputStream.h"
16 #include "nsIProtocolHandler.h"
17 #include "nsNetUtil.h"
18 #include "nsTextFormatter.h"
19 #include "nsDirectoryServiceDefs.h"
21 #include "nsIConsoleService.h"
22 #include "nsIScriptError.h"
23 #include "nsIScriptGlobalObject.h"
24 #include "nsIContentPolicy.h"
25 #include "nsComponentManagerUtils.h"
26 #include "nsContentPolicyUtils.h"
28 #include "nsXPCOMCIDInternal.h"
29 #include "nsUnicharInputStream.h"
30 #include "nsContentUtils.h"
31 #include "mozilla/BasePrincipal.h"
32 #include "mozilla/IntegerTypeTraits.h"
33 #include "mozilla/NullPrincipal.h"
34 #include "mozilla/Telemetry.h"
35 #include "mozilla/TelemetryComms.h"
37 #include "mozilla/Logging.h"
39 using mozilla::fallible
;
40 using mozilla::LogLevel
;
41 using mozilla::MakeStringSpan
;
42 using mozilla::dom::Document
;
44 #define kExpatSeparatorChar 0xFFFF
46 static const char16_t kUTF16
[] = {'U', 'T', 'F', '-', '1', '6', '\0'};
48 static mozilla::LazyLogModule
gExpatDriverLog("expatdriver");
50 // Use the same maximum tree depth as Chromium (see
51 // https://chromium.googlesource.com/chromium/src/+/f464165c1dedff1c955d3c051c5a9a1c6a0e8f6b/third_party/WebKit/Source/core/xml/parser/XMLDocumentParser.cpp#85).
52 static const uint16_t sMaxXMLTreeDepth
= 5000;
54 /***************************** EXPAT CALL BACKS ******************************/
55 // The callback handlers that get called from the expat parser.
57 static void Driver_HandleXMLDeclaration(void* aUserData
,
58 const XML_Char
* aVersion
,
59 const XML_Char
* aEncoding
,
61 NS_ASSERTION(aUserData
, "expat driver should exist");
63 nsExpatDriver
* driver
= static_cast<nsExpatDriver
*>(aUserData
);
64 driver
->HandleXMLDeclaration(aVersion
, aEncoding
, aStandalone
);
68 static void Driver_HandleCharacterData(void* aUserData
, const XML_Char
* aData
,
70 NS_ASSERTION(aUserData
, "expat driver should exist");
72 nsExpatDriver
* driver
= static_cast<nsExpatDriver
*>(aUserData
);
73 driver
->HandleCharacterData(aData
, uint32_t(aLength
));
77 static void Driver_HandleComment(void* aUserData
, const XML_Char
* aName
) {
78 NS_ASSERTION(aUserData
, "expat driver should exist");
80 static_cast<nsExpatDriver
*>(aUserData
)->HandleComment(aName
);
84 static void Driver_HandleProcessingInstruction(void* aUserData
,
85 const XML_Char
* aTarget
,
86 const XML_Char
* aData
) {
87 NS_ASSERTION(aUserData
, "expat driver should exist");
89 nsExpatDriver
* driver
= static_cast<nsExpatDriver
*>(aUserData
);
90 driver
->HandleProcessingInstruction(aTarget
, aData
);
94 static void Driver_HandleDefault(void* aUserData
, const XML_Char
* aData
,
96 NS_ASSERTION(aUserData
, "expat driver should exist");
98 nsExpatDriver
* driver
= static_cast<nsExpatDriver
*>(aUserData
);
99 driver
->HandleDefault(aData
, uint32_t(aLength
));
103 static void Driver_HandleStartCdataSection(void* aUserData
) {
104 NS_ASSERTION(aUserData
, "expat driver should exist");
106 static_cast<nsExpatDriver
*>(aUserData
)->HandleStartCdataSection();
110 static void Driver_HandleEndCdataSection(void* aUserData
) {
111 NS_ASSERTION(aUserData
, "expat driver should exist");
113 static_cast<nsExpatDriver
*>(aUserData
)->HandleEndCdataSection();
117 static void Driver_HandleStartDoctypeDecl(void* aUserData
,
118 const XML_Char
* aDoctypeName
,
119 const XML_Char
* aSysid
,
120 const XML_Char
* aPubid
,
121 int aHasInternalSubset
) {
122 NS_ASSERTION(aUserData
, "expat driver should exist");
124 static_cast<nsExpatDriver
*>(aUserData
)->HandleStartDoctypeDecl(
125 aDoctypeName
, aSysid
, aPubid
, !!aHasInternalSubset
);
129 static void Driver_HandleEndDoctypeDecl(void* aUserData
) {
130 NS_ASSERTION(aUserData
, "expat driver should exist");
132 static_cast<nsExpatDriver
*>(aUserData
)->HandleEndDoctypeDecl();
136 static int Driver_HandleExternalEntityRef(void* aExternalEntityRefHandler
,
137 const XML_Char
* aOpenEntityNames
,
138 const XML_Char
* aBase
,
139 const XML_Char
* aSystemId
,
140 const XML_Char
* aPublicId
) {
141 NS_ASSERTION(aExternalEntityRefHandler
, "expat driver should exist");
142 if (!aExternalEntityRefHandler
) {
146 nsExpatDriver
* driver
=
147 static_cast<nsExpatDriver
*>(aExternalEntityRefHandler
);
149 return driver
->HandleExternalEntityRef(aOpenEntityNames
, aBase
, aSystemId
,
153 /***************************** END CALL BACKS ********************************/
155 /***************************** CATALOG UTILS *********************************/
157 // Initially added for bug 113400 to switch from the remote "XHTML 1.0 plus
158 // MathML 2.0" DTD to the the lightweight customized version that Mozilla uses.
159 // Since Mozilla is not validating, no need to fetch a *huge* file at each
161 // XXX The cleanest solution here would be to fix Bug 98413: Implement XML
163 struct nsCatalogData
{
164 const char* mPublicID
;
165 const char* mLocalDTD
;
166 const char* mAgentSheet
;
169 // The order of this table is guestimated to be in the optimum order
170 static const nsCatalogData kCatalogTable
[] = {
171 {"-//W3C//DTD XHTML 1.0 Transitional//EN", "htmlmathml-f.ent", nullptr},
172 {"-//W3C//DTD XHTML 1.1//EN", "htmlmathml-f.ent", nullptr},
173 {"-//W3C//DTD XHTML 1.0 Strict//EN", "htmlmathml-f.ent", nullptr},
174 {"-//W3C//DTD XHTML 1.0 Frameset//EN", "htmlmathml-f.ent", nullptr},
175 {"-//W3C//DTD XHTML Basic 1.0//EN", "htmlmathml-f.ent", nullptr},
176 {"-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN", "htmlmathml-f.ent", nullptr},
177 {"-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN",
178 "htmlmathml-f.ent", nullptr},
179 {"-//W3C//DTD MathML 2.0//EN", "htmlmathml-f.ent", nullptr},
180 {"-//WAPFORUM//DTD XHTML Mobile 1.0//EN", "htmlmathml-f.ent", nullptr},
181 {nullptr, nullptr, nullptr}};
183 static const nsCatalogData
* LookupCatalogData(const char16_t
* aPublicID
) {
184 nsDependentString
publicID(aPublicID
);
186 // linear search for now since the number of entries is going to
187 // be negligible, and the fix for bug 98413 would get rid of this
189 const nsCatalogData
* data
= kCatalogTable
;
190 while (data
->mPublicID
) {
191 if (publicID
.EqualsASCII(data
->mPublicID
)) {
200 // This function provides a resource URI to a local DTD
201 // in resource://gre/res/dtd/ which may or may not exist.
202 // If aCatalogData is provided, it is used to remap the
203 // DTD instead of taking the filename from the URI. aDTD
204 // may be null in some cases that are relying on
205 // aCatalogData working for them.
206 static void GetLocalDTDURI(const nsCatalogData
* aCatalogData
, nsIURI
* aDTD
,
208 nsAutoCString fileName
;
210 // remap the DTD to a known local DTD
211 fileName
.Assign(aCatalogData
->mLocalDTD
);
214 if (fileName
.IsEmpty()) {
215 // Try to see if the user has installed the DTD file -- we extract the
216 // filename.ext of the DTD here. Hence, for any DTD for which we have
217 // no predefined mapping, users just have to copy the DTD file to our
218 // special DTD directory and it will be picked.
219 nsCOMPtr
<nsIURL
> dtdURL
= do_QueryInterface(aDTD
);
221 // Not a URL with a filename, or maybe it was null. Either way, nothing
222 // else we can do here.
226 dtdURL
->GetFileName(fileName
);
227 if (fileName
.IsEmpty()) {
232 nsAutoCString
respath("resource://gre/res/dtd/");
234 NS_NewURI(aResult
, respath
);
237 /***************************** END CATALOG UTILS *****************************/
239 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsExpatDriver
)
240 NS_INTERFACE_MAP_ENTRY(nsITokenizer
)
241 NS_INTERFACE_MAP_ENTRY(nsIDTD
)
242 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIDTD
)
245 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsExpatDriver
)
246 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsExpatDriver
)
248 NS_IMPL_CYCLE_COLLECTION(nsExpatDriver
, mSink
)
250 nsExpatDriver::nsExpatDriver()
251 : mExpatParser(nullptr),
253 mInInternalSubset(false),
254 mInExternalDTD(false),
255 mMadeFinalCallToExpat(false),
256 mIsFinalChunk(false),
257 mInternalState(NS_OK
),
260 mCatalogData(nullptr),
263 nsExpatDriver::~nsExpatDriver() {
265 XML_ParserFree(mExpatParser
);
270 void nsExpatDriver::HandleStartElement(void* aUserData
, const char16_t
* aName
,
271 const char16_t
** aAtts
) {
272 nsExpatDriver
* self
= static_cast<nsExpatDriver
*>(aUserData
);
274 NS_ASSERTION(self
->mSink
, "content sink not found!");
276 // Calculate the total number of elements in aAtts.
277 // XML_GetSpecifiedAttributeCount will only give us the number of specified
278 // attrs (twice that number, actually), so we have to check for default attrs
280 uint32_t attrArrayLength
;
281 for (attrArrayLength
= XML_GetSpecifiedAttributeCount(self
->mExpatParser
);
282 aAtts
[attrArrayLength
]; attrArrayLength
+= 2) {
283 // Just looping till we find out what the length is
287 // We store the tagdepth in a PRUint16, so make sure the limit fits in a
291 std::numeric_limits
<decltype(nsExpatDriver::mTagDepth
)>::max());
293 if (++self
->mTagDepth
> sMaxXMLTreeDepth
) {
294 self
->MaybeStopParser(NS_ERROR_HTMLPARSER_HIERARCHYTOODEEP
);
298 nsresult rv
= self
->mSink
->HandleStartElement(
299 aName
, aAtts
, attrArrayLength
,
300 XML_GetCurrentLineNumber(self
->mExpatParser
),
301 XML_GetCurrentColumnNumber(self
->mExpatParser
));
302 self
->MaybeStopParser(rv
);
307 void nsExpatDriver::HandleStartElementForSystemPrincipal(
308 void* aUserData
, const char16_t
* aName
, const char16_t
** aAtts
) {
309 nsExpatDriver
* self
= static_cast<nsExpatDriver
*>(aUserData
);
311 if (!MOZ_XML_ProcessingEntityValue(self
->mExpatParser
)) {
312 HandleStartElement(aUserData
, aName
, aAtts
);
314 nsCOMPtr
<Document
> doc
=
315 do_QueryInterface(self
->mOriginalSink
->GetTarget());
317 // Adjust the column number so that it is one based rather than zero based.
318 uint32_t colNumber
= XML_GetCurrentColumnNumber(self
->mExpatParser
) + 1;
319 uint32_t lineNumber
= XML_GetCurrentLineNumber(self
->mExpatParser
);
322 RefPtr
<nsAtom
> prefix
, localName
;
323 nsContentUtils::SplitExpatName(aName
, getter_AddRefs(prefix
),
324 getter_AddRefs(localName
), &nameSpaceID
);
327 error
.AppendLiteral("Ignoring element <");
329 error
.Append(prefix
->GetUTF16String());
332 error
.Append(localName
->GetUTF16String());
333 error
.AppendLiteral("> created from entity value.");
335 nsContentUtils::ReportToConsoleNonLocalized(
336 error
, nsIScriptError::warningFlag
, "XML Document"_ns
, doc
, nullptr,
337 u
""_ns
, lineNumber
, colNumber
);
342 void nsExpatDriver::HandleEndElement(void* aUserData
, const char16_t
* aName
) {
343 nsExpatDriver
* self
= static_cast<nsExpatDriver
*>(aUserData
);
345 NS_ASSERTION(self
->mSink
, "content sink not found!");
346 NS_ASSERTION(self
->mInternalState
!= NS_ERROR_HTMLPARSER_BLOCK
,
347 "Shouldn't block from HandleStartElement.");
349 if (self
->mSink
&& self
->mInternalState
!= NS_ERROR_HTMLPARSER_STOPPARSING
) {
350 nsresult rv
= self
->mSink
->HandleEndElement(aName
);
352 self
->MaybeStopParser(rv
);
357 void nsExpatDriver::HandleEndElementForSystemPrincipal(void* aUserData
,
358 const char16_t
* aName
) {
359 nsExpatDriver
* self
= static_cast<nsExpatDriver
*>(aUserData
);
361 if (!MOZ_XML_ProcessingEntityValue(self
->mExpatParser
)) {
362 HandleEndElement(aUserData
, aName
);
366 nsresult
nsExpatDriver::HandleCharacterData(const char16_t
* aValue
,
367 const uint32_t aLength
) {
368 NS_ASSERTION(mSink
, "content sink not found!");
371 if (!mCDataText
.Append(aValue
, aLength
, fallible
)) {
372 MaybeStopParser(NS_ERROR_OUT_OF_MEMORY
);
375 nsresult rv
= mSink
->HandleCharacterData(aValue
, aLength
);
382 nsresult
nsExpatDriver::HandleComment(const char16_t
* aValue
) {
383 NS_ASSERTION(mSink
, "content sink not found!");
385 if (mInExternalDTD
) {
386 // Ignore comments from external DTDs
390 if (mInInternalSubset
) {
391 mInternalSubset
.AppendLiteral("<!--");
392 mInternalSubset
.Append(aValue
);
393 mInternalSubset
.AppendLiteral("-->");
395 nsresult rv
= mSink
->HandleComment(aValue
);
402 nsresult
nsExpatDriver::HandleProcessingInstruction(const char16_t
* aTarget
,
403 const char16_t
* aData
) {
404 NS_ASSERTION(mSink
, "content sink not found!");
406 if (mInExternalDTD
) {
407 // Ignore PIs in external DTDs for now. Eventually we want to
408 // pass them to the sink in a way that doesn't put them in the DOM
412 if (mInInternalSubset
) {
413 mInternalSubset
.AppendLiteral("<?");
414 mInternalSubset
.Append(aTarget
);
415 mInternalSubset
.Append(' ');
416 mInternalSubset
.Append(aData
);
417 mInternalSubset
.AppendLiteral("?>");
419 nsresult rv
= mSink
->HandleProcessingInstruction(aTarget
, aData
);
426 nsresult
nsExpatDriver::HandleXMLDeclaration(const char16_t
* aVersion
,
427 const char16_t
* aEncoding
,
428 int32_t aStandalone
) {
430 nsresult rv
= mSink
->HandleXMLDeclaration(aVersion
, aEncoding
, aStandalone
);
437 nsresult
nsExpatDriver::HandleDefault(const char16_t
* aValue
,
438 const uint32_t aLength
) {
439 NS_ASSERTION(mSink
, "content sink not found!");
441 if (mInExternalDTD
) {
442 // Ignore newlines in external DTDs
446 if (mInInternalSubset
) {
447 mInternalSubset
.Append(aValue
, aLength
);
450 nsresult rv
= mInternalState
;
451 for (i
= 0; i
< aLength
&& NS_SUCCEEDED(rv
); ++i
) {
452 if (aValue
[i
] == '\n' || aValue
[i
] == '\r') {
453 rv
= mSink
->HandleCharacterData(&aValue
[i
], 1);
462 nsresult
nsExpatDriver::HandleStartCdataSection() {
468 nsresult
nsExpatDriver::HandleEndCdataSection() {
469 NS_ASSERTION(mSink
, "content sink not found!");
474 mSink
->HandleCDataSection(mCDataText
.get(), mCDataText
.Length());
477 mCDataText
.Truncate();
482 nsresult
nsExpatDriver::HandleStartDoctypeDecl(const char16_t
* aDoctypeName
,
483 const char16_t
* aSysid
,
484 const char16_t
* aPubid
,
485 bool aHasInternalSubset
) {
486 mDoctypeName
= aDoctypeName
;
490 if (aHasInternalSubset
) {
491 // Consuming a huge internal subset translates to numerous
492 // allocations. In an effort to avoid too many allocations
493 // setting mInternalSubset's capacity to be 1K ( just a guesstimate! ).
494 mInInternalSubset
= true;
495 mInternalSubset
.SetCapacity(1024);
497 // Distinguish missing internal subset from an empty one
498 mInternalSubset
.SetIsVoid(true);
504 nsresult
nsExpatDriver::HandleEndDoctypeDecl() {
505 NS_ASSERTION(mSink
, "content sink not found!");
507 mInInternalSubset
= false;
510 // let the sink know any additional knowledge that we have about the
511 // document (currently, from bug 124570, we only expect to pass additional
512 // agent sheets needed to layout the XML vocabulary of the document)
513 nsCOMPtr
<nsIURI
> data
;
515 if (mCatalogData
&& mCatalogData
->mAgentSheet
) {
516 NS_NewURI(getter_AddRefs(data
), mCatalogData
->mAgentSheet
);
520 // The unused support for "catalog style sheets" was removed. It doesn't
521 // look like we'll ever fix bug 98413 either.
522 MOZ_ASSERT(!mCatalogData
|| !mCatalogData
->mAgentSheet
,
523 "Need to add back support for catalog style sheets");
525 // Note: mInternalSubset already doesn't include the [] around it.
526 nsresult rv
= mSink
->HandleDoctypeDecl(mInternalSubset
, mDoctypeName
,
527 mSystemID
, mPublicID
, data
);
531 mInternalSubset
.Truncate();
536 static nsresult
ExternalDTDStreamReaderFunc(nsIUnicharInputStream
* aIn
,
538 const char16_t
* aFromSegment
,
539 uint32_t aToOffset
, uint32_t aCount
,
540 uint32_t* aWriteCount
) {
541 // Pass the buffer to expat for parsing.
542 if (XML_Parse((XML_Parser
)aClosure
, (const char*)aFromSegment
,
543 aCount
* sizeof(char16_t
), 0) == XML_STATUS_OK
) {
544 *aWriteCount
= aCount
;
551 return NS_ERROR_FAILURE
;
554 int nsExpatDriver::HandleExternalEntityRef(const char16_t
* openEntityNames
,
555 const char16_t
* base
,
556 const char16_t
* systemId
,
557 const char16_t
* publicId
) {
558 if (mInInternalSubset
&& !mInExternalDTD
&& openEntityNames
) {
559 mInternalSubset
.Append(char16_t('%'));
560 mInternalSubset
.Append(nsDependentString(openEntityNames
));
561 mInternalSubset
.Append(char16_t(';'));
564 // Load the external entity into a buffer.
565 nsCOMPtr
<nsIInputStream
> in
;
567 nsresult rv
= OpenInputStreamFromExternalDTD(publicId
, systemId
, base
,
568 getter_AddRefs(in
), absURL
);
571 nsCString
message("Failed to open external DTD: publicId \"");
572 AppendUTF16toUTF8(MakeStringSpan(publicId
), message
);
573 message
+= "\" systemId \"";
574 AppendUTF16toUTF8(MakeStringSpan(systemId
), message
);
575 message
+= "\" base \"";
576 AppendUTF16toUTF8(MakeStringSpan(base
), message
);
577 message
+= "\" URL \"";
578 AppendUTF16toUTF8(absURL
, message
);
580 NS_WARNING(message
.get());
585 nsCOMPtr
<nsIUnicharInputStream
> uniIn
;
586 rv
= NS_NewUnicharInputStream(in
, getter_AddRefs(uniIn
));
587 NS_ENSURE_SUCCESS(rv
, 1);
591 XML_Parser entParser
=
592 XML_ExternalEntityParserCreate(mExpatParser
, 0, kUTF16
);
594 XML_SetBase(entParser
, absURL
.get());
596 mInExternalDTD
= true;
600 rv
= uniIn
->ReadSegments(ExternalDTDStreamReaderFunc
, entParser
,
601 uint32_t(-1), &totalRead
);
602 } while (NS_SUCCEEDED(rv
) && totalRead
> 0);
604 result
= XML_Parse(entParser
, nullptr, 0, 1);
606 mInExternalDTD
= false;
608 XML_ParserFree(entParser
);
615 nsresult
nsExpatDriver::OpenInputStreamFromExternalDTD(const char16_t
* aFPIStr
,
616 const char16_t
* aURLStr
,
617 const char16_t
* aBaseURL
,
618 nsIInputStream
** aStream
,
619 nsAString
& aAbsURL
) {
620 nsCOMPtr
<nsIURI
> baseURI
;
622 NS_NewURI(getter_AddRefs(baseURI
), NS_ConvertUTF16toUTF8(aBaseURL
));
623 NS_ENSURE_SUCCESS(rv
, rv
);
625 nsCOMPtr
<nsIURI
> uri
;
626 rv
= NS_NewURI(getter_AddRefs(uri
), NS_ConvertUTF16toUTF8(aURLStr
), nullptr,
628 // Even if the URI is malformed (most likely because we have a
629 // non-hierarchical base URI and a relative DTD URI, with the latter
630 // being the normal XHTML DTD case), we can try to see whether we
631 // have catalog data for aFPIStr.
632 if (NS_WARN_IF(NS_FAILED(rv
) && rv
!= NS_ERROR_MALFORMED_URI
)) {
636 // make sure the URI, if we have one, is allowed to be loaded in sync
637 bool isUIResource
= false;
639 rv
= NS_URIChainHasFlags(uri
, nsIProtocolHandler::URI_IS_UI_RESOURCE
,
641 NS_ENSURE_SUCCESS(rv
, rv
);
644 nsCOMPtr
<nsIURI
> localURI
;
646 // Check to see if we can map the DTD to a known local DTD, or if a DTD
647 // file of the same name exists in the special DTD directory
649 // see if the Formal Public Identifier (FPI) maps to a catalog entry
650 mCatalogData
= LookupCatalogData(aFPIStr
);
651 GetLocalDTDURI(mCatalogData
, uri
, getter_AddRefs(localURI
));
654 return NS_ERROR_NOT_IMPLEMENTED
;
658 nsCOMPtr
<nsIChannel
> channel
;
661 rv
= NS_NewChannel(getter_AddRefs(channel
), uri
,
662 nsContentUtils::GetSystemPrincipal(),
663 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
664 nsIContentPolicy::TYPE_DTD
);
665 NS_ENSURE_SUCCESS(rv
, rv
);
668 mSink
== nsCOMPtr
<nsIExpatSink
>(do_QueryInterface(mOriginalSink
)),
669 "In nsExpatDriver::OpenInputStreamFromExternalDTD: "
670 "mOriginalSink not the same object as mSink?");
671 nsContentPolicyType policyType
= nsIContentPolicy::TYPE_INTERNAL_DTD
;
673 nsCOMPtr
<Document
> doc
;
674 doc
= do_QueryInterface(mOriginalSink
->GetTarget());
676 if (doc
->SkipDTDSecurityChecks()) {
677 policyType
= nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD
;
680 getter_AddRefs(channel
), uri
, doc
,
681 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT
|
682 nsILoadInfo::SEC_ALLOW_CHROME
,
684 NS_ENSURE_SUCCESS(rv
, rv
);
688 nsCOMPtr
<nsIPrincipal
> nullPrincipal
=
689 mozilla::NullPrincipal::CreateWithoutOriginAttributes();
691 getter_AddRefs(channel
), uri
, nullPrincipal
,
692 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT
|
693 nsILoadInfo::SEC_ALLOW_CHROME
,
695 NS_ENSURE_SUCCESS(rv
, rv
);
699 nsAutoCString absURL
;
700 rv
= uri
->GetSpec(absURL
);
701 NS_ENSURE_SUCCESS(rv
, rv
);
702 CopyUTF8toUTF16(absURL
, aAbsURL
);
704 channel
->SetContentType("application/xml"_ns
);
705 return channel
->Open(aStream
);
708 static nsresult
CreateErrorText(const char16_t
* aDescription
,
709 const char16_t
* aSourceURL
,
710 const uint32_t aLineNumber
,
711 const uint32_t aColNumber
,
712 nsString
& aErrorString
, bool spoofEnglish
) {
713 aErrorString
.Truncate();
716 nsresult rv
= nsParserMsgUtils::GetLocalizedStringByName(
717 spoofEnglish
? XMLPARSER_PROPERTIES_en_US
: XMLPARSER_PROPERTIES
,
718 "XMLParsingError", msg
);
719 NS_ENSURE_SUCCESS(rv
, rv
);
721 // XML Parsing Error: %1$S\nLocation: %2$S\nLine Number %3$u, Column %4$u:
722 nsTextFormatter::ssprintf(aErrorString
, msg
.get(), aDescription
, aSourceURL
,
723 aLineNumber
, aColNumber
);
727 static nsresult
AppendErrorPointer(const int32_t aColNumber
,
728 const char16_t
* aSourceLine
,
729 nsString
& aSourceString
) {
730 aSourceString
.Append(char16_t('\n'));
732 // Last character will be '^'.
733 int32_t last
= aColNumber
- 1;
735 uint32_t minuses
= 0;
736 for (i
= 0; i
< last
; ++i
) {
737 if (aSourceLine
[i
] == '\t') {
738 // Since this uses |white-space: pre;| a tab stop equals 8 spaces.
739 uint32_t add
= 8 - (minuses
% 8);
740 aSourceString
.AppendASCII("--------", add
);
743 aSourceString
.Append(char16_t('-'));
747 aSourceString
.Append(char16_t('^'));
752 nsresult
nsExpatDriver::HandleError() {
753 int32_t code
= XML_GetErrorCode(mExpatParser
);
754 NS_ASSERTION(code
> XML_ERROR_NONE
, "unexpected XML error code");
756 // Map Expat error code to an error string
757 // XXX Deal with error returns.
758 nsAutoString description
;
759 nsCOMPtr
<Document
> doc
;
761 doc
= do_QueryInterface(mOriginalSink
->GetTarget());
765 nsContentUtils::SpoofLocaleEnglish() && (!doc
|| !doc
->AllowsL10n());
766 nsParserMsgUtils::GetLocalizedStringByID(
767 spoofEnglish
? XMLPARSER_PROPERTIES_en_US
: XMLPARSER_PROPERTIES
, code
,
770 if (code
== XML_ERROR_TAG_MISMATCH
) {
772 * Expat can send the following:
774 * namespaceURI<separator>localName
775 * namespaceURI<separator>localName<separator>prefix
777 * and we use 0xFFFF for the <separator>.
780 const char16_t
* mismatch
= MOZ_XML_GetMismatchedTag(mExpatParser
);
781 const char16_t
* uriEnd
= nullptr;
782 const char16_t
* nameEnd
= nullptr;
784 for (pos
= mismatch
; *pos
; ++pos
) {
785 if (*pos
== kExpatSeparatorChar
) {
794 nsAutoString tagName
;
795 if (uriEnd
&& nameEnd
) {
797 tagName
.Append(nameEnd
+ 1, pos
- nameEnd
- 1);
798 tagName
.Append(char16_t(':'));
800 const char16_t
* nameStart
= uriEnd
? uriEnd
+ 1 : mismatch
;
801 tagName
.Append(nameStart
, (nameEnd
? nameEnd
: pos
) - nameStart
);
804 nsParserMsgUtils::GetLocalizedStringByName(
805 spoofEnglish
? XMLPARSER_PROPERTIES_en_US
: XMLPARSER_PROPERTIES
,
808 // . Expected: </%S>.
809 nsAutoString message
;
810 nsTextFormatter::ssprintf(message
, msg
.get(), tagName
.get());
811 description
.Append(message
);
814 // Adjust the column number so that it is one based rather than zero based.
815 uint32_t colNumber
= XML_GetCurrentColumnNumber(mExpatParser
) + 1;
816 uint32_t lineNumber
= XML_GetCurrentLineNumber(mExpatParser
);
818 nsAutoString errorText
;
819 CreateErrorText(description
.get(), XML_GetBase(mExpatParser
), lineNumber
,
820 colNumber
, errorText
, spoofEnglish
);
822 nsAutoString
sourceText(mLastLine
);
823 AppendErrorPointer(colNumber
, mLastLine
.get(), sourceText
);
825 if (doc
&& nsContentUtils::IsChromeDoc(doc
)) {
826 nsCString path
= doc
->GetDocumentURI()->GetSpecOrDefault();
827 nsCOMPtr
<nsISupports
> container
= doc
->GetContainer();
828 nsCOMPtr
<nsIDocShell
> docShell
= do_QueryInterface(container
);
829 nsCString
docShellDestroyed("unknown"_ns
);
831 bool destroyed
= false;
832 docShell
->IsBeingDestroyed(&destroyed
);
833 docShellDestroyed
.Assign(destroyed
? "true"_ns
: "false"_ns
);
836 mozilla::Maybe
<nsTArray
<mozilla::Telemetry::EventExtraEntry
>> extra
=
837 mozilla::Some
<nsTArray
<mozilla::Telemetry::EventExtraEntry
>>({
838 mozilla::Telemetry::EventExtraEntry
{"error_code"_ns
,
839 nsPrintfCString("%u", code
)},
840 mozilla::Telemetry::EventExtraEntry
{
841 "location"_ns
, nsPrintfCString("%u:%u", lineNumber
, colNumber
)},
842 mozilla::Telemetry::EventExtraEntry
{
843 "last_line"_ns
, NS_ConvertUTF16toUTF8(mLastLine
)},
844 mozilla::Telemetry::EventExtraEntry
{
845 "last_line_len"_ns
, nsPrintfCString("%u", mLastLine
.Length())},
846 mozilla::Telemetry::EventExtraEntry
{
847 "hidden"_ns
, doc
->Hidden() ? "true"_ns
: "false"_ns
},
848 mozilla::Telemetry::EventExtraEntry
{"destroyed"_ns
,
852 mozilla::Telemetry::SetEventRecordingEnabled("ysod"_ns
, true);
853 mozilla::Telemetry::RecordEvent(
854 mozilla::Telemetry::EventID::Ysod_Shown_Ysod
, mozilla::Some(path
),
858 // Try to create and initialize the script error.
859 nsCOMPtr
<nsIScriptError
> serr(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID
));
860 nsresult rv
= NS_ERROR_FAILURE
;
862 rv
= serr
->InitWithWindowID(errorText
, mURISpec
, mLastLine
, lineNumber
,
863 colNumber
, nsIScriptError::errorFlag
,
864 "malformed-xml", mInnerWindowID
);
867 // If it didn't initialize, we can't do any logging.
868 bool shouldReportError
= NS_SUCCEEDED(rv
);
870 // mSink might be null here if our parser was terminated.
871 if (mSink
&& shouldReportError
) {
872 rv
= mSink
->ReportError(errorText
.get(), sourceText
.get(), serr
,
875 shouldReportError
= true;
879 // mOriginalSink might be null here if our parser was terminated.
881 nsCOMPtr
<Document
> doc
= do_QueryInterface(mOriginalSink
->GetTarget());
882 if (doc
&& doc
->SuppressParserErrorConsoleMessages()) {
883 shouldReportError
= false;
887 if (shouldReportError
) {
888 nsCOMPtr
<nsIConsoleService
> cs(do_GetService(NS_CONSOLESERVICE_CONTRACTID
));
890 cs
->LogMessage(serr
);
894 return NS_ERROR_HTMLPARSER_STOPPARSING
;
897 void nsExpatDriver::ParseBuffer(const char16_t
* aBuffer
, uint32_t aLength
,
898 bool aIsFinal
, uint32_t* aConsumed
) {
899 NS_ASSERTION((aBuffer
&& aLength
!= 0) || (!aBuffer
&& aLength
== 0), "?");
900 NS_ASSERTION(mInternalState
!= NS_OK
|| aIsFinal
|| aBuffer
,
901 "Useless call, we won't call Expat");
902 MOZ_ASSERT(!BlockedOrInterrupted() || !aBuffer
,
903 "Non-null buffer when resuming");
904 MOZ_ASSERT(XML_GetCurrentByteIndex(mExpatParser
) % sizeof(char16_t
) == 0,
905 "Consumed part of a char16_t?");
907 if (mExpatParser
&& (mInternalState
== NS_OK
|| BlockedOrInterrupted())) {
908 int32_t parserBytesBefore
= XML_GetCurrentByteIndex(mExpatParser
);
909 NS_ASSERTION(parserBytesBefore
>= 0, "Unexpected value");
912 if (BlockedOrInterrupted()) {
913 mInternalState
= NS_OK
; // Resume in case we're blocked.
914 status
= XML_ResumeParser(mExpatParser
);
916 status
= XML_Parse(mExpatParser
, reinterpret_cast<const char*>(aBuffer
),
917 aLength
* sizeof(char16_t
), aIsFinal
);
920 int32_t parserBytesConsumed
= XML_GetCurrentByteIndex(mExpatParser
);
922 NS_ASSERTION(parserBytesConsumed
>= 0, "Unexpected value");
923 NS_ASSERTION(parserBytesConsumed
>= parserBytesBefore
,
924 "How'd this happen?");
925 NS_ASSERTION(parserBytesConsumed
% sizeof(char16_t
) == 0,
926 "Consumed part of a char16_t?");
928 // Consumed something.
929 *aConsumed
= (parserBytesConsumed
- parserBytesBefore
) / sizeof(char16_t
);
930 NS_ASSERTION(*aConsumed
<= aLength
+ mExpatBuffered
,
931 "Too many bytes consumed?");
933 NS_ASSERTION(status
!= XML_STATUS_SUSPENDED
|| BlockedOrInterrupted(),
934 "Inconsistent expat suspension state.");
936 if (status
== XML_STATUS_ERROR
) {
937 mInternalState
= NS_ERROR_HTMLPARSER_STOPPARSING
;
945 nsExpatDriver::ConsumeToken(nsScanner
& aScanner
, bool& aFlushTokens
) {
946 // We keep the scanner pointing to the position where Expat will start
948 nsScannerIterator currentExpatPosition
;
949 aScanner
.CurrentPosition(currentExpatPosition
);
951 // This is the start of the first buffer that we need to pass to Expat.
952 nsScannerIterator start
= currentExpatPosition
;
953 start
.advance(mExpatBuffered
);
955 // This is the end of the last buffer (at this point, more data could come in
957 nsScannerIterator end
;
958 aScanner
.EndReading(end
);
960 MOZ_LOG(gExpatDriverLog
, LogLevel::Debug
,
961 ("Remaining in expat's buffer: %i, remaining in scanner: %zu.",
962 mExpatBuffered
, Distance(start
, end
)));
964 // We want to call Expat if we have more buffers, or if we know there won't
965 // be more buffers (and so we want to flush the remaining data), or if we're
966 // currently blocked and there's data in Expat's buffer.
967 while (start
!= end
|| (mIsFinalChunk
&& !mMadeFinalCallToExpat
) ||
968 (BlockedOrInterrupted() && mExpatBuffered
> 0)) {
969 bool noMoreBuffers
= start
== end
&& mIsFinalChunk
;
970 bool blocked
= BlockedOrInterrupted();
972 const char16_t
* buffer
;
974 if (blocked
|| noMoreBuffers
) {
975 // If we're blocked we just resume Expat so we don't need a buffer, if
976 // there aren't any more buffers we pass a null buffer to Expat.
982 gExpatDriverLog
, LogLevel::Debug
,
983 ("Resuming Expat, will parse data remaining in Expat's "
984 "buffer.\nContent of Expat's buffer:\n-----\n%s\n-----\n",
985 NS_ConvertUTF16toUTF8(currentExpatPosition
.get(), mExpatBuffered
)
988 NS_ASSERTION(mExpatBuffered
== Distance(currentExpatPosition
, end
),
989 "Didn't pass all the data to Expat?");
991 gExpatDriverLog
, LogLevel::Debug
,
992 ("Last call to Expat, will parse data remaining in Expat's "
993 "buffer.\nContent of Expat's buffer:\n-----\n%s\n-----\n",
994 NS_ConvertUTF16toUTF8(currentExpatPosition
.get(), mExpatBuffered
)
998 buffer
= start
.get();
999 length
= uint32_t(start
.size_forward());
1001 MOZ_LOG(gExpatDriverLog
, LogLevel::Debug
,
1002 ("Calling Expat, will parse data remaining in Expat's buffer and "
1003 "new data.\nContent of Expat's buffer:\n-----\n%s\n-----\nNew "
1004 "data:\n-----\n%s\n-----\n",
1005 NS_ConvertUTF16toUTF8(currentExpatPosition
.get(), mExpatBuffered
)
1007 NS_ConvertUTF16toUTF8(start
.get(), length
).get()));
1011 ParseBuffer(buffer
, length
, noMoreBuffers
, &consumed
);
1013 nsScannerIterator oldExpatPosition
= currentExpatPosition
;
1014 currentExpatPosition
.advance(consumed
);
1016 // We consumed some data, we want to store the last line of data that
1017 // was consumed in case we run into an error (to show the line in which
1018 // the error occurred).
1020 // The length of the last line that Expat has parsed.
1021 XML_Size lastLineLength
= XML_GetCurrentColumnNumber(mExpatParser
);
1023 if (lastLineLength
<= consumed
) {
1024 // The length of the last line was less than what expat consumed, so
1025 // there was at least one line break in the consumed data. Store the
1026 // last line until the point where we stopped parsing.
1027 nsScannerIterator startLastLine
= currentExpatPosition
;
1028 startLastLine
.advance(-((ptrdiff_t)lastLineLength
));
1029 if (!CopyUnicodeTo(startLastLine
, currentExpatPosition
, mLastLine
)) {
1030 return (mInternalState
= NS_ERROR_OUT_OF_MEMORY
);
1033 // There was no line break in the consumed data, append the consumed
1035 if (!AppendUnicodeTo(oldExpatPosition
, currentExpatPosition
,
1037 return (mInternalState
= NS_ERROR_OUT_OF_MEMORY
);
1042 mExpatBuffered
+= length
- consumed
;
1044 if (BlockedOrInterrupted()) {
1045 MOZ_LOG(gExpatDriverLog
, LogLevel::Debug
,
1046 ("Blocked or interrupted parser (probably for loading linked "
1047 "stylesheets or scripts)."));
1049 aScanner
.SetPosition(currentExpatPosition
, true);
1052 return mInternalState
;
1055 if (noMoreBuffers
&& mExpatBuffered
== 0) {
1056 mMadeFinalCallToExpat
= true;
1059 if (NS_FAILED(mInternalState
)) {
1060 if (XML_GetErrorCode(mExpatParser
) != XML_ERROR_NONE
) {
1061 NS_ASSERTION(mInternalState
== NS_ERROR_HTMLPARSER_STOPPARSING
,
1062 "Unexpected error");
1064 // Look for the next newline after the last one we consumed
1065 nsScannerIterator lastLine
= currentExpatPosition
;
1066 while (lastLine
!= end
) {
1067 length
= uint32_t(lastLine
.size_forward());
1068 uint32_t endOffset
= 0;
1069 const char16_t
* buffer
= lastLine
.get();
1070 while (endOffset
< length
&& buffer
[endOffset
] != '\n' &&
1071 buffer
[endOffset
] != '\r') {
1074 mLastLine
.Append(Substring(buffer
, buffer
+ endOffset
));
1075 if (endOffset
< length
) {
1076 // We found a newline.
1080 lastLine
.advance(length
);
1086 return mInternalState
;
1089 // Either we have more buffers, or we were blocked (and we'll flush in the
1090 // next iteration), or we should have emptied Expat's buffer.
1091 NS_ASSERTION(!noMoreBuffers
|| blocked
||
1092 (mExpatBuffered
== 0 && currentExpatPosition
== end
),
1093 "Unreachable data left in Expat's buffer");
1095 start
.advance(length
);
1097 // It's possible for start to have passed end if we received more data
1098 // (e.g. if we spun the event loop in an inline script). Reload end now
1100 aScanner
.EndReading(end
);
1103 aScanner
.SetPosition(currentExpatPosition
, true);
1106 MOZ_LOG(gExpatDriverLog
, LogLevel::Debug
,
1107 ("Remaining in expat's buffer: %i, remaining in scanner: %zu.",
1108 mExpatBuffered
, Distance(currentExpatPosition
, end
)));
1110 return NS_SUCCEEDED(mInternalState
) ? NS_ERROR_HTMLPARSER_EOF
: NS_OK
;
1114 nsExpatDriver::WillBuildModel(const CParserContext
& aParserContext
,
1115 nsITokenizer
* aTokenizer
, nsIContentSink
* aSink
) {
1116 mSink
= do_QueryInterface(aSink
);
1118 NS_ERROR("nsExpatDriver didn't get an nsIExpatSink");
1119 // Make sure future calls to us bail out as needed
1120 mInternalState
= NS_ERROR_UNEXPECTED
;
1121 return mInternalState
;
1124 mOriginalSink
= aSink
;
1126 static const XML_Memory_Handling_Suite memsuite
= {malloc
, realloc
, free
};
1128 static const char16_t kExpatSeparator
[] = {kExpatSeparatorChar
, '\0'};
1130 mExpatParser
= XML_ParserCreate_MM(kUTF16
, &memsuite
, kExpatSeparator
);
1131 NS_ENSURE_TRUE(mExpatParser
, NS_ERROR_FAILURE
);
1133 XML_SetReturnNSTriplet(mExpatParser
, XML_TRUE
);
1136 XML_SetParamEntityParsing(mExpatParser
, XML_PARAM_ENTITY_PARSING_ALWAYS
);
1139 mURISpec
= aParserContext
.mScanner
->GetFilename();
1141 XML_SetBase(mExpatParser
, mURISpec
.get());
1143 nsCOMPtr
<Document
> doc
= do_QueryInterface(mOriginalSink
->GetTarget());
1145 nsCOMPtr
<nsPIDOMWindowOuter
> win
= doc
->GetWindow();
1146 nsCOMPtr
<nsPIDOMWindowInner
> inner
;
1148 inner
= win
->GetCurrentInnerWindow();
1150 bool aHasHadScriptHandlingObject
;
1151 nsIScriptGlobalObject
* global
=
1152 doc
->GetScriptHandlingObject(aHasHadScriptHandlingObject
);
1154 inner
= do_QueryInterface(global
);
1158 mInnerWindowID
= inner
->WindowID();
1162 // Set up the callbacks
1163 XML_SetXmlDeclHandler(mExpatParser
, Driver_HandleXMLDeclaration
);
1164 if (doc
&& doc
->NodePrincipal()->IsSystemPrincipal()) {
1165 XML_SetElementHandler(mExpatParser
, HandleStartElementForSystemPrincipal
,
1166 HandleEndElementForSystemPrincipal
);
1168 XML_SetElementHandler(mExpatParser
, HandleStartElement
, HandleEndElement
);
1170 XML_SetCharacterDataHandler(mExpatParser
, Driver_HandleCharacterData
);
1171 XML_SetProcessingInstructionHandler(mExpatParser
,
1172 Driver_HandleProcessingInstruction
);
1173 XML_SetDefaultHandlerExpand(mExpatParser
, Driver_HandleDefault
);
1174 XML_SetExternalEntityRefHandler(
1176 (XML_ExternalEntityRefHandler
)Driver_HandleExternalEntityRef
);
1177 XML_SetExternalEntityRefHandlerArg(mExpatParser
, this);
1178 XML_SetCommentHandler(mExpatParser
, Driver_HandleComment
);
1179 XML_SetCdataSectionHandler(mExpatParser
, Driver_HandleStartCdataSection
,
1180 Driver_HandleEndCdataSection
);
1182 XML_SetParamEntityParsing(mExpatParser
,
1183 XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE
);
1184 XML_SetDoctypeDeclHandler(mExpatParser
, Driver_HandleStartDoctypeDecl
,
1185 Driver_HandleEndDoctypeDecl
);
1187 // Set up the user data.
1188 XML_SetUserData(mExpatParser
, this);
1190 return mInternalState
;
1194 nsExpatDriver::BuildModel(nsITokenizer
* aTokenizer
, nsIContentSink
* aSink
) {
1195 return mInternalState
;
1199 nsExpatDriver::DidBuildModel(nsresult anErrorCode
) {
1200 mOriginalSink
= nullptr;
1206 nsExpatDriver::WillTokenize(bool aIsFinalChunk
) {
1207 mIsFinalChunk
= aIsFinalChunk
;
1211 NS_IMETHODIMP_(void)
1212 nsExpatDriver::Terminate() {
1213 // XXX - not sure what happens to the unparsed data.
1215 XML_StopParser(mExpatParser
, XML_FALSE
);
1217 mInternalState
= NS_ERROR_HTMLPARSER_STOPPARSING
;
1220 NS_IMETHODIMP_(int32_t)
1221 nsExpatDriver::GetType() { return NS_IPARSER_FLAG_XML
; }
1223 NS_IMETHODIMP_(nsDTDMode
)
1224 nsExpatDriver::GetMode() const { return eDTDMode_full_standards
; }
1226 /*************************** Unused methods **********************************/
1228 NS_IMETHODIMP_(bool)
1229 nsExpatDriver::IsContainer(int32_t aTag
) const { return true; }
1231 NS_IMETHODIMP_(bool)
1232 nsExpatDriver::CanContain(int32_t aParent
, int32_t aChild
) const {
1236 void nsExpatDriver::MaybeStopParser(nsresult aState
) {
1237 if (NS_FAILED(aState
)) {
1238 // If we had a failure we want to override NS_ERROR_HTMLPARSER_INTERRUPTED
1239 // and we want to override NS_ERROR_HTMLPARSER_BLOCK but not with
1240 // NS_ERROR_HTMLPARSER_INTERRUPTED.
1241 if (NS_SUCCEEDED(mInternalState
) ||
1242 mInternalState
== NS_ERROR_HTMLPARSER_INTERRUPTED
||
1243 (mInternalState
== NS_ERROR_HTMLPARSER_BLOCK
&&
1244 aState
!= NS_ERROR_HTMLPARSER_INTERRUPTED
)) {
1245 mInternalState
= (aState
== NS_ERROR_HTMLPARSER_INTERRUPTED
||
1246 aState
== NS_ERROR_HTMLPARSER_BLOCK
)
1248 : NS_ERROR_HTMLPARSER_STOPPARSING
;
1251 // If we get an error then we need to stop Expat (by calling XML_StopParser
1252 // with false as the last argument). If the parser should be blocked or
1253 // interrupted we need to pause Expat (by calling XML_StopParser with
1254 // true as the last argument).
1255 XML_StopParser(mExpatParser
, BlockedOrInterrupted());
1256 } else if (NS_SUCCEEDED(mInternalState
)) {
1257 // Only clobber mInternalState with the success code if we didn't block or
1258 // interrupt before.
1259 mInternalState
= aState
;