use std::{io, path, time, ffi};
use crate::tar::ustar;
use crate::tar::ustar::{format_tar_numeral, format_tar_string};
use crate::tar::gnu::{format_gnu_numeral, format_gnu_time};
use crate::tar::header::{TarHeader, TarFileType};
use crate::tar::label::TarLabel;
use crate::tar::canonicalized_tar_path;
fn format_pax_attribute(key: &str, val: &str) -> Vec<u8> {
let key_bytes = key.as_bytes();
let val_bytes = val.as_bytes();
let minimum_length = 1 + key_bytes.len() + 1 + val_bytes.len() + 1; let mut number_length = (minimum_length as f32).log(10.0).floor() as usize + 1;
while (number_length as f32 + minimum_length as f32).log(10.0).floor() as usize + 1 > number_length {
number_length += 1;
}
let mut result = format!("{} ", minimum_length + number_length).into_bytes();
result.extend(key_bytes);
result.extend("=".as_bytes());
result.extend(val_bytes);
result.extend("\n".as_bytes());
result
}
fn format_pax_time(dirtime: &time::SystemTime) -> io::Result<String> {
match dirtime.duration_since(time::UNIX_EPOCH) {
Ok(unix_duration) => Ok(format!("{}", unix_duration.as_secs())),
Err(_) => Err(io::Error::new(io::ErrorKind::InvalidData, "File older than UNIX")) }
}
pub fn format_pax_legacy_filename(canonical_path: &String) -> io::Result<(Vec<u8>, Vec<u8>, bool)> {
let is_ascii = canonical_path.is_ascii();
let mut relapath_encoded = canonical_path.replace(|c: char| !c.is_ascii(), "").into_bytes();
relapath_encoded.push(0);
if relapath_encoded.len() <= 100 {
relapath_encoded.resize(100, 0);
return Ok((relapath_encoded, vec![0; 155], !is_ascii));
}
for i in (1..100).rev() {
if relapath_encoded[relapath_encoded.len() - i] == '/' as u8 {
let splitpoint = relapath_encoded.len() - i;
let mut oldname_part = relapath_encoded.split_off(splitpoint + 1);
let newname_length = relapath_encoded.len();
assert!(oldname_part.len() < 100);
relapath_encoded.remove(newname_length - 1);
oldname_part.resize(100, 0);
let cannot_truncate_losslessly = relapath_encoded.len() > 155;
if cannot_truncate_losslessly {
for j in (1..157).rev() {
if relapath_encoded[relapath_encoded.len() - j] == '/' as u8 {
let new_splitpoint = relapath_encoded.len() - j;
let mut newname_part = relapath_encoded.split_off(new_splitpoint + 1);
newname_part.resize(155, 0);
return Ok((oldname_part, newname_part, true));
}
}
}
relapath_encoded.resize(155, 0);
return Ok((oldname_part, relapath_encoded, !is_ascii || cannot_truncate_losslessly));
}
}
if relapath_encoded.len() < 155 {
relapath_encoded.resize(155, 0);
return Ok((vec![0;100], relapath_encoded, !is_ascii));
}
let offending_length = relapath_encoded.len();
let truncation_point = offending_length.checked_sub(100).unwrap_or(0);
let second_truncation_point = truncation_point.checked_sub(155).unwrap_or(0);
let mut unixpart = relapath_encoded[truncation_point..offending_length].to_vec();
let mut extpart = relapath_encoded[second_truncation_point..truncation_point].to_vec();
unixpart.resize(100, 0);
extpart.resize(155, 0);
return Ok((unixpart, extpart, true));
}
pub fn compute_fallback_path<P: AsRef<path::Path>>(path: &P, filetype: TarFileType) -> io::Result<(Vec<u8>, Vec<u8>, bool)> {
let pathref = path.as_ref();
let mut component_count = 0;
for _ in pathref.components() {
component_count += 1
}
let mut pax_prefixed_path : path::PathBuf = pathref.to_path_buf();
if component_count > 1 {
pax_prefixed_path = pax_prefixed_path.with_file_name("PaxHeaders");
pax_prefixed_path.push(pathref.file_name().unwrap_or(&ffi::OsString::from(".")));
} else {
pax_prefixed_path = path::PathBuf::from(r"./PaxHeaders");
pax_prefixed_path.push(pathref.to_path_buf());
}
format_pax_legacy_filename(&canonicalized_tar_path(&pax_prefixed_path, filetype))
}
pub fn pax_header(tarheader: &TarHeader) -> io::Result<Vec<u8>> {
let mut item_path = tarheader.path.clone();
if let TarFileType::Directory = tarheader.file_type {
item_path.push(&ffi::OsString::from(""));
}
let canonical_path = canonicalized_tar_path(&item_path, tarheader.file_type);
let (relapath_unix, relapath_extended, legacy_format_truncated) = format_pax_legacy_filename(&canonical_path)?;
assert_eq!(relapath_unix.len(), 100);
assert_eq!(relapath_extended.len(), 155);
let ustar_uname = format_tar_string(&tarheader.unix_uname, 32);
let ustar_gname = format_tar_string(&tarheader.unix_gname, 32);
let ustar_mtime = format_gnu_time(&tarheader.mtime.unwrap_or(time::UNIX_EPOCH)).ok();
let mut extended_stream : Vec<u8> = Vec::with_capacity(512);
if let None = format_tar_numeral(tarheader.file_size, 12) {
extended_stream.extend(format_pax_attribute("size", &format!("{}", tarheader.file_size)));
}
if legacy_format_truncated {
extended_stream.extend(format_pax_attribute("path", &canonical_path));
}
if let None = ustar_mtime {
if let Some(mtime) = tarheader.mtime {
extended_stream.extend(format_pax_attribute("mtime", &format_pax_time(&mtime)?));
}
}
if let Some(atime) = tarheader.atime {
extended_stream.extend(format_pax_attribute("atime", &format_pax_time(&atime)?));
}
if let None = ustar_uname {
extended_stream.extend(format_pax_attribute("uname", &tarheader.unix_uname));
}
if let None = ustar_gname {
extended_stream.extend(format_pax_attribute("gname", &tarheader.unix_gname));
}
if let Some(birthtime) = tarheader.birthtime {
extended_stream.extend(format_pax_attribute("LIBARCHIVE.creationtime", &format_pax_time(&birthtime)?));
}
let mut header : Vec<u8> = Vec::with_capacity(1536);
if extended_stream.len() > 0 {
let (pax_relapath_unix, pax_relapath_extended, _) = compute_fallback_path(&tarheader.path.as_ref(), tarheader.file_type)?;
header.extend(pax_relapath_unix); header.extend(format_gnu_numeral(tarheader.unix_mode, 8).ok_or(io::Error::new(io::ErrorKind::InvalidData, "UNIX mode is too long"))?); header.extend(format_gnu_numeral(tarheader.unix_uid, 8).unwrap_or(vec![0; 8]));
header.extend(format_gnu_numeral(tarheader.unix_gid, 8).unwrap_or(vec![0; 8]));
header.extend(format_gnu_numeral(extended_stream.len() as u64, 12).ok_or(io::Error::new(io::ErrorKind::InvalidData, "File extended header is too long"))?); header.extend(ustar_mtime.clone().unwrap_or(vec![0; 12])); header.extend(" ".as_bytes()); header.extend("x".as_bytes());
header.extend(vec![0; 100]); header.extend("ustar\0".as_bytes()); header.extend("00".as_bytes()); header.extend(ustar_uname.clone().unwrap_or(vec![0; 32]));
header.extend(ustar_gname.clone().unwrap_or(vec![0; 32]));
header.extend(format_gnu_numeral(tarheader.unix_devmajor, 8).unwrap_or(vec![0; 8])); header.extend(format_gnu_numeral(tarheader.unix_devminor, 8).unwrap_or(vec![0; 8])); header.extend(pax_relapath_extended);
header.extend(vec![0; 12]);
let padding_needed = (extended_stream.len() % 512) as usize;
if padding_needed != 0 {
extended_stream.extend(&vec![0; 512 - padding_needed]);
}
header.extend(extended_stream); }
header.extend(relapath_unix); header.extend(format_gnu_numeral(tarheader.unix_mode, 8).ok_or(io::Error::new(io::ErrorKind::InvalidData, "UNIX mode is too long"))?); header.extend(format_gnu_numeral(tarheader.unix_uid, 8).unwrap_or(vec![0; 8])); header.extend(format_gnu_numeral(tarheader.unix_gid, 8).unwrap_or(vec![0; 8])); if let TarFileType::FileStream = tarheader.file_type {
header.extend(format_gnu_numeral(tarheader.file_size, 12).unwrap_or(vec![0; 12])); } else {
header.extend(format_gnu_numeral(0, 12).unwrap_or(vec![0; 12])); }
header.extend(ustar_mtime.unwrap_or(vec![0; 12])); header.extend(" ".as_bytes()); header.push(tarheader.file_type.type_flag() as u8); header.extend(vec![0; 100]); header.extend("ustar\0".as_bytes()); header.extend("00".as_bytes()); header.extend(ustar_uname.unwrap_or(vec![0; 32])); header.extend(ustar_gname.unwrap_or(vec![0; 32])); header.extend(format_gnu_numeral(tarheader.unix_devmajor, 8).unwrap_or(vec![0; 8])); header.extend(format_gnu_numeral(tarheader.unix_devminor, 8).unwrap_or(vec![0; 8])); header.extend(relapath_extended);
header.extend(vec![0; 12]);
Ok(header)
}
pub fn pax_label(tarlabel: &TarLabel) -> io::Result<Vec<u8>> {
let label_path = path::PathBuf::from(format!("/tmp/GlobalHead.{}.{}", tarlabel.nabla, tarlabel.volume_identifier.unwrap_or(0).checked_sub(1).unwrap_or(0)));
let mut extended_stream : Vec<u8> = Vec::with_capacity(512);
if let Some(ref label_str) = tarlabel.label {
extended_stream.extend(format_pax_attribute("GNU.volume.label", &label_str));
}
if let Some(recovery_file_type) = tarlabel.recovery_file_type {
if let Some(ref recovery_path) = tarlabel.recovery_path {
let canonical_recovery_path = canonicalized_tar_path(&recovery_path.clone(), recovery_file_type);
extended_stream.extend(format_pax_attribute("GNU.volume.filename", &canonical_recovery_path));
}
if let Some(recovery_remaining_size) = tarlabel.recovery_remaining_size {
extended_stream.extend(format_pax_attribute("GNU.volume.size", &format!("{}", recovery_remaining_size)));
}
if let Some(recovery_seek_offset) = tarlabel.recovery_seek_offset {
extended_stream.extend(format_pax_attribute("GNU.volume.offset", &format!("{}", recovery_seek_offset)));
}
}
if extended_stream.len() > 0 {
let mut label = Vec::with_capacity(512 + extended_stream.len());
let (relapath_unix, relapath_extended, _) = format_pax_legacy_filename(&canonicalized_tar_path(&label_path, TarFileType::FileStream))?;
assert_eq!(relapath_unix.len(), 100);
assert_eq!(relapath_extended.len(), 155);
label.extend(relapath_unix);
label.extend(format_gnu_numeral(0o644, 8).ok_or(io::Error::new(io::ErrorKind::InvalidData, "UNIX mode is too long"))?); label.extend(format_gnu_numeral(0, 8).unwrap_or(vec![0; 8])); label.extend(format_gnu_numeral(0, 8).unwrap_or(vec![0; 8])); label.extend(format_gnu_numeral(extended_stream.len() as u64, 12).unwrap_or(vec![0; 12])); label.extend(format_gnu_time(&time::SystemTime::now()).unwrap_or(vec![0; 12])); label.extend(" ".as_bytes()); label.extend("g".as_bytes()); label.extend(vec![0; 100]); label.extend("ustar\0".as_bytes()); label.extend("00".as_bytes()); label.extend(vec![0; 32]); label.extend(vec![0; 32]); label.extend(vec![0; 8]); label.extend(vec![0; 8]); label.extend(relapath_extended);
label.extend(vec![0; 12]);
let padding_needed = (extended_stream.len() % 512) as usize;
if padding_needed != 0 {
extended_stream.extend(&vec![0; 512 - padding_needed]);
}
label.extend(extended_stream);
Ok(label)
} else {
Ok(Vec::with_capacity(0))
}
}
pub fn checksum_header(header: &mut Vec<u8>) {
if header.len() < 512 {
return;
}
ustar::checksum_header(&mut header[0..512]);
if header.len() >= 1024 {
let header_len = header.len();
ustar::checksum_header(&mut header[header_len - 512..header_len]);
}
}
#[cfg(test)]
mod tests {
use std::{path};
use crate::tar::pax::{format_pax_attribute, format_pax_legacy_filename, canonicalized_tar_path};
use crate::tar::header::TarFileType;
#[test]
fn pax_attribute() {
let fmtd = format_pax_attribute("x", "y");
assert_eq!(fmtd, "6 x=y\n".as_bytes());
}
#[test]
fn pax_attribute_longkey() {
let fmtd = format_pax_attribute("xxxxxx", "y");
assert_eq!(fmtd, "12 xxxxxx=y\n".as_bytes());
}
#[test]
fn pax_attribute_longval() {
let fmtd = format_pax_attribute("x", "yyyyyy");
assert_eq!(fmtd, "12 x=yyyyyy\n".as_bytes());
}
#[test]
fn pax_attribute_fixedpoint_underflow() {
let fmtd = format_pax_attribute("x", "yyyy");
assert_eq!(fmtd, "9 x=yyyy\n".as_bytes());
}
#[test]
fn pax_attribute_fixedpoint_overflow() {
let fmtd = format_pax_attribute("x", "yyyyy");
assert_eq!(fmtd, "11 x=yyyyy\n".as_bytes());
}
#[test]
fn pax_legacy_filename_short() {
let (old, posix, was_truncated) = format_pax_legacy_filename(&canonicalized_tar_path(path::Path::new("quux"), TarFileType::FileStream)).unwrap();
assert_eq!(was_truncated, false);
assert_eq!(old.len(), 100);
assert_eq!(posix.len(), 155);
assert_eq!("quux".as_bytes(), &old[0..4]);
assert_eq!(vec![0 as u8; 96], &old[4..]);
assert_eq!(vec![0 as u8; 155], posix);
}
#[test]
fn pax_legacy_filename_medium() {
let (old, posix, was_truncated) = format_pax_legacy_filename(&canonicalized_tar_path(path::Path::new("1/2/3/4/5/6/7/8/9/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/quux"), TarFileType::FileStream)).unwrap();
assert_eq!(was_truncated, false);
assert_eq!(old.len(), 100);
assert_eq!(posix.len(), 155);
assert_eq!("6/7/8/9/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/quux".as_bytes(), &old[0..97]);
assert_eq!(vec![0 as u8; 3], &old[97..]);
assert_eq!("1/2/3/4/5".as_bytes(), &posix[0..9]);
assert_eq!(vec![0 as u8; 146], &posix[9..]);
}
#[test]
fn pax_legacy_filename_long() {
let (old, posix, was_truncated) = format_pax_legacy_filename(&canonicalized_tar_path(path::Path::new("1/2/3/4/5/6/7/8/9/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/vqw/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/1/2/3/4/5/6/7/8/9/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/1/2/3/4/5/6/7/8/9/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/quux"), TarFileType::FileStream)).unwrap();
assert_eq!(was_truncated, true);
assert_eq!(old.len(), 100);
assert_eq!(posix.len(), 155);
assert_eq!("6/7/8/9/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/quux".as_bytes(), &old[0..97]);
assert_eq!(vec![0 as u8; 3], &old[97..]);
assert_eq!("vqw/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/1/2/3/4/5/6/7/8/9/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/1/2/3/4/5".as_bytes(), &posix[0..155]);
}
#[test]
fn pax_legacy_filename_long_tricky() {
let (old, posix, was_truncated) = format_pax_legacy_filename(&canonicalized_tar_path(path::Path::new("1/2/3/4/5/6/7/8/9/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/uqv/w/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/1/2/3/4/5/6/7/8/9/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/1/2/3/4/5/6/7/8/9/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/quux"), TarFileType::FileStream)).unwrap();
assert_eq!(was_truncated, true);
assert_eq!(old.len(), 100);
assert_eq!(posix.len(), 155);
assert_eq!("6/7/8/9/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/quux".as_bytes(), &old[0..97]);
assert_eq!(vec![0 as u8; 3], &old[97..]);
assert_eq!("w/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/1/2/3/4/5/6/7/8/9/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/aa/ab/ac/ad/ae/af/ag/ah/ai/aj/ak/1/2/3/4/5".as_bytes(), &posix[0..153]);
assert_eq!(vec![0 as u8; 2], &posix[153..]);
}
}