use super::super::fileio;
use super::super::messages as m;
use super::super::{PK_COOL, PK_ERR, Uz};
use crate::compat::host::Host;
const EF_UNIPATH: u16 = 0x7075;
const MAX_ESCAPE_BYTES: usize = 8;
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum UniPath {
None,
Standard,
Name(Vec<u8>),
}
fn at(ef: &[u8], at: usize) -> u8 {
ef.get(at).copied().unwrap_or(0)
}
pub(crate) fn get_unicode_data(uz: &mut Uz, ef: &[u8], full: Option<&[u8]>) -> (i32, UniPath) {
let mut unipath = UniPath::None;
let mut ef = ef;
while ef.len() >= 4 {
let eb_id = fileio::makeword(ef);
let eb_len = usize::from(fileio::makeword(&ef[2..]));
if eb_len > ef.len() - 4 {
break;
}
if eb_id == EF_UNIPATH {
let mut offset = 4usize;
let ulen = (eb_len as u16).wrapping_sub(5); let version = at(ef, offset);
offset += 1;
if version > 1 {
uz.info(0x401, m::UNICODE_VERSION_ERROR.as_bytes());
return (PK_ERR, unipath);
}
let checksum = u32::from_le_bytes([
at(ef, offset),
at(ef, offset + 1),
at(ef, offset + 2),
at(ef, offset + 3),
]);
offset += 4;
let Some(full) = full else {
return (PK_ERR, unipath);
};
let mut crc = flate2::Crc::new();
crc.update(fileio::c_str(full));
if crc.sum() != checksum {
uz.info(0x401, m::UNICODE_MISMATCH_ERROR.as_bytes());
return (PK_ERR, unipath);
}
unipath = if ulen == 0 {
UniPath::Standard
} else {
let name: Vec<u8> = (offset..offset + usize::from(ulen))
.map(|i| at(ef, i))
.take_while(|&b| b != 0)
.collect();
if name.is_empty() {
UniPath::Standard
} else {
UniPath::Name(name)
}
};
uz.zip64 = true;
}
ef = &ef[4 + eb_len..];
}
(PK_COOL, unipath)
}
fn utf8_char_bytes(utf8: &[u8]) -> Option<usize> {
let lead = at(utf8, 0);
let r = match lead {
0x00..=0x7f => 1, 0x80..=0xbf => return None, 0xc0..=0xdf => 2, 0xe0..=0xef => 3, 0xf0..=0xf7 => 4, 0xf8..=0xfb => 5, 0xfc..=0xfd => 6, _ => return None, };
if (1..r).any(|t| !(0x80..0xc0).contains(&at(utf8, t))) {
return None; }
Some(r)
}
pub(crate) fn utf8_to_wide_string(utf8: &[u8]) -> Option<Vec<u32>> {
let mut wide = Vec::new();
let mut s = utf8;
loop {
let bytes = utf8_char_bytes(s)?;
let lead = at(s, 0);
let mut ch = if bytes == 1 {
u32::from(lead) } else {
u32::from(lead & (0x7f >> bytes))
};
for t in 1..bytes {
ch = (ch << 6) | u32::from(at(s, t) & 0x3f);
}
if ch == 0 {
return Some(wide);
}
wide.push(ch);
s = &s[bytes.min(s.len())..];
}
}
fn wide_to_escape_string(w: u32) -> Vec<u8> {
let mut b = Vec::new();
let mut v = w;
while v != 0 {
b.push((v % 0x100) as u8);
v /= 0x100;
}
let mut e = b"#".to_vec();
if b.len() <= 2 {
b.resize(2, 0);
e.push(b'U');
} else {
e.push(b'L');
}
for byte in b.iter().rev() {
e.extend_from_slice(format!("{byte:02x}").as_bytes());
}
e
}
fn wide_to_local_string(uz: &mut Uz, wide: &[u32], escape_all: bool) -> Vec<u8> {
let buffer_size = wide.len() * MAX_ESCAPE_BYTES + 1;
let mut buffer: Vec<u8> = Vec::new();
let _state_dependent = wctomb(uz, None);
for &w in wide {
let mb = wctomb(uz, Some(w));
if escape_all {
match mb.as_deref() {
Some([c]) if *c <= 0x7f => buffer.push(*c),
_ => buffer.extend_from_slice(&wide_to_escape_string(w)),
}
} else if let Some(mb) = mb {
buffer.extend_from_slice(&mb);
} else {
let escape = wide_to_escape_string(w);
let room = buffer_size.saturating_sub(buffer.len() + 1);
let n = if escape.len() > room {
uz.info(0x401, m::UFILENAME_TOO_LONG_P1.as_bytes());
room
} else {
escape.len()
};
buffer.extend_from_slice(&escape[..n]);
}
}
buffer
}
pub(crate) fn utf8_to_local_string(uz: &mut Uz, utf8: &[u8], escape_all: bool) -> Option<Vec<u8>> {
let wide = utf8_to_wide_string(utf8)?;
Some(wide_to_local_string(uz, &wide, escape_all))
}
fn wctomb(uz: &Uz, wc: Option<u32>) -> Option<Vec<u8>> {
if matches!(uz.host, Host::Real) {
return libc_wctomb(wc);
}
match wc {
Some(c) if c <= 0x7f => Some(vec![c as u8]),
Some(_) => None,
None => Some(Vec::new()),
}
}
#[cfg(unix)]
unsafe extern "C" {
#[link_name = "wctomb"]
fn c_wctomb(s: *mut libc::c_char, wc: libc::wchar_t) -> libc::c_int;
}
#[cfg(unix)]
fn libc_wctomb(wc: Option<u32>) -> Option<Vec<u8>> {
let Some(c) = wc else {
unsafe { c_wctomb(std::ptr::null_mut(), libc::wchar_t::from(b'a')) };
return Some(Vec::new());
};
let wc = libc::wchar_t::try_from(c).ok()?;
let mut buf = [0 as libc::c_char; 16];
let n = unsafe { c_wctomb(buf.as_mut_ptr(), wc) };
let n = usize::try_from(n).ok()?;
Some(buf[..n.min(buf.len())].iter().map(|&b| b as u8).collect())
}
#[cfg(not(unix))]
fn libc_wctomb(wc: Option<u32>) -> Option<Vec<u8>> {
match wc {
Some(c) if c <= 0x7f => Some(vec![c as u8]),
Some(_) => None,
None => Some(Vec::new()),
}
}