rcstring/
lib.rs

1/* The MIT License (MIT)
2 *
3 * Copyright (c) 2015 William Orr <will@worrbase.com>
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in all
13 * copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 */
23#![no_std]
24
25use core::cmp::Ordering::{self,Less,Greater,Equal};
26use core::marker::PhantomData;
27use core::result::Result;
28use core::slice;
29
30/// Platform-specific c_char type. This is defined for cases where
31/// the user cannot import libc.
32///
33/// Defined as u8 on arm and i8 everywhere else
34#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
35#[allow(non_camel_case_types)]
36pub type c_char = i8;
37#[cfg(any(target_arch = "arm", target_arch = "aarch64", target_arch = "powerpc"))]
38#[allow(non_camel_case_types)]
39pub type c_char = u8;
40
41
42/// Constructs a new `CString` from a string literal
43///
44/// # Examples
45///
46/// ```
47/// # #[macro_use] extern crate rcstring;
48/// # fn main () {
49/// let cs = cstr!("foo");
50/// # }
51/// ```
52#[macro_export]
53macro_rules! cstr {
54    ($arg:expr) => ($crate::CString::new(concat!($arg, "\0")).unwrap());
55}
56
57/// Type representing a C-compatible string.
58pub struct CString<'a> {
59    data: *const c_char,
60    len: usize,
61    _marker: PhantomData<&'a c_char>
62}
63
64/// Error returned when a `CString` was initialized without a trailing
65/// `NULL` byte, or if there are non-ASCII characters in the string
66#[derive(Debug)]
67pub struct Error(&'static str);
68
69fn strlen(ptr: *const c_char) -> isize {
70    let mut ctr = 0isize;
71    loop {
72        if unsafe { *ptr.offset(ctr) == 0 as c_char } {
73            break
74        }
75
76        ctr += 1;
77    }
78
79    ctr
80}
81
82fn ascii_guard(ptr: *const c_char, len: usize) -> bool {
83    let mut ctr = 0usize;
84    while ctr < len {
85        if unsafe { *ptr.offset(ctr as isize) < 0 as c_char } {
86            return false;
87        }
88
89        ctr += 1;
90    }
91
92    true
93}
94
95impl<'a> CString<'a> {
96    /// Constructs a new CString from a string slice
97    ///
98    /// The caller **MUST** ensure that there is a trailing NULL to terminate
99    /// the string. To make this easier, the `cstr!` macro is provided.
100    ///
101    /// CStrings **MUST NOT** have any non-ASCII characters, even extended
102    /// ASCII characters aren't allowed.
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// use rcstring::CString;
108    ///
109    /// let cs = CString::new("foo\0").unwrap();
110    /// ```
111    pub fn new(s: &'a str) -> Result<CString<'a>, Error> {
112        if s.len() == 0 {
113            return Err(Error("0-length cstring found"));
114        }
115
116        let ret = CString {
117            data: s.as_ptr() as *const c_char,
118            len: s.len(),
119            _marker: PhantomData
120        };
121
122        if ! ascii_guard(ret.data, ret.len) {
123            return Err(Error("Invalid character in string"));
124        }
125
126        if unsafe { *ret.into_raw().offset(ret.len as isize - 1) != 0 } {
127            Err(Error("No NULL terminator present"))
128        } else {
129            Ok(ret)
130        }
131    }
132
133    /// Converts an exising raw pointer to a string and converts it to a
134    /// `CString` with a length determined by an internal `strlen`
135    /// implementation.
136    pub unsafe fn from_raw(ptr: *const c_char) -> Result<CString<'a>, Error> {
137        let siz = strlen(ptr) as usize + 1usize;
138
139        let ret = CString {
140            data: ptr,
141            len: siz,
142            _marker: PhantomData
143        };
144
145        if ! ascii_guard(ret.data, ret.len) {
146            Err(Error("Invalid character in string"))
147        } else {
148            Ok(ret)
149        }
150    }
151
152    /// Returns a mutable pointer to a CString for use in functions taking
153    /// a classic C string
154    pub unsafe fn into_raw(&self) -> *const c_char {
155        self.data
156    }
157
158    /// Returns length of string, *including* trailing NULL
159    pub fn len(&self) -> usize {
160        self.len
161    }
162}
163
164impl<'a> Eq for CString<'a> { }
165impl<'a> PartialEq for CString<'a> {
166    fn eq(&self, other: &CString) -> bool {
167        unsafe {
168            slice::from_raw_parts(self.data, other.len()) ==
169                slice::from_raw_parts(other.data, other.len())
170        }
171    }
172
173    fn ne(&self, other: &CString) -> bool {
174        ! self.eq(other)
175    }
176}
177
178impl<'a> PartialOrd for CString<'a> {
179    fn partial_cmp(&self, other: &CString) -> Option<Ordering> {
180        unsafe {
181            slice::from_raw_parts(self.data, self.len())
182                .partial_cmp(slice::from_raw_parts(other.data, other.len()))
183        }
184    }
185
186    fn le(&self, other: &CString) -> bool {
187        match self.partial_cmp(other) {
188            Some(Less) => true,
189            Some(Equal) => true,
190            _ => false
191        }
192    }
193
194    fn ge(&self, other: &CString) -> bool {
195        match self.partial_cmp(other) {
196            Some(Greater) => true,
197            Some(Equal) => true,
198            _ => false
199        }
200    }
201
202    fn lt(&self, other: &CString) -> bool {
203        match self.partial_cmp(other) {
204            Some(Less) => true,
205            _ => false
206        }
207    }
208
209    fn gt(&self, other: &CString) -> bool {
210        match self.partial_cmp(other) {
211            Some(Greater) => true,
212            _ => false
213        }
214    }
215}
216
217impl<'a> Ord for CString<'a> {
218    fn cmp(&self, other: &CString) -> Ordering {
219        self.data.cmp(&other.data)
220    }
221}
222
223#[cfg(test)] #[macro_use] extern crate std;
224
225#[cfg(test)]
226mod test {
227    use super::{c_char,CString,Error};
228    use std::ffi::CString as OldString;
229    use std::slice;
230    use std::string::String;
231
232    #[test]
233    fn new_cstring() {
234        {
235            let cstr = cstr!("foo");
236            let buf = "foo\0".as_ptr() as *const c_char;
237
238            unsafe {
239                assert_eq!(slice::from_raw_parts(cstr.data, cstr.len()),
240                    slice::from_raw_parts(buf, 4));
241            }
242        }
243        {
244            let raw = OldString::new("foo").unwrap().into_raw();
245            let cstr = unsafe { CString::from_raw(raw).unwrap() };
246            let buf = "foo\0".as_ptr() as *const c_char;
247
248            unsafe {
249                assert_eq!(slice::from_raw_parts(cstr.data, cstr.len()),
250                    slice::from_raw_parts(buf, 4));
251            }
252        }
253        {
254            let cstr_res = CString::new("foo");
255
256            assert!(! cstr_res.is_ok());
257        }
258        {
259            let cstr_res = CString::new("");
260
261            assert!(! cstr_res.is_ok());
262        }
263        {
264            let cstr_res = CString::new("ñ\0");
265
266            assert!(! cstr_res.is_ok());
267        }
268        {
269            let cstr_res = CString::new("ай да, пирожки\0");
270
271            assert!(! cstr_res.is_ok());
272        }
273        {
274            {
275                let s = String::from("foo\0");
276                let cstr_res: Result<CString, Error> = CString::new(s.as_str());
277                assert!(cstr_res.is_ok());
278            }
279        }
280    }
281
282    #[test]
283    fn from_cstring() {
284        {
285            let cstr = cstr!("foo");
286            let raw = unsafe { cstr.into_raw() };
287            let other = OldString::new("foo").unwrap().into_raw();
288
289            unsafe {
290                assert_eq!(*raw, *other);
291            }
292        }
293    }
294
295    #[test]
296    fn cstring_len() {
297        {
298            let cstr = cstr!("foo");
299            assert_eq!(cstr.len(), 4);
300        }
301        {
302            let raw = OldString::new("foo").unwrap().into_raw();
303            let cstr = unsafe { CString::from_raw(raw).unwrap() };
304            assert_eq!(cstr.len(), 4);
305        }
306    }
307
308    #[test]
309    fn eq() {
310        {
311            let cstr = cstr!("foo");
312            let other = cstr!("bar");
313
314            assert!(cstr != other);
315        }
316        {
317            let cstr = cstr!("foo");
318            let other = cstr!("foo");
319
320            assert!(cstr == other);
321        }
322    }
323
324    #[test]
325    fn cmp() {
326        {
327            let cstr = cstr!("foo");
328            let other = cstr!("bar");
329
330            assert!(cstr > other);
331        }
332        {
333            let cstr = cstr!("foo");
334            let other = cstr!("bar");
335
336            assert!(other < cstr);
337        }
338    }
339}