#![deny(missing_docs)]
extern crate memchr;
use std::borrow::Cow;
use std::error;
use std::ffi::{CStr, CString};
use std::fmt;
use std::result;
use memchr::memchr;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NulError<T> {
inner: T,
pos: usize,
}
impl<T> NulError<T> {
#[inline]
pub fn nul_position(&self) -> usize {
self.pos
}
#[inline]
pub fn into_inner(self) -> T {
self.inner
}
}
impl<T> fmt::Display for NulError<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"nul byte found before end of provided data at position: {}",
self.pos
)
}
}
impl<T: fmt::Debug> error::Error for NulError<T> {
fn description(&self) -> &str {
"nul byte found before end of data"
}
}
type Result<T, S> = result::Result<T, NulError<S>>;
pub trait CStrArgument: fmt::Debug + Sized {
type Output: AsRef<CStr>;
fn try_into_cstr(self) -> Result<Self::Output, Self>;
fn into_cstr(self) -> Self::Output {
self.try_into_cstr()
.expect("string contained an interior null byte")
}
}
impl<'a> CStrArgument for CString {
type Output = Self;
#[inline]
fn try_into_cstr(self) -> Result<Self, Self> {
Ok(self)
}
}
impl<'a> CStrArgument for &'a CString {
type Output = &'a CStr;
#[inline]
fn try_into_cstr(self) -> Result<Self::Output, Self> {
Ok(self)
}
}
impl<'a> CStrArgument for &'a CStr {
type Output = Self;
#[inline]
fn try_into_cstr(self) -> Result<Self, Self> {
Ok(self)
}
}
impl CStrArgument for String {
type Output = CString;
#[inline]
fn try_into_cstr(self) -> Result<Self::Output, Self> {
self.into_bytes().try_into_cstr().map_err(|e| NulError {
inner: unsafe { String::from_utf8_unchecked(e.inner) },
pos: e.pos,
})
}
}
impl<'a> CStrArgument for &'a String {
type Output = Cow<'a, CStr>;
#[inline]
fn try_into_cstr(self) -> Result<Self::Output, Self> {
self.as_bytes().try_into_cstr().map_err(|e| NulError {
inner: self,
pos: e.pos,
})
}
}
impl<'a> CStrArgument for &'a str {
type Output = Cow<'a, CStr>;
#[inline]
fn try_into_cstr(self) -> Result<Self::Output, Self> {
self.as_bytes().try_into_cstr().map_err(|e| NulError {
inner: self,
pos: e.pos,
})
}
}
impl<'a> CStrArgument for Vec<u8> {
type Output = CString;
fn try_into_cstr(mut self) -> Result<Self::Output, Self> {
match memchr(0, &self) {
Some(n) if n == (self.len() - 1) => {
self.pop();
Ok(unsafe { CString::from_vec_unchecked(self) })
}
Some(n) => Err(NulError {
inner: self,
pos: n,
}),
None => Ok(unsafe { CString::from_vec_unchecked(self) }),
}
}
}
impl<'a> CStrArgument for &'a Vec<u8> {
type Output = Cow<'a, CStr>;
#[inline]
fn try_into_cstr(self) -> Result<Self::Output, Self> {
self.as_slice().try_into_cstr().map_err(|e| NulError {
inner: self,
pos: e.pos,
})
}
}
impl<'a> CStrArgument for &'a [u8] {
type Output = Cow<'a, CStr>;
fn try_into_cstr(self) -> Result<Self::Output, Self> {
match memchr(0, self) {
Some(n) if n == (self.len() - 1) => Ok(Cow::Borrowed(unsafe {
CStr::from_bytes_with_nul_unchecked(self)
})),
Some(n) => Err(NulError {
inner: self,
pos: n,
}),
None => Ok(Cow::Owned(unsafe {
CString::from_vec_unchecked(self.into())
})),
}
}
}
#[cfg(test)]
mod tests {
use super::{CStrArgument, NulError};
fn test<T, F, R>(t: T, f: F) -> R
where
T: CStrArgument,
F: FnOnce(Result<T::Output, NulError<T>>) -> R, {
f(t.try_into_cstr())
}
#[test]
fn test_basic() {
let case = "";
test(case, |s| {
let s = s.unwrap();
assert_eq!(s.to_bytes_with_nul().len(), case.len() + 1);
assert_ne!(s.as_ptr() as *const u8, case.as_ptr());
});
test(case.to_owned(), |s| {
let s = s.unwrap();
assert_eq!(s.to_bytes_with_nul().len(), case.len() + 1);
});
test(case.as_bytes(), |s| {
let s = s.unwrap();
assert_eq!(s.to_bytes_with_nul().len(), case.len() + 1);
assert_ne!(s.as_ptr() as *const u8, case.as_ptr());
});
let case = "hello";
test(case, |s| {
let s = s.unwrap();
assert_eq!(s.to_bytes_with_nul().len(), case.len() + 1);
assert_ne!(s.as_ptr() as *const u8, case.as_ptr());
});
test(case.to_owned(), |s| {
let s = s.unwrap();
assert_eq!(s.to_bytes_with_nul().len(), case.len() + 1);
});
test(case.as_bytes(), |s| {
let s = s.unwrap();
assert_eq!(s.to_bytes_with_nul().len(), case.len() + 1);
assert_ne!(s.as_ptr() as *const u8, case.as_ptr());
});
}
#[test]
fn test_terminating_null() {
let case = "\0";
test(case, |s| {
let s = s.unwrap();
assert_eq!(s.to_bytes_with_nul().len(), case.len());
assert_eq!(s.as_ptr() as *const u8, case.as_ptr());
});
test(case.to_owned(), |s| {
let s = s.unwrap();
assert_eq!(s.to_bytes_with_nul().len(), case.len());
});
test(case.as_bytes(), |s| {
let s = s.unwrap();
assert_eq!(s.to_bytes_with_nul().len(), case.len());
assert_eq!(s.as_ptr() as *const u8, case.as_ptr());
});
let case = "hello\0";
test(case, |s| {
let s = s.unwrap();
assert_eq!(s.to_bytes_with_nul().len(), case.len());
assert_eq!(s.as_ptr() as *const u8, case.as_ptr());
});
test(case.to_owned(), |s| {
let s = s.unwrap();
assert_eq!(s.to_bytes_with_nul().len(), case.len());
});
test(case.as_bytes(), |s| {
let s = s.unwrap();
assert_eq!(s.to_bytes_with_nul().len(), case.len());
assert_eq!(s.as_ptr() as *const u8, case.as_ptr());
});
}
#[test]
fn test_interior_null() {
let case = "hello\0world";
test(case, |s| s.unwrap_err());
test(case.to_owned(), |s| s.unwrap_err());
test(case.as_bytes(), |s| s.unwrap_err());
}
#[test]
fn test_interior_and_terminating_null() {
let case = "\0\0";
test(case, |s| s.unwrap_err());
test(case.to_owned(), |s| s.unwrap_err());
test(case.as_bytes(), |s| s.unwrap_err());
let case = "hello\0world\0";
test(case, |s| s.unwrap_err());
test(case.to_owned(), |s| s.unwrap_err());
test(case.as_bytes(), |s| s.unwrap_err());
let case = "hello world\0\0";
test(case, |s| s.unwrap_err());
test(case.to_owned(), |s| s.unwrap_err());
test(case.as_bytes(), |s| s.unwrap_err());
}
#[test]
#[should_panic]
fn test_interior_null_panic() {
"\0\0".into_cstr();
}
}