use std::ffi::{OsStr, OsString};
use super::types::{PmpxStr, PMPX_ERR_INVALID_ARGS};
pub fn leak_bytes(bytes: &[u8]) -> PmpxStr {
let boxed: Box<[u8]> = bytes.to_vec().into_boxed_slice();
let out = PmpxStr {
ptr: boxed.as_ptr(),
len: boxed.len(),
};
std::mem::forget(boxed);
out
}
pub fn leak_str(s: &str) -> PmpxStr {
leak_bytes(s.as_bytes())
}
pub unsafe fn free_str(s: PmpxStr) {
if s.ptr.is_null() {
return;
}
let raw = std::ptr::slice_from_raw_parts_mut(s.ptr as *mut u8, s.len);
drop(unsafe { Box::from_raw(raw) });
}
pub unsafe fn read_os(s: PmpxStr) -> OsString {
if s.len == 0 {
return OsString::new();
}
let bytes = unsafe { std::slice::from_raw_parts(s.ptr, s.len) };
bytes_to_os(bytes)
}
pub unsafe fn read_str<'a>(s: PmpxStr) -> Result<&'a str, u32> {
if s.len == 0 {
return Ok("");
}
let bytes = unsafe { std::slice::from_raw_parts(s.ptr, s.len) };
std::str::from_utf8(bytes).map_err(|_| PMPX_ERR_INVALID_ARGS)
}
#[cfg(unix)]
pub fn bytes_to_os(bytes: &[u8]) -> OsString {
use std::os::unix::ffi::OsStringExt;
OsString::from_vec(bytes.to_vec())
}
#[cfg(not(unix))]
pub fn bytes_to_os(bytes: &[u8]) -> OsString {
String::from_utf8_lossy(bytes).into_owned().into()
}
#[cfg(unix)]
pub fn os_to_bytes(s: &OsStr) -> Vec<u8> {
use std::os::unix::ffi::OsStrExt;
s.as_bytes().to_vec()
}
#[cfg(not(unix))]
pub fn os_to_bytes(s: &OsStr) -> Vec<u8> {
s.to_string_lossy().into_owned().into_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_str_reads_as_empty() {
assert_eq!(unsafe { read_os(PmpxStr::EMPTY) }, OsString::new());
assert_eq!(unsafe { read_str(PmpxStr::EMPTY) }.unwrap(), "");
}
#[test]
fn leak_and_free_round_trip() {
let s = leak_str("hello");
assert_eq!(s.len, 5);
assert_eq!(
unsafe { std::slice::from_raw_parts(s.ptr, s.len) },
b"hello"
);
unsafe { free_str(s) };
}
#[test]
fn free_str_tolerates_null() {
unsafe { free_str(PmpxStr::EMPTY) };
}
#[test]
fn leak_and_free_an_empty_string() {
let s = leak_str("");
assert_eq!(s.len, 0);
assert!(!s.ptr.is_null(), "an empty Box dangles but is not null");
unsafe { free_str(s) };
}
#[test]
fn read_str_rejects_invalid_utf8() {
let bytes = [0xff, 0xfe];
let s = PmpxStr {
ptr: bytes.as_ptr(),
len: bytes.len(),
};
assert_eq!(unsafe { read_str(s) }, Err(PMPX_ERR_INVALID_ARGS));
}
#[test]
fn read_os_round_trips_valid_utf8() {
let bytes = "/tmp/projéct/ünïcode".as_bytes();
let s = PmpxStr {
ptr: bytes.as_ptr(),
len: bytes.len(),
};
let got = unsafe { read_os(s) };
assert_eq!(os_to_bytes(&got), bytes);
}
#[cfg(unix)]
#[test]
fn read_os_keeps_arbitrary_bytes_on_unix() {
let bytes = [0x2f, 0x62, 0x61, 0x64, 0xff];
let s = PmpxStr {
ptr: bytes.as_ptr(),
len: bytes.len(),
};
let got = unsafe { read_os(s) };
assert_eq!(os_to_bytes(&got), bytes, "must be lossless on Unix");
}
#[cfg(not(unix))]
#[test]
fn read_os_replaces_invalid_utf8_off_unix() {
let bytes = [0x2f, 0x62, 0xff];
let s = PmpxStr {
ptr: bytes.as_ptr(),
len: bytes.len(),
};
let got = unsafe { read_os(s) };
let expected = String::from_utf8_lossy(&bytes).into_owned().into_bytes();
assert_eq!(os_to_bytes(&got), expected);
assert_ne!(os_to_bytes(&got), bytes, "off Unix it really is lossy");
}
}