master
cpp 459 lines 14.1 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 string.cpp
8
9 Abstract:
10
11 This file contains string helper function definitions.
12
13 --*/
14
15 #include "precomp.h"
16 #include <charconv>
17 #include <cmath>
18 #include <limits>
19 #include <sstream>
20
21 std::vector<std::string> wsl::windows::common::string::InitializeStringSet(_In_count_(BufferSize) LPCSTR Buffer, _In_ SIZE_T BufferSize)
22 {
23 // Ensure the buffer ends with two NULL terminators.
24 THROW_HR_IF(E_INVALIDARG, ((BufferSize < 2) || (Buffer[BufferSize - 1] != ANSI_NULL) || (Buffer[BufferSize - 2] != ANSI_NULL)));
25
26 std::vector<std::string> values{};
27 for (LPCSTR current = Buffer; ANSI_NULL != *current; current += strlen(current) + 1)
28 {
29 values.push_back(current);
30 }
31
32 return values;
33 }
34
35 bool wsl::windows::common::string::IsPathComponentEqual(const std::wstring_view String1, const std::wstring_view String2)
36 {
37 return CompareStringOrdinal(String1.data(), static_cast<int>(String1.size()), String2.data(), static_cast<int>(String2.size()), true) == CSTR_EQUAL;
38 }
39
40 std::wstring wsl::windows::common::string::MultiByteToWide(_In_ LPCSTR Source, _In_ size_t CharacterCount)
41 {
42 if (CharacterCount == -1)
43 {
44 CharacterCount = Source ? strlen(Source) : 0;
45 }
46
47 if (CharacterCount == 0)
48 {
49 return {};
50 }
51
52 THROW_HR_IF(E_BOUNDS, (CharacterCount > static_cast<size_t>(std::numeric_limits<int>::max())));
53
54 int required = MultiByteToWideChar(CP_UTF8, 0, Source, gsl::narrow_cast<int>(CharacterCount), nullptr, 0);
55 THROW_LAST_ERROR_IF(required == 0);
56
57 std::wstring converted(required, L'\0');
58 required = MultiByteToWideChar(CP_UTF8, 0, Source, gsl::narrow_cast<int>(CharacterCount), converted.data(), required);
59 THROW_LAST_ERROR_IF(required == 0);
60
61 return converted;
62 }
63
64 std::wstring wsl::windows::common::string::MultiByteToWide(_In_ std::string_view Source)
65 {
66 return MultiByteToWide(Source.data(), Source.size());
67 }
68
69 std::wstring_view wsl::windows::common::string::StripLeadingWhitespace(_In_ std::wstring_view String)
70 {
71 const size_t Index = String.find_first_not_of(L" \t");
72 if (Index != std::wstring_view::npos)
73 {
74 String.remove_prefix(Index);
75 }
76 else
77 {
78 String = {};
79 }
80
81 return String;
82 }
83
84 std::wstring_view wsl::windows::common::string::StripQuotes(_In_ std::wstring_view String)
85 {
86 // If the string begins and ends with a quote character, remove them.
87 std::wstring_view Stripped = String;
88 if ((Stripped.size() > 1) && (Stripped[0] == L'\"') && (Stripped[Stripped.size() - 1] == L'\"'))
89 {
90 Stripped.remove_prefix(1);
91 Stripped.remove_suffix(1);
92 }
93
94 return Stripped;
95 }
96
97 std::string wsl::windows::common::string::IpPrefixAddressToString(const IP_ADDRESS_PREFIX& ipAddressPrefix)
98 {
99 return std::format("{}/{}", SockAddrInetToString(ipAddressPrefix.Prefix), static_cast<uint32_t>(ipAddressPrefix.PrefixLength));
100 }
101
102 std::string wsl::windows::common::string::SockAddrInetToString(const SOCKADDR_INET& sockAddrInet)
103 {
104 std::string ipAddress(INET6_ADDRSTRLEN, '\0');
105 switch (sockAddrInet.si_family)
106 {
107 case AF_INET:
108 RtlIpv4AddressToStringA(&sockAddrInet.Ipv4.sin_addr, ipAddress.data());
109 break;
110 case AF_INET6:
111 RtlIpv6AddressToStringA(&sockAddrInet.Ipv6.sin6_addr, ipAddress.data());
112 break;
113 default:
114 ipAddress = std::format("[[ADDRESS_FAMILY {}]]", sockAddrInet.si_family);
115 break;
116 }
117 ipAddress.resize(std::strlen(ipAddress.data()));
118 return ipAddress;
119 }
120
121 std::wstring wsl::windows::common::string::SockAddrInetToWstring(const SOCKADDR_INET& sockAddrInet)
122 {
123 std::wstring ipAddress(INET6_ADDRSTRLEN, '\0');
124 switch (sockAddrInet.si_family)
125 {
126 case AF_INET:
127 RtlIpv4AddressToStringW(&sockAddrInet.Ipv4.sin_addr, ipAddress.data());
128 break;
129 case AF_INET6:
130 RtlIpv6AddressToStringW(&sockAddrInet.Ipv6.sin6_addr, ipAddress.data());
131 break;
132 default:
133 ipAddress = std::format(L"[[ADDRESS_FAMILY {}]]", sockAddrInet.si_family);
134 break;
135 }
136 ipAddress.resize(std::wcslen(ipAddress.data()));
137 return ipAddress;
138 }
139
140 std::wstring wsl::windows::common::string::IntegerIpv4ToWstring(const uint32_t ipAddress)
141 {
142 in_addr address{};
143 address.S_un.S_addr = ipAddress;
144
145 std::wstring stringAddress(INET_ADDRSTRLEN, '\0');
146 WI_VERIFY(InetNtopW(AF_INET, &address, stringAddress.data(), stringAddress.size()) != nullptr);
147 stringAddress.resize(wcslen(stringAddress.c_str()));
148
149 return stringAddress;
150 }
151
152 SOCKADDR_INET wsl::windows::common::string::StringToSockAddrInet(const std::wstring& stringIpAddress)
153 {
154 SOCKADDR_INET returnSockaddr{};
155 if (stringIpAddress.empty())
156 {
157 // return an empty IPv4 sockaddr
158 returnSockaddr.si_family = AF_INET;
159 }
160 else if (stringIpAddress.find(':', 0) == std::string::npos)
161 {
162 returnSockaddr.si_family = AF_INET;
163 const wchar_t* terminator;
164 THROW_IF_WIN32_ERROR_MSG(
165 RtlIpv4StringToAddressW(stringIpAddress.c_str(), TRUE, &terminator, &returnSockaddr.Ipv4.sin_addr),
166 "RtlIpv4StringToAddressW(%ws)",
167 stringIpAddress.c_str());
168 }
169 else
170 {
171 returnSockaddr.si_family = AF_INET6;
172 const wchar_t* terminator;
173 THROW_IF_WIN32_ERROR_MSG(
174 RtlIpv6StringToAddressW(stringIpAddress.c_str(), &terminator, &returnSockaddr.Ipv6.sin6_addr),
175 "RtlIpv6StringToAddressW(%ws)",
176 stringIpAddress.c_str());
177 }
178
179 return returnSockaddr;
180 }
181
182 std::wstring wsl::windows::common::string::BytesToHex(const std::vector<BYTE>& bytes)
183 {
184 std::wstringstream str;
185
186 str << L"0x";
187 str << std::hex;
188
189 for (const auto e : bytes)
190 {
191 str << std::setw(2) << std::setfill(L'0') << static_cast<int>(e);
192 }
193
194 return str.str();
195 }
196
197 namespace {
198 bool IsHexSpecifier(char first, char second)
199 {
200 return first == '0' && tolower(static_cast<unsigned char>(second)) == 'x';
201 }
202
203 bool IsHexSpecifier(wchar_t first, wchar_t second)
204 {
205 return first == L'0' && towlower(second) == L'x';
206 }
207
208 BYTE ConvertHexByte(const char* hex, char** endPtr)
209 {
210 return static_cast<BYTE>(strtoul(hex, endPtr, 16));
211 }
212
213 BYTE ConvertHexByte(const wchar_t* hex, wchar_t** endPtr)
214 {
215 return static_cast<BYTE>(wcstoul(hex, endPtr, 16));
216 }
217
218 template <typename T>
219 std::vector<BYTE> HexToBytesT(std::basic_string_view<T> input)
220 {
221 if (input.length() % 2 != 0)
222 {
223 THROW_HR_WITH_USER_ERROR(E_INVALIDARG, wsl::shared::Localization::MessageInvalidHexString(std::basic_string<T>{input}));
224 }
225
226 std::vector<BYTE> result;
227 result.reserve(input.length() / 2);
228 T currentHex[3]{};
229 for (size_t i = 0; i < input.size(); i += 2)
230 {
231 // Skip '0x', if any
232 if (i == 0 && IsHexSpecifier(input[0], input[1]))
233 {
234 continue;
235 }
236
237 currentHex[0] = input[i];
238 currentHex[1] = input[i + 1];
239 T* endPtr{};
240
241 const auto byte = ConvertHexByte(currentHex, &endPtr);
242 if (endPtr != currentHex + 2)
243 {
244 THROW_HR_WITH_USER_ERROR(E_INVALIDARG, wsl::shared::Localization::MessageInvalidHexString(std::basic_string<T>{input}));
245 }
246
247 result.push_back(byte);
248 }
249
250 return result;
251 }
252 } // namespace
253
254 std::vector<BYTE> wsl::windows::common::string::HexToBytes(std::string_view input)
255 {
256 return HexToBytesT(input);
257 }
258
259 std::vector<BYTE> wsl::windows::common::string::HexToBytes(std::wstring_view input)
260 {
261 return HexToBytesT(input);
262 }
263
264 std::string wsl::windows::common::string::WideToMultiByte(_In_opt_ LPCWSTR Source, _In_ size_t CharacterCount)
265 {
266 if (CharacterCount == -1)
267 {
268 CharacterCount = Source ? wcslen(Source) : 0;
269 }
270
271 if (CharacterCount == 0)
272 {
273 return {};
274 }
275
276 THROW_HR_IF(E_BOUNDS, (CharacterCount > static_cast<size_t>(std::numeric_limits<int>::max())));
277
278 int required = WideCharToMultiByte(CP_UTF8, 0, Source, gsl::narrow_cast<int>(CharacterCount), nullptr, 0, nullptr, nullptr);
279 THROW_LAST_ERROR_IF(required == 0);
280
281 std::string converted(required, '\0');
282 required = WideCharToMultiByte(CP_UTF8, 0, Source, gsl::narrow_cast<int>(CharacterCount), converted.data(), required, nullptr, nullptr);
283 THROW_LAST_ERROR_IF(required == 0);
284
285 return converted;
286 }
287
288 std::string wsl::windows::common::string::WideToMultiByte(_In_ std::wstring_view Source)
289 {
290 return WideToMultiByte(Source.data(), Source.length());
291 }
292
293 std::optional<uint64_t> wsl::windows::common::string::ParseStorageSize(std::wstring_view String, StorageSizeUnit Unit)
294 {
295 std::wstring_view number;
296 std::wstring_view suffix;
297 const auto space = String.find(L' ');
298 if (space != std::wstring_view::npos)
299 {
300 number = String.substr(0, space);
301 suffix = String.substr(space + 1);
302 }
303 else
304 {
305 const auto numberEnd = String.find_last_of(L"0123456789.");
306 if (numberEnd == std::wstring_view::npos)
307 {
308 return {};
309 }
310
311 number = String.substr(0, numberEnd + 1);
312 suffix = String.substr(numberEnd + 1);
313 }
314
315 auto narrowNumber = WideToMultiByte(number);
316 if (!narrowNumber.empty() && narrowNumber.front() == '+')
317 {
318 narrowNumber.erase(0, 1);
319 }
320
321 uint64_t multiplier = 1;
322 if (!suffix.empty())
323 {
324 auto normalizedSuffix = wsl::shared::string::AsciiToLower(suffix);
325 if (normalizedSuffix != L"b")
326 {
327 if (normalizedSuffix.size() > 3 || (normalizedSuffix.size() == 2 && normalizedSuffix[1] != L'b') ||
328 (normalizedSuffix.size() == 3 && normalizedSuffix.substr(1) != L"ib"))
329 {
330 return {};
331 }
332
333 constexpr std::wstring_view c_memoryUnits = L"kmgtp";
334 const auto unitIndex = c_memoryUnits.find(normalizedSuffix[0]);
335 if (unitIndex == std::wstring_view::npos)
336 {
337 return {};
338 }
339
340 const uint64_t base = Unit == StorageSizeUnit::Decimal ? 1000 : 1024;
341 for (size_t index = 0; index <= unitIndex; ++index)
342 {
343 multiplier *= base;
344 }
345 }
346 }
347
348 if (!narrowNumber.empty() && narrowNumber.find_first_not_of("0123456789") == std::string::npos)
349 {
350 uint64_t value{};
351 const auto result = std::from_chars(narrowNumber.data(), narrowNumber.data() + narrowNumber.size(), value);
352 if (result.ec != std::errc() || result.ptr != narrowNumber.data() + narrowNumber.size() ||
353 value > std::numeric_limits<uint64_t>::max() / multiplier)
354 {
355 return {};
356 }
357
358 return value * multiplier;
359 }
360
361 // Fractional and exponent forms require floating-point parsing and may lose precision above 2^53.
362 double value{};
363 const auto result = std::from_chars(narrowNumber.data(), narrowNumber.data() + narrowNumber.size(), value, std::chars_format::general);
364 if (result.ec != std::errc() || result.ptr != narrowNumber.data() + narrowNumber.size() || !std::isfinite(value) || value < 0)
365 {
366 return {};
367 }
368
369 const double bytes = value * static_cast<double>(multiplier);
370 if (!std::isfinite(bytes) || bytes >= static_cast<double>(std::numeric_limits<uint64_t>::max()))
371 {
372 return {};
373 }
374
375 return static_cast<uint64_t>(bytes);
376 }
377
378 std::wstring wsl::windows::common::string::FormatHumanReadableSize(uint64_t Bytes, uint32_t Precision, StorageSizeUnit Unit)
379 {
380 constexpr size_t c_unitCount = 9;
381 constexpr std::array<std::wstring_view, c_unitCount> c_decimalUnits{
382 L"B", L"kB", L"MB", L"GB", L"TB", L"PB", L"EB", L"ZB", L"YB"};
383 constexpr std::array<std::wstring_view, c_unitCount> c_binaryUnits{
384 L"B", L"KiB", L"MiB", L"GiB", L"TiB", L"PiB", L"EiB", L"ZiB", L"YiB"};
385
386 const double base = Unit == StorageSizeUnit::Decimal ? 1000.0 : 1024.0;
387 const auto& units = Unit == StorageSizeUnit::Decimal ? c_decimalUnits : c_binaryUnits;
388
389 auto value = static_cast<double>(Bytes);
390 size_t unitIndex = 0;
391 while (value >= base && unitIndex + 1 < c_unitCount)
392 {
393 value /= base;
394 unitIndex++;
395 }
396
397 return std::format(L"{:.{}g}{}", value, Precision, units[unitIndex]);
398 }
399
400 std::wstring wsl::windows::common::string::TruncateId(_In_ std::wstring_view id, bool shortenLength)
401 {
402 return TruncateIdImpl(id, shortenLength);
403 }
404
405 std::string wsl::windows::common::string::TruncateId(_In_ std::string_view id, bool shortenLength)
406 {
407 return TruncateIdImpl(id, shortenLength);
408 }
409
410 // Returns the number of terminal columns a code point occupies. This mirrors docker's charWidth, which treats
411 // East Asian wide and fullwidth code points as two columns and everything else as one.
412 static size_t CharacterWidth(UChar32 CodePoint)
413 {
414 const auto width = u_getIntPropertyValue(CodePoint, UCHAR_EAST_ASIAN_WIDTH);
415 return (width == U_EA_WIDE || width == U_EA_FULLWIDTH) ? 2 : 1;
416 }
417
418 std::wstring wsl::windows::common::string::Ellipsis(_In_ std::wstring_view Value, _In_ size_t MaxDisplayWidth)
419 {
420 if (MaxDisplayWidth == 0 || Value.empty())
421 {
422 return {};
423 }
424
425 const auto length = gsl::narrow_cast<int32_t>(Value.size());
426 if (MaxDisplayWidth == 1)
427 {
428 // There is no room for both content and an ellipsis, so the leading code point is kept as-is even
429 // if it is wider than the limit.
430 int32_t index = 0;
431 UChar32 codePoint{};
432 U16_NEXT(Value.data(), index, length, codePoint);
433 return std::wstring{Value.substr(0, index)};
434 }
435
436 // The ellipsis occupies one column, so the retained content has one column less to work with.
437 const auto budget = MaxDisplayWidth - 1;
438 size_t totalWidth = 0;
439 size_t cutoff = 0;
440 for (int32_t index = 0; index < length;)
441 {
442 UChar32 codePoint{};
443 U16_NEXT(Value.data(), index, length, codePoint);
444 totalWidth += CharacterWidth(codePoint);
445 if (totalWidth <= budget)
446 {
447 cutoff = index;
448 }
449 }
450
451 // A cutoff of zero means the first code point alone leaves no room for the ellipsis, in which case docker
452 // returns the value untouched.
453 if (totalWidth <= MaxDisplayWidth || cutoff == 0)
454 {
455 return std::wstring{Value};
456 }
457
458 return std::wstring{Value.substr(0, cutoff)} + L'\u2026';
459 }