|
6 | 6 | */
|
7 | 7 |
|
8 | 8 | use std::convert::Infallible;
|
9 |
| -use std::ffi::c_char; |
10 | 9 | use std::fmt;
|
11 | 10 | use std::fmt::Write;
|
12 | 11 |
|
13 | 12 | use godot_ffi as sys;
|
14 | 13 | use sys::types::OpaqueString;
|
15 | 14 | use sys::{ffi_methods, interface_fn, GodotFfi};
|
16 | 15 |
|
| 16 | +use crate::builtin::string::Encoding; |
17 | 17 | use crate::builtin::{inner, NodePath, StringName, Variant};
|
| 18 | +use crate::meta::error::StringError; |
18 | 19 | use crate::meta::AsArg;
|
19 | 20 | use crate::{impl_shared_string_api, meta};
|
20 | 21 |
|
@@ -77,6 +78,73 @@ impl GString {
|
77 | 78 | Self::default()
|
78 | 79 | }
|
79 | 80 |
|
| 81 | + /// Convert string from bytes with given encoding, returning `Err` on validation errors. |
| 82 | + /// |
| 83 | + /// Intermediate `NUL` characters are not accepted in Godot and always return `Err`. |
| 84 | + /// |
| 85 | + /// Some notes on the encodings: |
| 86 | + /// - **Latin-1:** Since every byte is a valid Latin-1 character, no validation besides the `NUL` byte is performed. |
| 87 | + /// It is your responsibility to ensure that the input is valid Latin-1. |
| 88 | + /// - **ASCII**: Subset of Latin-1, which is additionally validated to be valid, non-`NUL` ASCII characters. |
| 89 | + /// - **UTF-8**: The input is validated to be UTF-8. |
| 90 | + /// |
| 91 | + /// Specifying incorrect encoding is safe, but may result in unintended string values. |
| 92 | + pub fn try_from_bytes(bytes: &[u8], encoding: Encoding) -> Result<Self, StringError> { |
| 93 | + Self::try_from_bytes_with_nul_check(bytes, encoding, true) |
| 94 | + } |
| 95 | + |
| 96 | + /// Convert string from C-string with given encoding, returning `Err` on validation errors. |
| 97 | + /// |
| 98 | + /// Convenience function for [`try_from_bytes()`](Self::try_from_bytes); see its docs for more information. |
| 99 | + pub fn try_from_cstr(cstr: &std::ffi::CStr, encoding: Encoding) -> Result<Self, StringError> { |
| 100 | + Self::try_from_bytes_with_nul_check(cstr.to_bytes(), encoding, false) |
| 101 | + } |
| 102 | + |
| 103 | + pub(super) fn try_from_bytes_with_nul_check( |
| 104 | + bytes: &[u8], |
| 105 | + encoding: Encoding, |
| 106 | + check_nul: bool, |
| 107 | + ) -> Result<Self, StringError> { |
| 108 | + match encoding { |
| 109 | + Encoding::Ascii => { |
| 110 | + // If the bytes are ASCII, we can fall back to Latin-1, which is always valid (except for NUL). |
| 111 | + // is_ascii() does *not* check for the NUL byte, so the check in the Latin-1 branch is still necessary. |
| 112 | + if bytes.is_ascii() { |
| 113 | + Self::try_from_bytes_with_nul_check(bytes, Encoding::Latin1, check_nul) |
| 114 | + .map_err(|_e| StringError::new("intermediate NUL byte in ASCII string")) |
| 115 | + } else { |
| 116 | + Err(StringError::new("invalid ASCII")) |
| 117 | + } |
| 118 | + } |
| 119 | + Encoding::Latin1 => { |
| 120 | + // Intermediate NUL bytes are not accepted in Godot. Both ASCII + Latin-1 encodings need to explicitly check for this. |
| 121 | + if check_nul && bytes.contains(&0) { |
| 122 | + // Error overwritten when called from ASCII branch. |
| 123 | + return Err(StringError::new("intermediate NUL byte in Latin-1 string")); |
| 124 | + } |
| 125 | + |
| 126 | + let s = unsafe { |
| 127 | + Self::new_with_string_uninit(|string_ptr| { |
| 128 | + let ctor = interface_fn!(string_new_with_latin1_chars_and_len); |
| 129 | + ctor( |
| 130 | + string_ptr, |
| 131 | + bytes.as_ptr() as *const std::ffi::c_char, |
| 132 | + bytes.len() as i64, |
| 133 | + ); |
| 134 | + }) |
| 135 | + }; |
| 136 | + Ok(s) |
| 137 | + } |
| 138 | + Encoding::Utf8 => { |
| 139 | + // from_utf8() also checks for intermediate NUL bytes. |
| 140 | + let utf8 = std::str::from_utf8(bytes); |
| 141 | + |
| 142 | + utf8.map(GString::from) |
| 143 | + .map_err(|e| StringError::with_source("invalid UTF-8", e)) |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | + |
80 | 148 | /// Number of characters in the string.
|
81 | 149 | ///
|
82 | 150 | /// _Godot equivalent: `length`_
|
@@ -260,7 +328,7 @@ impl From<&str> for GString {
|
260 | 328 | let ctor = interface_fn!(string_new_with_utf8_chars_and_len);
|
261 | 329 | ctor(
|
262 | 330 | string_ptr,
|
263 |
| - bytes.as_ptr() as *const c_char, |
| 331 | + bytes.as_ptr() as *const std::ffi::c_char, |
264 | 332 | bytes.len() as i64,
|
265 | 333 | );
|
266 | 334 | })
|
@@ -307,7 +375,7 @@ impl From<&GString> for String {
|
307 | 375 |
|
308 | 376 | interface_fn!(string_to_utf8_chars)(
|
309 | 377 | string.string_sys(),
|
310 |
| - buf.as_mut_ptr() as *mut c_char, |
| 378 | + buf.as_mut_ptr() as *mut std::ffi::c_char, |
311 | 379 | len,
|
312 | 380 | );
|
313 | 381 |
|
|
0 commit comments