1#![allow(
12 clippy::undocumented_unsafe_blocks,
13 reason = "each function documents the buffer it requires under `# Safety`; \
14 the Win32 conversion calls are bounded by lengths computed by \
15 the preceding sizing call"
16)]
17
18use alloc::string::String;
19use alloc::vec::Vec;
20
21#[cfg(all(feature = "ansi", feature = "unicode"))]
22compile_error!(
23 "the `ansi` and `unicode` features of nsis-plugin are mutually exclusive; \
24 build an ANSI plug-in with `default-features = false, features = [\"ansi\"]`"
25);
26
27#[cfg(not(any(feature = "ansi", feature = "unicode")))]
28compile_error!("nsis-plugin needs exactly one of the `ansi` or `unicode` features");
29
30#[cfg(feature = "unicode")]
32pub type Tchar = u16;
33
34#[cfg(all(feature = "ansi", not(feature = "unicode")))]
36pub type Tchar = u8;
37
38pub unsafe fn read_bounded(ptr: *const Tchar, max: usize) -> String {
46 let len = unsafe { strlen_bounded(ptr, max) };
47 let units = unsafe { core::slice::from_raw_parts(ptr, len) };
48 decode(units)
49}
50
51pub unsafe fn strlen_bounded(ptr: *const Tchar, max: usize) -> usize {
56 let mut len = 0;
57 while len < max && unsafe { *ptr.add(len) } != 0 {
58 len += 1;
59 }
60 len
61}
62
63pub unsafe fn write_bounded(dst: *mut Tchar, capacity: usize, src: &[Tchar]) -> bool {
71 if capacity == 0 {
72 return src.is_empty();
73 }
74 let room = capacity - 1;
75 let n = src.len().min(room);
76 unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), dst, n) };
77 unsafe { *dst.add(n) = 0 };
78 src.len() <= room
79}
80
81#[cfg(feature = "unicode")]
85pub fn encode(s: &str) -> Vec<Tchar> {
86 s.encode_utf16().collect()
87}
88
89#[cfg(feature = "unicode")]
91pub fn decode(units: &[Tchar]) -> String {
92 decode_utf16(units)
93}
94
95#[cfg(any(feature = "unicode", target_os = "windows"))]
97fn decode_utf16(units: &[u16]) -> String {
98 char::decode_utf16(units.iter().copied())
99 .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER))
100 .collect()
101}
102
103#[cfg(all(feature = "ansi", not(feature = "unicode"), target_os = "windows"))]
106mod ansi {
107 use alloc::string::String;
108 use alloc::vec;
109 use alloc::vec::Vec;
110 use core::ffi::{c_char, c_int, c_void};
111
112 const CP_ACP: u32 = 0;
113
114 #[link(name = "kernel32")]
115 unsafe extern "system" {
116 fn MultiByteToWideChar(
117 codepage: u32,
118 flags: u32,
119 mbstr: *const c_char,
120 cbmb: c_int,
121 wcstr: *mut u16,
122 cchwc: c_int,
123 ) -> c_int;
124 fn WideCharToMultiByte(
125 codepage: u32,
126 flags: u32,
127 wcstr: *const u16,
128 cchwc: c_int,
129 mbstr: *mut c_char,
130 cbmb: c_int,
131 default_char: *const c_char,
132 used_default: *mut c_void,
133 ) -> c_int;
134 }
135
136 pub fn encode(s: &str) -> Vec<u8> {
137 let wide: Vec<u16> = s.encode_utf16().collect();
138 if wide.is_empty() {
139 return Vec::new();
140 }
141 let len = wide.len() as c_int;
142 let needed = unsafe {
144 WideCharToMultiByte(
145 CP_ACP,
146 0,
147 wide.as_ptr(),
148 len,
149 core::ptr::null_mut(),
150 0,
151 core::ptr::null(),
152 core::ptr::null_mut(),
153 )
154 };
155 if needed <= 0 {
156 return Vec::new();
157 }
158 let mut buf = vec![0u8; needed as usize];
159 unsafe {
160 WideCharToMultiByte(
161 CP_ACP,
162 0,
163 wide.as_ptr(),
164 len,
165 buf.as_mut_ptr().cast(),
166 needed,
167 core::ptr::null(),
168 core::ptr::null_mut(),
169 )
170 };
171 buf
172 }
173
174 pub fn decode(units: &[u8]) -> String {
175 if units.is_empty() {
176 return String::new();
177 }
178 let len = units.len() as c_int;
179 let needed = unsafe {
180 MultiByteToWideChar(
181 CP_ACP,
182 0,
183 units.as_ptr().cast(),
184 len,
185 core::ptr::null_mut(),
186 0,
187 )
188 };
189 if needed <= 0 {
190 return String::new();
191 }
192 let mut wide = vec![0u16; needed as usize];
193 unsafe {
194 MultiByteToWideChar(
195 CP_ACP,
196 0,
197 units.as_ptr().cast(),
198 len,
199 wide.as_mut_ptr(),
200 needed,
201 )
202 };
203 super::decode_utf16(&wide)
204 }
205}
206
207#[cfg(all(feature = "ansi", not(feature = "unicode"), not(target_os = "windows")))]
211mod ansi {
212 use alloc::string::String;
213 use alloc::vec::Vec;
214
215 pub fn encode(s: &str) -> Vec<u8> {
216 s.chars()
217 .map(|c| if (c as u32) < 0x100 { c as u8 } else { b'?' })
218 .collect()
219 }
220
221 pub fn decode(units: &[u8]) -> String {
222 units.iter().map(|&b| b as char).collect()
223 }
224}
225
226#[cfg(all(feature = "ansi", not(feature = "unicode")))]
228pub fn encode(s: &str) -> Vec<Tchar> {
229 ansi::encode(s)
230}
231
232#[cfg(all(feature = "ansi", not(feature = "unicode")))]
234pub fn decode(units: &[Tchar]) -> String {
235 ansi::decode(units)
236}