use memchr;
use std::ascii;
use std::borrow::{Borrow, Cow};
use std::cmp::Ordering;
use std::error::Error;
use std::fmt::{self, Write};
use std::hash::{Hash, Hasher};
use std::io;
use std::mem;
use std::ops;
use std::os::raw::c_char;
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::ptr;
use std::slice;
use std::str::{self, Utf8Error};
use sys;
#[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Clone)]
pub struct CString {
inner: Box<[u8]>,
}
extern "C" {
type CStrRepr;
}
pub struct CStr {
inner: CStrRepr,
}
unsafe impl Send for CStr {}
unsafe impl Sync for CStr {}
impl UnwindSafe for CStr {}
impl RefUnwindSafe for CStr {}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct NulError(usize, Vec<u8>);
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct FromBytesWithNulError {
kind: FromBytesWithNulErrorKind,
}
#[derive(Clone, PartialEq, Eq, Debug)]
enum FromBytesWithNulErrorKind {
InteriorNul(usize),
NotNulTerminated,
}
impl FromBytesWithNulError {
fn interior_nul(pos: usize) -> FromBytesWithNulError {
FromBytesWithNulError {
kind: FromBytesWithNulErrorKind::InteriorNul(pos),
}
}
fn not_nul_terminated() -> FromBytesWithNulError {
FromBytesWithNulError {
kind: FromBytesWithNulErrorKind::NotNulTerminated,
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct IntoStringError {
inner: CString,
error: Utf8Error,
}
impl CString {
pub fn new<T: Into<Vec<u8>>>(t: T) -> Result<CString, NulError> {
Self::_new(t.into())
}
fn _new(bytes: Vec<u8>) -> Result<CString, NulError> {
match memchr::memchr(0, &bytes) {
Some(i) => Err(NulError(i, bytes)),
None => Ok(unsafe { CString::from_vec_unchecked(bytes) }),
}
}
pub unsafe fn from_vec_unchecked(mut v: Vec<u8>) -> CString {
v.reserve_exact(1);
v.push(0);
CString {
inner: v.into_boxed_slice(),
}
}
pub unsafe fn from_raw(ptr: *mut c_char) -> CString {
let len = sys::strlen(ptr) + 1; let slice = slice::from_raw_parts_mut(ptr, len as usize);
CString {
inner: Box::from_raw(slice as *mut [c_char] as *mut [u8]),
}
}
#[inline]
pub fn into_raw(self) -> *mut c_char {
Box::into_raw(self.into_inner()) as *mut c_char
}
pub fn into_string(self) -> Result<String, IntoStringError> {
String::from_utf8(self.into_bytes()).map_err(|e| IntoStringError {
error: e.utf8_error(),
inner: unsafe { CString::from_vec_unchecked(e.into_bytes()) },
})
}
pub fn into_bytes(self) -> Vec<u8> {
let mut vec = self.into_inner().into_vec();
let _nul = vec.pop();
debug_assert_eq!(_nul, Some(0u8));
vec
}
pub fn into_bytes_with_nul(self) -> Vec<u8> {
self.into_inner().into_vec()
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.inner[..self.inner.len() - 1]
}
#[inline]
pub fn as_bytes_with_nul(&self) -> &[u8] {
&self.inner
}
#[inline]
pub fn as_c_str(&self) -> &CStr {
&*self
}
pub fn into_boxed_c_str(self) -> Box<CStr> {
unsafe { Box::from_raw(Box::into_raw(self.into_inner()) as *mut CStr) }
}
fn into_inner(self) -> Box<[u8]> {
unsafe {
let result = ptr::read(&self.inner);
mem::forget(self);
result
}
}
}
impl Drop for CString {
#[inline]
fn drop(&mut self) {
unsafe {
*self.inner.get_unchecked_mut(0) = 0;
}
}
}
impl ops::Deref for CString {
type Target = CStr;
#[inline]
fn deref(&self) -> &CStr {
unsafe { CStr::from_bytes_with_nul_unchecked(self.as_bytes_with_nul()) }
}
}
impl fmt::Debug for CString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl From<CString> for Vec<u8> {
#[inline]
fn from(s: CString) -> Vec<u8> {
s.into_bytes()
}
}
impl fmt::Debug for CStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "\"")?;
for byte in self
.to_bytes()
.iter()
.flat_map(|&b| ascii::escape_default(b))
{
f.write_char(byte as char)?;
}
write!(f, "\"")
}
}
impl<'a> Default for &'a CStr {
fn default() -> &'a CStr {
const SLICE: &'static [c_char] = &[0];
unsafe { CStr::from_ptr(SLICE.as_ptr()) }
}
}
impl Default for CString {
fn default() -> CString {
let a: &CStr = Default::default();
a.to_owned()
}
}
impl Borrow<CStr> for CString {
#[inline]
fn borrow(&self) -> &CStr {
self
}
}
impl<'a> From<&'a CStr> for Box<CStr> {
fn from(s: &'a CStr) -> Box<CStr> {
let boxed: Box<[u8]> = Box::from(s.to_bytes_with_nul());
unsafe { Box::from_raw(Box::into_raw(boxed) as *mut CStr) }
}
}
impl From<Box<CStr>> for CString {
#[inline]
fn from(s: Box<CStr>) -> CString {
s.into_c_string()
}
}
impl From<CString> for Box<CStr> {
#[inline]
fn from(s: CString) -> Box<CStr> {
s.into_boxed_c_str()
}
}
impl Default for Box<CStr> {
fn default() -> Box<CStr> {
let boxed: Box<[u8]> = Box::from([0]);
unsafe { Box::from_raw(Box::into_raw(boxed) as *mut CStr) }
}
}
impl NulError {
pub fn nul_position(&self) -> usize {
self.0
}
pub fn into_vec(self) -> Vec<u8> {
self.1
}
}
impl Error for NulError {
fn description(&self) -> &str {
"nul byte found in data"
}
}
impl fmt::Display for NulError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "nul byte found in provided data at position: {}", self.0)
}
}
impl From<NulError> for io::Error {
fn from(_: NulError) -> io::Error {
io::Error::new(
io::ErrorKind::InvalidInput,
"data provided contains a nul byte",
)
}
}
impl Error for FromBytesWithNulError {
fn description(&self) -> &str {
match self.kind {
FromBytesWithNulErrorKind::InteriorNul(..) => {
"data provided contains an interior nul byte"
}
FromBytesWithNulErrorKind::NotNulTerminated => "data provided is not nul terminated",
}
}
}
impl fmt::Display for FromBytesWithNulError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.description())?;
if let FromBytesWithNulErrorKind::InteriorNul(pos) = self.kind {
write!(f, " at byte pos {}", pos)?;
}
Ok(())
}
}
impl IntoStringError {
pub fn into_cstring(self) -> CString {
self.inner
}
pub fn utf8_error(&self) -> Utf8Error {
self.error
}
}
impl Error for IntoStringError {
fn description(&self) -> &str {
"C string contained non-utf8 bytes"
}
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.error)
}
}
impl fmt::Display for IntoStringError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.description().fmt(f)
}
}
impl CStr {
#[inline]
pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a CStr {
(ptr as *const CStr)
.as_ref()
.expect("cannot convert a null pointer to a CStr")
}
pub fn from_bytes_with_nul(bytes: &[u8]) -> Result<&CStr, FromBytesWithNulError> {
let nul_pos = memchr::memchr(0, bytes);
if let Some(nul_pos) = nul_pos {
if nul_pos + 1 != bytes.len() {
return Err(FromBytesWithNulError::interior_nul(nul_pos));
}
Ok(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) })
} else {
Err(FromBytesWithNulError::not_nul_terminated())
}
}
#[inline]
pub unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr {
Self::from_ptr(bytes.as_ptr() as *const _)
}
#[inline]
pub fn as_ptr(&self) -> *const c_char {
&self.inner as *const _ as *const c_char
}
#[inline]
pub fn to_bytes(&self) -> &[u8] {
let bytes = self.to_bytes_with_nul();
&bytes[..bytes.len() - 1]
}
#[inline]
pub fn to_bytes_with_nul(&self) -> &[u8] {
unsafe {
let ptr = self.as_ptr();
let len = sys::strlen(ptr) + 1; slice::from_raw_parts(ptr as *const _, len as usize)
}
}
pub fn to_str(&self) -> Result<&str, str::Utf8Error> {
str::from_utf8(self.to_bytes())
}
pub fn to_string_lossy(&self) -> Cow<str> {
String::from_utf8_lossy(self.to_bytes())
}
pub fn into_c_string(self: Box<CStr>) -> CString {
unsafe {
let len = sys::strlen(self.as_ptr()) + 1;
let raw = Box::into_raw(self) as *mut u8;
let slice = slice::from_raw_parts_mut(raw, len) as *mut [u8];
CString {
inner: Box::from_raw(slice),
}
}
}
}
impl PartialEq for CStr {
fn eq(&self, other: &CStr) -> bool {
self.to_bytes().eq(other.to_bytes())
}
}
impl Eq for CStr {}
impl PartialOrd for CStr {
fn partial_cmp(&self, other: &CStr) -> Option<Ordering> {
self.to_bytes().partial_cmp(&other.to_bytes())
}
}
impl Ord for CStr {
fn cmp(&self, other: &CStr) -> Ordering {
self.to_bytes().cmp(&other.to_bytes())
}
}
impl Hash for CStr {
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.to_bytes_with_nul().hash(hasher);
}
}
impl ToOwned for CStr {
type Owned = CString;
fn to_owned(&self) -> CString {
CString {
inner: self.to_bytes_with_nul().into(),
}
}
}
impl<'a> From<&'a CStr> for CString {
fn from(s: &'a CStr) -> CString {
s.to_owned()
}
}
impl ops::Index<ops::RangeFull> for CString {
type Output = CStr;
#[inline]
fn index(&self, _index: ops::RangeFull) -> &CStr {
self
}
}
impl AsRef<CStr> for CStr {
#[inline]
fn as_ref(&self) -> &CStr {
self
}
}
impl AsRef<CStr> for CString {
#[inline]
fn as_ref(&self) -> &CStr {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::borrow::Cow::{Borrowed, Owned};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::os::raw::c_char;
#[test]
fn cstr_is_not_fat() {
use std::mem::size_of;
assert_eq!(size_of::<&'static CStr>(), size_of::<&'static c_char>());
assert_eq!(size_of::<Box<CStr>>(), size_of::<Box<c_char>>());
}
#[test]
fn c_to_rust() {
let data = b"123\0";
let ptr = data.as_ptr() as *const c_char;
unsafe {
assert_eq!(CStr::from_ptr(ptr).to_bytes(), b"123");
assert_eq!(CStr::from_ptr(ptr).to_bytes_with_nul(), b"123\0");
}
}
#[test]
fn simple() {
let s = CString::new("1234").unwrap();
assert_eq!(s.as_bytes(), b"1234");
assert_eq!(s.as_bytes_with_nul(), b"1234\0");
}
#[test]
fn build_with_zero1() {
assert!(CString::new(&b"\0"[..]).is_err());
}
#[test]
fn build_with_zero2() {
assert!(CString::new(vec![0]).is_err());
}
#[test]
fn build_with_zero3() {
unsafe {
let s = CString::from_vec_unchecked(vec![0]);
assert_eq!(s.as_bytes(), b"\0");
}
}
#[test]
fn formatted() {
let s = CString::new(&b"abc\x01\x02\n\xE2\x80\xA6\xFF"[..]).unwrap();
assert_eq!(format!("{:?}", s), r#""abc\x01\x02\n\xe2\x80\xa6\xff""#);
}
#[test]
fn borrowed() {
unsafe {
let s = CStr::from_ptr(b"12\0".as_ptr() as *const _);
assert_eq!(s.to_bytes(), b"12");
assert_eq!(s.to_bytes_with_nul(), b"12\0");
}
}
#[test]
fn to_str() {
let data = b"123\xE2\x80\xA6\0";
let ptr = data.as_ptr() as *const c_char;
unsafe {
assert_eq!(CStr::from_ptr(ptr).to_str(), Ok("123…"));
assert_eq!(CStr::from_ptr(ptr).to_string_lossy(), Borrowed("123…"));
}
let data = b"123\xE2\0";
let ptr = data.as_ptr() as *const c_char;
unsafe {
assert!(CStr::from_ptr(ptr).to_str().is_err());
assert_eq!(
CStr::from_ptr(ptr).to_string_lossy(),
Owned::<str>(format!("123\u{FFFD}"))
);
}
}
#[test]
fn to_owned() {
let data = b"123\0";
let ptr = data.as_ptr() as *const c_char;
let owned = unsafe { CStr::from_ptr(ptr).to_owned() };
assert_eq!(owned.as_bytes_with_nul(), data);
}
#[test]
fn equal_hash() {
let data = b"123\xE2\xFA\xA6\0";
let ptr = data.as_ptr() as *const c_char;
let cstr: &'static CStr = unsafe { CStr::from_ptr(ptr) };
let mut s = DefaultHasher::new();
cstr.hash(&mut s);
let cstr_hash = s.finish();
let mut s = DefaultHasher::new();
CString::new(&data[..data.len() - 1]).unwrap().hash(&mut s);
let cstring_hash = s.finish();
assert_eq!(cstr_hash, cstring_hash);
}
#[test]
fn from_bytes_with_nul() {
let data = b"123\0";
let cstr = CStr::from_bytes_with_nul(data);
assert_eq!(cstr.map(CStr::to_bytes), Ok(&b"123"[..]));
let cstr = CStr::from_bytes_with_nul(data);
assert_eq!(cstr.map(CStr::to_bytes_with_nul), Ok(&b"123\0"[..]));
unsafe {
let cstr = CStr::from_bytes_with_nul(data);
let cstr_unchecked = CStr::from_bytes_with_nul_unchecked(data);
assert_eq!(cstr, Ok(cstr_unchecked));
}
}
#[test]
fn from_bytes_with_nul_unterminated() {
let data = b"123";
let cstr = CStr::from_bytes_with_nul(data);
assert!(cstr.is_err());
}
#[test]
fn from_bytes_with_nul_interior() {
let data = b"1\023\0";
let cstr = CStr::from_bytes_with_nul(data);
assert!(cstr.is_err());
}
#[test]
fn into_boxed() {
let orig: &[u8] = b"Hello, world!\0";
let cstr = CStr::from_bytes_with_nul(orig).unwrap();
let boxed: Box<CStr> = Box::from(cstr);
let cstring = cstr.to_owned().into_boxed_c_str().into_c_string();
assert_eq!(cstr, &*boxed);
assert_eq!(&*boxed, &*cstring);
assert_eq!(&*cstring, cstr);
}
#[test]
fn boxed_default() {
let boxed = <Box<CStr>>::default();
assert_eq!(boxed.to_bytes_with_nul(), &[0]);
}
}