use super::Zr;
use super::messages::{self as m, printf};
use super::readzip::{cstr, get_extra_field, lg, sh};
use crate::compat::host::Host;
pub(crate) const UTF8_PATH_EF_TAG: u16 = 0x7075;
pub(crate) const UTF8_BIT: u16 = 1 << 11;
pub(crate) fn utf8_locale(host: &Host) -> bool {
if !matches!(host, Host::Real) {
return true;
}
real_utf8_locale()
}
#[cfg(unix)]
fn real_utf8_locale() -> bool {
unsafe {
let l = libc::newlocale(
libc::LC_CTYPE_MASK,
c"en_US.UTF-8".as_ptr(),
std::ptr::null_mut(),
);
if l.is_null() {
return false;
}
libc::freelocale(l);
true
}
}
#[cfg(not(unix))]
fn real_utf8_locale() -> bool {
false
}
pub(crate) fn is_ascii(s: Option<&[u8]>) -> bool {
s.is_some_and(|s| cstr(s).iter().all(|&c| c <= 0x7f))
}
pub(crate) fn local_to_wide(utf8: bool, s: &[u8]) -> Option<Vec<u32>> {
let s = cstr(s);
let mut w = Vec::with_capacity(s.len());
let mut i = 0;
while i < s.len() {
let lead = s[i];
if lead < 0x80 {
w.push(u32::from(lead));
i += 1;
continue;
}
if !utf8 {
return None;
}
let (n, min, first) = match lead {
0xc2..=0xdf => (2, 0x80, lead & 0x1f),
0xe0..=0xef => (3, 0x800, lead & 0x0f),
0xf0..=0xf7 => (4, 0x1_0000, lead & 0x07),
0xf8..=0xfb => (5, 0x20_0000, lead & 0x03),
0xfc..=0xfd => (6, 0x400_0000, lead & 0x01),
_ => return None,
};
if i + n > s.len() {
return None;
}
let mut c = u32::from(first);
for &t in &s[i + 1..i + n] {
if t & 0xc0 != 0x80 {
return None;
}
c = (c << 6) | u32::from(t & 0x3f);
}
if c < min || (0xd800..=0xdfff).contains(&c) {
return None;
}
w.push(c);
i += n;
}
Some(w)
}
fn wctomb(utf8: bool, c: u32) -> Option<Vec<u8>> {
if c <= 0x7f {
return Some(vec![c as u8]);
}
if !utf8 || (0xd800..=0xdfff).contains(&c) || c > 0x7fff_ffff {
return None;
}
let mut b = Vec::with_capacity(6);
utf8_encode(c, &mut b);
Some(b)
}
fn utf8_encode(c: u32, b: &mut Vec<u8>) {
let n = match c {
0..=0x7f => {
b.push(c as u8);
return;
}
0x80..=0x7ff => 2,
0x800..=0xffff => 3,
0x1_0000..=0x1f_ffff => 4,
0x20_0000..=0x3ff_ffff => 5,
_ => 6,
};
let lead_mask: u8 = match n {
2 => 0xc0,
3 => 0xe0,
4 => 0xf0,
5 => 0xf8,
_ => 0xfc,
};
b.push(lead_mask | (c >> (6 * (n - 1))) as u8);
for k in (0..n - 1).rev() {
b.push(0x80 | ((c >> (6 * k)) & 0x3f) as u8);
}
}
pub(crate) fn wide_char_to_escape(c: u32) -> Vec<u8> {
if c < 0x1_0000 {
format!("#U{:04x}", c).into_bytes()
} else {
format!("#L{:06x}", c & 0xff_ffff).into_bytes()
}
}
pub(crate) fn wide_to_local(utf8: bool, escape_all: bool, w: &[u32]) -> Vec<u8> {
let mut out = Vec::with_capacity(w.len());
for &c in w.iter().take_while(|&&c| c != 0) {
let b = wctomb(utf8, c);
match b {
Some(b) if escape_all && !(b.len() == 1 && b[0] <= 0x7f) => {
out.extend_from_slice(&wide_char_to_escape(c));
}
Some(b) => out.extend_from_slice(&b),
None => out.extend_from_slice(&wide_char_to_escape(c)),
}
}
out
}
pub(crate) fn wide_to_escape(w: &[u32]) -> Vec<u8> {
let mut out = Vec::with_capacity(w.len());
for &c in w.iter().take_while(|&&c| c != 0) {
if (0x20..0x7f).contains(&c) {
out.push(c as u8);
} else {
out.extend_from_slice(&wide_char_to_escape(c));
}
}
out
}
pub(crate) fn utf8_to_wide(s: &[u8]) -> Option<Vec<u32>> {
let s = cstr(s);
let mut w = Vec::with_capacity(s.len());
let mut i = 0;
while i < s.len() {
let lead = s[i];
let n = 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,
};
for k in 1..n {
let t = *s.get(i + k)?;
if !(0x80..0xc0).contains(&t) {
return None;
}
}
let mut c = if n == 1 {
u32::from(lead)
} else {
u32::from(lead & (0x7f >> n))
};
for &t in &s[i + 1..i + n] {
c = (c << 6) | u32::from(t & 0x3f);
}
w.push(c);
i += n;
}
Some(w)
}
pub(crate) fn wide_to_utf8(w: &[u32]) -> Option<Vec<u8>> {
let mut out = Vec::with_capacity(w.len());
for &ch in w.iter().take_while(|&&c| c != 0) {
if ch > 0x7fff_ffff {
return None;
}
if ch < 0x7f {
out.push(ch as u8);
continue;
}
let mut trailing = 0u32;
let mut leadmask: u32 = 0x80;
let mut leadbits: u32 = 0x3f;
let mut tch = ch;
loop {
trailing += 1;
leadmask = (leadmask >> 1) | 0x80;
leadbits >>= 1;
tch >>= 6;
if tch & !leadbits == 0 {
break;
}
}
out.push((leadmask | (ch >> (6 * trailing))) as u8);
for k in (0..trailing).rev() {
out.push((0x80 | ((ch >> (6 * k)) & 0x3f)) as u8);
}
}
Some(out)
}
pub(crate) fn local_to_utf8(utf8: bool, s: &[u8]) -> Option<Vec<u8>> {
wide_to_utf8(&local_to_wide(utf8, s)?)
}
pub(crate) fn utf8_to_local(utf8: bool, escape_all: bool, s: &[u8]) -> Option<Vec<u8>> {
Some(wide_to_local(utf8, escape_all, &utf8_to_wide(s)?))
}
pub(crate) fn utf8_to_escape(s: &[u8]) -> Option<Vec<u8>> {
Some(wide_to_escape(&utf8_to_wide(s)?))
}
pub(crate) fn local_to_escape(utf8: bool, s: &[u8]) -> Option<Vec<u8>> {
Some(wide_to_escape(&local_to_wide(utf8, s)?))
}
pub(crate) fn segv(z: &mut Zr) -> super::Exit {
if z.program == "zip" {
super::scan::crashed(z)
} else {
super::readzip::null_out_crash(z)
}
}
pub(crate) fn add_unicode_path_field(extra: &mut Vec<u8>, iname: &[u8], uname: &[u8]) {
let uname = cstr(uname);
let ulen = uname.len() as u16;
let data_len = 1u16.wrapping_add(4).wrapping_add(ulen);
let mut field = Vec::with_capacity(usize::from(data_len) + 4);
field.extend_from_slice(&UTF8_PATH_EF_TAG.to_le_bytes());
field.extend_from_slice(&data_len.to_le_bytes());
field.push(1);
let mut crc = flate2::Crc::new();
crc.update(cstr(iname));
field.extend_from_slice(&crc.sum().to_le_bytes());
field.extend_from_slice(uname);
match get_extra_field(UTF8_PATH_EF_TAG, extra) {
None => extra.extend_from_slice(&field),
Some(at) => {
let blocksize = usize::from(sh(extra, at + 2));
let end = (at + 4 + blocksize).min(extra.len());
if blocksize == usize::from(data_len) && end - at == field.len() {
extra[at..end].copy_from_slice(&field);
} else {
extra.drain(at..end);
extra.extend_from_slice(&field);
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PathReader<'a> {
Central,
CentralFix {
oname: &'a [u8],
name: &'a [u8],
},
Copy(&'a [u8]),
ReadLocal,
}
pub(crate) fn read_unicode_path(
z: &mut Zr,
extra: &[u8],
iname: &[u8],
reader: PathReader<'_>,
) -> Result<Option<Vec<u8>>, super::Exit> {
let Some(at) = get_extra_field(UTF8_PATH_EF_TAG, extra) else {
return Ok(None);
};
let mut head = extra[at..].to_vec();
head.resize(head.len().max(9), 0);
let elen = sh(&head, 2);
let version = head[4];
let oname: &[u8] = match reader {
PathReader::CentralFix { oname, .. } => oname,
_ => b"(null)",
};
if version > 1 {
z.zipwarn(m::UNICODE_VERSION_SKIP.as_bytes(), oname);
return Ok(None);
}
let chksum = lg(&head, 5);
let mut crc = flate2::Crc::new();
crc.update(cstr(iname));
if crc.sum() != chksum {
if matches!(reader, PathReader::Central | PathReader::CentralFix { .. }) {
let mut l = Vec::new();
printf(
&mut l,
m::UNICODE_MISMATCH_DEBUG,
&[z.o.unicode_mismatch.to_string().as_bytes()],
);
z.stdout_write(&l);
}
match z.o.unicode_mismatch {
1 => z.zipwarn(m::UNICODE_MISMATCH_WARN.as_bytes(), oname),
0 => {
let mut l = Vec::new();
printf(&mut l, m::UNICODE_MISMATCH_QUIT, &[oname]);
z.zipwarn(&l, b"");
return Err(z.ziperr(super::readzip::ZE_FORM, m::UNICODE_PATH_ERROR.as_bytes()));
}
_ => {}
}
return Ok(None);
}
let ulen = elen.wrapping_sub(5);
if ulen == 0 {
return match reader {
PathReader::Copy(name) | PathReader::CentralFix { name, .. } => {
Ok(Some(cstr(name).to_vec()))
}
_ => Err(segv(z)),
};
}
let from = (at + 9).min(extra.len());
let to = (at + 9 + usize::from(ulen)).min(extra.len());
Ok(Some(cstr(&extra[from..to]).to_vec()))
}
#[cfg(test)]
mod tests;