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
1 change: 0 additions & 1 deletion Lib/test/test_os/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -5078,7 +5078,6 @@ def test_current_directory(self):
finally:
os.chdir(old_dir)

@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; FileNotFoundError")
@unittest.skipIf(sys.platform != 'win32', "Win32 specific test")
def test_windows_trailing_space_path(self):
import pathlib
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_winsound.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ def test_keyword_args(self):

class PlaySoundTest(unittest.TestCase):

@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; TypeError: a bytes-like object is required, not 'str'")
def test_errors(self):
self.assertRaises(TypeError, winsound.PlaySound)
self.assertRaises(TypeError, winsound.PlaySound, "bad", "bad")
Expand Down
11 changes: 7 additions & 4 deletions crates/host_env/src/mmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,9 +414,9 @@ pub fn last_error() -> u32 {
}

#[cfg(windows)]
pub fn create_named_mapping(
pub fn create_mapping(
file_handle: Handle,
tag: &str,
tag: Option<&str>,
access: AccessMode,
offset: i64,
map_size: usize,
Expand All @@ -432,7 +432,8 @@ pub fn create_named_mapping(
.ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?;
let size_hi = (total_size >> 32) as u32;
let size_lo = total_size as u32;
let tag_wide: Vec<u16> = tag.encode_utf16().chain(core::iter::once(0)).collect();
let tag_wide: Option<Vec<u16>> =
tag.map(|tag| tag.encode_utf16().chain(core::iter::once(0)).collect());

let map_handle = unsafe {
CreateFileMappingW(
Expand All @@ -441,7 +442,9 @@ pub fn create_named_mapping(
fl_protect,
size_hi,
size_lo,
tag_wide.as_ptr(),
tag_wide
.as_ref()
.map_or(core::ptr::null(), |tag| tag.as_ptr()),
)
}
.into_owned()
Expand Down
33 changes: 32 additions & 1 deletion crates/host_env/src/nt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,20 @@ impl core::fmt::Debug for Scandir {
/// `rposix_scandir.opendir`: join `*.*` onto the directory and open the walk.
pub fn scandir(path: &widestring::WideCStr) -> io::Result<Scandir> {
let dir = path.as_slice().to_vec();
let mut mask = join_path_filename(&dir, &[b'*' as u16, b'.' as u16, b'*' as u16]);
// Normalize before appending the wildcard, as CPython does. Otherwise a
// trailing space becomes an interior path component and is not stripped.
// Extended paths deliberately retain their literal spelling.
let normalized;
let search_dir = if !dir.is_empty()
&& !dir.starts_with(&[b'\\' as u16, b'\\' as u16, b'?' as u16, b'\\' as u16])
{
use std::os::windows::ffi::OsStrExt;
normalized = getfullpathname(path)?.encode_wide().collect::<Vec<_>>();
&normalized
} else {
&dir
};
let mut mask = join_path_filename(search_dir, &[b'*' as u16, b'.' as u16, b'*' as u16]);
mask.push(0);
let mut data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() };
let handle = unsafe { FindFirstFileW(mask.as_ptr(), &mut data) };
Expand Down Expand Up @@ -2499,6 +2512,24 @@ pub fn execve(
}
}

#[cfg(test)]
mod scandir_tests {
use super::*;

#[test]
fn trailing_space_is_normalized_except_in_extended_paths() {
let mut path = getfullpathname(widestring::widecstr!(".")).unwrap();
path.push(" ");
let wide = WideCString::from_os_str(&path).unwrap();
assert!(scandir(&wide).is_ok());

let mut extended = OsString::from("\\\\?\\");
extended.push(path);
let wide = WideCString::from_os_str(&extended).unwrap();
assert!(scandir(&wide).is_err());
}
}

#[cfg(test)]
mod find_last_utf8_boundary_tests {
use super::find_last_utf8_boundary;
Expand Down
47 changes: 40 additions & 7 deletions crates/stdlib/src/mmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ mod mmap {
pos: AtomicCell<usize>, // relative to offset
exports: AtomicCell<usize>,
access: AccessMode,
#[cfg(unix)]
trackfd: bool,
#[cfg(target_os = "linux")]
anonymous: bool,
Expand Down Expand Up @@ -263,6 +262,8 @@ mod mmap {
access: AccessMode,
#[pyarg(any, default)]
offset: i64,
#[pyarg(named, default = true)]
trackfd: bool,
}

impl MmapNewArgs {
Expand Down Expand Up @@ -509,6 +510,7 @@ mod mmap {
tagname,
access,
offset,
trackfd,
..
} = args;

Expand Down Expand Up @@ -589,8 +591,9 @@ mod mmap {
}
}

// When tagname is provided, use raw Win32 APIs for named shared memory
if let Some(ref tag) = tag_str {
// memmap2 retains a duplicated file handle, so untracked mappings
// must use Win32 directly as well as named shared memory mappings.
if tag_str.is_some() || !trackfd {
let fh = if let Some(fh) = fh {
// Close the duplicated handle - we'll use the original
// file handle for CreateFileMappingW
Expand All @@ -602,9 +605,9 @@ mod mmap {
host_mmap::INVALID_HANDLE
};

let named = host_mmap::create_named_mapping(
let named = host_mmap::create_mapping(
fh,
tag,
tag_str.as_deref(),
match access {
AccessMode::Default => host_mmap::AccessMode::Default,
AccessMode::Read => host_mmap::AccessMode::Read,
Expand All @@ -631,12 +634,16 @@ mod mmap {
pos: AtomicCell::new(0),
exports: AtomicCell::new(0),
access,
trackfd,
});
}

let (handle, mmap) = if duplicated_handle != host_mmap::INVALID_HANDLE {
let mmap = Self::create_mmap_windows(duplicated_handle, offset, map_size, &access)
.map_err(|e| e.to_pyexception(vm))?;
.map_err(|e| {
host_mmap::close_handle(duplicated_handle);
e.to_pyexception(vm)
})?;
(duplicated_handle as isize, mmap)
} else {
// Anonymous mapping
Expand All @@ -653,6 +660,7 @@ mod mmap {
pos: AtomicCell::new(0),
exports: AtomicCell::new(0),
access,
trackfd,
})
}
}
Expand Down Expand Up @@ -812,7 +820,6 @@ mod mmap {
return Err(vm.new_buffer_error("mmap can't resize with extant buffers exported."));
}

#[cfg(unix)]
if !self.trackfd {
drop(mmap);
return Err(vm.new_value_error("mmap can't resize with trackfd=False."));
Expand Down Expand Up @@ -1312,6 +1319,9 @@ mod mmap {
#[pymethod]
fn size(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIntRef> {
let _mmap = zelf.check_valid(vm)?;
if !zelf.trackfd {
return Err(vm.new_value_error("can't get size with trackfd=False"));
}
let handle = zelf.handle.load();
if host_mmap::is_invalid_handle_value(handle) {
// Anonymous mapping, return the mmap size
Expand Down Expand Up @@ -1642,6 +1652,29 @@ mod mmap {
rejects_borrowed_buffer(false);
}

#[cfg(windows)]
#[test]
fn untracked_mapping_cannot_resize() {
crate::vm::Interpreter::without_stdlib(Default::default()).enter(|vm| {
let mapped = PyMmap::py_new(
vm.ctx.types.type_type,
MmapNewArgs {
fileno: -1,
length: 64,
tagname: None,
access: AccessMode::Default,
offset: 0,
trackfd: false,
},
vm,
)
.unwrap();
assert_eq!(mapped.__len__(), 64);
assert!(!mapped.trackfd);
assert!(mapped.check_resizeable(vm).is_err());
});
}

#[test]
fn resize_rejects_borrowed_buffer() {
rejects_borrowed_buffer(true);
Expand Down
59 changes: 41 additions & 18 deletions crates/stdlib/src/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3485,17 +3485,9 @@ mod _socket {
flags: opts.flags,
};

// Encode host: str uses IDNA encoding, bytes must be valid UTF-8
// Encode non-ASCII hosts with IDNA; bytes must be valid UTF-8.
let host_encoded: Option<String> = match opts.host.as_ref() {
Some(ArgStrOrBytesLike::Str(s)) => {
let encoded =
vm.state
.codec_registry
.encode_text(s.to_owned(), "idna", None, vm)?;
let host_str = core::str::from_utf8(encoded.as_bytes())
.map_err(|_| vm.new_runtime_error("idna output is not utf8"))?;
Some(host_str.to_owned())
}
Some(ArgStrOrBytesLike::Str(s)) => Some(encode_hostname(s.as_wtf8(), vm)?),
Some(ArgStrOrBytesLike::Buf(b)) => {
let bytes = b.borrow_buf();
let host_str = core::str::from_utf8(&bytes).map_err(|e| {
Expand Down Expand Up @@ -3864,7 +3856,7 @@ mod _socket {
{
return Ok(SocketAddr::V4(net::SocketAddrV4::new(addr, 0)));
}
if matches!(af, c::AF_INET | c::AF_UNSPEC)
if matches!(af, c::AF_INET6 | c::AF_UNSPEC)
&& !name.contains('%')
&& let Ok(addr) = name.parse::<Ipv6Addr>()
{
Expand All @@ -3874,17 +3866,30 @@ mod _socket {
address: af,
..Default::default()
};
let name = vm
.state
.codec_registry
.encode_text(pyname.into_wtf8(), "idna", None, vm)?;
let name = core::str::from_utf8(name.as_bytes())
.map_err(|_| vm.new_runtime_error("idna output is not utf8"))?;
let mut res = host_socket::dns::getaddrinfo(Some(name), None, Some(hints))
let name = encode_hostname(pyname.as_wtf8(), vm)?;
let mut res = host_socket::dns::getaddrinfo(Some(&name), None, Some(hints))
.map_err(|e| convert_socket_error(vm, e, SocketError::GaiError))?;
Ok(res.next().unwrap().map(|ainfo| ainfo.sockaddr)?)
}

fn encode_hostname(name: &crate::common::wtf8::Wtf8, vm: &VirtualMachine) -> PyResult<String> {
// Like CPython's idna_converter, ASCII hostnames need no codec lookup.
if let Ok(name) = name.as_str()
&& name.is_ascii()
{
return Ok(name.to_owned());
}
let encoded = vm.state.codec_registry.encode_text(
vm.ctx.new_str(name.to_owned()),
"idna",
None,
vm,
)?;
core::str::from_utf8(encoded.as_bytes())
.map(str::to_owned)
.map_err(|_| vm.new_runtime_error("idna output is not utf8"))
}

fn sock_from_raw(fileno: RawSocket, vm: &VirtualMachine) -> PyResult<Socket> {
let invalid = cfg_select! {
windows => fileno == INVALID_SOCKET,
Expand Down Expand Up @@ -4064,6 +4069,24 @@ mod _socket {
.ok_or_else(|| vm.new_overflow_error("CMSG_SPACE() argument out of range"))
}

#[cfg(test)]
mod address_tests {
use super::*;

#[test]
fn ascii_hosts_do_not_require_idna() {
crate::vm::Interpreter::without_stdlib(Default::default()).enter(|vm| {
for host in ["localhost", "127.0.0.1", "::1", "fe80::1%1"] {
assert_eq!(encode_hostname(host.into(), vm).unwrap(), host);
}
let addr = get_addr(vm, vm.ctx.new_utf8_str("::1"), c::AF_INET6)
.map_err(|err| err.into_pyexception(vm))
.unwrap();
assert_eq!(addr.ip(), net::IpAddr::V6(Ipv6Addr::LOCALHOST));
});
}
}

#[cfg(test)]
#[cfg(all(feature = "ssl-openssl", unix, not(target_os = "aix")))]
mod ssl_identity_tests {
Expand Down
20 changes: 14 additions & 6 deletions crates/unicode/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,14 @@ fn lookup_property_diff<T: Copy>(
/// library (`unicodedata.unidata_version`).
#[must_use]
pub fn unicode_version() -> String {
format!(
"{}.{}.{}",
char::UNICODE_VERSION.0,
char::UNICODE_VERSION.1,
char::UNICODE_VERSION.2
)
// Report the vendored database version, not the compiler's Unicode version.
include_str!("../unicode/latest/NameAliases.txt")
.lines()
.next()
.and_then(|header| header.strip_prefix("# NameAliases-"))
.and_then(|header| header.strip_suffix(".txt"))
.expect("vendored NameAliases.txt must start with its Unicode version")
.into()
}

/// Look up a name in the latest UCD (`unicodedata.lookup`).
Expand Down Expand Up @@ -535,6 +537,12 @@ mod tests {
CodePoint::from(ch)
}

#[test]
fn version_matches_vendored_database() {
assert_eq!(super::unicode_version(), "17.0.0");
assert_eq!(Ucd::new(true).unidata_version(), "17.0.0");
}

#[test]
fn data_queries_match_unicodedata_behavior() {
let ucd = Ucd::new(true);
Expand Down
14 changes: 13 additions & 1 deletion crates/vm/src/getpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,13 @@ fn program_name() -> String {

/// Get the current executable path
fn get_executable_path() -> Option<PathBuf> {
#[cfg(not(target_arch = "wasm32"))]
#[cfg(windows)]
{
// CreateProcess permits argv[0] to differ from the executable filename.
// Like CPython's GetModuleFileNameW, use the loaded image's path.
env::current_exe().ok()
}
#[cfg(all(not(windows), not(target_arch = "wasm32")))]
{
let exec_arg = env::args_os().next()?;
crate::host_env::fs::which(exec_arg)
Expand Down Expand Up @@ -545,6 +551,12 @@ fn parse_pyvenv_home(pyvenv_cfg: &Path) -> Option<Option<String>> {
mod tests {
use super::*;

#[cfg(windows)]
#[test]
fn executable_is_the_loaded_image() {
assert_eq!(get_executable_path(), env::current_exe().ok());
}

#[test]
fn build_time_paths_follow_configured_paths_and_zip() {
for configured in [vec![], vec!["first".to_owned(), "second".to_owned()]] {
Expand Down
Loading
Loading