Skip to content
46 changes: 42 additions & 4 deletions crates/common/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ pub struct LocaleInfo {
pub decimal_point: String,
/// Grouping pattern from `lconv.grouping`.
/// Each element is a group size. The last non-zero element repeats.
/// e.g. `[3, 0]` means groups of 3 repeating forever.
/// e.g. `[3, 0]` means groups of 3 repeating forever. A `CHAR_MAX`
/// element ends grouping, e.g. `[3, CHAR_MAX]` groups only the last 3 digits.
pub grouping: Vec<u8>,
}

Expand Down Expand Up @@ -617,10 +618,16 @@ impl FormatSpec {
return int_part.to_string();
}

#[allow(
clippy::unnecessary_cast,
reason = "core::ffi::c_char is not u8 on all platforms"
)]
const CHAR_MAX: u8 = core::ffi::c_char::MAX as u8;

let mut group_idx = 0;
let mut group_size = locale.grouping[0] as usize;

if group_size == 0 {
if group_size == 0 || locale.grouping[0] == CHAR_MAX {
return int_part.to_string();
}

Expand All @@ -640,9 +647,13 @@ impl FormatSpec {

// Advance to next group size
if group_idx + 1 < locale.grouping.len() {
let next = locale.grouping[group_idx + 1] as usize;
let next = locale.grouping[group_idx + 1];
if next == CHAR_MAX {
groups.push(&int_part[..pos]);
break;
}
if next != 0 {
group_size = next;
group_size = next as usize;
group_idx += 1;
}
// 0 means repeat previous group size forever
Expand Down Expand Up @@ -1897,6 +1908,33 @@ mod tests {
);
}

#[test]
fn format_int_locale_stops_grouping_at_char_max() {
#[allow(
clippy::unnecessary_cast,
reason = "core::ffi::c_char is not u8 on all platforms"
)]
const CHAR_MAX: u8 = core::ffi::c_char::MAX as u8;
let locale = |grouping| LocaleInfo {
thousands_sep: ",".to_owned(),
decimal_point: ".".to_owned(),
grouping,
};
let digits = "1".repeat(200);
assert_eq!(
FormatSpec::insert_locale_grouping(&digits, &locale(vec![CHAR_MAX])),
digits
);
assert_eq!(
FormatSpec::insert_locale_grouping("1234567", &locale(vec![3, CHAR_MAX])),
"1234,567"
);
assert_eq!(
FormatSpec::insert_locale_grouping("1234567", &locale(vec![3, 0])),
"1,234,567"
);
}

#[test]
fn format_complex_locale_rejects_zero_padding_before_after_sign_alignment() {
let locale = LocaleInfo {
Expand Down
160 changes: 158 additions & 2 deletions crates/host_env/src/cert_store.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use core::ffi::CStr;
use std::io;

use schannel::{RawPointer, cert_context::ValidUses, cert_store::CertStore};
use windows_sys::Win32::{
Foundation::{CRYPT_E_NOT_FOUND, GetLastError},
Security::Cryptography::{
CERT_CONTEXT, CRL_CONTEXT, CertCloseStore, CertEnumCRLsInStore, CertOpenSystemStoreW,
PKCS_7_ASN_ENCODING, X509_ASN_ENCODING,
CERT_CONTEXT, CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG, CERT_FIND_PROP_ONLY_ENHKEY_USAGE_FLAG,
CERT_STORE_PROV_SYSTEM_A, CERT_STORE_READONLY_FLAG, CERT_SYSTEM_STORE_LOCAL_MACHINE,
CRL_CONTEXT, CTL_USAGE, CertCloseStore, CertEnumCRLsInStore, CertEnumCertificatesInStore,
CertFreeCertificateContext, CertGetEnhancedKeyUsage, CertOpenStore, CertOpenSystemStoreW,
HCERTSTORE, PKCS_7_ASN_ENCODING, X509_ASN_ENCODING,
},
};

Expand Down Expand Up @@ -51,6 +55,158 @@ fn encoding_type(raw: u32) -> EncodingType {
}
}

fn encoding_type_exact(raw: u32) -> EncodingType {
match raw {
X509_ASN_ENCODING => EncodingType::X509Asn,
PKCS_7_ASN_ENCODING => EncodingType::Pkcs7Asn,
other => EncodingType::Other(other),
}
}

fn open_local_machine(store_name: &CStr) -> io::Result<HCERTSTORE> {
let store = unsafe {
CertOpenStore(
CERT_STORE_PROV_SYSTEM_A,
0,
0,
CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_READONLY_FLAG,
store_name.as_ptr().cast(),
)
};
if store.is_null() {
Err(io::Error::last_os_error())
} else {
Ok(store)
}
}

unsafe fn certificate_uses_for_flags(
cert: *const CERT_CONTEXT,
flags: u32,
) -> io::Result<Option<CertificateUses>> {
let mut size = 0;
if unsafe { CertGetEnhancedKeyUsage(cert, flags, core::ptr::null_mut(), &mut size) } == 0 {
let error = io::Error::last_os_error();
if error.raw_os_error() == Some(CRYPT_E_NOT_FOUND) {
return Ok(None);
}
return Err(error);
}

let words = (size as usize)
.max(core::mem::size_of::<CTL_USAGE>())
.div_ceil(core::mem::size_of::<usize>());
let mut buffer = vec![0usize; words];
let usage = buffer.as_mut_ptr().cast::<CTL_USAGE>();
if unsafe { CertGetEnhancedKeyUsage(cert, flags, usage, &mut size) } == 0 {
let error = io::Error::last_os_error();
if error.raw_os_error() == Some(CRYPT_E_NOT_FOUND) {
return Ok(None);
}
return Err(error);
}

let usage = unsafe { &*usage };
let mut oids = Vec::with_capacity(usage.cUsageIdentifier as usize);
for index in 0..usage.cUsageIdentifier as usize {
let oid = unsafe { *usage.rgpszUsageIdentifier.add(index) };
if !oid.is_null() {
oids.push(
unsafe { CStr::from_ptr(oid.cast()) }
.to_string_lossy()
.into_owned(),
);
}
}
Ok(Some(CertificateUses::Oids(oids)))
}

unsafe fn certificate_uses(cert: *const CERT_CONTEXT) -> io::Result<CertificateUses> {
if let Some(uses) =
unsafe { certificate_uses_for_flags(cert, CERT_FIND_PROP_ONLY_ENHKEY_USAGE_FLAG) }?
{
return Ok(uses);
}
Ok(
unsafe { certificate_uses_for_flags(cert, CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG) }?
.unwrap_or(CertificateUses::All),
)
}

/// Enumerate one read-only local-machine system store, matching the store
/// selected by CPython's Windows SSL helpers. Opening and closing the store
/// are both fallible; a close failure takes precedence over an enumeration
/// failure because it happens last.
pub fn enum_certificates_local_machine(store_name: &CStr) -> io::Result<Vec<CertificateEntry>> {
let store = open_local_machine(store_name)?;
let result = (|| {
let mut entries = Vec::new();
let mut context: *const CERT_CONTEXT = core::ptr::null();
loop {
context = unsafe { CertEnumCertificatesInStore(store, context) };
if context.is_null() {
let error = io::Error::last_os_error();
if error.raw_os_error() == Some(CRYPT_E_NOT_FOUND) {
break;
}
return Err(error);
}
let cert = unsafe { &*context };
let valid_uses = unsafe { certificate_uses(context) };
if let Err(error) = valid_uses {
unsafe { CertFreeCertificateContext(context) };
return Err(error);
}
entries.push(CertificateEntry {
der: unsafe {
core::slice::from_raw_parts(cert.pbCertEncoded, cert.cbCertEncoded as usize)
}
.to_vec(),
encoding: encoding_type_exact(cert.dwCertEncodingType),
valid_uses,
});
}
Ok(entries)
})();
if unsafe { CertCloseStore(store, 0) } == 0 {
return Err(io::Error::last_os_error());
}
result
}

/// Enumerate certificate-revocation lists from one read-only local-machine
/// system store.
pub fn enum_crls_local_machine(store_name: &CStr) -> io::Result<Vec<CrlEntry>> {
let store = open_local_machine(store_name)?;
let result = (|| {
let mut entries = Vec::new();
let mut context: *const CRL_CONTEXT = core::ptr::null();
loop {
context = unsafe { CertEnumCRLsInStore(store, context) };
if context.is_null() {
let error = io::Error::last_os_error();
if error.raw_os_error() == Some(CRYPT_E_NOT_FOUND) {
break;
}
return Err(error);
}
let crl = unsafe { &*context };
entries.push(CrlEntry {
der: unsafe {
core::slice::from_raw_parts(crl.pbCrlEncoded, crl.cbCrlEncoded as usize)
}
.to_vec(),
encoding: encoding_type_exact(crl.dwCertEncodingType),
});
}
Ok(entries)
})();
if unsafe { CertCloseStore(store, 0) } == 0 {
return Err(io::Error::last_os_error());
}
result
}

pub fn enum_certificates(store_name: &str) -> CertificateEntries {
let open_fns = [CertStore::open_current_user, CertStore::open_local_machine];
let mut had_open_store = false;
Expand Down
19 changes: 17 additions & 2 deletions crates/host_env/src/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,12 @@ fn copy_grouping(ptr: *const libc::c_char) -> Vec<libc::c_char> {
let mut out = Vec::new();
let mut cur = ptr;
unsafe {
while ![0, libc::c_char::MAX].contains(&*cur) {
out.push(*cur);
while *cur != 0 {
let value = *cur;
out.push(value);
if value == libc::c_char::MAX {
break;
}
cur = cur.add(1);
}
}
Expand Down Expand Up @@ -707,3 +711,14 @@ mod snapshot {
}
}
}

#[cfg(test)]
mod tests {
use super::copy_grouping;

#[test]
fn grouping_keeps_char_max_terminator() {
let grouping = [3, libc::c_char::MAX, 2, 0];
assert_eq!(copy_grouping(grouping.as_ptr()), [3, libc::c_char::MAX]);
}
}
10 changes: 10 additions & 0 deletions crates/host_env/src/posix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2062,6 +2062,16 @@ pub fn sendfile(
}
}

#[cfg(target_os = "linux")]
/// Send from the input descriptor's current position and update that position.
pub fn sendfile_no_offset(
out_fd: BorrowedFd<'_>,
in_fd: BorrowedFd<'_>,
count: usize,
) -> std::io::Result<usize> {
nix::sys::sendfile::sendfile(out_fd, in_fd, None, count).map_err(std::io::Error::from)
}

#[cfg(target_os = "macos")]
pub fn sendfile(
in_fd: i32,
Expand Down
29 changes: 27 additions & 2 deletions crates/host_env/src/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -804,11 +804,30 @@ pub fn setsockopt_none(fd: libc::c_int, level: i32, name: i32, optlen: u32) -> i
target_os = "openbsd",
))]
pub fn if_nameindex() -> io::Result<Vec<(u32, String)>> {
Ok(if_nameindex_bytes()?
.into_iter()
.map(|(index, name)| (index, String::from_utf8_lossy(&name).into_owned()))
.collect())
}

#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
))]
/// Return interface names as their original platform bytes without applying
/// a lossy text conversion.
pub fn if_nameindex_bytes() -> io::Result<Vec<(u32, Vec<u8>)>> {
let list = nix::net::if_::if_nameindex().map_err(io::Error::from)?;
Ok(list
.to_slice()
.iter()
.map(|iface| (iface.index(), iface.name().to_string_lossy().into_owned()))
.map(|iface| (iface.index(), iface.name().to_bytes().to_vec()))
.collect())
}

Expand All @@ -824,6 +843,12 @@ pub fn if_nametoindex_checked(name: &CStr) -> io::Result<u32> {

#[cfg(unix)]
pub fn if_indextoname_checked(index: u32) -> io::Result<String> {
if_indextoname_bytes(index).map(|name| String::from_utf8_lossy(&name).into_owned())
}

#[cfg(unix)]
/// Return an interface name as its original platform bytes.
pub fn if_indextoname_bytes(index: u32) -> io::Result<Vec<u8>> {
let mut buf = [0u8; libc::IF_NAMESIZE];
let ret = unsafe {
libc::if_indextoname(index as libc::c_uint, buf.as_mut_ptr() as *mut libc::c_char)
Expand All @@ -832,7 +857,7 @@ pub fn if_indextoname_checked(index: u32) -> io::Result<String> {
Err(io::Error::last_os_error())
} else {
let buf = unsafe { CStr::from_ptr(buf.as_ptr() as *const libc::c_char) };
Ok(buf.to_string_lossy().into_owned())
Ok(buf.to_bytes().to_vec())
}
}

Expand Down
15 changes: 11 additions & 4 deletions crates/host_env/src/winsound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,20 @@ pub fn play_sound(source: PlaySoundSource<'_>, flags: u32) -> Result<(), PlaySou
if matches!(source, PlaySoundSource::Memory(_)) && flags & SND_ASYNC != 0 {
return Err(PlaySoundError::MemoryAsyncRejected);
}
// `SND_MEMORY` requires a `Memory(_)` source; an empty pointer would
// dereference garbage.
if !matches!(source, PlaySoundSource::Memory(_)) && flags & SND_MEMORY != 0 {
// `SND_MEMORY` with a name would reinterpret the name's UTF-16 allocation
// as a RIFF buffer. A null source is still the documented stop operation,
// for which the remaining flags are ignored.
if matches!(source, PlaySoundSource::Name(_)) && flags & SND_MEMORY != 0 {
return Err(PlaySoundError::MemoryFlagWithoutBuffer);
}
// An empty slice's pointer is a dangling sentinel. Give WinMM zeroed
// storage instead; zero bytes do not start a RIFF header.
static EMPTY_IMAGE: [u8; 64] = [0; 64];
// SND_MEMORY makes the `LPCWSTR` parameter point at a byte-oriented RIFF
// image. The pointer is only passed through, so it needs no `u16` alignment.
let ptr: *const u16 = match source {
PlaySoundSource::Stop => core::ptr::null(),
PlaySoundSource::Memory([]) => EMPTY_IMAGE.as_ptr().cast(),
PlaySoundSource::Memory(buf) => buf.as_ptr().cast(),
PlaySoundSource::Name(s) => s.as_ptr(),
};
Expand All @@ -64,7 +71,7 @@ pub enum PlaySoundError {
CallFailed,
/// `Memory(_)` source cannot be combined with `SND_ASYNC` in the safe API.
MemoryAsyncRejected,
/// `SND_MEMORY` set in `flags` but no `Memory(_)` buffer supplied.
/// `SND_MEMORY` set in `flags` for a named source.
MemoryFlagWithoutBuffer,
}

Expand Down
Loading
Loading