| // Copyright 2015 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #if defined(STARBOARD) |
| #include "starboard/client_porting/poem/string_leaks_poem.h" |
| #endif // defined(STARBOARD) |
| |
| #include "cobalt/dom_parser/libxml_parser_wrapper.h" |
| |
| #include "base/logging.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversion_utils.h" |
| #include "base/third_party/icu/icu_utf.h" |
| #include "cobalt/base/tokens.h" |
| #include "cobalt/dom/cdata_section.h" |
| #include "cobalt/dom/comment.h" |
| #include "cobalt/dom/element.h" |
| #include "cobalt/dom/text.h" |
| #if defined(OS_STARBOARD) |
| #include "starboard/configuration.h" |
| #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT) |
| #define HANDLE_CORE_DUMP |
| #include "base/lazy_instance.h" |
| #include STARBOARD_CORE_DUMP_HANDLER_INCLUDE |
| #endif // SB_HAS(CORE_DUMP_HANDLER_SUPPORT) |
| #endif // defined(OS_STARBOARD) |
| #include "third_party/libxml/src/include/libxml/xmlerror.h" |
| |
| namespace cobalt { |
| namespace dom_parser { |
| namespace { |
| |
| #if defined(HANDLE_CORE_DUMP) |
| |
| class LibxmlParserWrapperLog { |
| public: |
| LibxmlParserWrapperLog() |
| : total_parsed_bytes_(0), |
| total_warning_count_(0), |
| total_error_count_(0), |
| total_fatal_count_(0) { |
| SbCoreDumpRegisterHandler(CoreDumpHandler, this); |
| } |
| ~LibxmlParserWrapperLog() { |
| SbCoreDumpUnregisterHandler(CoreDumpHandler, this); |
| } |
| |
| static void CoreDumpHandler(void* context) { |
| SbCoreDumpLogInteger( |
| "LibxmlParserWrapper total parsed bytes", |
| static_cast<LibxmlParserWrapperLog*>(context)->total_parsed_bytes_); |
| SbCoreDumpLogInteger( |
| "LibxmlParserWrapper total warning count", |
| static_cast<LibxmlParserWrapperLog*>(context)->total_warning_count_); |
| SbCoreDumpLogInteger( |
| "LibxmlParserWrapper total error count", |
| static_cast<LibxmlParserWrapperLog*>(context)->total_error_count_); |
| SbCoreDumpLogInteger( |
| "LibxmlParserWrapper total fatal error count", |
| static_cast<LibxmlParserWrapperLog*>(context)->total_fatal_count_); |
| SbCoreDumpLogString("LibxmlParserWrapper last fatal error", |
| static_cast<LibxmlParserWrapperLog*>(context) |
| ->last_fatal_message_.c_str()); |
| } |
| |
| void IncrementParsedBytes(int length) { total_parsed_bytes_ += length; } |
| void LogParsingIssue(LibxmlParserWrapper::IssueSeverity severity, |
| const std::string& message) { |
| if (severity == LibxmlParserWrapper::kWarning) { |
| total_warning_count_++; |
| } else if (severity == LibxmlParserWrapper::kError) { |
| total_error_count_++; |
| } else if (severity == LibxmlParserWrapper::kFatal) { |
| total_fatal_count_++; |
| last_fatal_message_ = message; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| private: |
| int total_parsed_bytes_; |
| int total_warning_count_; |
| int total_error_count_; |
| int total_fatal_count_; |
| std::string last_fatal_message_; |
| DISALLOW_COPY_AND_ASSIGN(LibxmlParserWrapperLog); |
| }; |
| |
| base::LazyInstance<LibxmlParserWrapperLog>::DestructorAtExit |
| libxml_parser_wrapper_log = LAZY_INSTANCE_INITIALIZER; |
| |
| #endif // defined(HANDLE_CORE_DUMP) |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // Helpers |
| ///////////////////////////////////////////////////////////////////////////// |
| |
| LibxmlParserWrapper* ToLibxmlParserWrapper(void* context) { |
| return reinterpret_cast<LibxmlParserWrapper*>(context); |
| } |
| |
| const char* ToCString(const xmlChar* xmlstring) { |
| // xmlChar*s are UTF-8, so this cast is safe. |
| return reinterpret_cast<const char*>(xmlstring); |
| } |
| |
| std::string StringPrintVAndTrim(const char* message, va_list arguments) { |
| const std::string formatted_message = base::StringPrintV(message, arguments); |
| |
| std::string trimmed_message; |
| TrimWhitespaceASCII(formatted_message, base::TRIM_ALL, &trimmed_message); |
| |
| return trimmed_message; |
| } |
| |
| } // namespace |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // Libxml SAX Handlers |
| ///////////////////////////////////////////////////////////////////////////// |
| |
| void StartDocument(void* context) { |
| ToLibxmlParserWrapper(context)->OnStartDocument(); |
| } |
| |
| void EndDocument(void* context) { |
| ToLibxmlParserWrapper(context)->OnEndDocument(); |
| } |
| |
| void StartElement(void* context, const xmlChar* name, |
| const xmlChar** attribute_pairs) { |
| LibxmlParserWrapper::ParserAttributeVector attributes; |
| |
| // attribute_pairs is an array of attribute pairs (name, value) terminated by |
| // a pair of NULLs. |
| if (attribute_pairs) { |
| // Count the number of attributes and preallocate the attributes vectors. |
| const xmlChar** end_attribute_pairs = attribute_pairs; |
| while (end_attribute_pairs[0] || end_attribute_pairs[1]) { |
| end_attribute_pairs += 2; |
| } |
| |
| const size_t num_attributes = |
| static_cast<size_t>((end_attribute_pairs - attribute_pairs) / 2); |
| attributes.reserve(num_attributes); |
| |
| for (size_t i = 0; i < num_attributes; ++i, attribute_pairs += 2) { |
| attributes.push_back(LibxmlParserWrapper::ParserAttribute( |
| ToCString(attribute_pairs[0]), ToCString(attribute_pairs[1]))); |
| } |
| } |
| |
| ToLibxmlParserWrapper(context)->OnStartElement(ToCString(name), attributes); |
| } |
| |
| void EndElement(void* context, const xmlChar* name) { |
| ToLibxmlParserWrapper(context)->OnEndElement(ToCString(name)); |
| } |
| |
| void Characters(void* context, const xmlChar* ch, int len) { |
| ToLibxmlParserWrapper(context) |
| ->OnCharacters(std::string(ToCString(ch), static_cast<size_t>(len))); |
| } |
| |
| void Comment(void* context, const xmlChar* value) { |
| ToLibxmlParserWrapper(context)->OnComment(ToCString(value)); |
| } |
| |
| void ParserWarning(void* context, const char* message, ...) { |
| va_list arguments; |
| va_start(arguments, message); |
| |
| ToLibxmlParserWrapper(context)->OnParsingIssue( |
| LibxmlParserWrapper::kWarning, StringPrintVAndTrim(message, arguments)); |
| } |
| |
| void ParserError(void* context, const char* message, ...) { |
| va_list arguments; |
| va_start(arguments, message); |
| |
| ToLibxmlParserWrapper(context)->OnParsingIssue( |
| LibxmlParserWrapper::kError, StringPrintVAndTrim(message, arguments)); |
| } |
| |
| void ParserFatal(void* context, const char* message, ...) { |
| va_list arguments; |
| va_start(arguments, message); |
| |
| ToLibxmlParserWrapper(context)->OnParsingIssue( |
| LibxmlParserWrapper::kFatal, StringPrintVAndTrim(message, arguments)); |
| } |
| |
| void CDATABlock(void* context, const xmlChar* value, int len) { |
| ToLibxmlParserWrapper(context) |
| ->OnCDATABlock(std::string(ToCString(value), static_cast<size_t>(len))); |
| } |
| |
| ////////////////////////////////////////////////////////////////// |
| // LibxmlParserWrapper |
| ////////////////////////////////////////////////////////////////// |
| |
| void LibxmlParserWrapper::OnStartDocument() { node_stack_.push(parent_node_); } |
| |
| void LibxmlParserWrapper::OnEndDocument() { |
| // Libxml can call OnEndDocument without calling OnStartDocument. |
| if (node_stack_.empty()) { |
| LOG(WARNING) << "OnEndDocument is called without OnStartDocument."; |
| } else { |
| while (parent_node_ != node_stack_.top()) { |
| LOG(WARNING) << "some elements did not get called on OnEndElement()"; |
| node_stack_.pop(); |
| } |
| DCHECK_GT(node_stack_.size(), static_cast<uint64_t>(0)); |
| DCHECK_EQ(parent_node_, node_stack_.top()); |
| node_stack_.pop(); |
| } |
| |
| if (!node_stack_.empty() && !load_complete_callback_.is_null()) { |
| load_complete_callback_.Run( |
| std::string("Node stack not empty at end of document.")); |
| } |
| |
| if (IsFullDocument()) { |
| document_->PostToDispatchEventName(FROM_HERE, |
| base::Tokens::domcontentloaded()); |
| } |
| } |
| |
| void LibxmlParserWrapper::OnStartElement( |
| const std::string& name, const ParserAttributeVector& attributes) { |
| scoped_refptr<dom::Element> element = document_->CreateElement(name); |
| for (size_t i = 0; i < attributes.size(); ++i) { |
| element->SetAttribute(attributes[i].name.as_string(), |
| attributes[i].value.as_string()); |
| } |
| |
| if (static_cast<int>(node_stack_.size()) <= dom_max_element_depth_) { |
| element->OnParserStartTag(GetSourceLocation()); |
| node_stack_.top()->InsertBefore(element, reference_node_); |
| } else { |
| if (!depth_limit_exceeded_) { |
| depth_limit_exceeded_ = true; |
| LOG(WARNING) << "Parser discarded deeply nested elements."; |
| } |
| } |
| |
| node_stack_.push(element); |
| } |
| |
| void LibxmlParserWrapper::OnEndElement(const std::string& name) { |
| while (!node_stack_.empty()) { |
| scoped_refptr<dom::Element> element = node_stack_.top()->AsElement(); |
| node_stack_.pop(); |
| |
| if (static_cast<int>(node_stack_.size()) <= dom_max_element_depth_) { |
| element->OnParserEndTag(); |
| } |
| |
| if (element->local_name() == name) { |
| return; |
| } |
| } |
| if (node_stack_.empty() && !load_complete_callback_.is_null()) { |
| load_complete_callback_.Run( |
| std::string("Node stack empty when encountering end tag.")); |
| } |
| } |
| |
| void LibxmlParserWrapper::OnCharacters(const std::string& value) { |
| // The content of a sufficiently long text node can be provided as a sequence |
| // of calls to OnCharacter. |
| // If this is the first call in this sequence, a new Text node will be |
| // create. Otherwise, the provided value will be appended to the previous |
| // created Text node. |
| scoped_refptr<dom::Node> last_child = node_stack_.top()->last_child(); |
| if (last_child && last_child->IsText() && !last_child->IsCDATASection()) { |
| dom::Text* text = last_child->AsText(); |
| std::string data = text->data(); |
| data.append(value.data(), value.size()); |
| text->set_data(data); |
| } else { |
| node_stack_.top()->AppendChild(new dom::Text(document_, value)); |
| } |
| } |
| |
| void LibxmlParserWrapper::OnComment(const std::string& comment) { |
| node_stack_.top()->AppendChild(new dom::Comment(document_, comment)); |
| } |
| |
| void LibxmlParserWrapper::OnParsingIssue(IssueSeverity severity, |
| const std::string& message) { |
| DCHECK(severity >= kWarning && severity <= kFatal); |
| |
| xmlErrorPtr error = xmlGetLastError(); |
| if (error && error->code == XML_HTML_UNKNOWN_TAG) { |
| return; |
| } |
| |
| if (severity > max_severity_) { |
| max_severity_ = severity; |
| } |
| if (severity < LibxmlParserWrapper::kFatal) { |
| LOG(WARNING) << "Libxml " |
| << (severity == kWarning ? "Warning: " : "Error: ") << message; |
| } else if (severity == LibxmlParserWrapper::kFatal) { |
| LOG(ERROR) << "Libxml Fatal Error: " << message; |
| if (!load_complete_callback_.is_null()) { |
| load_complete_callback_.Run(message); |
| } |
| } else { |
| NOTREACHED(); |
| } |
| |
| #if defined(HANDLE_CORE_DUMP) |
| libxml_parser_wrapper_log.Get().LogParsingIssue(severity, message); |
| #endif |
| } |
| |
| void LibxmlParserWrapper::OnCDATABlock(const std::string& value) { |
| node_stack_.top()->AppendChild(new dom::CDATASection(document_, value)); |
| } |
| |
| void LibxmlParserWrapper::PreprocessChunk(const char* data, size_t size, |
| std::string* current_chunk) { |
| DCHECK(current_chunk); |
| // Check the total input size. |
| total_input_size_ += size; |
| if (total_input_size_ > kMaxTotalInputSize) { |
| static const char kMessageInputTooLong[] = "Parser input is too long."; |
| OnParsingIssue(kFatal, kMessageInputTooLong); |
| return; |
| } |
| |
| // Check the encoding of the input. |
| std::string input = next_chunk_start_ + std::string(data, size); |
| base::TruncateUTF8ToByteSize(input, input.size(), current_chunk); |
| next_chunk_start_ = input.substr(current_chunk->size()); |
| if (!base::IsStringUTF8(*current_chunk)) { |
| current_chunk->clear(); |
| static const char kMessageInputNotUTF8[] = |
| "Parser input contains non-UTF8 characters."; |
| OnParsingIssue(kFatal, kMessageInputNotUTF8); |
| return; |
| } |
| |
| #if defined(HANDLE_CORE_DUMP) |
| libxml_parser_wrapper_log.Get().IncrementParsedBytes(static_cast<int>(size)); |
| #endif |
| } |
| |
| } // namespace dom_parser |
| } // namespace cobalt |