master
h 1,356 lines 35.4 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 stringshared.h
8
9 Abstract:
10
11 This file contains shared string helper functions.
12
13 --*/
14
15 #pragma once
16 #include <algorithm>
17 #include <cctype>
18 #include <cwctype>
19 #include <set>
20 #include <vector>
21 #include <string>
22 #include <string_view>
23 #include <sstream>
24 #include <fstream>
25 #include <optional>
26 #include <gsl/gsl>
27 #include <format>
28 #include <source_location>
29 #include <type_traits>
30
31 #ifndef WIN32
32 #include <string.h>
33 #include "lxdef.h"
34 #include "lxwil.h"
35 #include "defs.h"
36 #else
37 #include "string.hpp"
38 #endif
39
40 #define STRING_TO_WIDE_STRING_INNER(_str) L##_str
41 #define STRING_TO_WIDE_STRING(_str) STRING_TO_WIDE_STRING_INNER(_str)
42
43 #define GUID_FORMAT_STRING "{%08x-%04hx-%04hx-%02x%02x-%02x%02x%02x%02x%02x%02x}"
44 #define GUID_SSCANF_STRING "%8x-%4hx-%4hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx"
45 #define GUID_BRACES_SSCANF_STRING "{" GUID_SSCANF_STRING "}"
46
47 #define MAC_ADDRESS_FORMAT_STRING "%02X%c%02X%c%02X%c%02X%c%02X%c%02X"
48
49 namespace wsl::shared::string {
50
51 using MacAddress = std::array<std::uint8_t, 6>;
52
53 inline unsigned int CopyToSpan(const std::string_view String, const gsl::span<gsl::byte> Span, size_t& Offset)
54 {
55 gsl::copy(as_bytes(gsl::make_span(String.data(), String.size())), Span.subspan(Offset));
56 Span[Offset + String.size()] = gsl::byte{0};
57 const auto PreviousOffset = gsl::narrow_cast<unsigned int>(Offset);
58 Offset += String.size() + 1;
59 return PreviousOffset;
60 }
61
62 template <class T>
63 inline bool EndsWith(const std::basic_string<T>& String, const std::basic_string_view<T> Suffix)
64 {
65 if (Suffix.size() > String.size())
66 {
67 return false;
68 }
69
70 return std::equal(Suffix.rbegin(), Suffix.rend(), String.rbegin());
71 }
72
73 // Lowercases ASCII 'A'-'Z' only, leaving every other code unit untouched. Unlike std::tolower this is
74 // locale-independent (no Turkish-'I' surprises) and has no signed-char UB, which is what you want when
75 // normalizing ASCII protocol tokens such as buildx CSV keys.
76 template <class T>
77 inline std::basic_string<T> AsciiToLower(const std::basic_string_view<T>& String)
78 {
79 std::basic_string<T> Result(String);
80 for (auto& Ch : Result)
81 {
82 if (Ch >= static_cast<T>('A') && Ch <= static_cast<T>('Z'))
83 {
84 Ch = static_cast<T>(Ch - static_cast<T>('A') + static_cast<T>('a'));
85 }
86 }
87
88 return Result;
89 }
90
91 // Trims leading and trailing ASCII whitespace (space, tab, CR, LF, vertical tab, form feed), matching
92 // the ASCII subset of Go's strings.TrimSpace. Returns a view into the input, so the input must outlive
93 // the result. The stdlib has no trim, so this centralizes the find_first/last_not_of idiom.
94 template <class T>
95 inline std::basic_string_view<T> TrimAscii(const std::basic_string_view<T>& String)
96 {
97 constexpr T Whitespace[] = {
98 static_cast<T>(' '), static_cast<T>('\t'), static_cast<T>('\r'), static_cast<T>('\n'), static_cast<T>('\v'), static_cast<T>('\f'), static_cast<T>('\0')};
99
100 const auto First = String.find_first_not_of(Whitespace);
101 if (First == std::basic_string_view<T>::npos)
102 {
103 return {};
104 }
105
106 const auto Last = String.find_last_not_of(Whitespace);
107 return String.substr(First, Last - First + 1);
108 }
109
110 template <class T, class TInput>
111 inline std::basic_string<T> Join(const std::vector<TInput>& Input, T Separator)
112 {
113 std::basic_stringstream<T> Out;
114 for (size_t Index = 0; Index < Input.size(); Index += 1)
115 {
116 if (Index != 0)
117 {
118 Out << Separator;
119 }
120
121 Out << Input[Index];
122 }
123
124 return Out.str();
125 }
126
127 template <class T>
128 inline std::vector<std::basic_string<T>> Split(const std::basic_string<T>& String, T Separator)
129 {
130 std::vector<std::basic_string<T>> Output;
131 std::basic_istringstream<T> Input(String);
132 std::basic_string<T> Entry;
133 while (std::getline(Input, Entry, Separator))
134 {
135 if (!Entry.empty())
136 {
137 Output.emplace_back(std::move(Entry));
138 }
139 }
140
141 return Output;
142 }
143
144 template <class T>
145 inline std::vector<std::basic_string_view<T>> SplitPreserveEmpty(const std::basic_string_view<T> String, T Separator)
146 {
147 std::vector<std::basic_string_view<T>> Output;
148 size_t Start = 0;
149 while (Start <= String.size())
150 {
151 const auto End = String.find(Separator, Start);
152 if (End == std::basic_string_view<T>::npos)
153 {
154 Output.emplace_back(String.substr(Start));
155 break;
156 }
157
158 Output.emplace_back(String.substr(Start, End - Start));
159 Start = End + 1;
160 }
161
162 return Output;
163 }
164
165 template <class T>
166 inline std::vector<std::basic_string<T>> SplitByMultipleSeparators(const std::basic_string<T>& String, const std::basic_string<T>& Separators)
167 {
168 std::vector<std::basic_string<T>> Output;
169 size_t CurrentIndex = 0;
170
171 while (true)
172 {
173 CurrentIndex = String.find_first_not_of(Separators, CurrentIndex);
174 if (CurrentIndex == std::string::npos)
175 {
176 break;
177 }
178
179 const size_t NextSeparator = String.find_first_of(Separators, CurrentIndex);
180
181 if (NextSeparator == std::string::npos)
182 {
183 Output.emplace_back(std::move(String.substr(CurrentIndex)));
184 break;
185 }
186 else
187 {
188 Output.emplace_back(std::move(String.substr(CurrentIndex, NextSeparator - CurrentIndex)));
189 CurrentIndex = NextSeparator;
190 }
191 }
192
193 return Output;
194 }
195
196 // Splits a single CSV record into fields using the grammar Go's encoding/csv applies to one record
197 // (which docker buildx relies on via go-csvvalue), so a spec is parsed the way buildx would:
198 // - fields are separated by commas;
199 // - a field may be wrapped in double quotes, in which case a comma is a literal character and a
200 // doubled quote ("") is a single literal quote;
201 // - an unquoted field may not contain a double quote (Go's non-lazy ErrBareQuote).
202 // Returns std::nullopt when the record is malformed: an unterminated quoted field, text immediately
203 // after a closing quote, or a bare quote in an unquoted field.
204 //
205 // Deviation from Go/RFC 4180: this parses exactly one record. Go would treat an unquoted CR/LF as a
206 // record separator; here CR/LF are always ordinary field characters (never a record separator), which
207 // is what a single-line command-line spec needs.
208 template <class T>
209 inline std::optional<std::vector<std::basic_string<T>>> SplitCsvFields(const std::basic_string<T>& Record)
210 {
211 constexpr T Quote = static_cast<T>('"');
212 constexpr T Comma = static_cast<T>(',');
213
214 std::vector<std::basic_string<T>> Fields;
215 std::basic_string<T> Field;
216 const size_t Length = Record.size();
217 size_t Index = 0;
218
219 while (true)
220 {
221 Field.clear();
222 if (Index < Length && Record[Index] == Quote)
223 {
224 ++Index;
225 bool Closed = false;
226 while (Index < Length)
227 {
228 if (Record[Index] == Quote)
229 {
230 // A doubled quote inside a quoted field is a single literal quote.
231 if (Index + 1 < Length && Record[Index + 1] == Quote)
232 {
233 Field.push_back(Quote);
234 Index += 2;
235 continue;
236 }
237
238 ++Index;
239 Closed = true;
240 break;
241 }
242
243 Field.push_back(Record[Index]);
244 ++Index;
245 }
246
247 if (!Closed)
248 {
249 return std::nullopt; // unterminated quoted field
250 }
251
252 // After a closing quote only a comma (end of field) or end of record is valid.
253 if (Index < Length && Record[Index] != Comma)
254 {
255 return std::nullopt;
256 }
257 }
258 else
259 {
260 while (Index < Length && Record[Index] != Comma)
261 {
262 // Go (non-lazy) rejects a bare double quote in an unquoted field (ErrBareQuote).
263 if (Record[Index] == Quote)
264 {
265 return std::nullopt;
266 }
267
268 Field.push_back(Record[Index]);
269 ++Index;
270 }
271 }
272
273 Fields.push_back(Field);
274 if (Index >= Length)
275 {
276 break;
277 }
278
279 ++Index; // consume the ',' and start the next field
280 }
281
282 return Fields;
283 }
284
285 // CSV-escapes a single field for round-tripping through JoinCsvFields/SplitCsvFields: if the field
286 // contains a comma, a double quote, CR, LF, or a leading/trailing space it is wrapped in double quotes
287 // with each embedded quote doubled; otherwise it is returned unchanged. This is not a byte-for-byte
288 // match of Go's encoding/csv writer (which quotes only a leading space and leaves an empty field
289 // unquoted); it is the minimal quoting needed for every field to parse back via SplitCsvFields.
290 template <class T>
291 inline std::basic_string<T> CsvEscapeField(const std::basic_string<T>& Field)
292 {
293 constexpr T Quote = static_cast<T>('"');
294 const T Special[] = {static_cast<T>(','), Quote, static_cast<T>('\r'), static_cast<T>('\n'), static_cast<T>(0)};
295
296 const bool NeedsQuote = Field.find_first_of(Special) != std::basic_string<T>::npos ||
297 (!Field.empty() && (Field.front() == static_cast<T>(' ') || Field.back() == static_cast<T>(' ')));
298 if (!NeedsQuote)
299 {
300 return Field;
301 }
302
303 std::basic_string<T> Result;
304 Result.reserve(Field.size() + 2);
305 Result.push_back(Quote);
306 for (const T Ch : Field)
307 {
308 if (Ch == Quote)
309 {
310 Result.push_back(Quote);
311 }
312
313 Result.push_back(Ch);
314 }
315
316 Result.push_back(Quote);
317 return Result;
318 }
319
320 // Joins fields into a single CSV record, escaping each field as needed (see CsvEscapeField). The
321 // result parses back to the original fields via SplitCsvFields.
322 template <class T>
323 inline std::basic_string<T> JoinCsvFields(const std::vector<std::basic_string<T>>& Fields)
324 {
325 std::basic_string<T> Record;
326 for (size_t Index = 0; Index < Fields.size(); ++Index)
327 {
328 if (Index != 0)
329 {
330 Record.push_back(static_cast<T>(','));
331 }
332
333 Record += CsvEscapeField(Fields[Index]);
334 }
335
336 return Record;
337 }
338
339 inline const char* FromSpan(gsl::span<gsl::byte> Span, size_t Offset = 0)
340 {
341 THROW_INVALID_ARG_IF(Span.size() < Offset);
342
343 Span = Span.subspan(Offset);
344 const std::string_view String{reinterpret_cast<const char*>(Span.data()), Span.size()};
345 const auto End = String.find('\0');
346 THROW_INVALID_ARG_IF(End == String.npos);
347
348 return String.data();
349 }
350
351 template <typename T>
352 inline const char* FromMessageBuffer(const gsl::span<gsl::byte>& Span)
353 {
354 return FromSpan(Span, offsetof(T, Buffer));
355 }
356
357 inline std::vector<const char*> StringPointersFromArray(const std::vector<std::string>& Strings, bool insertNull)
358 {
359 std::vector<const char*> result(Strings.size());
360 std::transform(Strings.begin(), Strings.end(), result.begin(), [](const std::string& str) { return str.c_str(); });
361
362 if (insertNull)
363 {
364 result.push_back(nullptr);
365 }
366
367 return result;
368 }
369
370 inline std::vector<std::string> ArrayFromSpan(gsl::span<const gsl::byte> Span, size_t Offset = 0)
371 {
372 THROW_INVALID_ARG_IF(Span.size() < Offset);
373
374 Span = Span.subspan(Offset);
375
376 std::vector<std::string> Result;
377
378 auto it = Span.begin();
379
380 auto readSize = [&]() {
381 THROW_INVALID_ARG_IF(Span.end() - it < sizeof(int32_t));
382
383 auto size = *reinterpret_cast<const int32_t*>(&*it);
384 it += sizeof(int32_t);
385
386 return size;
387 };
388
389 while (true)
390 {
391 auto size = readSize();
392 if (size == -1)
393 {
394 break;
395 }
396
397 THROW_INVALID_ARG_IF(size < 0);
398 THROW_INVALID_ARG_IF(size > Span.end() - it);
399
400 const char* begin = reinterpret_cast<const char*>(&*it);
401 Result.emplace_back(begin, size);
402
403 it += size;
404 }
405
406 return Result;
407 }
408
409 constexpr auto c_defaultHostName = "localhost";
410
411 inline std::string CleanHostname(const std::string_view Hostname)
412 {
413 // A valid Linux hostname:
414 // - is composed of alphanumeric characters, hyphens, and up to one dot
415 // - cannot start or end with a hyphen or a dot
416 // - cannot have a hyphen follow a dot or another hyphen
417 // - cannot be empty
418 // - cannot be longer than 64 chars
419 bool dot = false;
420 std::string result;
421 for (const auto e : Hostname)
422 {
423 if (e == '.')
424 {
425 // There can be only one '.', it cannot be the first character, and it cannot follow a '-'.
426 if (dot || result.empty() || result.back() == '-')
427 {
428 continue;
429 }
430
431 dot = true;
432 result += e;
433 }
434 else if (e == '-')
435 {
436 // A '-' cannot be the first character, or follow another '-' or a '.'.
437 if (result.empty() || result.back() == '-' || result.back() == '.')
438 {
439 continue;
440 }
441
442 result += e;
443 }
444 else if (isalnum(e))
445 {
446 result += e;
447 }
448 }
449
450 if (result.size() > 64)
451 {
452 result.resize(64);
453 }
454
455 while (!result.empty() && (result.back() == '.' || result.back() == '-'))
456 {
457 result.pop_back();
458 }
459
460 if (result.empty())
461 {
462 result = c_defaultHostName;
463 }
464
465 return result;
466 }
467
468 template <typename T>
469 inline size_t Compare(const std::basic_string_view<T> String1, const std::basic_string_view<T> String2, bool CaseInsensitive = false)
470 {
471 // This method counts the number of matching characters at the beginning of two strings.
472 std::basic_string_view<T> firstString;
473 std::basic_string_view<T> secondString;
474 if (String1.size() <= String2.size())
475 {
476 firstString = String1;
477 secondString = String2;
478 }
479 else
480 {
481 firstString = String2;
482 secondString = String1;
483 }
484
485 if (CaseInsensitive)
486 {
487 std::locale loc{"C"};
488 auto result = std::mismatch(firstString.begin(), firstString.end(), secondString.begin(), [loc](T a, T b) {
489 return (std::tolower(a, loc) == std::tolower(b, loc));
490 });
491
492 return (result.first - firstString.begin());
493 }
494 else
495 {
496 auto result = std::mismatch(firstString.begin(), firstString.end(), secondString.begin());
497 return (result.first - firstString.begin());
498 }
499 }
500
501 inline bool IsEqual(const std::string_view String1, const std::string_view String2, bool CaseInsensitive = false)
502 {
503 if (String1.size() != String2.size())
504 {
505 return false;
506 }
507
508 return (Compare(String1, String2, CaseInsensitive) == String1.size());
509 }
510
511 inline bool IsEqual(const std::wstring_view String1, const std::wstring_view String2, bool CaseInsensitive = false)
512 {
513 if (String1.size() != String2.size())
514 {
515 return false;
516 }
517
518 return (Compare(String1, String2, CaseInsensitive) == String1.size());
519 }
520
521 template <class T>
522 inline bool IsEmptyOrWhitespace(const std::basic_string_view<T> String)
523 {
524 return String.empty() || std::all_of(String.begin(), String.end(), [](T Ch) {
525 if constexpr (std::is_same_v<T, wchar_t>)
526 {
527 return std::iswspace(static_cast<wint_t>(Ch));
528 }
529 else
530 {
531 return std::isspace(static_cast<unsigned char>(Ch));
532 }
533 });
534 }
535
536 // Parses a boolean from a string. By default only "1"/"0" and "true"/"false"
537 // (case-insensitive) are recognized. When AllowExtendedForms is true the single
538 // character forms "t"/"f" (case-insensitive) are also accepted, matching the full
539 // set understood by Go's strconv.ParseBool (and therefore the Docker CLI).
540 template <typename T>
541 inline std::optional<bool> ParseBool(const T* String, bool AllowExtendedForms = false)
542 {
543 if (!String)
544 {
545 return {};
546 }
547
548 const std::basic_string_view<T> StringView(String);
549 constexpr T One[] = {T('1'), T('\0')};
550 constexpr T True[] = {T('t'), T('r'), T('u'), T('e'), T('\0')};
551 constexpr T ShortTrue[] = {T('t'), T('\0')};
552 if (IsEqual(StringView, One) || IsEqual(StringView, True, true) || (AllowExtendedForms && IsEqual(StringView, ShortTrue, true)))
553 {
554 return true;
555 }
556
557 constexpr T Zero[] = {T('0'), T('\0')};
558 constexpr T False[] = {T('f'), T('a'), T('l'), T('s'), T('e'), T('\0')};
559 constexpr T ShortFalse[] = {T('f'), T('\0')};
560 if (IsEqual(StringView, Zero) || IsEqual(StringView, False, true) || (AllowExtendedForms && IsEqual(StringView, ShortFalse, true)))
561 {
562 return false;
563 }
564
565 return {};
566 }
567
568 template <typename T>
569 inline uint64_t ToUInt64(const T* String, T** End = nullptr, int Base = 10);
570
571 template <>
572 inline uint64_t ToUInt64<char>(const char* String, char** End, int Base)
573 {
574 return std::strtoull(String, End, Base);
575 }
576
577 template <>
578 inline uint64_t ToUInt64<wchar_t>(const wchar_t* String, wchar_t** End, int Base)
579 {
580 return std::wcstoull(String, End, Base);
581 }
582
583 template <typename T>
584 inline std::optional<uint64_t> ParseMemorySize(const T* String)
585 {
586 if (!String)
587 {
588 return {};
589 }
590
591 T* End{};
592 uint64_t Value = ToUInt64(String, &End, 10);
593 if (Value == 0)
594 {
595 if (String[0] != T('0') || End != String + 1)
596 {
597 return {};
598 }
599 }
600
601 const std::basic_string_view<T> Remainder(End);
602 if (Remainder.empty())
603 {
604 return Value;
605 }
606 else if (Remainder.size() > 2)
607 {
608 return {};
609 }
610
611 constexpr T Bytes[] = {T('B'), T('\0')};
612 constexpr T Kilobytes[] = {T('K'), T('B'), T('\0')};
613 constexpr T Megabytes[] = {T('M'), T('B'), T('\0')};
614 constexpr T Gigabytes[] = {T('G'), T('B'), T('\0')};
615 constexpr T Terabytes[] = {T('T'), T('B'), T('\0')};
616 const std::array<std::pair<std::basic_string_view<T>, uint64_t>, 5> Units{
617 std::make_pair(Bytes, 1ULL),
618 std::make_pair(Kilobytes, 1ULL << 10),
619 std::make_pair(Megabytes, 1ULL << 20),
620 std::make_pair(Gigabytes, 1ULL << 30),
621 std::make_pair(Terabytes, 1ULL << 40)};
622
623 for (const auto& [Suffix, Factor] : Units)
624 {
625 if ((Remainder == Suffix.substr(0, 1)) || (Remainder == Suffix))
626 {
627 return Value * Factor;
628 }
629 }
630
631 return {};
632 }
633
634 inline bool StartsWith(const std::string_view String, const std::string_view Prefix, bool CaseInsensitive = false)
635 {
636 if (String.size() < Prefix.size())
637 {
638 return false;
639 }
640
641 return (Compare(String.substr(0, Prefix.size()), Prefix, CaseInsensitive) == Prefix.size());
642 }
643
644 inline bool StartsWith(const std::wstring_view String, const std::wstring_view Prefix, bool CaseInsensitive = false)
645 {
646 if (String.size() < Prefix.size())
647 {
648 return false;
649 }
650
651 return (Compare(String.substr(0, Prefix.size()), Prefix, CaseInsensitive) == Prefix.size());
652 }
653
654 enum GuidToStringFlags
655 {
656 None = 0,
657 AddBraces = 1,
658 Uppercase = 2
659 };
660
661 template <typename TChar>
662 inline std::basic_string<TChar> GuidToString(const GUID& guid, GuidToStringFlags flags = GuidToStringFlags::AddBraces)
663 {
664 // N.B. std::string guarantees that the null terminator is always allocated:
665 // https://en.cppreference.com/w/cpp/string/basic_string/data
666 std::basic_string<TChar> output(38, '\0');
667
668 if constexpr (std::is_same_v<TChar, char>)
669 {
670 snprintf(
671 output.data(),
672 output.size() + 1,
673 GUID_FORMAT_STRING,
674 static_cast<unsigned int>(guid.Data1),
675 guid.Data2,
676 guid.Data3,
677 guid.Data4[0],
678 guid.Data4[1],
679 guid.Data4[2],
680 guid.Data4[3],
681 guid.Data4[4],
682 guid.Data4[5],
683 guid.Data4[6],
684 guid.Data4[7]);
685 }
686 else if constexpr (std::is_same_v<TChar, wchar_t>)
687 {
688 swprintf(
689 output.data(),
690 output.size() + 1,
691 STRING_TO_WIDE_STRING(GUID_FORMAT_STRING),
692 static_cast<unsigned int>(guid.Data1),
693 guid.Data2,
694 guid.Data3,
695 guid.Data4[0],
696 guid.Data4[1],
697 guid.Data4[2],
698 guid.Data4[3],
699 guid.Data4[4],
700 guid.Data4[5],
701 guid.Data4[6],
702 guid.Data4[7]);
703 }
704 else
705 {
706 static_assert(sizeof(TChar) != sizeof(TChar), "Unsupported character type");
707 }
708
709 if (WI_IsFlagClear(flags, GuidToStringFlags::AddBraces))
710 {
711 output.erase(output.begin());
712 output.pop_back();
713 }
714
715 if (WI_IsFlagSet(flags, GuidToStringFlags::Uppercase))
716 {
717 std::transform(output.begin(), output.end(), output.begin(), toupper);
718 }
719
720 return output;
721 }
722
723 template <typename TChar>
724 inline std::optional<GUID> ToGuid(const TChar* string, std::optional<size_t> length = {})
725 {
726 if (!string)
727 {
728 return {};
729 }
730
731 if (!length.has_value())
732 {
733 length = std::basic_string<TChar>{string}.size();
734 }
735
736 GUID guid;
737 int result{};
738 if constexpr (std::is_same_v<TChar, char>)
739 {
740 if (length.value() == 38 && string[0] == '{' && string[37] == '}')
741 {
742 result = sscanf(
743 string,
744 GUID_BRACES_SSCANF_STRING,
745 &guid.Data1,
746 &guid.Data2,
747 &guid.Data3,
748 &guid.Data4[0],
749 &guid.Data4[1],
750 &guid.Data4[2],
751 &guid.Data4[3],
752 &guid.Data4[4],
753 &guid.Data4[5],
754 &guid.Data4[6],
755 &guid.Data4[7]);
756 }
757 else if (length.value() == 36)
758 {
759 result = sscanf(
760 string,
761 GUID_SSCANF_STRING,
762 &guid.Data1,
763 &guid.Data2,
764 &guid.Data3,
765 &guid.Data4[0],
766 &guid.Data4[1],
767 &guid.Data4[2],
768 &guid.Data4[3],
769 &guid.Data4[4],
770 &guid.Data4[5],
771 &guid.Data4[6],
772 &guid.Data4[7]);
773 }
774 }
775 else if constexpr (std::is_same_v<TChar, wchar_t>)
776 {
777 if (length.value() == 38 && string[0] == '{' && string[37] == '}')
778 {
779 result = swscanf(
780 string,
781 STRING_TO_WIDE_STRING(GUID_BRACES_SSCANF_STRING),
782 &guid.Data1,
783 &guid.Data2,
784 &guid.Data3,
785 &guid.Data4[0],
786 &guid.Data4[1],
787 &guid.Data4[2],
788 &guid.Data4[3],
789 &guid.Data4[4],
790 &guid.Data4[5],
791 &guid.Data4[6],
792 &guid.Data4[7]);
793 }
794 else if (length.value() == 36)
795 {
796 result = swscanf(
797 string,
798 STRING_TO_WIDE_STRING(GUID_SSCANF_STRING),
799 &guid.Data1,
800 &guid.Data2,
801 &guid.Data3,
802 &guid.Data4[0],
803 &guid.Data4[1],
804 &guid.Data4[2],
805 &guid.Data4[3],
806 &guid.Data4[4],
807 &guid.Data4[5],
808 &guid.Data4[6],
809 &guid.Data4[7]);
810 }
811 }
812 else
813 {
814 static_assert(sizeof(TChar) != sizeof(TChar), "Unsupported character type");
815 }
816
817 if (result != 11)
818 {
819 return {};
820 }
821
822 return guid;
823 }
824
825 template <typename TChar>
826 inline std::optional<GUID> ToGuid(const std::basic_string_view<TChar> string)
827 {
828 return ToGuid(string.data(), string.size());
829 }
830
831 template <typename TChar>
832 inline std::optional<GUID> ToGuid(const std::basic_string<TChar>& string)
833 {
834 return ToGuid(string.data(), string.size());
835 }
836
837 template <typename TChar, typename TPath>
838 inline std::basic_string<TChar> ReadFile(const TPath* path)
839 {
840 std::basic_ifstream<TChar> file;
841 file.exceptions(std::ios::badbit | std::ios::failbit);
842
843 try
844 {
845 file.open(path);
846 return std::basic_string<TChar>{std::istreambuf_iterator<TChar>(file), {}};
847 }
848 catch (...)
849 {
850 THROW_LAST_ERROR();
851 }
852 }
853
854 inline std::wstring MultiByteToWide(const char* string)
855 {
856
857 #ifdef WIN32
858
859 // This uses MultiByteToWideChar which gets the desired CP_UTF8 behavior
860 return wsl::windows::common::string::MultiByteToWide(string);
861
862 #else
863
864 if (!string)
865 {
866 return {};
867 }
868
869 std::mbstate_t state{};
870 size_t size = std::mbsrtowcs(nullptr, &string, 0, &state);
871 THROW_LAST_ERROR_IF(size == -1);
872
873 if (size == 0)
874 {
875 return {};
876 }
877
878 std::wstring buffer(size, L'\0');
879 std::mbsrtowcs(buffer.data(), &string, size, &state);
880 return buffer;
881
882 #endif // WIN32
883 }
884
885 inline std::wstring MultiByteToWide(const std::string& string)
886 {
887 return MultiByteToWide(string.c_str());
888 }
889
890 inline std::string WideToMultiByte(const wchar_t* string)
891 {
892
893 #ifdef WIN32
894
895 // This uses WideCharToMultiByte which gets the desired CP_UTF8 behavior
896 return wsl::windows::common::string::WideToMultiByte(string);
897
898 #else
899
900 if (!string)
901 {
902 return {};
903 }
904
905 std::mbstate_t state{};
906 size_t size = std::wcsrtombs(nullptr, &string, 0, &state);
907 THROW_LAST_ERROR_IF(size == -1);
908
909 if (size == 0)
910 {
911 return {};
912 }
913
914 std::string buffer(size, '\0');
915 std::wcsrtombs(buffer.data(), &string, size, &state);
916 return buffer;
917
918 #endif // WIN32
919 }
920
921 inline std::string WideToMultiByte(const std::wstring& string)
922 {
923 return WideToMultiByte(string.c_str());
924 }
925
926 template <typename T>
927 inline uint8_t ParseNibble(T HexDigit)
928 {
929 // Clearing bit 0x20 will turn a-f to A-F.
930 return (HexDigit >= '0' && HexDigit <= '9') ? (HexDigit - '0') : ((HexDigit & ~0x20) - 'A' + 10);
931 }
932
933 template <typename T>
934 inline std::optional<MacAddress> ParseMacAddressNoThrow(const std::basic_string<T>& Input, T Separator = '\0')
935 {
936 if (Input.size() != 17)
937 {
938 return {};
939 }
940
941 if (Separator == '\0')
942 {
943 Separator = Input[2];
944 if (Separator != '-' && Separator != ':')
945 {
946 return {};
947 }
948 }
949
950 MacAddress result;
951 for (auto octet = 0; octet < 6; octet++)
952 {
953 size_t index = octet * 3;
954 if (!std::iswxdigit(Input[index]) || !std::iswxdigit(Input[index + 1]))
955 {
956 return {};
957 }
958
959 if (octet < 5 && Input[index + 2] != Separator)
960 {
961 return {};
962 }
963
964 result[octet] = ParseNibble(Input[index]) * 16 + ParseNibble(Input[index + 1]);
965 }
966
967 return result;
968 }
969
970 template <typename T>
971 inline MacAddress ParseMacAddress(const std::basic_string<T>& Input, T Separator = '\0')
972 {
973 auto result = ParseMacAddressNoThrow(Input, Separator);
974
975 #ifdef WIN32
976 THROW_HR_IF(E_INVALIDARG, !result.has_value());
977 #else
978 THROW_ERRNO_IF(EINVAL, !result.has_value());
979 #endif
980
981 return result.value();
982 }
983
984 template <typename TChar>
985 inline std::basic_string<TChar> FormatMacAddress(const MacAddress& input, TChar separator)
986 {
987 std::basic_string<TChar> output(17, '\0');
988
989 if constexpr (std::is_same_v<TChar, char>)
990 {
991 snprintf(
992 output.data(),
993 output.size() + 1,
994 MAC_ADDRESS_FORMAT_STRING,
995 input[0],
996 separator,
997 input[1],
998 separator,
999 input[2],
1000 separator,
1001 input[3],
1002 separator,
1003 input[4],
1004 separator,
1005 input[5]);
1006 }
1007 else if constexpr (std::is_same_v<TChar, wchar_t>)
1008 {
1009 swprintf(
1010 output.data(),
1011 output.size() + 1,
1012 STRING_TO_WIDE_STRING(MAC_ADDRESS_FORMAT_STRING),
1013 input[0],
1014 separator,
1015 input[1],
1016 separator,
1017 input[2],
1018 separator,
1019 input[3],
1020 separator,
1021 input[4],
1022 separator,
1023 input[5]);
1024 }
1025 else
1026 {
1027 static_assert(sizeof(TChar) != sizeof(TChar), "Unsupported character type");
1028 }
1029
1030 return output;
1031 }
1032
1033 struct CaseInsensitiveCompare
1034 {
1035 bool operator()(const std::string& left, const std::string& right) const
1036 {
1037 return _stricmp(left.c_str(), right.c_str()) < 0;
1038 }
1039
1040 bool operator()(const char* left, const char* right) const
1041 {
1042 return _stricmp(left, right) < 0;
1043 }
1044
1045 bool operator()(const wchar_t* left, const wchar_t* right) const
1046 {
1047 return _wcsicmp(left, right) < 0;
1048 }
1049
1050 bool operator()(const std::wstring& left, const std::wstring& right) const
1051 {
1052 return _wcsicmp(left.c_str(), right.c_str()) < 0;
1053 }
1054 };
1055
1056 template <typename TChar>
1057 inline std::basic_string<TChar> Trim(const std::basic_string<TChar>& input)
1058 {
1059 constexpr TChar whitespace[] = {TChar(' '), TChar('\t'), TChar('\n'), TChar('\r'), TChar('\f'), TChar('\v'), TChar('\0')};
1060 const auto first = input.find_first_not_of(whitespace);
1061 if (first == std::basic_string<TChar>::npos)
1062 {
1063 return {};
1064 }
1065
1066 const auto last = input.find_last_not_of(whitespace);
1067 return input.substr(first, last - first + 1);
1068 }
1069
1070 template <typename TChar>
1071 inline std::basic_string<TChar> UnescapeShell(const std::basic_string<TChar>& input)
1072 {
1073 enum class Quote
1074 {
1075 None,
1076 Single,
1077 Double
1078 };
1079
1080 Quote quote = Quote::None;
1081 std::basic_string<TChar> output;
1082 output.reserve(input.size());
1083
1084 for (size_t index = 0; index < input.size(); index += 1)
1085 {
1086 const auto current = input[index];
1087 if (quote == Quote::Single)
1088 {
1089 if (current == TChar('\''))
1090 {
1091 quote = Quote::None;
1092 }
1093 else
1094 {
1095 output.push_back(current);
1096 }
1097
1098 continue;
1099 }
1100
1101 if (current == TChar('\''))
1102 {
1103 if (quote == Quote::Double)
1104 {
1105 output.push_back(current);
1106 }
1107 else
1108 {
1109 quote = Quote::Single;
1110 }
1111
1112 continue;
1113 }
1114
1115 if (current == TChar('"'))
1116 {
1117 quote = (quote == Quote::Double) ? Quote::None : Quote::Double;
1118 continue;
1119 }
1120
1121 if (current != TChar('\\'))
1122 {
1123 output.push_back(current);
1124 continue;
1125 }
1126
1127 if (++index == input.size())
1128 {
1129 // return the original string if the escape is invalid.
1130 return input;
1131 }
1132
1133 const auto escaped = input[index];
1134 if (quote == Quote::None)
1135 {
1136 // "\\\n" out of escape means continue the line.
1137 if (escaped != TChar('\n'))
1138 {
1139 output.push_back(escaped);
1140 }
1141 }
1142 else if (escaped == TChar('"') || escaped == TChar('\\') || escaped == TChar('$') || escaped == TChar('`'))
1143 {
1144 output.push_back(escaped);
1145 }
1146 else if (escaped != TChar('\n'))
1147 {
1148 output.push_back(current);
1149 output.push_back(escaped);
1150 }
1151 }
1152
1153 // return the original string if the escape is invalid.
1154 return (quote == Quote::None) ? output : input;
1155 }
1156
1157 } // namespace wsl::shared::string
1158
1159 template <>
1160 struct std::formatter<std::wstring, char>
1161 {
1162 template <typename TCtx>
1163 static constexpr auto parse(TCtx& ctx)
1164 {
1165 return ctx.begin();
1166 }
1167
1168 template <typename TCtx>
1169 auto format(const std::wstring& str, TCtx& ctx) const
1170 {
1171 return std::format_to(ctx.out(), "{}", wsl::shared::string::WideToMultiByte(str));
1172 }
1173 };
1174
1175 template <>
1176 struct std::formatter<const wchar_t*, char>
1177 {
1178 template <typename TCtx>
1179 static constexpr auto parse(TCtx& ctx)
1180 {
1181 return ctx.begin();
1182 }
1183
1184 template <typename TCtx>
1185 auto format(const wchar_t* str, TCtx& ctx) const
1186 {
1187 return std::format_to(ctx.out(), "{}", wsl::shared::string::WideToMultiByte(str));
1188 }
1189 };
1190
1191 template <std::size_t N>
1192 struct std::formatter<wchar_t[N], char>
1193 {
1194 template <typename TCtx>
1195 static constexpr auto parse(TCtx& ctx)
1196 {
1197 return ctx.begin();
1198 }
1199
1200 template <typename TCtx>
1201 auto format(const wchar_t str[N], TCtx& ctx) const
1202 {
1203 return std::format_to(ctx.out(), "{}", wsl::shared::string::WideToMultiByte(str));
1204 }
1205 };
1206
1207 template <>
1208 struct std::formatter<std::source_location, char>
1209 {
1210 template <typename TCtx>
1211 static constexpr auto parse(TCtx& ctx)
1212 {
1213 return ctx.begin();
1214 }
1215
1216 template <typename TCtx>
1217 auto format(const std::source_location& location, TCtx& ctx) const
1218 {
1219 return std::format_to(ctx.out(), "{}[{}:{}]", location.function_name(), location.file_name(), location.line());
1220 }
1221 };
1222
1223 template <>
1224 struct std::formatter<std::source_location, wchar_t>
1225 {
1226 template <typename TCtx>
1227 static constexpr auto parse(TCtx& ctx)
1228 {
1229 return ctx.begin();
1230 }
1231
1232 template <typename TCtx>
1233 auto format(const std::source_location& location, TCtx& ctx) const
1234 {
1235 return std::format_to(ctx.out(), L"{}[{}:{}]", location.function_name(), location.file_name(), location.line());
1236 }
1237 };
1238
1239 // char -> wchar_t formatting is only used by the Windows components. libc++ (used to
1240 // build the Linux components) now provides these as deleted specializations per C++23
1241 // [format.formatter.spec], which would collide, so restrict them to Windows.
1242 #ifdef WIN32
1243
1244 template <>
1245 struct std::formatter<char*, wchar_t>
1246 {
1247 template <typename TCtx>
1248 static constexpr auto parse(TCtx& ctx)
1249 {
1250 return ctx.begin();
1251 }
1252
1253 template <typename TCtx>
1254 auto format(const char* str, TCtx& ctx) const
1255 {
1256 return std::format_to(ctx.out(), L"{}", wsl::shared::string::MultiByteToWide(str));
1257 }
1258 };
1259
1260 template <>
1261 struct std::formatter<const char*, wchar_t>
1262 {
1263 template <typename TCtx>
1264 static constexpr auto parse(TCtx& ctx)
1265 {
1266 return ctx.begin();
1267 }
1268
1269 template <typename TCtx>
1270 auto format(const char* str, TCtx& ctx) const
1271 {
1272 return std::format_to(ctx.out(), L"{}", wsl::shared::string::MultiByteToWide(str));
1273 }
1274 };
1275
1276 template <std::size_t N>
1277 struct std::formatter<char[N], wchar_t>
1278 {
1279 template <typename TCtx>
1280 static constexpr auto parse(TCtx& ctx)
1281 {
1282 return ctx.begin();
1283 }
1284
1285 template <typename TCtx>
1286 auto format(const char str[N], TCtx& ctx) const
1287 {
1288 return std::format_to(ctx.out(), L"{}", wsl::shared::string::MultiByteToWide(str));
1289 }
1290 };
1291
1292 template <class Traits, class Allocator>
1293 struct std::formatter<std::basic_string<char, Traits, Allocator>, wchar_t>
1294 {
1295 template <typename TCtx>
1296 static constexpr auto parse(TCtx& ctx)
1297 {
1298 return ctx.begin();
1299 }
1300
1301 template <typename TCtx>
1302 auto format(const std::basic_string<char, Traits, Allocator>& str, TCtx& ctx) const
1303 {
1304 return std::format_to(ctx.out(), L"{}", wsl::shared::string::MultiByteToWide(str));
1305 }
1306 };
1307
1308 #endif // WIN32
1309
1310 template <>
1311 struct std::formatter<std::filesystem::path, wchar_t>
1312 {
1313 template <typename TCtx>
1314 static constexpr auto parse(TCtx& ctx)
1315 {
1316 return ctx.begin();
1317 }
1318
1319 template <typename TCtx>
1320 auto format(const std::filesystem::path& str, TCtx& ctx) const
1321 {
1322 return std::format_to(ctx.out(), "{}", str.wstring());
1323 }
1324 };
1325
1326 template <>
1327 struct std::formatter<GUID, wchar_t>
1328 {
1329 template <typename TCtx>
1330 static constexpr auto parse(TCtx& ctx)
1331 {
1332 return ctx.begin();
1333 }
1334
1335 template <typename TCtx>
1336 auto format(const GUID& Guid, TCtx& ctx) const
1337 {
1338 return std::format_to(ctx.out(), "{}", wsl::shared::string::GuidToString<wchar_t>(Guid));
1339 }
1340 };
1341
1342 template <>
1343 struct std::formatter<wchar_t, char>
1344 {
1345 template <typename TCtx>
1346 static constexpr auto parse(TCtx& ctx)
1347 {
1348 return ctx.begin();
1349 }
1350
1351 template <typename TCtx>
1352 auto format(wchar_t str, TCtx& ctx) const
1353 {
1354 return std::format_to(ctx.out(), "{}", wsl::shared::string::WideToMultiByte(std::wstring{&str, 1}));
1355 }
1356 };