winget-cli

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

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 }