@@ -2021,6 +2021,14 @@ win32_wchdir(LPCWSTR path)
20212021#define HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES 1
20222022#define HAVE_STRUCT_STAT_ST_REPARSE_TAG 1
202320232024+/* The \\?\ prefix disables the path normalization, in particular
2025+ stripping of trailing dots and spaces. */
2026+static int
2027+is_extended_path(const wchar_t *path)
2028+{
2029+ return wcsncmp(path, L"\\\\?\\", 4) == 0;
2030+}
2031+20242032static void
20252033find_data_to_file_info(WIN32_FIND_DATAW *pFileData,
20262034 FILE_BASIC_INFO* basic_info,
@@ -2094,12 +2102,20 @@ update_st_mode_from_path(const wchar_t *path, DWORD attr,
20942102 GetSecurityInfo, OpenThreadToken/OpenProcessToken, and
20952103 AccessCheck to check for generic read, write, and execute
20962104 access. */
2097- const wchar_t *fileExtension = wcsrchr(path, '.');
2098- if (fileExtension) {
2099- if (_wcsicmp(fileExtension, L".exe") == 0 ||
2100- _wcsicmp(fileExtension, L".bat") == 0 ||
2101- _wcsicmp(fileExtension, L".cmd") == 0 ||
2102- _wcsicmp(fileExtension, L".com") == 0) {
2105+ size_t len = wcslen(path);
2106+ if (!is_extended_path(path)) {
2107+ /* Trailing dots and spaces are stripped from the last component
2108+ of the path. */
2109+ while (len > 0 && (path[len - 1] == L'.' || path[len - 1] == L' ')) {
2110+ len--;
2111+ }
2112+ }
2113+ if (len >= 4) {
2114+ const wchar_t *fileExtension = path + len - 4;
2115+ if (_wcsnicmp(fileExtension, L".exe", 4) == 0 ||
2116+ _wcsnicmp(fileExtension, L".bat", 4) == 0 ||
2117+ _wcsnicmp(fileExtension, L".cmd", 4) == 0 ||
2118+ _wcsnicmp(fileExtension, L".com", 4) == 0) {
21032119 result->st_mode |= 0111;
21042120 }
21052121 }
@@ -16718,12 +16734,6 @@ static PyType_Spec DirEntryType_spec = {
16718167341671916735#ifdef MS_WINDOWS
167201673616721-static int
16722-is_extended_path(const wchar_t *path)
16723-{
16724- return wcsncmp(path, L"\\\\?\\", 4) == 0;
16725-}
16726-1672716737static wchar_t *
1672816738join_path_filenameW(const wchar_t *path_wide, const wchar_t *filename,
1672916739 int normalize)