use super::super::{Host, Uz};
pub(crate) const MAX_CP_NAME: usize = 25;
const DOS_CHARSET_MAP: &[(&[u8], &[u8])] = &[
(b"ANSI_X3.4-1968", b"CP850"),
(b"ISO-8859-1", b"CP850"),
(b"CP1252", b"CP850"),
(b"UTF-8", b"CP866"),
(b"KOI8-R", b"CP866"),
(b"KOI8-U", b"CP866"),
(b"ISO-8859-5", b"CP866"),
];
pub(crate) fn cp_name(s: &[u8]) -> Vec<u8> {
let s = s.split(|&b| b == 0).next().unwrap_or(&[]);
s[..s.len().min(MAX_CP_NAME)].to_vec()
}
pub(crate) fn init_conversion_charsets(uz: &mut Uz) {
if uz.o.oem_cp.is_empty()
&& let Some(local) = local_charset(&uz.host)
&& let Some((_, archive)) = DOS_CHARSET_MAP
.iter()
.find(|(l, _)| l.eq_ignore_ascii_case(&local))
{
uz.o.oem_cp = cp_name(archive);
}
}
pub(crate) fn charset_to_intern(
host: &Host,
string: &[u8],
from: &[u8],
buflen: usize,
) -> Option<Vec<u8>> {
if from.is_empty() {
return None;
}
let local = local_charset(host)?;
let string = string.split(|&b| b == 0).next().unwrap_or(&[]);
let out = iconv(&local, from, string, buflen)?;
let end = out.iter().position(|&b| b == 0).unwrap_or(out.len());
Some(out[..end].to_vec())
}
fn local_charset(host: &Host) -> Option<Vec<u8>> {
if !matches!(host, Host::Real) {
return None;
}
real_codeset()
}
#[cfg(unix)]
fn real_codeset() -> Option<Vec<u8>> {
unsafe {
let p = libc::nl_langinfo(libc::CODESET);
if p.is_null() {
return None;
}
Some(std::ffi::CStr::from_ptr(p).to_bytes().to_vec())
}
}
#[cfg(not(unix))]
fn real_codeset() -> Option<Vec<u8>> {
None
}
#[cfg(unix)]
#[allow(deprecated)]
fn iconv(to: &[u8], from: &[u8], input: &[u8], buflen: usize) -> Option<Vec<u8>> {
use std::ffi::CString;
let to = CString::new(to).ok()?;
let from = CString::new(from).ok()?;
let mut inbuf = input.to_vec();
let mut buf = vec![0u8; buflen.max(1)];
let cd = unsafe { libc::iconv_open(to.as_ptr(), from.as_ptr()) };
if cd as isize == -1 {
return None;
}
let mut s = inbuf.as_mut_ptr().cast::<libc::c_char>();
let mut slen: libc::size_t = inbuf.len();
let mut d = buf.as_mut_ptr().cast::<libc::c_char>();
let mut dlen: libc::size_t = buf.len() - 1;
let r = unsafe { libc::iconv(cd, &mut s, &mut slen, &mut d, &mut dlen) };
unsafe { libc::iconv_close(cd) };
if r == libc::size_t::MAX {
return None;
}
Some(buf)
}
#[cfg(not(unix))]
fn iconv(_to: &[u8], _from: &[u8], _input: &[u8], _buflen: usize) -> Option<Vec<u8>> {
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_charset_name_is_kept_to_its_first_25_bytes() {
assert_eq!(cp_name(b"CP866"), b"CP866");
assert_eq!(cp_name(&[b'x'; 30]), vec![b'x'; 25]);
assert_eq!(cp_name(b""), b"");
}
#[test]
fn a_sandbox_converts_nothing() {
use crate::compat::host::{SANDBOX_OUTPUT_CAP, Sandbox};
use std::sync::atomic::AtomicU64;
use std::sync::{Arc, Mutex};
let host = Host::Sandbox(Sandbox {
dir: std::env::temp_dir(),
stdin: None,
stdout: Arc::new(Mutex::new(Vec::new())),
budget: Arc::new(AtomicU64::new(SANDBOX_OUTPUT_CAP)),
reads: Arc::new(Mutex::new(Default::default())),
});
assert_eq!(charset_to_intern(&host, b"\x82.txt", b"CP866", 4096), None);
assert_eq!(local_charset(&host), None);
}
#[cfg(target_os = "linux")]
#[test]
fn iconv_converts_and_refuses_as_glibc_does() {
let cp866 = iconv(b"UTF-8", b"CP866", b"\x82t", 4096).unwrap();
let end = cp866.iter().position(|&b| b == 0).unwrap_or(cp866.len());
assert_eq!(&cp866[..end], "\u{412}t".as_bytes());
assert_eq!(iconv(b"UTF-8", b"CP866", &[0x82; 3000], 4096), None);
assert_eq!(iconv(b"ANSI_X3.4-1968", b"CP850", b"\x82", 4096), None);
assert_eq!(iconv(b"UTF-8", b"NO-SUCH-SET", b"a", 4096), None);
}
}