Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Lib/test/test_os/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -5046,6 +5046,30 @@ def test_current_directory(self):
finally:
os.chdir(old_dir)

@unittest.skipIf(sys.platform != 'win32', "Win32 specific test")
def test_windows_trailing_space_path(self):
import pathlib

filename = self.create_file("file.txt")
path = self.path + " "

self.assertTrue(os.path.exists(path))
os.stat(path)
with open(filename + " ", "rb") as file:
self.assertEqual(file.read(), b"python")

self.assertEqual(os.listdir(path), ["file.txt"])
with os.scandir(path) as entries:
self.assertEqual([entry.name for entry in entries], ["file.txt"])
pathlib_entries = list(pathlib.Path(path).iterdir())
self.assertEqual([entry.name for entry in pathlib_entries], ["file.txt"])
del pathlib_entries

extended_path = "\\\\?\\" + path
self.assertFalse(os.path.exists(extended_path))
self.assertRaises(FileNotFoundError, os.listdir, extended_path)
self.assertRaises(FileNotFoundError, os.scandir, extended_path)

def test_repr(self):
entry = self.create_file_entry()
self.assertEqual(repr(entry), "<DirEntry 'file.txt'>")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Normalize non-extended Windows paths before appending the wildcard used by
``os.listdir()`` and ``os.scandir()``, making paths with trailing spaces
behave consistently with other filesystem APIs.
80 changes: 51 additions & 29 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4905,42 +4905,34 @@ os_link_impl(PyObject *module, path_t *src, path_t *dst, int src_dir_fd,


#if defined(MS_WINDOWS) && !defined(HAVE_OPENDIR)
static wchar_t *
join_path_filenameW(const wchar_t *path_wide, const wchar_t *filename,
int normalize);

static PyObject *
_listdir_windows_no_opendir(path_t *path, PyObject *list)
{
PyObject *v;
HANDLE hFindFile = INVALID_HANDLE_VALUE;
BOOL result, return_bytes;
wchar_t namebuf[MAX_PATH+4]; /* Overallocate for "\*.*" */
/* only claim to have space for MAX_PATH */
Py_ssize_t len = Py_ARRAY_LENGTH(namebuf)-4;
wchar_t *wnamebuf = NULL;

WIN32_FIND_DATAW wFileData;
const wchar_t *po_wchars;

if (!path->wide) { /* Default arg: "." */
po_wchars = L".";
len = 1;
return_bytes = 0;
} else {
po_wchars = path->wide;
len = wcslen(path->wide);
return_bytes = PyBytes_Check(path->object);
}
/* The +5 is so we can append "\\*.*\0" */
wnamebuf = PyMem_New(wchar_t, len + 5);
if (!wnamebuf) {
PyErr_NoMemory();

wnamebuf = join_path_filenameW(po_wchars, L"*.*", 1);
if (wnamebuf == NULL) {
goto exit;
}
wcscpy(wnamebuf, po_wchars);
if (len > 0) {
wchar_t wch = wnamebuf[len-1];
if (wch != SEP && wch != ALTSEP && wch != L':')
wnamebuf[len++] = SEP;
wcscpy(wnamebuf + len, L"*.*");
}

if ((list = PyList_New(0)) == NULL) {
goto exit;
}
Expand Down Expand Up @@ -16579,13 +16571,19 @@ static PyType_Spec DirEntryType_spec = {

#ifdef MS_WINDOWS

static int
is_extended_path(const wchar_t *path)
{
return wcsncmp(path, L"\\\\?\\", 4) == 0;
}

static wchar_t *
join_path_filenameW(const wchar_t *path_wide, const wchar_t *filename)
join_path_filenameW(const wchar_t *path_wide, const wchar_t *filename,
int normalize)
{
Py_ssize_t path_len;
Py_ssize_t size;
wchar_t *result;
wchar_t ch;
wchar_t *path_allocated = NULL;

if (!path_wide) { /* Default arg: "." */
path_wide = L".";
Expand All @@ -16595,20 +16593,44 @@ join_path_filenameW(const wchar_t *path_wide, const wchar_t *filename)
path_len = wcslen(path_wide);
}

/* The +1's are for the path separator and the NUL */
size = path_len + 1 + wcslen(filename) + 1;
if (path_len == 0) {
result = PyMem_New(wchar_t, 1);
if (result == NULL) {
PyErr_NoMemory();
return NULL;
}
result[0] = L'\0';
return result;
}

if (normalize && !is_extended_path(path_wide)) {
int err = _PyOS_getfullpathname(path_wide, &path_allocated);
if (err < 0) {
PyErr_SetFromWindowsErr(0);
return NULL;
}
if (path_allocated == NULL) {
PyErr_NoMemory();
return NULL;
}
path_wide = path_allocated;
path_len = wcslen(path_wide);
}

size_t size = (size_t)path_len + 1 + wcslen(filename) + 1;
result = PyMem_New(wchar_t, size);
if (!result) {
if (result == NULL) {
PyMem_RawFree(path_allocated);
PyErr_NoMemory();
return NULL;
}
wcscpy(result, path_wide);
if (path_len > 0) {
ch = result[path_len - 1];
if (ch != SEP && ch != ALTSEP && ch != L':')
result[path_len++] = SEP;
wcscpy(result + path_len, filename);
wchar_t ch = result[path_len - 1];
if (ch != SEP && ch != ALTSEP && ch != L':') {
result[path_len++] = SEP;
}
wcscpy(result + path_len, filename);
PyMem_RawFree(path_allocated);
return result;
}

Expand Down Expand Up @@ -16640,7 +16662,7 @@ DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW)
goto error;
}

joined_path = join_path_filenameW(path->wide, dataW->cFileName);
joined_path = join_path_filenameW(path->wide, dataW->cFileName, 0);
if (!joined_path)
goto error;

Expand Down Expand Up @@ -17076,7 +17098,7 @@ os_scandir_impl(PyObject *module, path_t *path)
#ifdef MS_WINDOWS
iterator->first_time = 1;

path_strW = join_path_filenameW(iterator->path.wide, L"*.*");
path_strW = join_path_filenameW(iterator->path.wide, L"*.*", 1);
if (!path_strW)
goto error;

Expand Down
Loading