1 /* Copyright 2018-2019 Mozilla Foundation
2  *
3  * Licensed under the Apache License (Version 2.0), or the MIT license,
4  * (the "Licenses") at your option. You may not use this file except in
5  * compliance with one of the Licenses. You may obtain copies of the
6  * Licenses at:
7  *
8  *    http://www.apache.org/licenses/LICENSE-2.0
9  *    http://opensource.org/licenses/MIT
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the Licenses is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the Licenses for the specific language governing permissions and
15  * limitations under the Licenses. */
16 
17 use std::ffi::CStr;
18 use std::marker::PhantomData;
19 use std::os::raw::c_char;
20 
21 /// `FfiStr<'a>` is a safe (`#[repr(transparent)]`) wrapper around a
22 /// nul-terminated `*const c_char` (e.g. a C string). Conceptually, it is
23 /// similar to [`std::ffi::CStr`], except that it may be used in the signatures
24 /// of extern "C" functions.
25 ///
26 /// Functions accepting strings should use this instead of accepting a C string
27 /// directly. This allows us to write those functions using safe code without
28 /// allowing safe Rust to cause memory unsafety.
29 ///
30 /// A single function for constructing these from Rust ([`FfiStr::from_raw`])
31 /// has been provided. Most of the time, this should not be necessary, and users
32 /// should accept `FfiStr` in the parameter list directly.
33 ///
34 /// ## Caveats
35 ///
36 /// An effort has been made to make this struct hard to misuse, however it is
37 /// still possible, if the `'static` lifetime is manually specified in the
38 /// struct. E.g.
39 ///
40 /// ```rust,no_run
41 /// # use ffi_support::FfiStr;
42 /// // NEVER DO THIS
43 /// #[no_mangle]
44 /// extern "C" fn never_do_this(s: FfiStr<'static>) {
45 ///     // save `s` somewhere, and access it after this
46 ///     // function returns.
47 /// }
48 /// ```
49 ///
50 /// Instead, one of the following patterns should be used:
51 ///
52 /// ```
53 /// # use ffi_support::FfiStr;
54 /// #[no_mangle]
55 /// extern "C" fn valid_use_1(s: FfiStr<'_>) {
56 ///     // Use of `s` after this function returns is impossible
57 /// }
58 /// // Alternative:
59 /// #[no_mangle]
60 /// extern "C" fn valid_use_2(s: FfiStr) {
61 ///     // Use of `s` after this function returns is impossible
62 /// }
63 /// ```
64 #[repr(transparent)]
65 pub struct FfiStr<'a> {
66     cstr: *const c_char,
67     _boo: PhantomData<&'a ()>,
68 }
69 
70 impl<'a> FfiStr<'a> {
71     /// Construct an `FfiStr` from a raw pointer.
72     ///
73     /// This should not be needed most of the time, and users should instead
74     /// accept `FfiStr` in function parameter lists.
75     ///
76     /// # Safety
77     ///
78     /// Dereferences a pointer and is thus unsafe.
79     #[inline]
from_raw(ptr: *const c_char) -> Self80     pub unsafe fn from_raw(ptr: *const c_char) -> Self {
81         Self {
82             cstr: ptr,
83             _boo: PhantomData,
84         }
85     }
86 
87     /// Construct a FfiStr from a `std::ffi::CStr`. This is provided for
88     /// completeness, as a safe method of producing an `FfiStr` in Rust.
89     #[inline]
from_cstr(cstr: &'a CStr) -> Self90     pub fn from_cstr(cstr: &'a CStr) -> Self {
91         Self {
92             cstr: cstr.as_ptr(),
93             _boo: PhantomData,
94         }
95     }
96 
97     /// Get an `&str` out of the `FfiStr`. This will panic in any case that
98     /// [`FfiStr::as_opt_str`] would return `None` (e.g. null pointer or invalid
99     /// UTF-8).
100     ///
101     /// If the string should be optional, you should use [`FfiStr::as_opt_str`]
102     /// instead. If an owned string is desired, use [`FfiStr::into_string`] or
103     /// [`FfiStr::into_opt_string`].
104     #[inline]
as_str(&self) -> &'a str105     pub fn as_str(&self) -> &'a str {
106         self.as_opt_str()
107             .expect("Unexpected null string pointer passed to rust")
108     }
109 
110     /// Get an `Option<&str>` out of the `FfiStr`. If this stores a null
111     /// pointer, then None will be returned. If a string containing invalid
112     /// UTF-8 was passed, then an error will be logged and `None` will be
113     /// returned.
114     ///
115     /// If the string is a required argument, use [`FfiStr::as_str`], or
116     /// [`FfiStr::into_string`] instead. If `Option<String>` is desired, use
117     /// [`FfiStr::into_opt_string`] (which will handle invalid UTF-8 by
118     /// replacing with the replacement character).
as_opt_str(&self) -> Option<&'a str>119     pub fn as_opt_str(&self) -> Option<&'a str> {
120         if self.cstr.is_null() {
121             return None;
122         }
123         unsafe {
124             match std::ffi::CStr::from_ptr(self.cstr).to_str() {
125                 Ok(s) => Some(s),
126                 Err(e) => {
127                     log::error!("Invalid UTF-8 was passed to rust! {:?}", e);
128                     None
129                 }
130             }
131         }
132     }
133 
134     /// Get an `Option<String>` out of the `FfiStr`. Returns `None` if this
135     /// `FfiStr` holds a null pointer. Note that unlike [`FfiStr::as_opt_str`],
136     /// invalid UTF-8 is replaced with the replacement character instead of
137     /// causing us to return None.
138     ///
139     /// If the string should be mandatory, you should use
140     /// [`FfiStr::into_string`] instead. If an owned string is not needed, you
141     /// may want to use [`FfiStr::as_str`] or [`FfiStr::as_opt_str`] instead,
142     /// (however, note the differences in how invalid UTF-8 is handled, should
143     /// this be relevant to your use).
into_opt_string(self) -> Option<String>144     pub fn into_opt_string(self) -> Option<String> {
145         if !self.cstr.is_null() {
146             unsafe { Some(CStr::from_ptr(self.cstr).to_string_lossy().to_string()) }
147         } else {
148             None
149         }
150     }
151 
152     /// Get a `String` out of a `FfiStr`. This function is essential a
153     /// convenience wrapper for `ffi_str.into_opt_string().unwrap()`, with a
154     /// message that indicates that a null argument was passed to rust when it
155     /// should be mandatory. As with [`FfiStr::into_opt_string`], invalid UTF-8
156     /// is replaced with the replacement character if encountered.
157     ///
158     /// If the string should *not* be mandatory, you should use
159     /// [`FfiStr::into_opt_string`] instead. If an owned string is not needed,
160     /// you may want to use [`FfiStr::as_str`] or [`FfiStr::as_opt_str`]
161     /// instead, (however, note the differences in how invalid UTF-8 is handled,
162     /// should this be relevant to your use).
163     #[inline]
into_string(self) -> String164     pub fn into_string(self) -> String {
165         self.into_opt_string()
166             .expect("Unexpected null string pointer passed to rust")
167     }
168 }
169 
170 impl<'a> std::fmt::Debug for FfiStr<'a> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result171     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172         if let Some(s) = self.as_opt_str() {
173             write!(f, "FfiStr({:?})", s)
174         } else {
175             write!(f, "FfiStr(null)")
176         }
177     }
178 }
179 
180 // Conversions...
181 
182 impl<'a> From<FfiStr<'a>> for String {
183     #[inline]
from(f: FfiStr<'a>) -> Self184     fn from(f: FfiStr<'a>) -> Self {
185         f.into_string()
186     }
187 }
188 
189 impl<'a> From<FfiStr<'a>> for Option<String> {
190     #[inline]
from(f: FfiStr<'a>) -> Self191     fn from(f: FfiStr<'a>) -> Self {
192         f.into_opt_string()
193     }
194 }
195 
196 impl<'a> From<FfiStr<'a>> for Option<&'a str> {
197     #[inline]
from(f: FfiStr<'a>) -> Self198     fn from(f: FfiStr<'a>) -> Self {
199         f.as_opt_str()
200     }
201 }
202 
203 impl<'a> From<FfiStr<'a>> for &'a str {
204     #[inline]
from(f: FfiStr<'a>) -> Self205     fn from(f: FfiStr<'a>) -> Self {
206         f.as_str()
207     }
208 }
209 
210 // TODO: `AsRef<str>`?
211 
212 // Comparisons...
213 
214 // Compare FfiStr with eachother
215 impl<'a> PartialEq for FfiStr<'a> {
216     #[inline]
eq(&self, other: &FfiStr<'a>) -> bool217     fn eq(&self, other: &FfiStr<'a>) -> bool {
218         self.as_opt_str() == other.as_opt_str()
219     }
220 }
221 
222 // Compare FfiStr with str
223 impl<'a> PartialEq<str> for FfiStr<'a> {
224     #[inline]
eq(&self, other: &str) -> bool225     fn eq(&self, other: &str) -> bool {
226         self.as_opt_str() == Some(other)
227     }
228 }
229 
230 // Compare FfiStr with &str
231 impl<'a, 'b> PartialEq<&'b str> for FfiStr<'a> {
232     #[inline]
eq(&self, other: &&'b str) -> bool233     fn eq(&self, other: &&'b str) -> bool {
234         self.as_opt_str() == Some(*other)
235     }
236 }
237 
238 // rhs/lhs swap version of above
239 impl<'a> PartialEq<FfiStr<'a>> for str {
240     #[inline]
eq(&self, other: &FfiStr<'a>) -> bool241     fn eq(&self, other: &FfiStr<'a>) -> bool {
242         Some(self) == other.as_opt_str()
243     }
244 }
245 
246 // rhs/lhs swap...
247 impl<'a, 'b> PartialEq<FfiStr<'a>> for &'b str {
248     #[inline]
eq(&self, other: &FfiStr<'a>) -> bool249     fn eq(&self, other: &FfiStr<'a>) -> bool {
250         Some(*self) == other.as_opt_str()
251     }
252 }
253