winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

YamlWrapper.cpp (20256B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include <pch.h>
      4 #include "YamlWrapper.h"
      5 #include "AppInstallerErrors.h"
      6 #include "AppInstallerLogging.h"
      7 #include "AppInstallerStrings.h"
      8 
      9 
     10 namespace AppInstaller::YAML::Wrapper
     11 {
     12     namespace
     13     {
     14         Node::Type ConvertNodeType(yaml_node_type_t type)
     15         {
     16             switch (type)
     17             {
     18             case YAML_NO_NODE:
     19                 return Node::Type::None;
     20             case YAML_SCALAR_NODE:
     21                 return Node::Type::Scalar;
     22             case YAML_SEQUENCE_NODE:
     23                 return Node::Type::Sequence;
     24             case YAML_MAPPING_NODE:
     25                 return Node::Type::Mapping;
     26             }
     27 
     28             THROW_HR(E_UNEXPECTED);
     29         }
     30 
     31         Exception::Type ConvertErrorType(yaml_error_type_t type)
     32         {
     33             switch (type)
     34             {
     35             case YAML_NO_ERROR:
     36                 return Exception::Type::None;
     37             case YAML_MEMORY_ERROR:
     38                 return Exception::Type::Memory;
     39             case YAML_READER_ERROR:
     40                 return Exception::Type::Reader;
     41             case YAML_SCANNER_ERROR:
     42                 return Exception::Type::Scanner;
     43             case YAML_PARSER_ERROR:
     44                 return Exception::Type::Parser;
     45             case YAML_COMPOSER_ERROR:
     46                 return Exception::Type::Composer;
     47             case YAML_WRITER_ERROR:
     48                 return Exception::Type::Writer;
     49             case YAML_EMITTER_ERROR:
     50                 return Exception::Type::Emitter;
     51             }
     52 
     53             THROW_HR(E_UNEXPECTED);
     54         }
     55 
     56         Mark ConvertMark(const yaml_mark_t& mark)
     57         {
     58             return { mark.line + 1, mark.column + 1 };
     59         }
     60 
     61         std::string ConvertYamlString(yaml_char_t* string, const yaml_mark_t& mark, size_t length = std::string::npos)
     62         {
     63             std::string_view resultView;
     64 
     65             if (length == std::string::npos)
     66             {
     67                 resultView = { reinterpret_cast<char*>(string) };
     68             }
     69             else
     70             {
     71                 resultView = { reinterpret_cast<char*>(string), length };
     72             }
     73 
     74             size_t invalidCharacter = Utility::FindControlCodeToConvert(resultView);
     75             if (invalidCharacter != std::string::npos)
     76             {
     77                 THROW_EXCEPTION(Exception(Exception::Type::Policy, "unsupported control character", ConvertMark(mark)));
     78             }
     79 
     80             return std::string{ resultView };
     81         }
     82 
     83         std::string ConvertScalarToString(yaml_node_t* node, const yaml_mark_t& mark)
     84         {
     85             return ConvertYamlString(node->data.scalar.value, mark, node->data.scalar.length);
     86         }
     87 
     88         yaml_scalar_style_t ConvertStyle(ScalarStyle style)
     89         {
     90             switch (style)
     91             {
     92             case ScalarStyle::Any: return yaml_scalar_style_t::YAML_ANY_SCALAR_STYLE;
     93             case ScalarStyle::Plain: return yaml_scalar_style_t::YAML_PLAIN_SCALAR_STYLE;
     94             case ScalarStyle::SingleQuoted: return yaml_scalar_style_t::YAML_SINGLE_QUOTED_SCALAR_STYLE;
     95             case ScalarStyle::DoubleQuoted: return yaml_scalar_style_t::YAML_DOUBLE_QUOTED_SCALAR_STYLE;
     96             case ScalarStyle::Literal: return yaml_scalar_style_t::YAML_LITERAL_SCALAR_STYLE;
     97             case ScalarStyle::Folded: return yaml_scalar_style_t::YAML_FOLDED_SCALAR_STYLE;
     98             default: THROW_HR(E_UNEXPECTED);
     99             }
    100         }
    101     }
    102 
    103     Document::Document(bool init) :
    104         m_token(true)
    105     {
    106         if (init)
    107         {
    108             // Initialize with no version directive or tags, and implicit start and end.
    109             if (!yaml_document_initialize(&m_document, NULL, NULL, NULL, 1, 1))
    110             {
    111                 THROW_HR(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED);
    112             }
    113         }
    114         else
    115         {
    116             memset(&m_document, 0, sizeof(m_document));
    117         }
    118     }
    119 
    120     Document::~Document()
    121     {
    122         if (m_token)
    123         {
    124             yaml_document_delete(&m_document);
    125         }
    126     }
    127 
    128     bool Document::HasRoot()
    129     {
    130         return yaml_document_get_root_node(&m_document) != nullptr;
    131     }
    132 
    133     Node Document::GetRoot()
    134     {
    135         yaml_node_t* root = yaml_document_get_root_node(&m_document);
    136 
    137         if (!root)
    138         {
    139             return {};
    140         }
    141 
    142         Node result(ConvertNodeType(root->type), ConvertYamlString(root->tag, root->start_mark), ConvertMark(root->start_mark));
    143 
    144         struct StackItem
    145         {
    146             StackItem(yaml_node_t* yn, Node* n) :
    147                 yamlNode(yn), node(n) {}
    148 
    149             yaml_node_t* yamlNode = nullptr;
    150             Node* node = nullptr;
    151             size_t childOffset = 0;
    152         };
    153 
    154         static int YAML_DOCUMENT_NEST_LEVEL_LIMIT = 100;
    155         int nestLevel = 0;
    156 
    157         std::stack<StackItem> resultStack;
    158         resultStack.emplace(root, &result);
    159 
    160         while (!resultStack.empty())
    161         {
    162             StackItem& stackItem = resultStack.top();
    163             bool pop = false;
    164 
    165             switch (stackItem.yamlNode->type)
    166             {
    167             case YAML_NO_NODE:
    168                 pop = true;
    169                 break;
    170             case YAML_SCALAR_NODE:
    171                 stackItem.node->SetScalar(
    172                     ConvertScalarToString(stackItem.yamlNode, stackItem.yamlNode->start_mark),
    173                     stackItem.yamlNode->data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE ||
    174                     stackItem.yamlNode->data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE);
    175                 pop = true;
    176                 break;
    177             case YAML_SEQUENCE_NODE:
    178             {
    179                 if (stackItem.childOffset == 0)
    180                 {
    181                     // We've entered the sequence.
    182                     nestLevel++;
    183                 }
    184 
    185                 yaml_node_item_t* child = stackItem.yamlNode->data.sequence.items.start + stackItem.childOffset++;
    186                 if (child < stackItem.yamlNode->data.sequence.items.top)
    187                 {
    188                     yaml_node_t* childYamlNode = GetNode(*child);
    189                     Node& childNode = stackItem.node->AddSequenceNode(ConvertNodeType(childYamlNode->type), ConvertYamlString(childYamlNode->tag, childYamlNode->start_mark), ConvertMark(childYamlNode->start_mark));
    190                     resultStack.emplace(childYamlNode, &childNode);
    191                 }
    192                 else
    193                 {
    194                     // We've reached the end of the sequence
    195                     pop = true;
    196                     nestLevel--;
    197                 }
    198                 break;
    199             }
    200             case YAML_MAPPING_NODE:
    201             {
    202                 if (stackItem.childOffset == 0)
    203                 {
    204                     // We've entered the mapping.
    205                     nestLevel++;
    206                 }
    207 
    208                 yaml_node_pair_t* child = stackItem.yamlNode->data.mapping.pairs.start + stackItem.childOffset++;
    209                 if (child < stackItem.yamlNode->data.mapping.pairs.top)
    210                 {
    211                     yaml_node_t* keyYamlNode = GetNode(child->key);
    212                     THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY, keyYamlNode->type != YAML_SCALAR_NODE);
    213 
    214                     Node keyNode(ConvertNodeType(keyYamlNode->type), ConvertYamlString(keyYamlNode->tag, keyYamlNode->start_mark), ConvertMark(keyYamlNode->start_mark));
    215                     keyNode.SetScalar(ConvertScalarToString(keyYamlNode, keyYamlNode->start_mark));
    216 
    217                     yaml_node_t* valueYamlNode = GetNode(child->value);
    218 
    219                     Node& childNode = stackItem.node->AddMappingNode(std::move(keyNode), ConvertNodeType(valueYamlNode->type), ConvertYamlString(valueYamlNode->tag, valueYamlNode->start_mark), ConvertMark(valueYamlNode->start_mark));
    220                     resultStack.emplace(valueYamlNode, &childNode);
    221                 }
    222                 else
    223                 {
    224                     // We've reached the end of the mapping
    225                     pop = true;
    226                     nestLevel--;
    227                 }
    228                 break;
    229             }
    230             }
    231 
    232             if (pop)
    233             {
    234                 resultStack.pop();
    235             }
    236 
    237             THROW_HR_IF_MSG(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, nestLevel > YAML_DOCUMENT_NEST_LEVEL_LIMIT, "Too many layers of nested nodes.");
    238         }
    239 
    240         return result;
    241     }
    242 
    243     int Document::AddScalar(std::string_view value, ScalarStyle style)
    244     {
    245         int result = yaml_document_add_scalar(&m_document, NULL, reinterpret_cast<const yaml_char_t*>(value.data()), static_cast<int>(value.size()), ConvertStyle(style));
    246         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, result == 0);
    247         return result;
    248     }
    249 
    250     int Document::AddSequence()
    251     {
    252         int result = yaml_document_add_sequence(&m_document, NULL, YAML_ANY_SEQUENCE_STYLE);
    253         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, result == 0);
    254         return result;
    255     }
    256 
    257     int Document::AddMapping()
    258     {
    259         int result = yaml_document_add_mapping(&m_document, NULL, YAML_ANY_MAPPING_STYLE);
    260         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, result == 0);
    261         return result;
    262     }
    263 
    264     void Document::AppendSequenceItem(int sequence, int item)
    265     {
    266         if (!yaml_document_append_sequence_item(&m_document, sequence, item))
    267         {
    268             THROW_HR(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED);
    269         }
    270     }
    271 
    272     void Document::AppendMappingPair(int mapping, int key, int value)
    273     {
    274         if (!yaml_document_append_mapping_pair(&m_document, mapping, key, value))
    275         {
    276             THROW_HR(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED);
    277         }
    278     }
    279 
    280     yaml_node_t* Document::GetNode(yaml_node_item_t index)
    281     {
    282         yaml_node_t* result = yaml_document_get_node(&m_document, index);
    283         THROW_HR_IF(E_BOUNDS, !result);
    284         return result;
    285     }
    286 
    287     Parser::Parser(std::string_view input) : m_token(true), m_input(input)
    288     {
    289         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_parser_initialize(&m_parser));
    290 
    291         PrepareInput();
    292         yaml_parser_set_input_string(&m_parser, reinterpret_cast<const unsigned char*>(m_input.c_str()), m_input.size());
    293     }
    294 
    295     Parser::Parser(std::istream& input, Utility::SHA256::HashBuffer* hashOut) : m_token(true)
    296     {
    297         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_parser_initialize(&m_parser));
    298 
    299         m_input = Utility::ReadEntireStream(input);
    300 
    301         if (hashOut)
    302         {
    303             *hashOut = Utility::SHA256::ComputeHash(reinterpret_cast<const uint8_t*>(m_input.data()), static_cast<uint32_t>(m_input.size()));
    304         }
    305 
    306         PrepareInput();
    307         yaml_parser_set_input_string(&m_parser, reinterpret_cast<const unsigned char*>(m_input.c_str()), m_input.size());
    308     }
    309 
    310     Parser::~Parser()
    311     {
    312         if (m_token)
    313         {
    314             yaml_parser_delete(&m_parser);
    315         }
    316     }
    317 
    318     Document Parser::Load()
    319     {
    320         Document result;
    321 
    322         if (!yaml_parser_load(&m_parser, &result))
    323         {
    324             Exception::Type type = ConvertErrorType(m_parser.error);
    325 
    326             switch (type)
    327             {
    328             case Exception::Type::Memory:
    329                 THROW_EXCEPTION(Exception(type));
    330             case Exception::Type::Reader:
    331                 THROW_EXCEPTION(Exception(type, m_parser.problem, m_parser.problem_offset, m_parser.problem_value));
    332             case Exception::Type::Scanner:
    333             case Exception::Type::Parser:
    334             case Exception::Type::Composer:
    335                 THROW_EXCEPTION(Exception(type, m_parser.problem, ConvertMark(m_parser.problem_mark), m_parser.context, ConvertMark(m_parser.context_mark)));
    336             default:
    337                 THROW_EXCEPTION(Exception(type, "An unexpected error type occurred in Parser::Load"));
    338             }
    339         }
    340 
    341         return result;
    342     }
    343 
    344     void Parser::PrepareInput()
    345     {
    346         constexpr char c_utf16LEBOM[2] = { static_cast<char>(0xFF), static_cast<char>(0xFE) };
    347         constexpr char c_utf16BEBOM[2] = { static_cast<char>(0xFE), static_cast<char>(0xFF) };
    348         constexpr char c_utf8BOM[3] = { static_cast<char>(0xEF), static_cast<char>(0xBB), static_cast<char>(0xBF) };
    349 
    350         // If input has a BOM, we want to remove it to prevent errors with checking for comments within the input document.
    351 
    352         // Check for UTF-16 BOMs
    353         if (m_input.size() >= sizeof(c_utf16LEBOM) && std::memcmp(m_input.data(), c_utf16LEBOM, sizeof(c_utf16LEBOM)) == 0)
    354         {
    355             AICLI_LOG(YAML, Verbose, << "Found UTF-16 LE BOM");
    356             yaml_parser_set_encoding(&m_parser, YAML_UTF16LE_ENCODING); // Without the BOM, the encoding must be explicitly set
    357             m_input.erase(0, sizeof(c_utf16LEBOM)); // Remove the BOM from the input
    358             return;
    359         }
    360 
    361         if (m_input.size() >= sizeof(c_utf16BEBOM) && std::memcmp(m_input.data(), c_utf16BEBOM, sizeof(c_utf16BEBOM)) == 0)
    362         {
    363             AICLI_LOG(YAML, Verbose, << "Found UTF-16 BE BOM");
    364             yaml_parser_set_encoding(&m_parser, YAML_UTF16BE_ENCODING); // Without the BOM, the encoding must be explicitly set
    365             m_input.erase(0, sizeof(c_utf16BEBOM)); // Remove the BOM from the input
    366             return;
    367         }
    368 
    369         // Check for UTF-8 BOM
    370         if (m_input.size() >= sizeof(c_utf8BOM) && std::memcmp(m_input.data(), c_utf8BOM, sizeof(c_utf8BOM)) == 0)
    371         {
    372             AICLI_LOG(YAML, Verbose, << "Found UTF-8 BOM");
    373             yaml_parser_set_encoding(&m_parser, YAML_UTF8_ENCODING); // Without the BOM, the encoding must be explicitly set
    374             m_input.erase(0, sizeof(c_utf8BOM)); // Remove the BOM from the input
    375             return;
    376         }
    377 
    378         // Check for BOM-less UTF-16 LE
    379         INT expectedTests = IS_TEXT_UNICODE_ASCII16 | IS_TEXT_UNICODE_STATISTICS | IS_TEXT_UNICODE_CONTROLS;
    380         INT testResults = expectedTests;
    381         if (IsTextUnicode(m_input.data(), wil::safe_cast<int>(m_input.size()), &testResults) || testResults == expectedTests)
    382         {
    383             AICLI_LOG(YAML, Verbose, << "Detected UTF-16 LE");
    384             yaml_parser_set_encoding(&m_parser, YAML_UTF16LE_ENCODING);
    385             return;
    386         }
    387 
    388         // Check for BOM-less UTF-16 BE
    389         expectedTests = IS_TEXT_UNICODE_REVERSE_ASCII16 | IS_TEXT_UNICODE_REVERSE_STATISTICS | IS_TEXT_UNICODE_REVERSE_CONTROLS;
    390         testResults = expectedTests;
    391         if (IsTextUnicode(m_input.data(), wil::safe_cast<int>(m_input.size()), &testResults) || testResults == expectedTests)
    392         {
    393             AICLI_LOG(YAML, Verbose, << "Detected UTF-16 BE");
    394             yaml_parser_set_encoding(&m_parser, YAML_UTF16BE_ENCODING);
    395             return;
    396         }
    397 
    398         // Check for BOM-less UTF-8
    399         UINT nChars = MultiByteToWideChar(
    400             CP_UTF8,
    401             MB_ERR_INVALID_CHARS,
    402             m_input.data(),
    403             wil::safe_cast<int>(m_input.size()),
    404             NULL,
    405             0);
    406 
    407         if (nChars > 0 || GetLastError() != ERROR_NO_UNICODE_TRANSLATION)
    408         {
    409             AICLI_LOG(YAML, Verbose, << "Detected UTF-8");
    410             yaml_parser_set_encoding(&m_parser, YAML_UTF8_ENCODING);
    411             return;
    412         }
    413 
    414         // Must be ANSI (Windows-1252 assumed), convert to UTF-8
    415         AICLI_LOG(YAML, Verbose, << "Assuming ANSI Windows-1252");
    416         std::wstring utf16 = Utility::ConvertToUTF16(m_input, 1252);
    417         m_input = Utility::ConvertToUTF8(utf16);
    418         yaml_parser_set_encoding(&m_parser, YAML_UTF8_ENCODING);
    419     }
    420 
    421     Event::~Event()
    422     {
    423         if (m_token)
    424         {
    425             yaml_event_delete(&m_event);
    426         }
    427     }
    428 
    429     Event Event::StreamStart()
    430     {
    431         Event result;
    432         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_stream_start_event_initialize(&result, YAML_UTF8_ENCODING));
    433         result.m_token = true;
    434         return result;
    435     }
    436 
    437     Event Event::StreamEnd()
    438     {
    439         Event result;
    440         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_stream_end_event_initialize(&result));
    441         result.m_token = true;
    442         return result;
    443     }
    444 
    445     Event Event::DocumentStart()
    446     {
    447         Event result;
    448         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_document_start_event_initialize(&result, NULL, NULL, NULL, 1));
    449         result.m_token = true;
    450         return result;
    451     }
    452 
    453     Event Event::DocumentEnd()
    454     {
    455         Event result;
    456         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_document_end_event_initialize(&result, 1));
    457         result.m_token = true;
    458         return result;
    459     }
    460 
    461     Event Event::SequenceStart()
    462     {
    463         Event result;
    464         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_sequence_start_event_initialize(&result, NULL, NULL, 1, YAML_ANY_SEQUENCE_STYLE));
    465         result.m_token = true;
    466         return result;
    467     }
    468 
    469     Event Event::SequenceEnd()
    470     {
    471         Event result;
    472         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_sequence_end_event_initialize(&result));
    473         result.m_token = true;
    474         return result;
    475     }
    476 
    477     Event Event::MappingStart()
    478     {
    479         Event result;
    480         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_mapping_start_event_initialize(&result, NULL, NULL, 1, YAML_ANY_MAPPING_STYLE));
    481         result.m_token = true;
    482         return result;
    483     }
    484 
    485     Event Event::MappingEnd()
    486     {
    487         Event result;
    488         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_mapping_end_event_initialize(&result));
    489         result.m_token = true;
    490         return result;
    491     }
    492 
    493     Emitter::Emitter(std::ostream& output) :
    494         m_token(true), m_outputStream(&output)
    495     {
    496         THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_emitter_initialize(&m_emitter));
    497         yaml_emitter_set_output(&m_emitter, StreamWriteHandler, this);
    498         yaml_emitter_set_encoding(&m_emitter, YAML_UTF8_ENCODING);
    499     }
    500 
    501     Emitter::~Emitter()
    502     {
    503         if (m_token)
    504         {
    505             yaml_emitter_delete(&m_emitter);
    506         }
    507     }
    508 
    509     void Emitter::Emit(Event& event)
    510     {
    511         event.Detach();
    512         if (!yaml_emitter_emit(&m_emitter, &event))
    513         {
    514             ThrowError();
    515         }
    516     }
    517 
    518     void Emitter::Emit(Event&& event)
    519     {
    520         event.Detach();
    521         if (!yaml_emitter_emit(&m_emitter, &event))
    522         {
    523             ThrowError();
    524         }
    525     }
    526 
    527     void Emitter::Dump(Document& document)
    528     {
    529         document.Detach();
    530         if (!yaml_emitter_dump(&m_emitter, &document))
    531         {
    532             ThrowError();
    533         }
    534     }
    535 
    536     void Emitter::Flush()
    537     {
    538         if (!yaml_emitter_flush(&m_emitter))
    539         {
    540             ThrowError();
    541         }
    542     }
    543 
    544     int Emitter::StreamWriteHandler(
    545         void* data,
    546         unsigned char* buffer,
    547         size_t size)
    548     {
    549         Emitter& emitter = *reinterpret_cast<Emitter*>(data);
    550 
    551         try
    552         {
    553             emitter.m_outputStream->write(reinterpret_cast<char*>(buffer), size);
    554         }
    555         catch (...)
    556         {
    557             LOG_CAUGHT_EXCEPTION();
    558             return 0;
    559         }
    560 
    561         return 1;
    562     }
    563 
    564     void Emitter::ThrowError()
    565     {
    566         Exception::Type type = ConvertErrorType(m_emitter.error);
    567 
    568         switch (type)
    569         {
    570         case Exception::Type::Memory:
    571             THROW_EXCEPTION(Exception(type));
    572         case Exception::Type::Emitter:
    573         case Exception::Type::Writer:
    574             THROW_EXCEPTION(Exception(type, m_emitter.problem));
    575         default:
    576             THROW_EXCEPTION(Exception(type, "An unexpected error type occurred in Emitter"));
    577         }
    578     }
    579 }