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 }