AppInstallerStrings.cpp (34672B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Public/AppInstallerStrings.h" 5 #include "Public/AppInstallerErrors.h" 6 #include "Public/AppInstallerLogging.h" 7 #include "Public/AppInstallerSHA256.h" 8 9 namespace AppInstaller::Utility 10 { 11 // Same as std::isspace(char) 12 #define AICLI_SPACE_CHARS " \f\n\r\t\v"sv 13 14 using namespace std::string_view_literals; 15 constexpr std::string_view s_SpaceChars = AICLI_SPACE_CHARS; 16 constexpr std::wstring_view s_WideSpaceChars = L"" AICLI_SPACE_CHARS; 17 18 namespace 19 { 20 // Contains the ICU objects necessary to do break iteration. 21 struct ICUBreakIterator 22 { 23 ICUBreakIterator(std::string_view input, UBreakIteratorType type) 24 { 25 UErrorCode err = U_ZERO_ERROR; 26 27 m_text.reset(utext_openUTF8(nullptr, input.data(), wil::safe_cast<int64_t>(input.length()), &err)); 28 if (U_FAILURE(err)) 29 { 30 AICLI_LOG(Core, Error, << "utext_openUTF8 returned " << err); 31 THROW_HR(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR); 32 } 33 34 m_brk.reset(ubrk_open(type, nullptr, nullptr, 0, &err)); 35 if (U_FAILURE(err)) 36 { 37 AICLI_LOG(Core, Error, << "ubrk_open returned " << err); 38 THROW_HR(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR); 39 } 40 41 ubrk_setUText(m_brk.get(), m_text.get(), &err); 42 if (U_FAILURE(err)) 43 { 44 AICLI_LOG(Core, Error, << "ubrk_setUText returned " << err); 45 THROW_HR(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR); 46 } 47 48 int32_t i = ubrk_first(m_brk.get()); 49 if (i != 0) 50 { 51 AICLI_LOG(Core, Error, << "ubrk_first returned " << i); 52 THROW_HR(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR); 53 } 54 } 55 56 // Gets the current break value; the byte offset or UBRK_DONE. 57 int32_t CurrentBreak() const { return m_currentBrk; } 58 59 // Gets the current byte offset, throwing if the value is UBRK_DONE or negative. 60 size_t CurrentOffset() const 61 { 62 THROW_HR_IF(E_NOT_VALID_STATE, m_currentBrk < 0); 63 return static_cast<size_t>(m_currentBrk); 64 } 65 66 // Returns the byte offset of the next break in the string 67 int32_t Next() 68 { 69 m_currentBrk = ubrk_next(m_brk.get()); 70 return m_currentBrk; 71 } 72 73 // Returns the byte offset of the next count'th break in the string 74 int32_t Advance(size_t count) 75 { 76 for (size_t i = 0; i < count && m_currentBrk != UBRK_DONE; ++i) 77 { 78 Next(); 79 } 80 return m_currentBrk; 81 } 82 83 // Returns code point of the character at m_currentBrk, or U_SENTINEL if m_currentBrk points to the end. 84 UChar32 CurrentCodePoint() 85 { 86 return utext_char32At(m_text.get(), m_currentBrk); 87 } 88 89 // Returns the status from the break rule that determined the most recently break position. 90 int32_t CurrentRuleStatus() 91 { 92 return ubrk_getRuleStatus(m_brk.get()); 93 } 94 95 private: 96 wil::unique_any<UText*, decltype(utext_close), &utext_close> m_text; 97 wil::unique_any<UBreakIterator*, decltype(ubrk_close), &ubrk_close> m_brk; 98 int32_t m_currentBrk = 0; 99 }; 100 } 101 102 bool CaseInsensitiveEquals(std::string_view a, std::string_view b) 103 { 104 return ToLower(a) == ToLower(b); 105 } 106 107 bool CaseInsensitiveEquals(std::wstring_view a, std::wstring_view b) 108 { 109 return ToLower(a) == ToLower(b); 110 } 111 112 bool CaseInsensitiveContains(const std::vector<std::string_view>& a, std::string_view b) 113 { 114 auto B = ToLower(b); 115 return std::any_of(a.begin(), a.end(), [&](const std::string_view& s) { return ToLower(s) == B; }); 116 } 117 118 bool CaseInsensitiveStartsWith(std::string_view a, std::string_view b) 119 { 120 return a.length() >= b.length() && CaseInsensitiveEquals(a.substr(0, b.length()), b); 121 } 122 123 bool CaseInsensitiveStartsWith(std::wstring_view a, std::wstring_view b) 124 { 125 return a.length() >= b.length() && CaseInsensitiveEquals(a.substr(0, b.length()), b); 126 } 127 128 bool CaseInsensitiveContainsSubstring(std::string_view a, std::string_view b) 129 { 130 auto it = std::search( 131 a.begin(), a.end(), 132 b.begin(), b.end(), 133 [](char ch1, char ch2) { return std::tolower(ch1) == std::tolower(ch2); } 134 ); 135 return (it != a.end()); 136 } 137 138 bool ICUCaseInsensitiveEquals(std::string_view a, std::string_view b) 139 { 140 return FoldCase(a) == FoldCase(b); 141 } 142 143 bool ICUCaseInsensitiveStartsWith(std::string_view a, std::string_view b) 144 { 145 return a.length() >= b.length() && ICUCaseInsensitiveEquals(a.substr(0, b.length()), b); 146 } 147 148 std::string ConvertToUTF8(std::wstring_view input) 149 { 150 if (input.empty()) 151 { 152 return {}; 153 } 154 155 int utf8ByteCount = WideCharToMultiByte(CP_UTF8, 0, input.data(), wil::safe_cast<int>(input.length()), nullptr, 0, nullptr, nullptr); 156 THROW_LAST_ERROR_IF(utf8ByteCount == 0); 157 158 // Since the string view should not contain the null char, the result won't either. 159 // This allows us to use the resulting size value directly in the string constructor. 160 std::string result(wil::safe_cast<size_t>(utf8ByteCount), '\0'); 161 162 int utf8BytesWritten = WideCharToMultiByte(CP_UTF8, 0, input.data(), wil::safe_cast<int>(input.length()), &result[0], wil::safe_cast<int>(result.size()), nullptr, nullptr); 163 FAIL_FAST_HR_IF(E_UNEXPECTED, utf8ByteCount != utf8BytesWritten); 164 165 return result; 166 } 167 168 std::wstring ConvertToUTF16(std::string_view input, UINT codePage) 169 { 170 if (input.empty()) 171 { 172 return {}; 173 } 174 175 int utf16CharCount = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast<int>(input.length()), nullptr, 0); 176 THROW_LAST_ERROR_IF(utf16CharCount == 0); 177 178 // Since the string view should not contain the null char, the result won't either. 179 // This allows us to use the resulting size value directly in the string constructor. 180 std::wstring result(wil::safe_cast<size_t>(utf16CharCount), L'\0'); 181 182 int utf16CharsWritten = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast<int>(input.length()), &result[0], wil::safe_cast<int>(result.size())); 183 FAIL_FAST_HR_IF(E_UNEXPECTED, utf16CharCount != utf16CharsWritten); 184 185 return result; 186 } 187 188 std::optional<std::wstring> TryConvertToUTF16(std::string_view input, UINT codePage) 189 { 190 if (input.empty()) 191 { 192 return std::wstring{}; 193 } 194 195 int utf16CharCount = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast<int>(input.length()), nullptr, 0); 196 if (utf16CharCount == 0) 197 { 198 return {}; 199 } 200 201 // Since the string view should not contain the null char, the result won't either. 202 // This allows us to use the resulting size value directly in the string constructor. 203 std::wstring result(wil::safe_cast<size_t>(utf16CharCount), L'\0'); 204 205 int utf16CharsWritten = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast<int>(input.length()), &result[0], wil::safe_cast<int>(result.size())); 206 if (utf16CharCount != utf16CharsWritten) 207 { 208 return {}; 209 } 210 211 return std::optional{ result }; 212 } 213 214 std::u32string ConvertToUTF32(std::string_view input) 215 { 216 if (input.empty()) 217 { 218 return {}; 219 } 220 221 UErrorCode errorCode = UErrorCode::U_ZERO_ERROR; 222 auto utf32ByteCount= ucnv_convert("UTF-32", "UTF-8", nullptr, 0, input.data(), static_cast<int32_t>(input.size()), &errorCode); 223 224 if (errorCode != U_BUFFER_OVERFLOW_ERROR) 225 { 226 AICLI_LOG(Core, Error, << "ucnv_convert returned " << errorCode); 227 THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR); 228 } 229 230 FAIL_FAST_HR_IF(E_UNEXPECTED, utf32ByteCount % sizeof(char32_t) != 0); 231 auto utf32CharCount = utf32ByteCount / sizeof(char32_t); 232 std::u32string result(utf32CharCount, U'\0'); 233 234 errorCode = UErrorCode::U_ZERO_ERROR; 235 236 auto utf32BytesWritten = ucnv_convert("UTF-32", "UTF-8", (char*)(result.data()), utf32ByteCount, input.data(), static_cast<int32_t>(input.size()), &errorCode); 237 238 // The size we pass to ucnv_convert is not enough for it to put in the null terminator, 239 // which wouldn't work anyways as it puts a single byte. 240 if (errorCode != U_STRING_NOT_TERMINATED_WARNING) 241 { 242 AICLI_LOG(Core, Error, << "ucnv_convert returned " << errorCode); 243 THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR); 244 } 245 246 FAIL_FAST_HR_IF(E_UNEXPECTED, utf32ByteCount != utf32BytesWritten); 247 248 return result; 249 } 250 251 size_t UTF8Length(std::string_view input) 252 { 253 ICUBreakIterator itr{ input, UBRK_CHARACTER }; 254 255 size_t numGraphemeClusters = 0; 256 257 while (itr.Next() != UBRK_DONE) 258 { 259 numGraphemeClusters++; 260 } 261 262 return numGraphemeClusters; 263 } 264 265 size_t UTF8ColumnWidth(const NormalizedUTF8<NormalizationC>& input) 266 { 267 ICUBreakIterator itr{ input, UBRK_CHARACTER }; 268 269 size_t columnWidth = 0; 270 UChar32 currentCP = 0; 271 272 currentCP = itr.CurrentCodePoint(); 273 while (itr.Next() != UBRK_DONE && currentCP != U_SENTINEL) 274 { 275 int32_t width = u_getIntPropertyValue(currentCP, UCHAR_EAST_ASIAN_WIDTH); 276 columnWidth += width == U_EA_FULLWIDTH || width == U_EA_WIDE ? 2 : 1; 277 278 currentCP = itr.CurrentCodePoint(); 279 } 280 281 return columnWidth; 282 } 283 284 std::string_view UTF8Substring(std::string_view input, size_t offset, size_t count) 285 { 286 ICUBreakIterator itr{ input, UBRK_CHARACTER }; 287 288 // Offset was past end, throw just like std::string::substr 289 if (itr.Advance(offset) == UBRK_DONE) 290 { 291 throw std::out_of_range("UTF8Substring: offset past end of input"); 292 } 293 294 size_t utf8Offset = itr.CurrentOffset(); 295 size_t utf8Count = 0; 296 297 // Count past end, convert to npos to get all of string 298 if (itr.Advance(count) == UBRK_DONE) 299 { 300 utf8Count = std::string_view::npos; 301 } 302 else 303 { 304 utf8Count = itr.CurrentOffset() - utf8Offset; 305 } 306 307 return input.substr(utf8Offset, utf8Count); 308 } 309 310 std::string UTF8TrimRightToColumnWidth(const NormalizedUTF8<NormalizationC>& input, size_t expectedWidth, size_t& actualWidth) 311 { 312 ICUBreakIterator itr{ input, UBRK_CHARACTER }; 313 314 size_t columnWidth = 0; 315 UChar32 currentCP = 0; 316 int32_t currentBrk = 0; 317 int32_t nextBrk = 0; 318 319 currentCP = itr.CurrentCodePoint(); 320 currentBrk = itr.CurrentBreak(); 321 nextBrk = itr.Next(); 322 while (nextBrk != UBRK_DONE && currentCP != U_SENTINEL) 323 { 324 int32_t width = u_getIntPropertyValue(currentCP, UCHAR_EAST_ASIAN_WIDTH); 325 int charWidth = width == U_EA_FULLWIDTH || width == U_EA_WIDE ? 2 : 1; 326 columnWidth += charWidth; 327 328 if (columnWidth > expectedWidth) 329 { 330 columnWidth -= charWidth; 331 break; 332 } 333 334 currentCP = itr.CurrentCodePoint(); 335 currentBrk = nextBrk; 336 nextBrk = itr.Next(); 337 } 338 339 actualWidth = columnWidth; 340 341 return input.substr(0, currentBrk); 342 } 343 344 std::string Normalize(std::string_view input, NORM_FORM form) 345 { 346 if (input.empty()) 347 { 348 return {}; 349 } 350 351 return ConvertToUTF8(Normalize(ConvertToUTF16(input), form)); 352 } 353 354 std::wstring Normalize(std::wstring_view input, NORM_FORM form) 355 { 356 if (input.empty()) 357 { 358 return {}; 359 } 360 361 std::wstring result; 362 363 int cchEstimate = NormalizeString(form, input.data(), static_cast<int>(input.length()), NULL, 0); 364 for (;;) 365 { 366 result.resize(cchEstimate); 367 cchEstimate = NormalizeString(form, input.data(), static_cast<int>(input.length()), &result[0], cchEstimate); 368 369 if (cchEstimate > 0) 370 { 371 result.resize(cchEstimate); 372 return result; 373 } 374 else 375 { 376 DWORD dwError = GetLastError(); 377 THROW_LAST_ERROR_IF(dwError != ERROR_INSUFFICIENT_BUFFER); 378 379 // New guess is negative of the return value. 380 cchEstimate = -cchEstimate; 381 382 THROW_HR_IF_MSG(E_UNEXPECTED, static_cast<size_t>(cchEstimate) <= result.size(), "New estimate should never be less than previous value"); 383 } 384 } 385 } 386 387 void ReplaceEmbeddedNullCharacters(std::string& s, char c) 388 { 389 for (size_t i = 0; i < s.length(); ++i) 390 { 391 if (s[i] == '\0') 392 { 393 s[i] = c; 394 } 395 } 396 } 397 398 std::string ToLower(std::string_view in) 399 { 400 std::string result(in); 401 std::transform(result.begin(), result.end(), result.begin(), 402 [](unsigned char c) { return static_cast<char>(std::tolower(c)); }); 403 return result; 404 } 405 406 std::wstring ToLower(std::wstring_view in) 407 { 408 std::wstring result(in); 409 std::transform(result.begin(), result.end(), result.begin(), 410 [](unsigned short c) { return std::towlower(c); }); 411 return result; 412 } 413 414 std::string FoldCase(std::string_view input) 415 { 416 if (input.empty()) 417 { 418 return {}; 419 } 420 421 wil::unique_any<UCaseMap*, decltype(ucasemap_close), &ucasemap_close> caseMap; 422 UErrorCode errorCode = UErrorCode::U_ZERO_ERROR; 423 caseMap.reset(ucasemap_open(nullptr, U_FOLD_CASE_DEFAULT, &errorCode)); 424 425 if (U_FAILURE(errorCode)) 426 { 427 AICLI_LOG(Core, Error, << "ucasemap_open returned " << errorCode); 428 THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR); 429 } 430 431 int32_t cch = ucasemap_utf8FoldCase(caseMap.get(), nullptr, 0, input.data(), static_cast<int32_t>(input.size()), &errorCode); 432 if (errorCode != U_BUFFER_OVERFLOW_ERROR) 433 { 434 AICLI_LOG(Core, Error, << "ucasemap_utf8FoldCase returned " << errorCode); 435 THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR); 436 } 437 438 errorCode = UErrorCode::U_ZERO_ERROR; 439 440 std::string result(cch, '\0'); 441 cch = ucasemap_utf8FoldCase(caseMap.get(), &result[0], cch, input.data(), static_cast<int32_t>(input.size()), &errorCode); 442 if (U_FAILURE(errorCode)) 443 { 444 AICLI_LOG(Core, Error, << "ucasemap_utf8FoldCase returned " << errorCode); 445 THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR); 446 } 447 448 while (result.back() == '\0') 449 { 450 result.pop_back(); 451 } 452 453 return result; 454 } 455 456 NormalizedString FoldCase(const NormalizedString& input) 457 { 458 NormalizedString result; 459 result.assign(FoldCase(static_cast<std::string_view>(input))); 460 return result; 461 } 462 463 bool IsEmptyOrWhitespace(std::string_view str) 464 { 465 if (str.empty()) 466 { 467 return true; 468 } 469 470 return str.find_last_not_of(s_SpaceChars) == std::string_view::npos; 471 } 472 473 bool IsEmptyOrWhitespace(std::wstring_view str) 474 { 475 if (str.empty()) 476 { 477 return true; 478 } 479 480 return str.find_last_not_of(s_WideSpaceChars) == std::wstring_view::npos; 481 } 482 483 bool FindAndReplace(std::string& inputStr, std::string_view token, std::string_view value) 484 { 485 bool result = false; 486 std::string::size_type pos = 0u; 487 while ((pos = inputStr.find(token, pos)) != std::string::npos) 488 { 489 result = true; 490 inputStr.replace(pos, token.length(), value); 491 pos += value.length(); 492 } 493 return result; 494 } 495 496 std::wstring ReplaceWhileCopying(std::wstring_view input, std::wstring_view token, std::wstring_view value) 497 { 498 if (token.empty()) 499 { 500 return std::wstring{ input }; 501 } 502 503 std::wstring result; 504 result.reserve(input.size()); 505 506 std::wstring::size_type pos = 0u; 507 do 508 { 509 std::wstring::size_type findPos = input.find(token, pos); 510 511 if (findPos == std::wstring::npos) 512 { 513 result.append(input.substr(pos)); 514 } 515 else 516 { 517 result.append(input.substr(pos, findPos - pos)); 518 result.append(value); 519 findPos += token.length(); 520 } 521 522 pos = findPos; 523 } 524 while (pos != std::wstring::npos); 525 526 return result; 527 } 528 529 std::string& Trim(std::string& str) 530 { 531 if (!str.empty()) 532 { 533 size_t begin = str.find_first_not_of(s_SpaceChars); 534 size_t end = str.find_last_not_of(s_SpaceChars); 535 536 if (begin == std::string_view::npos || end == std::string_view::npos) 537 { 538 str.clear(); 539 } 540 else if (begin != 0 || end != str.length() - 1) 541 { 542 str = str.substr(begin, (end - begin) + 1); 543 } 544 } 545 546 return str; 547 } 548 549 std::wstring& Trim(std::wstring& str) 550 { 551 if (!str.empty()) 552 { 553 size_t begin = str.find_first_not_of(s_WideSpaceChars); 554 size_t end = str.find_last_not_of(s_WideSpaceChars); 555 556 if (begin == std::string_view::npos || end == std::string_view::npos) 557 { 558 str.clear(); 559 } 560 else if (begin != 0 || end != str.length() - 1) 561 { 562 str = str.substr(begin, (end - begin) + 1); 563 } 564 } 565 566 return str; 567 } 568 569 std::string Trim(std::string&& str) 570 { 571 std::string result = std::move(str); 572 Utility::Trim(result); 573 return result; 574 } 575 576 std::string ReadEntireStream(std::istream& stream) 577 { 578 std::streampos currentPos = stream.tellg(); 579 stream.seekg(0, std::ios_base::end); 580 581 auto offset = stream.tellg() - currentPos; 582 stream.seekg(currentPos); 583 584 // Don't allow use of this API for reading very large streams. 585 THROW_HR_IF(E_OUTOFMEMORY, offset > static_cast<std::streamoff>(std::numeric_limits<uint32_t>::max())); 586 std::string result(static_cast<size_t>(offset), '\0'); 587 stream.read(&result[0], offset); 588 589 return result; 590 } 591 592 std::vector<std::uint8_t> ReadEntireStreamAsByteArray(std::istream& stream) 593 { 594 std::streampos currentPos = stream.tellg(); 595 stream.seekg(0, std::ios_base::end); 596 597 auto offset = stream.tellg() - currentPos; 598 stream.seekg(currentPos); 599 600 // Don't allow use of this API for reading very large streams. 601 THROW_HR_IF(E_OUTOFMEMORY, offset > static_cast<std::streamoff>(std::numeric_limits<uint32_t>::max())); 602 std::vector<std::uint8_t> result; 603 result.resize(static_cast<size_t>(offset)); 604 stream.read(reinterpret_cast<char*>(result.data()), offset); 605 606 return result; 607 } 608 609 std::wstring ExpandEnvironmentVariables(const std::wstring& input) 610 { 611 if (input.empty()) 612 { 613 return {}; 614 } 615 616 DWORD charCount = ExpandEnvironmentStringsW(input.c_str(), nullptr, 0); 617 THROW_LAST_ERROR_IF(charCount == 0); 618 619 std::wstring result(wil::safe_cast<size_t>(charCount), L'\0'); 620 621 DWORD charCountWritten = ExpandEnvironmentStringsW(input.c_str(), &result[0], charCount); 622 THROW_HR_IF(E_UNEXPECTED, charCount != charCountWritten); 623 624 if (result.back() == L'\0') 625 { 626 result.resize(result.size() - 1); 627 } 628 629 return result; 630 } 631 632 // Follow the rules at https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file to replace 633 // invalid characters in a candidate path part. 634 // Additionally, based on https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits 635 // limit the number of characters to 255. 636 std::string MakeSuitablePathPart(std::string_view candidate) 637 { 638 constexpr char replaceChar = '_'; 639 constexpr std::string_view illegalChars = R"(<>:"/\|?*)"; 640 constexpr size_t pathLengthLimit = 255; 641 642 // First, walk the string and replace illegal characters 643 std::string result; 644 result.reserve(candidate.size()); 645 646 ICUBreakIterator itr{ candidate, UBRK_CHARACTER }; 647 size_t resultBreakCount = 0; 648 649 while (itr.CurrentBreak() != UBRK_DONE && itr.CurrentOffset() < candidate.size() && resultBreakCount <= pathLengthLimit) 650 { 651 UChar32 current = itr.CurrentCodePoint(); 652 bool isIllegal = current < 32 || (current < 256 && illegalChars.find(static_cast<char>(current)) != std::string::npos); 653 654 int32_t offset = itr.CurrentBreak(); 655 int32_t nextOffset = itr.Next(); 656 657 // Don't allow a . at the end of a name 658 if (static_cast<size_t>(nextOffset) >= candidate.size()) 659 { 660 if (current == static_cast<UChar32>('.')) 661 { 662 isIllegal = true; 663 } 664 } 665 666 if (isIllegal) 667 { 668 result.append(1, replaceChar); 669 } 670 else 671 { 672 size_t count = (nextOffset == UBRK_DONE ? std::string::npos : static_cast<size_t>(nextOffset) - static_cast<size_t>(offset)); 673 result.append(candidate.substr(static_cast<size_t>(offset), count)); 674 } 675 676 ++resultBreakCount; 677 } 678 679 // If there are too many characters for a single path; switch to a hash. 680 // This should basically never happen, but if it does it will prevent collisions better. 681 if (resultBreakCount > pathLengthLimit) 682 { 683 return SHA256::ConvertToString(SHA256::ComputeHash(candidate)); 684 } 685 686 // Second, look for any newly formed illegal names. 687 // For now just error on these cases; they should not happen often. 688 for (const auto& illegalName : { 689 "."sv, "CON"sv, "PRN"sv, "AUX"sv, "NUL"sv, "COM1"sv, "COM2"sv, "COM3"sv, "COM4"sv, "COM5"sv, "COM6"sv, "COM7"sv, "COM8"sv, "COM9"sv, 690 "LPT1"sv, "LPT2"sv, "LPT3"sv, "LPT4"sv, "LPT5"sv, "LPT6"sv, "LPT7"sv, "LPT8"sv, "LPT9"sv }) 691 { 692 // Either equals the illegal name (starts with and same length) or starts with and the first character after is a . 693 if (CaseInsensitiveStartsWith(result, illegalName) && (result.size() == illegalName.size() || result[illegalName.size()] == '.')) 694 { 695 THROW_HR(E_INVALIDARG); 696 } 697 } 698 699 return result; 700 } 701 702 std::pair<std::string, std::filesystem::path> SplitFileNameFromURI(std::string_view uri) 703 { 704 std::filesystem::path filename = GetFileNameFromURI(uri); 705 return { std::string{ uri.substr(0, uri.size() - filename.u8string().size()) }, filename }; 706 } 707 708 std::filesystem::path GetFileNameFromURI(std::string_view uri) 709 { 710 winrt::Windows::Foundation::Uri winrtUri{ winrt::hstring{ ConvertToUTF16(uri) } }; 711 std::filesystem::path path{ static_cast<std::wstring_view>(winrtUri.Path()) }; 712 713 return path.filename(); 714 } 715 716 std::vector<std::string> SplitIntoWords(std::string_view input) 717 { 718 ICUBreakIterator itr{ input, UBRK_WORD }; 719 std::size_t currentOffset = 0; 720 721 std::vector<std::string> result; 722 while (itr.Next() != UBRK_DONE) 723 { 724 std::size_t nextOffset = itr.CurrentOffset(); 725 726 // Ignore spaces and punctuation, accept words and numbers 727 if (itr.CurrentRuleStatus() != UBRK_WORD_NONE) 728 { 729 auto wordSize = nextOffset - currentOffset; 730 result.emplace_back(input, currentOffset, wordSize); 731 } 732 733 currentOffset = nextOffset; 734 } 735 736 return result; 737 } 738 739 std::vector<std::string> SplitIntoLines(std::string_view input, size_t maximum) 740 { 741 std::size_t currentOffset = 0; 742 std::vector<std::string> result; 743 744 while (currentOffset < input.size() && (!maximum || result.size() < maximum)) 745 { 746 std::size_t nextOffset = input.find_first_of("\r\n", currentOffset); 747 if (nextOffset == std::string_view::npos) 748 { 749 nextOffset = input.size(); 750 } 751 752 if (nextOffset - currentOffset > 1) 753 { 754 result.emplace_back(input.substr(currentOffset, nextOffset - currentOffset)); 755 } 756 757 currentOffset = nextOffset + 1; 758 } 759 760 return result; 761 } 762 763 bool LimitOutputLines(std::vector<std::string>& lines, size_t lineWidth, size_t maximum) 764 { 765 size_t totalLines = 0; 766 size_t currentLine = 0; 767 bool result = false; 768 769 for (; currentLine < lines.size() && totalLines < maximum; ++currentLine) 770 { 771 size_t currentLineWidth = UTF8ColumnWidth(lines[currentLine]); 772 // If current line is empty, the cost is 1 line (0 + 1). 773 // If not, round up to the next line count (by rounding down through integer division after subtracting 1 + 1). 774 size_t currentLineActualLineCount = (currentLineWidth ? (currentLineWidth - 1) / lineWidth : 0) + 1; 775 776 // The current line may be too big to be the last line, or it may be just the right size but we will end up trimming 777 // additional lines. In either case, append an ellipsis to indicate that we trimmed the value. 778 size_t availableLines = maximum - totalLines; 779 if (currentLineActualLineCount > availableLines || 780 (currentLineActualLineCount == availableLines && currentLine != lines.size() - 1)) 781 { 782 size_t actualWidth = 0; 783 std::string trimmedLine = UTF8TrimRightToColumnWidth(lines[currentLine], (availableLines * lineWidth) - 1, actualWidth); 784 trimmedLine += "\xE2\x80\xA6"; // UTF8 encoding of ellipsis (�) character 785 lines[currentLine] = trimmedLine; 786 787 currentLineActualLineCount = availableLines; 788 result = true; 789 } 790 791 totalLines += currentLineActualLineCount; 792 } 793 794 // Drop any unprocessed lines 795 if (currentLine != lines.size()) 796 { 797 lines.resize(currentLine); 798 result = true; 799 } 800 801 return result; 802 } 803 804 std::string ConvertToHexString(const std::vector<uint8_t>& buffer, size_t byteCount) 805 { 806 if (byteCount && buffer.size() != byteCount) 807 { 808 THROW_HR_MSG(E_INVALIDARG, "ConvertToHexString: Invalid buffer size"); 809 } 810 811 std::string result(2 * buffer.size(), '\0'); 812 static constexpr std::array<char, 16> hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 813 814 for (size_t i = 0; i < buffer.size(); ++i) 815 { 816 result[2 * i] = hexChars[(buffer[i] >> 4) & 0xF]; 817 result[2 * i + 1] = hexChars[buffer[i] & 0xF]; 818 } 819 820 return result; 821 } 822 823 std::vector<uint8_t> ParseFromHexString(const std::string& value, size_t byteCount) 824 { 825 if ((byteCount && value.size() != (2 * byteCount)) || 826 (value.size() % 2)) 827 { 828 THROW_HR_MSG(E_INVALIDARG, "ParseFromHexString: Invalid value size"); 829 } 830 831 const char* valuePtr = value.c_str(); 832 std::vector<uint8_t> result; 833 result.resize(value.size() / 2); 834 835 for (size_t i = 0; i < result.size(); i++) 836 { 837 sscanf_s(valuePtr + 2 * i, "%02hhx", &result[i]); 838 } 839 840 return result; 841 } 842 843 template <typename StringLike> 844 static std::string JoinInternal(std::string_view separator, const std::vector<StringLike>& vector) 845 { 846 auto vectorSize = vector.size(); 847 if (vectorSize == 0) 848 { 849 return {}; 850 } 851 852 std::ostringstream ssJoin; 853 ssJoin << vector[0]; 854 for (size_t i = 1; i < vectorSize; ++i) 855 { 856 ssJoin << separator << vector[i]; 857 } 858 return ssJoin.str(); 859 } 860 861 LocIndString Join(LocIndView separator, const std::vector<LocIndString>& vector) 862 { 863 return LocIndString{ JoinInternal(separator, vector) }; 864 } 865 866 std::string Join(std::string_view separator, const std::vector<std::string>& vector) 867 { 868 return JoinInternal(separator, vector); 869 } 870 871 std::vector<std::string> Split(const std::string& input, char separator, bool trim) 872 { 873 std::vector<std::string> result; 874 size_t startIndex = 0; 875 size_t endIndex = 0; 876 877 while ((endIndex = input.find(separator, startIndex)) != std::string::npos) 878 { 879 std::string substring = input.substr(startIndex, endIndex - startIndex); 880 881 if (trim) 882 { 883 Utility::Trim(substring); 884 } 885 886 result.push_back(substring); 887 startIndex = endIndex + 1; 888 } 889 890 result.push_back(trim ? Utility::Trim(input.substr(startIndex)) : input.substr(startIndex)); 891 return result; 892 } 893 894 std::string_view ConvertBoolToString(bool value) 895 { 896 return value ? "true"sv : "false"sv; 897 } 898 899 std::string ConvertGuidToString(const GUID& value) 900 { 901 wchar_t buffer[40]; 902 THROW_HR_IF(E_UNEXPECTED, !StringFromGUID2(value, buffer, ARRAYSIZE(buffer))); 903 return ConvertToUTF8(buffer); 904 } 905 906 std::wstring CreateNewGuidNameWString() 907 { 908 GUID guid; 909 THROW_IF_FAILED(CoCreateGuid(&guid)); 910 911 wchar_t buffer[40]; 912 THROW_HR_IF(E_UNEXPECTED, StringFromGUID2(guid, buffer, ARRAYSIZE(buffer)) != 39); 913 914 return std::wstring{ &buffer[1], 36 }; 915 } 916 917 bool IsDwordFlagSet(const std::string& value) 918 { 919 if (std::empty(value)) 920 { 921 return false; 922 } 923 924 try 925 { 926 DWORD dwordValue = std::stoul(value); 927 928 // If the value is 0, then it is not set. 929 return dwordValue != 0; 930 } 931 catch (...) 932 { 933 return false; 934 } 935 } 936 937 size_t FindControlCodeToConvert(std::string_view input, size_t offset) 938 { 939 size_t nextControl = offset; 940 while (nextControl < input.size()) 941 { 942 char currentChar = input[nextControl]; 943 944 // Convert all low controls except tab, line feed and carriage return 945 if (currentChar >= 0 && currentChar < 0x20 && 946 currentChar != '\t' && 947 currentChar != '\n' && 948 currentChar != '\r') 949 { 950 break; 951 } 952 953 // Convert the Delete control 954 if (currentChar == 0x7F) 955 { 956 break; 957 } 958 959 ++nextControl; 960 } 961 962 return nextControl < input.size() ? nextControl : std::string::npos; 963 } 964 965 std::string ConvertControlCodesToPictures(std::string_view input) 966 { 967 std::string result; 968 size_t pos = 0; 969 970 while (pos < input.size()) 971 { 972 size_t nextControl = FindControlCodeToConvert(input, pos); 973 974 if (nextControl == std::string::npos) 975 { 976 // No more control codes found 977 result += input.substr(pos); 978 break; 979 } 980 else 981 { 982 result += input.substr(pos, nextControl - pos); 983 984 char currentChar = input[nextControl]; 985 986 if (currentChar >= 0 && currentChar < 0x20) 987 { 988 // ASCII 0x00 - 0x1F => UTF-8 0x2400 - 0x241F 989 // Then manually converted to UTF-8 since only the last character is affected 990 result += '\xE2'; 991 result += '\x90'; 992 result += ('\x80' + currentChar); 993 } 994 else if (currentChar == 0x7F) 995 { 996 // UTF-8 for control picture of DELETE 997 result += "\xE2\x90\xA1"; 998 } 999 1000 pos = nextControl + 1; 1001 } 1002 } 1003 1004 return result; 1005 } 1006 1007 std::string GetRandomString(size_t size) 1008 { 1009 static constexpr char chars[] = "0123456789abcdefghijklmnopqrstuvwxyz"; 1010 static std::default_random_engine randomEngine(std::random_device{}()); 1011 static std::uniform_int_distribution<long long> distribution(0, 35); 1012 1013 std::string result; 1014 result.resize(size); 1015 1016 for (size_t i = 0; i < size; i++) 1017 { 1018 result[i] = chars[distribution(randomEngine)]; 1019 } 1020 1021 return result; 1022 } 1023 }