• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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