pelite\util/
c_str.rs

1/*!
2Nul-terminated C string.
3*/
4
5use std::{cmp, fmt, mem, ops, str};
6
7use crate::util::{split_f, FromBytes};
8
9//----------------------------------------------------------------
10
11/// Nul-terminated C string.
12#[derive(Eq, Ord, Hash)]
13pub struct CStr {
14	bytes: [u8],
15}
16
17impl CStr {
18	/// Returns the empty nul-terminated C string.
19	pub fn empty() -> &'static CStr {
20		unsafe { CStr::from_bytes_unchecked(&[0]) }
21	}
22	/// Scans the byte slice for a nul-terminated C string.
23	///
24	/// Returns `None` if no nul byte was found.
25	///
26	/// # Examples
27	///
28	/// ```
29	/// use pelite::util::CStr;
30	///
31	/// let c_str = CStr::from_bytes(b"Hello\0World\0").unwrap();
32	/// assert_eq!(c_str.to_str(), Ok("Hello"));
33	/// assert_eq!(c_str.c_str(), b"Hello\0");
34	/// assert_eq!(c_str.len(), 5);
35	///
36	/// let no_nul = CStr::from_bytes(b"not nul terminated");
37	/// assert_eq!(no_nul, None);
38	/// ```
39	pub fn from_bytes(bytes: &[u8]) -> Option<&CStr> {
40		let len = bytes.iter().position(|&byte| byte == 0)?;
41		Some(unsafe { CStr::from_bytes_unchecked(bytes.get_unchecked(..len + 1)) })
42	}
43	/// Interprets a byte slice as a C string.
44	///
45	/// # Safety
46	///
47	/// Ensure that the byte slice ends with the only nul byte.
48	pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &CStr {
49		mem::transmute(bytes)
50	}
51	/// Gets the C string as a nul terminated byte slice.
52	pub fn c_str(&self) -> &[u8] {
53		&self.bytes
54	}
55	/// Casts the C string to an UTF8 validated `str`.
56	pub fn to_str(&self) -> Result<&str, str::Utf8Error> {
57		str::from_utf8(self.as_ref())
58	}
59}
60
61impl FromBytes for CStr {
62	const MIN_SIZE_OF: usize = 0;
63	const ALIGN_OF: usize = 1;
64	unsafe fn from_bytes(bytes: &[u8]) -> Option<&CStr> {
65		CStr::from_bytes(bytes)
66	}
67}
68
69//----------------------------------------------------------------
70
71impl<T: AsRef<[u8]> + ?Sized> PartialEq<T> for CStr {
72	fn eq(&self, rhs: &T) -> bool {
73		self.as_ref() == rhs.as_ref()
74	}
75}
76impl<T: AsRef<[u8]> + ?Sized> PartialOrd<T> for CStr {
77	fn partial_cmp(&self, rhs: &T) -> Option<cmp::Ordering> {
78		self.as_ref().partial_cmp(rhs.as_ref())
79	}
80}
81
82//----------------------------------------------------------------
83
84impl ops::Deref for CStr {
85	type Target = [u8];
86	fn deref(&self) -> &[u8] {
87		self.as_ref()
88	}
89}
90impl AsRef<[u8]> for CStr {
91	fn as_ref(&self) -> &[u8] {
92		// Strip the nul byte
93		let len = self.bytes.len() - 1;
94		unsafe { self.bytes.get_unchecked(..len) }
95	}
96}
97
98//----------------------------------------------------------------
99// Formatting
100
101impl fmt::Debug for CStr {
102	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103		f.write_str("\"")?;
104		let mut bytes = self.as_ref();
105		while bytes.len() > 0 {
106			let byte = bytes[0];
107			match byte {
108				b'\0' => {
109					bytes = &bytes[1..];
110					f.write_str("\\0")?;
111				},
112				b'\n' => {
113					bytes = &bytes[1..];
114					f.write_str("\\n")?;
115				},
116				b'\r' => {
117					bytes = &bytes[1..];
118					f.write_str("\\r")?;
119				},
120				b'\t' => {
121					bytes = &bytes[1..];
122					f.write_str("\\t")?;
123				},
124				b'"' => {
125					bytes = &bytes[1..];
126					f.write_str("\\\"")?;
127				},
128				b'\\' => {
129					bytes = &bytes[1..];
130					f.write_str("\\\\")?;
131				},
132				0x20...0x7E => {
133					let (s, tail) = split_f(bytes, |&byte| byte < 0x20 || byte >= 0x80 || byte == b'"' || byte == b'\\');
134					bytes = tail;
135					let s = unsafe { str::from_utf8_unchecked(s) };
136					f.write_str(s)?;
137				},
138				_ => {
139					let (s, tail) = split_f(bytes, |&byte| byte >= 0x20 && byte < 0x80);
140					bytes = tail;
141					for &byte in s {
142						write!(f, "\\x{:02X}", byte)?;
143					}
144				},
145			};
146		}
147		f.write_str("\"")
148	}
149}
150impl fmt::Display for CStr {
151	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152		let mut bytes = self.as_ref();
153		while bytes.len() > 0 {
154			let byte = bytes[0];
155			// Display ascii as expected
156			if byte < 0x80 {
157				let (s, tail) = split_f(bytes, |&byte| byte >= 0x80);
158				bytes = tail;
159				let s = unsafe { str::from_utf8_unchecked(s) };
160				f.write_str(s)?;
161			}
162			// Escape all other bytes
163			else {
164				let (s, tail) = split_f(bytes, |&byte| byte < 0x80);
165				bytes = tail;
166				for &byte in s {
167					write!(f, "\\x{:02X}", byte)?;
168				}
169			}
170		}
171		Ok(())
172	}
173}
174
175#[cfg(feature = "serde")]
176impl serde::Serialize for CStr {
177	fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
178		serializer.collect_str(self)
179	}
180}
181
182//----------------------------------------------------------------
183
184#[cfg(test)]
185mod tests {
186	use super::CStr;
187
188	#[test]
189	fn from_bytes() {
190		assert_eq!(CStr::from_bytes(b"this is a c str\0").unwrap().c_str(), b"this is a c str\0");
191		assert_eq!(CStr::from_bytes(b"no nul terminator"), None);
192		assert_eq!(CStr::from_bytes(b"valid utf8\0").unwrap().to_str(), Ok("valid utf8"));
193		assert_eq!(CStr::from_bytes(b"length is eighteen\0").unwrap().len(), 18);
194		assert_eq!(CStr::from_bytes(b"length is eighteen\0").unwrap().len(), 18);
195	}
196
197	#[test]
198	fn fmt() {
199		assert_eq!(format!("{}", unsafe { CStr::from_bytes_unchecked(b"\tabc\n\xFFhello\x80world\0") }), "\tabc\n\\xFFhello\\x80world");
200		assert_eq!(format!("{:?}", unsafe { CStr::from_bytes_unchecked(b"\tabc\n\xFFhello\x80world\0") }), r#""\tabc\n\xFFhello\x80world""#);
201	}
202}