From: Alanas Tebuev alanas.00@mail.ru
--- dlls/shlwapi/string.c | 252 ++++++++++++++++++++++++------------ dlls/shlwapi/tests/string.c | 73 ++++++++++- 2 files changed, 236 insertions(+), 89 deletions(-)
diff --git a/dlls/shlwapi/string.c b/dlls/shlwapi/string.c index 7aeeef246d6..f663c442a39 100644 --- a/dlls/shlwapi/string.c +++ b/dlls/shlwapi/string.c @@ -656,63 +656,116 @@ HRESULT WINAPI SHStrDupW(LPCWSTR src, LPWSTR * dest) }
/************************************************************************* - * SHLWAPI_WriteReverseNum + * SHLWAPI_StrFromTimeIntervalW_unsafe * - * Internal helper for SHLWAPI_WriteTimeClass. + * Internal helper for StrFromTimeIntervalA and StrFromTimeIntervalW. */ -static inline LPWSTR SHLWAPI_WriteReverseNum(LPWSTR lpszOut, DWORD dwNum) +static WCHAR *SHLWAPI_StrFromTimeIntervalW_unsafe(WCHAR *out, unsigned max_digits, unsigned ms) { - *lpszOut-- = '\0'; + const unsigned _1000hr = 3600000000u; + const unsigned _100hr = 360000000u; + const unsigned _10hr = 36000000u; + const unsigned _1hr = 3600000u; + const unsigned _10min = 600000u; + const unsigned _1min = 60000u; + const unsigned _10sec = 10000u; + const unsigned _1sec = 1000u;
- /* Write a decimal number to a string, backwards */ - do + unsigned used_digits = 0, abbreviation_len; + + ms += 500; /* 4294966796 ms ≈ 0 sec — windows xp, windows vista, windows 7, windows 8.1, windows 11 */ + + if (ms >= _1hr && used_digits < max_digits) { - DWORD dwNextDigit = dwNum % 10; - *lpszOut-- = '0' + dwNextDigit; - dwNum = (dwNum - dwNextDigit) / 10; - } while (dwNum > 0); + _Static_assert(sizeof ms == 4, "code for formatting hour part only works for 32-bit ms"); + *out++ = ' ';
- return lpszOut; -} + if (ms >= _1000hr) + { + *out++ = '1'; /* hour part is in range 1000 to 1193 inclusive if this line runs */ + used_digits++; + } + if (ms >= _100hr) + *out++ = used_digits++ < max_digits ? ms % _1000hr / _100hr + '0' : '0'; + if (ms >= _10hr) + *out++ = used_digits++ < max_digits ? ms % _100hr / _10hr + '0' : '0'; + *out++ = used_digits++ < max_digits ? ms % _10hr / _1hr + '0' : '0'; + + abbreviation_len = LoadStringW(shlwapi_hInstance, IDS_TIME_INTERVAL_HOURS, out, 32); + if (abbreviation_len == 0) + WARN("failed to load hours abbreviation\n"); + out += abbreviation_len; + }
-/************************************************************************* - * SHLWAPI_FormatSignificant - * - * Internal helper for SHLWAPI_WriteTimeClass. - */ -static inline int SHLWAPI_FormatSignificant(LPWSTR lpszNum, int dwDigits) -{ - /* Zero non significant digits, return remaining significant digits */ - while (*lpszNum) + if (ms % _1hr >= _1min && used_digits < max_digits) { - lpszNum++; - if (--dwDigits == 0) + *out++ = ' '; + + if (ms % _1hr >= _10min) { - while (*lpszNum) - *lpszNum++ = '0'; - return 0; + *out++ = ms % _1hr / _10min + '0'; + used_digits++; } + *out++ = used_digits++ < max_digits ? ms % _10min / _1min + '0' : '0'; + + abbreviation_len = LoadStringW(shlwapi_hInstance, IDS_TIME_INTERVAL_MINUTES, out, 32); + if (abbreviation_len == 0) + WARN("failed to load minutes abbreviation\n"); + out += abbreviation_len; } - return dwDigits; + + if (used_digits < max_digits) + { + *out++ = ' '; + + if (ms % _1min >= _10sec) + { + *out++ = ms % _1min / _10sec + '0'; + used_digits++; + } + *out++ = used_digits++ < max_digits ? ms % _10sec / _1sec + '0' : '0'; + + abbreviation_len = LoadStringW(shlwapi_hInstance, IDS_TIME_INTERVAL_SECONDS, out, 32); + if (abbreviation_len == 0) + WARN("failed to load seconds abbreviation\n"); + out += abbreviation_len; + } + + return out; }
-/************************************************************************* - * SHLWAPI_WriteTimeClass - * - * Internal helper for StrFromTimeIntervalW. - */ -static int SHLWAPI_WriteTimeClass(LPWSTR lpszOut, DWORD dwValue, - UINT uClassStringId, int iDigits) +enum { - WCHAR szBuff[64], *szOut = szBuff + 32; - - szOut = SHLWAPI_WriteReverseNum(szOut, dwValue); - iDigits = SHLWAPI_FormatSignificant(szOut + 1, iDigits); - *szOut = ' '; - LoadStringW(shlwapi_hInstance, uClassStringId, szBuff + 32, 32); - lstrcatW(lpszOut, szOut); - return iDigits; -} + SHLWAPI_StrFromTimeIntervalW_unsafe_buffer_len + = 1 /* space */ + + 4 /* hours digits */ + + 31 /* LoadStringW(shlwapi_hInstance, IDS_TIME_INTERVAL_HOURS, out, 32) */ + + + 1 /* space */ + + 2 /* minutes digits */ + + 31 /* LoadStringW(shlwapi_hInstance, IDS_TIME_INTERVAL_MINUTES, out, 32) */ + + + 1 /* space */ + + 2 /* seconds digits */ + + 31 /* LoadStringW(shlwapi_hInstance, IDS_TIME_INTERVAL_SECONDS, out, 32) */ + + 1, /* extra U+0000 character from LoadStringW (not part of result) */ + + /* multiplications by 2 because codepage encodings convert 1 wchar to at most 2 bytes */ + /* multiplications by 3 because utf8 encoding converts 1 wchar greater than U+07FF to 3 bytes (except surrogate pairs which are converted 2 wchars to 4 bytes) */ + SHLWAPI_StrFromTimeIntervalW_unsafe_convert_to_A_buffer_len + = 2 * 1 /* space */ + + 2 * 4 /* hours digits */ + + 3 * 31 /* LoadStringW(shlwapi_hInstance, IDS_TIME_INTERVAL_HOURS, out, 32) */ + + + 2 * 1 /* space */ + + 2 * 2 /* minutes digits */ + + 3 * 31 /* LoadStringW(shlwapi_hInstance, IDS_TIME_INTERVAL_MINUTES, out, 32) */ + + + 2 * 1 /* space */ + + 2 * 2 /* seconds digits */ + + 3 * 31 /* LoadStringW(shlwapi_hInstance, IDS_TIME_INTERVAL_SECONDS, out, 32) */ +}; +
/************************************************************************* * StrFromTimeIntervalA [SHLWAPI.@] @@ -742,25 +795,68 @@ static int SHLWAPI_WriteTimeClass(LPWSTR lpszOut, DWORD dwValue, * For example, given dwMS represents 138 hours,43 minutes and 15 seconds, the * following will result from the given values of iDigits: * - *| iDigits 1 2 3 4 5 ... - *| lpszStr "100 hr" "130 hr" "138 hr" "138 hr 40 min" "138 hr 43 min" ... + *| iDigits 1 2 3 4 5 ... + *| lpszStr " 100 hr" " 130 hr" " 138 hr" " 138 hr 40 min" " 138 hr 43 min" ... */ INT WINAPI StrFromTimeIntervalA(LPSTR lpszStr, UINT cchMax, DWORD dwMS, int iDigits) { - INT iRet = 0; + WCHAR tmpW[SHLWAPI_StrFromTimeIntervalW_unsafe_buffer_len]; + char tmpA[SHLWAPI_StrFromTimeIntervalW_unsafe_convert_to_A_buffer_len]; + unsigned result;
TRACE("(%p,%d,%ld,%d)\n", lpszStr, cchMax, dwMS, iDigits);
- if (lpszStr && cchMax) + if (lpszStr == 0) { - WCHAR szBuff[128]; - StrFromTimeIntervalW(szBuff, ARRAY_SIZE(szBuff), dwMS, iDigits); - WideCharToMultiByte(CP_ACP,0,szBuff,-1,lpszStr,cchMax,0,0); + if (iDigits == 0) /* don't let SHLWAPI_StrFromTimeIntervalW_unsafe(tmpW, iDigits, dwMS) - tmpW be 0 */ + return 0; + result = WideCharToMultiByte( + CP_ACP, + 0, + tmpW, + SHLWAPI_StrFromTimeIntervalW_unsafe(tmpW, iDigits, dwMS) - tmpW, + 0, + 0, + 0, + 0 + ); + if (result == 0) + WARN("WideCharToMultiByte failed\n"); + return result; + } + + if (cchMax == 0) + return lstrlenA(lpszStr); /* windows xp, windows vista, windows 7, windows 8.1, windows 11 are reading but not writing output and return 0 instead of crashing with invalid lpszStr */ + + if (iDigits == 0) /* don't let SHLWAPI_StrFromTimeIntervalW_unsafe(tmpW, iDigits, dwMS) - tmpW be 0 */ + { + lpszStr[0] = 0; + return 0; } - return iRet; -}
+ /* if cchMax >= 0x80000000 windows xp doesn't do anything weird */ + /* if cchMax >= 0x80000000 windows vista, windows 7 do {return lstrlenA(lpszStr);} */ + /* if cchMax >= 0x80000000 windows 8.1, windows 11 do {int r=lstrlenA(lpszStr);lpszStr[0]=0;return r;} */ + /* this behaves like windows xp */ + result = WideCharToMultiByte( + CP_ACP, + 0, + tmpW, + SHLWAPI_StrFromTimeIntervalW_unsafe(tmpW, iDigits, dwMS) - tmpW, + tmpA, + sizeof tmpA, + 0, + 0 + ); + if (result == 0) + WARN("WideCharToMultiByte failed\n"); + if (cchMax - 1 < result) + result = cchMax - 1; + memcpy(lpszStr, tmpA, result); + lpszStr[result] = 0; + return result; +}
/************************************************************************* * StrFromTimeIntervalW [SHLWAPI.@] @@ -770,43 +866,27 @@ INT WINAPI StrFromTimeIntervalA(LPSTR lpszStr, UINT cchMax, DWORD dwMS, INT WINAPI StrFromTimeIntervalW(LPWSTR lpszStr, UINT cchMax, DWORD dwMS, int iDigits) { - INT iRet = 0; + WCHAR tmp[SHLWAPI_StrFromTimeIntervalW_unsafe_buffer_len]; + unsigned wchars_to_copy;
TRACE("(%p,%d,%ld,%d)\n", lpszStr, cchMax, dwMS, iDigits);
- if (lpszStr && cchMax) - { - WCHAR szCopy[128]; - DWORD dwHours, dwMinutes; - - if (!iDigits || cchMax == 1) - { - *lpszStr = '\0'; - return 0; - } - - /* Calculate the time classes */ - dwMS = (dwMS + 500) / 1000; - dwHours = dwMS / 3600; - dwMS -= dwHours * 3600; - dwMinutes = dwMS / 60; - dwMS -= dwMinutes * 60; - - szCopy[0] = '\0'; - - if (dwHours) - iDigits = SHLWAPI_WriteTimeClass(szCopy, dwHours, IDS_TIME_INTERVAL_HOURS, iDigits); - - if (dwMinutes && iDigits) - iDigits = SHLWAPI_WriteTimeClass(szCopy, dwMinutes, IDS_TIME_INTERVAL_MINUTES, iDigits); - - if (iDigits) /* Always write seconds if we have significant digits */ - SHLWAPI_WriteTimeClass(szCopy, dwMS, IDS_TIME_INTERVAL_SECONDS, iDigits); - - lstrcpynW(lpszStr, szCopy, cchMax); - iRet = lstrlenW(lpszStr); - } - return iRet; + if (lpszStr == 0) + return SHLWAPI_StrFromTimeIntervalW_unsafe(tmp, iDigits, dwMS) - tmp; + + if (cchMax == 0) + return lstrlenW(lpszStr); /* windows xp, windows vista, windows 7, windows 8.1, windows 11 are reading but not writing output and return 0 instead of crashing with invalid lpszStr */ + + /* if cchMax >= 0x80000000 windows xp doesn't do anything weird */ + /* if cchMax >= 0x80000000 windows vista, windows 7 do {return lstrlenW(lpszStr);} */ + /* if cchMax >= 0x80000000 windows 8.1, windows 11 do {int r=lstrlenW(lpszStr);lpszStr[0]=0;return r;} */ + /* this behaves like windows xp */ + wchars_to_copy = SHLWAPI_StrFromTimeIntervalW_unsafe(tmp, iDigits, dwMS) - tmp; + if (cchMax - 1 < wchars_to_copy) + wchars_to_copy = cchMax - 1; + memcpy(lpszStr, tmp, wchars_to_copy * 2); + lpszStr[wchars_to_copy] = 0; + return wchars_to_copy; }
/* Structure for formatting byte strings */ diff --git a/dlls/shlwapi/tests/string.c b/dlls/shlwapi/tests/string.c index 8fa0b0534cb..da4ab65816e 100644 --- a/dlls/shlwapi/tests/string.c +++ b/dlls/shlwapi/tests/string.c @@ -200,6 +200,28 @@ static const StrFromTimeIntervalResult StrFromTimeInterval_results[] = { { 493999507, 6, " 137 hr 13 min 20 sec" }, { 493999507, 7, " 137 hr 13 min 20 sec" },
+ { 0xfffffe0c, 0, "" }, + { 0xfffffe0c, 1, " 0 sec" }, + { 0xfffffe0c, 2, " 0 sec" }, + { 0xfffffe0c, 3, " 0 sec" }, + { 0xfffffe0c, 4, " 0 sec" }, + { 0xfffffe0c, 5, " 0 sec" }, + { 0xfffffe0c, 6, " 0 sec" }, + { 0xfffffe0c, 7, " 0 sec" }, + { 0xfffffe0c, 8, " 0 sec" }, + + { 0xee6f4b07, 0, "" }, + { 0xee6f4b07, 1, " 1000 hr" }, + { 0xee6f4b07, 2, " 1100 hr" }, + { 0xee6f4b07, 3, " 1110 hr" }, + { 0xee6f4b07, 4, " 1111 hr" }, + { 0xee6f4b07, 5, " 1111 hr 10 min" }, + { 0xee6f4b07, 6, " 1111 hr 11 min" }, + { 0xee6f4b07, 7, " 1111 hr 11 min 10 sec" }, + { 0xee6f4b07, 8, " 1111 hr 11 min 11 sec" }, + { 0xee6f4b07, 111111, " 1111 hr 11 min 11 sec" }, + { 0xee6f4b07, -111111, " 1111 hr 11 min 11 sec" }, + { 0, 0, NULL } };
@@ -800,15 +822,57 @@ static void test_StrFromTimeIntervalA(void) { char szBuff[256]; const StrFromTimeIntervalResult* result = StrFromTimeInterval_results; + int len, ret;
while(result->ms) { - StrFromTimeIntervalA(szBuff, 256, result->ms, result->digits); - - ok(!strcmp(result->time_interval, szBuff), "Formatted %ld %d wrong: %s\n", + len = strlen(result->time_interval); + ret = StrFromTimeIntervalA(0, 0, result->ms, result->digits); + ok(ret == len, "StrFromTimeIntervalA returned %d (expected %d) when predicting length of formatted %lu ms with %d digits\n", + ret, len, result->ms, result->digits); + ret = StrFromTimeIntervalA(szBuff, 256, result->ms, result->digits); + ok(ret == len, "StrFromTimeIntervalA returned %d (expected %d) when formatting %lu ms with %d digits\n", + ret, len, result->ms, result->digits); + ok(!strcmp(result->time_interval, szBuff), "Formatted %lu ms with %d digits wrong: %s\n", result->ms, result->digits, szBuff); result++; } + + ret = StrFromTimeIntervalA(szBuff, 7, 1140011, 1); + ok(ret == 6, "StrFromTimeIntervalA truncating broken (returned %d, expected 6)\n", ret); + ok(!strcmp(" 10 mi", szBuff), "StrFromTimeIntervalA truncating broken (wrote "%s", expected " 10 mi")\n", szBuff); + + ret = StrFromTimeIntervalA(szBuff, 1, 1140011, 1); + ok(ret == 0, "StrFromTimeIntervalA truncating broken (returned %d, expected 0)\n", ret); + ok(szBuff[0] == 0, "StrFromTimeIntervalA truncating broken (wrote "%s", expected "")\n", szBuff); + + szBuff[0] = 'q'; + szBuff[1] = 'w'; + szBuff[2] = 0; + ret = StrFromTimeIntervalA(szBuff, 0, 111111, 111111); + ok(ret == 2, "StrFromTimeIntervalA doesn't work in lstrlenA mode (returned %d, expected 2)\n", ret); +} + +static void test_StrFromTimeIntervalW(void) +{ + WCHAR buf[7]; + int ret; + + ret = StrFromTimeIntervalW(0, 2, 8888, 1); + ok(ret == 6, "StrFromTimeIntervalW returned %d (expected 6) when predicting length of formatted 8888 ms with 1 digit\n", ret); + + ret = StrFromTimeIntervalW(buf, 7, 8888, 1); + ok(ret == 6, "StrFromTimeIntervalW returned %d (expected 6) when formatting 8888 ms with 1 digit\n", ret); + ok(!memcmp(" \09\0 \0s\0e\0c\0\0", buf, 14), "StrFromTimeIntervalW formatted 8888 ms with 1 digit wrong\n"); + + ret = StrFromTimeIntervalW(buf, 5, 8888, 1); + ok(ret == 4, "StrFromTimeIntervalW truncating broken (returned %d, expected 4)", ret); + ok(!memcmp(" \09\0 \0s\0\0", buf, 10), "StrFromTimeIntervalW truncating broken"); + + buf[0] = 'e'; + buf[1] = 0; + ret = StrFromTimeIntervalW(buf, 0, 8888, 1); + ok(ret == 1, "StrFromTimeIntervalW doesn't work in lstrlenW mode (returned %d, expected 1)\n", ret); }
static void test_StrCmpA(void) @@ -1977,7 +2041,10 @@ START_TEST(string) else skip("An English UI and locale is required for the StrFormat*Size tests\n"); if (is_lang_english()) + { test_StrFromTimeIntervalA(); + test_StrFromTimeIntervalW(); + } else skip("An English UI is required for the StrFromTimeInterval tests\n");