| /* | |
| * Copyright 2006 The Android Open Source Project | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include <array> | |
| #include "src/xml/SkXMLParser.h" | |
| #include "include/core/SkStream.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/private/SkTemplates.h" | |
| #include "include/private/SkTo.h" | |
| #include "src/core/SkRandom.h" | |
| #include "src/core/SkTime.h" | |
| #include "src/core/SkUtils.h" | |
| #include <expat.h> | |
| #include <vector> | |
| static const auto gErrorStrings = std::to_array<const char *>({ | |
| "empty or missing file ", | |
| "unknown element ", | |
| "unknown attribute name ", | |
| "error in attribute value ", | |
| "duplicate ID ", | |
| "unknown error " | |
| ,}); | |
| SkXMLParserError::SkXMLParserError() : fCode(kNoError), fLineNumber(-1), | |
| fNativeCode(-1) | |
| { | |
| reset(); | |
| } | |
| SkXMLParserError::~SkXMLParserError() | |
| { | |
| // need a virtual destructor for our subclasses | |
| } | |
| void SkXMLParserError::getErrorString(SkString* str) const | |
| { | |
| SkASSERT(str); | |
| SkString temp; | |
| if (fCode != kNoError) { | |
| if ((unsigned)fCode < std::size(gErrorStrings)) | |
| temp.set(gErrorStrings[fCode - 1]); | |
| temp.append(fNoun); | |
| } else | |
| SkXMLParser::GetNativeErrorString(fNativeCode, &temp); | |
| str->append(temp); | |
| } | |
| void SkXMLParserError::reset() { | |
| fCode = kNoError; | |
| fLineNumber = -1; | |
| fNativeCode = -1; | |
| } | |
| //////////////// | |
| namespace { | |
| // Return a random number based on the clock to be consistent | |
| // across platforms yet hard to guess and not leaking information. | |
| static uint32_t get_hash_salt() { | |
| static const uint32_t s_salt = []() { | |
| double nsecs = SkTime::GetNSecs(); | |
| uint64_t timeVal = sk_bit_cast<uint64_t>(nsecs); | |
| uint32_t seed = static_cast<uint32_t>(timeVal ^ timeVal >> 32); | |
| SkRandom rand(seed); | |
| uint32_t salt = rand.nextU(); | |
| while (salt == 0) [[unlikely]] { | |
| // Expat hash seed must be non-zero or the default cPRNG will be used. | |
| salt = rand.nextU(); | |
| } | |
| return salt; | |
| }(); | |
| return s_salt; | |
| } | |
| const XML_Memory_Handling_Suite sk_XML_alloc = { | |
| sk_malloc_throw, | |
| sk_realloc_throw, | |
| sk_free | |
| }; | |
| struct ParsingContext { | |
| ParsingContext(SkXMLParser* parser) | |
| : fParser(parser) | |
| , fXMLParser(XML_ParserCreate_MM(nullptr, &sk_XML_alloc, nullptr)) { } | |
| void flushText() { | |
| if (!fBufferedText.empty()) { | |
| fParser->text(fBufferedText.data(), SkTo<int>(fBufferedText.size())); | |
| fBufferedText.clear(); | |
| } | |
| } | |
| void appendText(const char* txt, size_t len) { | |
| fBufferedText.insert(fBufferedText.end(), txt, &txt[len]); | |
| } | |
| SkXMLParser* fParser; | |
| SkAutoTCallVProc<std::remove_pointer_t<XML_Parser>, XML_ParserFree> fXMLParser; | |
| private: | |
| std::vector<char> fBufferedText; | |
| }; | |
| #define HANDLER_CONTEXT(arg, name) ParsingContext* name = static_cast<ParsingContext*>(arg) | |
| void XMLCALL start_element_handler(void *data, const char* tag, const char** attributes) { | |
| HANDLER_CONTEXT(data, ctx); | |
| ctx->flushText(); | |
| ctx->fParser->startElement(tag); | |
| for (size_t i = 0; attributes[i]; i += 2) { | |
| ctx->fParser->addAttribute(attributes[i], attributes[i + 1]); | |
| } | |
| } | |
| void XMLCALL end_element_handler(void* data, const char* tag) { | |
| HANDLER_CONTEXT(data, ctx); | |
| ctx->flushText(); | |
| ctx->fParser->endElement(tag); | |
| } | |
| void XMLCALL text_handler(void *data, const char* txt, int len) { | |
| HANDLER_CONTEXT(data, ctx); | |
| ctx->appendText(txt, SkTo<size_t>(len)); | |
| } | |
| void XMLCALL entity_decl_handler(void *data, | |
| const XML_Char *entityName, | |
| int is_parameter_entity, | |
| const XML_Char *value, | |
| int value_length, | |
| const XML_Char *base, | |
| const XML_Char *systemId, | |
| const XML_Char *publicId, | |
| const XML_Char *notationName) { | |
| HANDLER_CONTEXT(data, ctx); | |
| SkDEBUGF("'%s' entity declaration found, stopping processing", entityName); | |
| XML_StopParser(ctx->fXMLParser, XML_FALSE); | |
| } | |
| } // anonymous namespace | |
| SkXMLParser::SkXMLParser(SkXMLParserError* parserError) : fParser(nullptr), fError(parserError) | |
| { | |
| } | |
| SkXMLParser::~SkXMLParser() | |
| { | |
| } | |
| bool SkXMLParser::parse(SkStream& docStream) | |
| { | |
| ParsingContext ctx(this); | |
| if (!ctx.fXMLParser) { | |
| SkDEBUGF("could not create XML parser\n"); | |
| return false; | |
| } | |
| // Expat calls to rand_s if no salt is set, which is not allowed on Windows. | |
| // https://crbug.com/40088338 | |
| // Setting the salt helps prevent DOS with a known hash sequence. Generating a | |
| // random salt once per process oursevles ensures the salt is non-deterministic | |
| // and unguessable, while preventing calls to rand_s inside sandboxed processes | |
| // on Windows. | |
| XML_SetHashSalt(ctx.fXMLParser, get_hash_salt()); | |
| XML_SetUserData(ctx.fXMLParser, &ctx); | |
| XML_SetElementHandler(ctx.fXMLParser, start_element_handler, end_element_handler); | |
| XML_SetCharacterDataHandler(ctx.fXMLParser, text_handler); | |
| // Disable entity processing, to inhibit internal entity expansion. See expat CVE-2013-0340. | |
| XML_SetEntityDeclHandler(ctx.fXMLParser, entity_decl_handler); | |
| XML_Status status = XML_STATUS_OK; | |
| if (docStream.getMemoryBase() && docStream.hasLength()) { | |
| const char* base = reinterpret_cast<const char*>(docStream.getMemoryBase()); | |
| status = XML_Parse(ctx.fXMLParser, | |
| base + docStream.getPosition(), | |
| docStream.getLength() - docStream.getPosition(), | |
| true); | |
| } else { | |
| static constexpr int kBufferSize = 4096; | |
| bool done = false; | |
| do { | |
| void* buffer = XML_GetBuffer(ctx.fXMLParser, kBufferSize); | |
| if (!buffer) { | |
| SkDEBUGF("could not buffer enough to continue\n"); | |
| return false; | |
| } | |
| size_t len = docStream.read(buffer, kBufferSize); | |
| done = docStream.isAtEnd(); | |
| status = XML_ParseBuffer(ctx.fXMLParser, SkToS32(len), done); | |
| if (XML_STATUS_ERROR == status) { | |
| break; | |
| } | |
| } while (!done); | |
| } | |
| if (XML_STATUS_ERROR == status) { | |
| #if defined(SK_DEBUG) | |
| XML_Error error = XML_GetErrorCode(ctx.fXMLParser); | |
| int line = XML_GetCurrentLineNumber(ctx.fXMLParser); | |
| int column = XML_GetCurrentColumnNumber(ctx.fXMLParser); | |
| const XML_LChar* errorString = XML_ErrorString(error); | |
| SkDEBUGF("parse error @%d:%d: %d (%s).\n", line, column, error, errorString); | |
| #endif | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool SkXMLParser::parse(const char doc[], size_t len) | |
| { | |
| SkMemoryStream docStream(doc, len); | |
| return this->parse(docStream); | |
| } | |
| void SkXMLParser::GetNativeErrorString(int error, SkString* str) | |
| { | |
| } | |
| bool SkXMLParser::startElement(const char elem[]) | |
| { | |
| return this->onStartElement(elem); | |
| } | |
| bool SkXMLParser::addAttribute(const char name[], const char value[]) | |
| { | |
| return this->onAddAttribute(name, value); | |
| } | |
| bool SkXMLParser::endElement(const char elem[]) | |
| { | |
| return this->onEndElement(elem); | |
| } | |
| bool SkXMLParser::text(const char text[], int len) | |
| { | |
| return this->onText(text, len); | |
| } | |
| //////////////////////////////////////////////////////////////////////////////// | |
| bool SkXMLParser::onStartElement(const char elem[]) {return false; } | |
| bool SkXMLParser::onAddAttribute(const char name[], const char value[]) {return false; } | |
| bool SkXMLParser::onEndElement(const char elem[]) { return false; } | |
| bool SkXMLParser::onText(const char text[], int len) {return false; } |