1 use super::JvmError;
2 use std::{
3 borrow::Cow,
4 ffi::{CStr, CString},
5 };
6
7 /// Converts `s: Cow<[u8]>` into a `Cow<CStr>`, adding a null byte if necessary.
8 ///
9 /// `original`, if present, is the original string, which will be moved into a [`JvmError]`
10 /// in the event of failure. If `original` is absent, then `s` *is* the original
11 /// string (i.e. is encoded in UTF-8), and is to be moved into the `JvmError` upon failure.
12 ///
13 /// # Errors
14 ///
15 /// This will fail if `s` contains any null bytes other than a single null byte at the end.
16 ///
17 /// # Safety
18 ///
19 /// If `original` is `None`, then `s` must contain valid UTF-8.
bytes_to_cstr<'a>( mut s: Cow<'a, [u8]>, original: Option<Cow<'_, str>>, ) -> Result<Cow<'a, CStr>, JvmError>20 pub(super) unsafe fn bytes_to_cstr<'a>(
21 mut s: Cow<'a, [u8]>,
22 original: Option<Cow<'_, str>>,
23 ) -> Result<Cow<'a, CStr>, JvmError> {
24 // Check if it has a null byte at the end already. If not, add one.
25 let mut null_byte_added = false;
26
27 if s.last() != Some(&0) {
28 s.to_mut().push(0);
29 null_byte_added = true;
30 }
31
32 // This function is called if conversion fails because the string has a null byte
33 // in the middle.
34 let convert_error = move |s: Cow<'a, [u8]>| -> JvmError {
35 // We need to get back to a `String` in order to insert it into the error. How
36 // to do that depends on whether we were given a separate original or not.
37 let s: String = {
38 if let Some(original) = original {
39 // Yes, there is a separate original. Use that.
40 original.into_owned()
41 } else {
42 // No, `s` *is* the original. Strip off the null byte if we
43 // added one, then assume the rest is valid UTF-8.
44 let mut s: Vec<u8> = s.into_owned();
45
46 if null_byte_added {
47 let _removed_null_byte: Option<u8> = s.pop();
48 debug_assert_eq!(_removed_null_byte, Some(0));
49 }
50
51 // Safety: The caller of this function asserts that this is valid UTF-8. We
52 // have not changed it other than adding a null byte at the end.
53 unsafe { String::from_utf8_unchecked(s) }
54 }
55 };
56
57 JvmError::NullOptString(s)
58 };
59
60 // Now, try to convert. Exactly how to do this, and exactly how to handle errors, depends
61 // on whether it's borrowed or owned.
62 let s: Cow<'a, CStr> = match s {
63 Cow::Owned(s) => Cow::Owned({
64 CString::from_vec_with_nul(s)
65 .map_err(|error| convert_error(Cow::Owned(error.into_bytes())))?
66 }),
67
68 Cow::Borrowed(s) => Cow::Borrowed({
69 CStr::from_bytes_with_nul(s).map_err(|_error| convert_error(Cow::Borrowed(s)))?
70 }),
71 };
72
73 // Done.
74 Ok(s)
75 }
76
77 /// Converts `s: Cow<str>` into a `Cow<CStr>`, still in UTF-8 encoding, adding a null byte if
78 /// necessary.
utf8_to_cstr<'a>(s: Cow<'a, str>) -> Result<Cow<'a, CStr>, JvmError>79 pub(super) fn utf8_to_cstr<'a>(s: Cow<'a, str>) -> Result<Cow<'a, CStr>, JvmError> {
80 let s: Cow<'a, [u8]> = match s {
81 Cow::Owned(s) => Cow::Owned(s.into_bytes()),
82 Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
83 };
84
85 // Safety: `s` was just converted from type `str`, so it's already known to contain valid
86 // UTF-8.
87 unsafe { bytes_to_cstr(s, None) }
88 }
89
90 #[test]
test()91 fn test() {
92 use assert_matches::assert_matches;
93
94 {
95 let result = utf8_to_cstr("Hello, world ".into()).unwrap();
96 assert_eq!(
97 result.to_bytes_with_nul(),
98 b"Hello, world \xf0\x9f\x98\x8e\0"
99 );
100 assert_matches!(result, Cow::Owned(_));
101 }
102
103 {
104 let result = utf8_to_cstr("Hello, world \0".into()).unwrap();
105 assert_eq!(
106 result.to_bytes_with_nul(),
107 b"Hello, world \xf0\x9f\x98\x8e\0"
108 );
109 assert_matches!(result, Cow::Borrowed(_));
110 }
111
112 {
113 let result = utf8_to_cstr("Hello,\0world".into()).unwrap_err();
114 let error_string = assert_matches!(result, JvmError::NullOptString(string) => string);
115 assert_eq!(error_string, "Hello,\0world");
116 }
117 }
118