use core::ffi::c_int;
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq)]
pub(crate) struct vis_flags: i32 {
const VIS_OCTAL = 0x0001;
const VIS_CSTYLE = 0x0002;
const VIS_TAB = 0x0008;
const VIS_NL = 0x0010;
const VIS_SAFE = 0x0020;
const VIS_NOSLASH = 0x0040;
const VIS_DQ = 0x0200;
}
}
pub unsafe fn vis_(dst: *mut u8, c: c_int, flag: vis_flags, nextc: c_int) -> *mut u8 {
unsafe {
match c as u8 {
b'\0' if !matches!(nextc as u8, b'0'..=b'7') => encode_cstyle(dst, b'0'),
b'\t' if flag.intersects(vis_flags::VIS_TAB) => encode_cstyle(dst, b't'),
b'\n' if flag.intersects(vis_flags::VIS_NL) => encode_cstyle(dst, b'n'),
b'\\' if !flag.intersects(vis_flags::VIS_NOSLASH) => encode_cstyle(dst, b'\\'),
b'"' if flag.intersects(vis_flags::VIS_DQ) => encode_cstyle(dst, b'"'),
7 | 8 | 13 if flag.intersects(vis_flags::VIS_SAFE) => encode_passthrough(dst, c),
7..9 | 11..14 => {
const CSTYLE: [u8; 7] = [b'a', b'b', 0, 0, b'v', b'f', b'r'];
encode_cstyle(dst, CSTYLE[c as usize - 7])
}
0..7 | 14..32 | 127.. => encode_octal(dst, c),
_ => encode_passthrough(dst, c),
}
}
}
pub fn vis__(dst: &mut Vec<u8>, c: c_int, flag: vis_flags, nextc: c_int) {
match c as u8 {
b'\0' if !matches!(nextc as u8, b'0'..=b'7') => encode_cstyle_(dst, b'0'),
b'\t' if flag.intersects(vis_flags::VIS_TAB) => encode_cstyle_(dst, b't'),
b'\n' if flag.intersects(vis_flags::VIS_NL) => encode_cstyle_(dst, b'n'),
b'\\' if !flag.intersects(vis_flags::VIS_NOSLASH) => encode_cstyle_(dst, b'\\'),
b'"' if flag.intersects(vis_flags::VIS_DQ) => encode_cstyle_(dst, b'"'),
7 | 8 | 13 if flag.intersects(vis_flags::VIS_SAFE) => encode_passthrough_(dst, c),
7..9 | 11..14 => {
const CSTYLE: [u8; 7] = [b'a', b'b', 0, 0, b'v', b'f', b'r'];
encode_cstyle_(dst, CSTYLE[c as usize - 7]);
}
0..7 | 14..32 | 127.. => encode_octal_(dst, c),
_ => encode_passthrough_(dst, c),
}
}
#[inline]
unsafe fn encode_passthrough(dst: *mut u8, ch: i32) -> *mut u8 {
unsafe {
*dst = ch as u8;
*dst.add(1) = b'\0';
dst.add(1)
}
}
#[inline]
fn encode_passthrough_(dst: &mut Vec<u8>, ch: i32) {
dst.push(ch as u8);
}
#[inline]
unsafe fn encode_cstyle(dst: *mut u8, ch: u8) -> *mut u8 {
unsafe {
*dst = b'\\';
*dst.add(1) = ch;
*dst.add(2) = b'\0';
dst.add(2)
}
}
#[inline]
fn encode_cstyle_(dst: &mut Vec<u8>, ch: u8) {
dst.push(b'\\');
dst.push(ch);
}
#[inline]
unsafe fn encode_octal(dst: *mut u8, c: i32) -> *mut u8 {
unsafe {
let c = c as u8;
let ones_place = c % 8;
let eights_place = (c / 8) % 8;
let sixty_four_place = c / 64;
*dst = b'\\';
*dst.add(1) = sixty_four_place + b'0';
*dst.add(2) = eights_place + b'0';
*dst.add(3) = ones_place + b'0';
*dst.add(4) = b'\0';
dst.add(4)
}
}
fn encode_octal_(dst: &mut Vec<u8>, c: i32) {
let c = c as u8;
let ones_place = c % 8;
let eights_place = (c / 8) % 8;
let sixty_four_place = c / 64;
dst.push(b'\\');
dst.push(sixty_four_place + b'0');
dst.push(eights_place + b'0');
dst.push(ones_place + b'0');
}
pub unsafe fn strvis(mut dst: *mut u8, mut src: *const u8, flag: vis_flags) -> i32 {
unsafe {
let start = dst;
while *src != 0 {
dst = vis_(dst, *src as i32, flag, *src.add(1) as i32);
src = src.add(1);
}
*dst = 0;
dst.offset_from(start) as i32
}
}
pub unsafe fn strnvis(mut dst: *mut u8, mut src: *const u8, dlen: usize, flag: vis_flags) -> i32 {
unsafe {
let mut tbuf = [0u8; 8]; let start = dst;
let end = start.wrapping_add(dlen).wrapping_sub(1);
let mut i: usize = 0;
while *src != 0 && dst < end {
let tend = vis_(tbuf.as_mut_ptr(), *src as i32, flag, *src.add(1) as i32);
i = tend.offset_from_unsigned(tbuf.as_mut_ptr());
src = src.add(1);
if dst.wrapping_add(i) <= end {
core::ptr::copy_nonoverlapping(tbuf.as_ptr(), dst, i);
dst = dst.add(i);
} else {
src = src.sub(1);
break;
}
}
if dlen > 0 {
*dst = 0;
}
if dst.wrapping_add(i) > end {
while *src != 0 {
let tend = vis_(tbuf.as_mut_ptr(), *src as i32, flag, *src.add(1) as i32);
dst = dst.wrapping_add(tend.offset_from_unsigned(tbuf.as_mut_ptr()));
src = src.add(1);
}
}
(dst as usize).wrapping_sub(start as usize) as i32
}
}
pub unsafe fn stravis(outp: *mut *mut u8, src: *const u8, flag: vis_flags) -> i32 {
unsafe {
let buf: *mut u8 = libc::calloc(4, crate::libc::strlen(src) + 1).cast();
if buf.is_null() {
return -1;
}
let len = strvis(buf, src, flag);
let serrno = crate::errno!();
*outp = libc::realloc(buf.cast(), len as usize + 1).cast();
if (*outp).is_null() {
*outp = buf;
crate::errno!() = serrno;
}
len
}
}
pub unsafe fn vis(dst: *mut u8, c: c_int, flag: vis_flags, nextc: c_int) -> *mut u8 {
unsafe { vis_(dst, c, flag, nextc) }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_vis() {
let mut c_dst_arr: [u8; 16] = [0; 16];
let mut rs_dst_arr: [u8; 16] = [0; 16];
let c_dst = &raw mut c_dst_arr as *mut u8;
let rs_dst = &raw mut rs_dst_arr as *mut u8;
unsafe {
for f1 in [
vis_flags::VIS_OCTAL,
vis_flags::VIS_CSTYLE,
vis_flags::VIS_OCTAL | vis_flags::VIS_CSTYLE,
] {
for f2 in [
vis_flags::VIS_TAB | vis_flags::VIS_NL,
vis_flags::VIS_TAB,
vis_flags::VIS_NL,
vis_flags::VIS_DQ,
vis_flags::VIS_NOSLASH,
vis_flags::VIS_SAFE,
vis_flags::VIS_SAFE | vis_flags::VIS_NOSLASH,
] {
for ch in 0..=u8::MAX {
for nextc in [b'\0' as i32, b'0' as i32] {
let flag = f1 | f2;
let rs_out = vis_(rs_dst, ch as i32, flag, nextc);
let c_out = vis(c_dst, ch as i32, flag, nextc);
assert_eq!(
c_dst_arr,
rs_dst_arr,
"mismatch when encoding vis(_, _, _, {ch}) => {} != {}",
crate::_s(c_dst),
crate::_s(rs_dst)
);
assert_eq!(rs_out.offset_from(rs_dst), c_out.offset_from(c_dst));
c_dst_arr.fill(0);
rs_dst_arr.fill(0);
}
}
}
}
}
}
const NONE: vis_flags = vis_flags::empty();
unsafe fn enc(c: i32, flag: vis_flags, nextc: i32) -> Vec<u8> {
unsafe {
let mut buf = [0u8; 8];
let end = vis_(buf.as_mut_ptr(), c, flag, nextc);
let len = end.offset_from(buf.as_ptr()) as usize;
buf[..len].to_vec()
}
}
#[test]
fn test_vis_printable_passthrough() {
unsafe {
assert_eq!(enc(b'A' as i32, NONE, 0), b"A");
assert_eq!(enc(b'z' as i32, NONE, 0), b"z");
assert_eq!(enc(b'~' as i32, NONE, 0), b"~");
assert_eq!(enc(b' ' as i32, NONE, 0), b" ");
}
}
#[test]
fn test_vis_backslash() {
unsafe {
assert_eq!(enc(b'\\' as i32, NONE, 0), b"\\\\");
assert_eq!(enc(b'\\' as i32, vis_flags::VIS_NOSLASH, 0), b"\\");
}
}
#[test]
fn test_vis_double_quote() {
unsafe {
assert_eq!(enc(b'"' as i32, vis_flags::VIS_DQ, 0), b"\\\"");
assert_eq!(enc(b'"' as i32, NONE, 0), b"\"");
}
}
#[test]
fn test_vis_tab() {
unsafe {
assert_eq!(enc(b'\t' as i32, vis_flags::VIS_TAB, 0), b"\\t");
assert_eq!(enc(b'\t' as i32, NONE, 0), b"\t");
}
}
#[test]
fn test_vis_newline() {
unsafe {
assert_eq!(enc(b'\n' as i32, vis_flags::VIS_NL, 0), b"\\n");
assert_eq!(enc(b'\n' as i32, NONE, 0), b"\n");
}
}
#[test]
fn test_vis_cstyle_controls() {
unsafe {
assert_eq!(enc(0x07, NONE, 0), b"\\a"); assert_eq!(enc(0x08, NONE, 0), b"\\b"); assert_eq!(enc(0x0b, NONE, 0), b"\\v"); assert_eq!(enc(0x0c, NONE, 0), b"\\f"); assert_eq!(enc(0x0d, NONE, 0), b"\\r"); }
}
#[test]
fn test_vis_nul() {
unsafe {
assert_eq!(enc(0, NONE, 0), b"\\0");
assert_eq!(enc(0, NONE, b'x' as i32), b"\\0");
assert_eq!(enc(0, NONE, b'0' as i32), b"\\000");
assert_eq!(enc(0, NONE, b'7' as i32), b"\\000");
}
}
#[test]
fn test_vis_octal_ranges() {
unsafe {
assert_eq!(enc(0x01, NONE, 0), b"\\001");
assert_eq!(enc(0x06, NONE, 0), b"\\006");
assert_eq!(enc(0x0e, NONE, 0), b"\\016");
assert_eq!(enc(0x1f, NONE, 0), b"\\037");
assert_eq!(enc(0x7f, NONE, 0), b"\\177"); assert_eq!(enc(0x80, NONE, 0), b"\\200");
assert_eq!(enc(0xff, NONE, 0), b"\\377");
}
}
#[test]
fn test_strvis_literals_and_length() {
unsafe {
let mut dst = [0u8; 32];
let ret = strvis(dst.as_mut_ptr(), crate::c!("abc"), NONE);
assert_eq!(ret, 3);
assert_eq!(&dst[..4], b"abc\0");
}
}
#[test]
fn test_strvis_tab_and_backslash() {
unsafe {
let mut dst = [0u8; 32];
let ret = strvis(dst.as_mut_ptr(), crate::c!("a\tb"), vis_flags::VIS_TAB);
assert_eq!(ret, 4);
assert_eq!(&dst[..5], b"a\\tb\0");
let mut dst2 = [0u8; 32];
let ret2 = strvis(dst2.as_mut_ptr(), crate::c!("a\\b"), NONE);
assert_eq!(ret2, 4);
assert_eq!(&dst2[..5], b"a\\\\b\0");
}
}
#[test]
fn test_strvis_control_expands_to_octal() {
unsafe {
let mut dst = [0u8; 32];
let ret = strvis(dst.as_mut_ptr(), crate::c!("\x01"), NONE);
assert_eq!(ret, 4);
assert_eq!(&dst[..5], b"\\001\0");
}
}
#[test]
fn test_strnvis_truncates_to_dlen() {
unsafe {
let mut dst = [0u8; 32];
let ret = strnvis(dst.as_mut_ptr(), crate::c!("abc"), 2, NONE);
assert_eq!(ret, 3);
assert_eq!(&dst[..2], b"a\0");
}
}
#[test]
fn test_strnvis_in_bounds() {
unsafe {
let mut dst = [0u8; 32];
let ret = strnvis(dst.as_mut_ptr(), crate::c!("abc"), 32, NONE);
assert_eq!(ret, 3);
assert_eq!(&dst[..4], b"abc\0");
let mut dst2 = [0u8; 32];
let ret2 = strnvis(dst2.as_mut_ptr(), crate::c!("a\tb"), 32, vis_flags::VIS_TAB);
assert_eq!(ret2, 4);
assert_eq!(&dst2[..5], b"a\\tb\0");
}
}
#[test]
fn test_strnvis_stops_at_multibyte_boundary() {
unsafe {
let mut dst = [0u8; 32];
let ret = strnvis(dst.as_mut_ptr(), crate::c!("a\tb"), 3, vis_flags::VIS_TAB);
assert_eq!(ret, 4);
assert_eq!(&dst[..2], b"a\0");
}
}
#[test]
fn test_strvis_roundtrips_through_strunvis() {
unsafe {
for src in [c"abc".as_ptr(), c"a\x01b".as_ptr(), c"x\\y".as_ptr(), c"\x7f\xff".as_ptr()] {
let mut enc = [0u8; 32];
let n = strvis(enc.as_mut_ptr(), src.cast(), NONE);
assert!(n >= 0);
let mut dec = [0u8; 32];
let m = crate::compat::strunvis(dec.as_mut_ptr(), enc.as_ptr());
assert!(m >= 0, "strunvis failed on encoding of {}", crate::_s(src.cast::<u8>()));
let orig = std::slice::from_raw_parts(src.cast::<u8>(), crate::libc::strlen(src.cast()));
assert_eq!(&dec[..m as usize], orig);
}
}
}
#[test]
fn test_strnvis_dlen_zero_writes_nothing() {
unsafe {
let mut dst = [0xAAu8; 8];
let ret = strnvis(dst.as_mut_ptr(), crate::c!("abc"), 0, NONE);
assert_eq!(ret, 3, "returns full would-be length");
assert!(dst.iter().all(|&b| b == 0xAA), "dst must be untouched");
}
}
#[test]
fn test_strvis_newline_flag() {
unsafe {
let mut dst = [0u8; 32];
let ret = strvis(dst.as_mut_ptr(), crate::c!("a\nb"), vis_flags::VIS_NL);
assert_eq!(ret, 4);
assert_eq!(&dst[..5], b"a\\nb\0");
let mut dst2 = [0u8; 32];
let ret2 = strvis(dst2.as_mut_ptr(), crate::c!("a\nb"), NONE);
assert_eq!(ret2, 3);
assert_eq!(&dst2[..4], b"a\nb\0");
}
}
#[test]
fn test_strnvis_exact_fit() {
unsafe {
let mut dst = [0u8; 8];
let ret = strnvis(dst.as_mut_ptr(), crate::c!("abc"), 4, NONE);
assert_eq!(ret, 3);
assert_eq!(&dst[..4], b"abc\0");
let mut dst2 = [0u8; 8];
let ret2 = strnvis(dst2.as_mut_ptr(), crate::c!("abc"), 3, NONE);
assert_eq!(ret2, 3);
assert_eq!(&dst2[..3], b"ab\0");
}
}
}