use std::borrow::Cow;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{cmp, fmt, marker};
use async_std::fs;
use async_std::fs::OpenOptions;
use async_std::io::prelude::*;
use async_std::io::{self, Error, ErrorKind, SeekFrom};
use async_std::path::{Component, Path, PathBuf};
use pin_project::{pin_project, project};
use filetime::{self, FileTime};
use crate::error::TarError;
use crate::header::bytes2path;
use crate::other;
use crate::pax::pax_extensions;
use crate::{Archive, Header, PaxExtensions};
#[pin_project]
pub struct Entry<R: Read + Unpin> {
#[pin]
fields: EntryFields<R>,
_ignored: marker::PhantomData<Archive<R>>,
}
impl<R: Read + Unpin> fmt::Debug for Entry<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Entry")
.field("fields", &self.fields)
.finish()
}
}
#[pin_project]
pub struct EntryFields<R: Read + Unpin> {
pub long_pathname: Option<Vec<u8>>,
pub long_linkname: Option<Vec<u8>>,
pub pax_extensions: Option<Vec<u8>>,
pub header: Header,
pub size: u64,
pub header_pos: u64,
pub file_pos: u64,
#[pin]
pub data: Vec<EntryIo<R>>,
pub unpack_xattrs: bool,
pub preserve_permissions: bool,
pub preserve_mtime: bool,
#[pin]
pub(crate) read_state: Option<EntryIo<R>>,
}
impl<R: Read + Unpin> fmt::Debug for EntryFields<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EntryFields")
.field("long_pathname", &self.long_pathname)
.field("long_linkname", &self.long_linkname)
.field("pax_extensions", &self.pax_extensions)
.field("header", &self.header)
.field("size", &self.size)
.field("header_pos", &self.header_pos)
.field("file_pos", &self.file_pos)
.field("data", &self.data)
.field("unpack_xattrs", &self.unpack_xattrs)
.field("preserve_permissions", &self.preserve_permissions)
.field("preserve_mtime", &self.preserve_mtime)
.field("read_state", &self.read_state)
.finish()
}
}
#[pin_project]
pub enum EntryIo<R: Read + Unpin> {
Pad(#[pin] io::Take<io::Repeat>),
Data(#[pin] io::Take<R>),
}
impl<R: Read + Unpin> fmt::Debug for EntryIo<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EntryIo::Pad(_) => write!(f, "EntryIo::Pad"),
EntryIo::Data(_) => write!(f, "EntryIo::Data"),
}
}
}
#[derive(Debug)]
pub enum Unpacked {
File(fs::File),
#[doc(hidden)]
__Nonexhaustive,
}
impl<R: Read + Unpin> Entry<R> {
pub fn path(&self) -> io::Result<Cow<Path>> {
self.fields.path()
}
pub fn path_bytes(&self) -> Cow<[u8]> {
self.fields.path_bytes()
}
pub fn link_name(&self) -> io::Result<Option<Cow<Path>>> {
self.fields.link_name()
}
pub fn link_name_bytes(&self) -> Option<Cow<[u8]>> {
self.fields.link_name_bytes()
}
pub async fn pax_extensions(&mut self) -> io::Result<Option<PaxExtensions<'_>>> {
self.fields.pax_extensions().await
}
pub fn header(&self) -> &Header {
&self.fields.header
}
pub fn raw_header_position(&self) -> u64 {
self.fields.header_pos
}
pub fn raw_file_position(&self) -> u64 {
self.fields.file_pos
}
pub async fn unpack<P: AsRef<Path>>(&mut self, dst: P) -> io::Result<Unpacked> {
self.fields.unpack(None, dst.as_ref()).await
}
pub async fn unpack_in<P: AsRef<Path>>(&mut self, dst: P) -> io::Result<bool> {
self.fields.unpack_in(dst.as_ref()).await
}
pub fn set_unpack_xattrs(&mut self, unpack_xattrs: bool) {
self.fields.unpack_xattrs = unpack_xattrs;
}
pub fn set_preserve_permissions(&mut self, preserve: bool) {
self.fields.preserve_permissions = preserve;
}
pub fn set_preserve_mtime(&mut self, preserve: bool) {
self.fields.preserve_mtime = preserve;
}
}
impl<R: Read + Unpin> Read for Entry<R> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
into: &mut [u8],
) -> Poll<io::Result<usize>> {
let mut this = self.project();
Pin::new(&mut *this.fields).poll_read(cx, into)
}
}
impl<R: Read + Unpin> EntryFields<R> {
pub fn from(entry: Entry<R>) -> Self {
entry.fields
}
pub fn into_entry(self) -> Entry<R> {
Entry {
fields: self,
_ignored: marker::PhantomData,
}
}
pub(crate) fn poll_read_all(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<Vec<u8>>> {
let cap = cmp::min(self.size, 128 * 1024);
let mut buf = Vec::with_capacity(cap as usize);
match async_std::task::ready!(poll_read_all_internal(self, cx, &mut buf)) {
Ok(_) => Poll::Ready(Ok(buf)),
Err(err) => Poll::Ready(Err(err)),
}
}
pub async fn read_all(&mut self) -> io::Result<Vec<u8>> {
let cap = cmp::min(self.size, 128 * 1024);
let mut v = Vec::with_capacity(cap as usize);
self.read_to_end(&mut v).await.map(|_| v)
}
fn path(&self) -> io::Result<Cow<'_, Path>> {
bytes2path(self.path_bytes())
}
fn path_bytes(&self) -> Cow<[u8]> {
match self.long_pathname {
Some(ref bytes) => {
if let Some(&0) = bytes.last() {
Cow::Borrowed(&bytes[..bytes.len() - 1])
} else {
Cow::Borrowed(bytes)
}
}
None => {
if let Some(ref pax) = self.pax_extensions {
let pax = pax_extensions(pax)
.filter_map(|f| f.ok())
.find(|f| f.key_bytes() == b"path")
.map(|f| f.value_bytes());
if let Some(field) = pax {
return Cow::Borrowed(field);
}
}
self.header.path_bytes()
}
}
}
fn path_lossy(&self) -> String {
String::from_utf8_lossy(&self.path_bytes()).to_string()
}
fn link_name(&self) -> io::Result<Option<Cow<Path>>> {
match self.link_name_bytes() {
Some(bytes) => bytes2path(bytes).map(Some),
None => Ok(None),
}
}
fn link_name_bytes(&self) -> Option<Cow<[u8]>> {
match self.long_linkname {
Some(ref bytes) => {
if let Some(&0) = bytes.last() {
Some(Cow::Borrowed(&bytes[..bytes.len() - 1]))
} else {
Some(Cow::Borrowed(bytes))
}
}
None => self.header.link_name_bytes(),
}
}
async fn pax_extensions(&mut self) -> io::Result<Option<PaxExtensions<'_>>> {
if self.pax_extensions.is_none() {
if !self.header.entry_type().is_pax_global_extensions()
&& !self.header.entry_type().is_pax_local_extensions()
{
return Ok(None);
}
self.pax_extensions = Some(self.read_all().await?);
}
Ok(Some(pax_extensions(self.pax_extensions.as_ref().unwrap())))
}
async fn unpack_in(&mut self, dst: &Path) -> io::Result<bool> {
let mut file_dst = dst.to_path_buf();
{
let path = self.path().map_err(|e| {
TarError::new(
&format!("invalid path in entry header: {}", self.path_lossy()),
e,
)
})?;
for part in path.components() {
match part {
Component::Prefix(..) | Component::RootDir | Component::CurDir => continue,
Component::ParentDir => return Ok(false),
Component::Normal(part) => file_dst.push(part),
}
}
}
if *dst == *file_dst {
return Ok(true);
}
let parent = match file_dst.parent() {
Some(p) => p,
None => return Ok(false),
};
if parent.symlink_metadata().await.is_err() {
fs::create_dir_all(&parent).await.map_err(|e| {
TarError::new(&format!("failed to create `{}`", parent.display()), e)
})?;
}
let canon_target = self.validate_inside_dst(&dst, parent).await?;
self.unpack(Some(&canon_target), &file_dst)
.await
.map_err(|e| TarError::new(&format!("failed to unpack `{}`", file_dst.display()), e))?;
Ok(true)
}
async fn unpack_dir(&mut self, dst: &Path) -> io::Result<()> {
match fs::create_dir(dst).await {
Ok(()) => Ok(()),
Err(err) => {
if err.kind() == ErrorKind::AlreadyExists {
let prev = fs::metadata(dst).await;
if prev.map(|m| m.is_dir()).unwrap_or(false) {
return Ok(());
}
}
Err(Error::new(
err.kind(),
format!("{} when creating dir {}", err, dst.display()),
))
}
}
}
async fn unpack(&mut self, target_base: Option<&Path>, dst: &Path) -> io::Result<Unpacked> {
let kind = self.header.entry_type();
if kind.is_dir() {
self.unpack_dir(dst).await?;
if let Ok(mode) = self.header.mode() {
set_perms(dst, None, mode, self.preserve_permissions).await?;
}
return Ok(Unpacked::__Nonexhaustive);
} else if kind.is_hard_link() || kind.is_symlink() {
let src = match self.link_name()? {
Some(name) => name,
None => {
return Err(other(&format!(
"hard link listed for {} but no link name found",
String::from_utf8_lossy(self.header.as_bytes())
)));
}
};
if src.iter().count() == 0 {
return Err(other(&format!(
"symlink destination for {} is empty",
String::from_utf8_lossy(self.header.as_bytes())
)));
}
if kind.is_hard_link() {
let link_src = match target_base {
Some(ref p) => {
let link_src = p.join(src);
self.validate_inside_dst(p, &link_src).await?;
link_src
}
None => src.into_owned(),
};
fs::hard_link(&link_src, dst).await.map_err(|err| {
Error::new(
err.kind(),
format!(
"{} when hard linking {} to {}",
err,
link_src.display(),
dst.display()
),
)
})?;
} else {
symlink(&src, dst).await.map_err(|err| {
Error::new(
err.kind(),
format!(
"{} when symlinking {} to {}",
err,
src.display(),
dst.display()
),
)
})?;
};
return Ok(Unpacked::__Nonexhaustive);
#[cfg(target_arch = "wasm32")]
#[allow(unused_variables)]
async fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
}
#[cfg(windows)]
async fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
async_std::os::windows::fs::symlink_file(src, dst).await
}
#[cfg(any(unix, target_os = "redox"))]
async fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
async_std::os::unix::fs::symlink(src, dst).await
}
} else if kind.is_pax_global_extensions()
|| kind.is_pax_local_extensions()
|| kind.is_gnu_longname()
|| kind.is_gnu_longlink()
{
return Ok(Unpacked::__Nonexhaustive);
};
if self.header.as_ustar().is_none() && self.path_bytes().ends_with(b"/") {
self.unpack_dir(dst).await?;
if let Ok(mode) = self.header.mode() {
set_perms(dst, None, mode, self.preserve_permissions).await?;
}
return Ok(Unpacked::__Nonexhaustive);
}
async fn open(dst: &Path) -> io::Result<fs::File> {
OpenOptions::new()
.write(true)
.create_new(true)
.open(dst)
.await
};
let mut f = async {
let mut f = match open(dst).await {
Ok(f) => Ok(f),
Err(err) => {
if err.kind() != ErrorKind::AlreadyExists {
Err(err)
} else {
match fs::remove_file(dst).await {
Ok(()) => open(dst).await,
Err(ref e) if e.kind() == io::ErrorKind::NotFound => open(dst).await,
Err(e) => Err(e),
}
}
}
}?;
for io in self.data.drain(..) {
match io {
EntryIo::Data(mut d) => {
let expected = d.limit();
if io::copy(&mut d, &mut f).await? != expected {
return Err(other("failed to write entire file"));
}
}
EntryIo::Pad(d) => {
let to = SeekFrom::Current(d.limit() as i64);
let size = f.seek(to).await?;
f.set_len(size).await?;
}
}
}
Ok::<fs::File, io::Error>(f)
}
.await
.map_err(|e| {
let header = self.header.path_bytes();
TarError::new(
&format!(
"failed to unpack `{}` into `{}`",
String::from_utf8_lossy(&header),
dst.display()
),
e,
)
})?;
if self.preserve_mtime {
if let Ok(mtime) = self.header.mtime() {
let mtime = FileTime::from_unix_time(mtime as i64, 0);
filetime::set_file_times(&dst, mtime, mtime).map_err(|e| {
TarError::new(&format!("failed to set mtime for `{}`", dst.display()), e)
})?;
}
}
if let Ok(mode) = self.header.mode() {
set_perms(dst, Some(&mut f), mode, self.preserve_permissions).await?;
}
if self.unpack_xattrs {
set_xattrs(self, dst).await?;
}
return Ok(Unpacked::File(f));
async fn set_perms(
dst: &Path,
f: Option<&mut fs::File>,
mode: u32,
preserve: bool,
) -> Result<(), TarError> {
_set_perms(dst, f, mode, preserve).await.map_err(|e| {
TarError::new(
&format!(
"failed to set permissions to {:o} \
for `{}`",
mode,
dst.display()
),
e,
)
})
}
#[cfg(any(unix, target_os = "redox"))]
async fn _set_perms(
dst: &Path,
f: Option<&mut fs::File>,
mode: u32,
preserve: bool,
) -> io::Result<()> {
use std::os::unix::prelude::*;
let mode = if preserve { mode } else { mode & 0o777 };
let perm = fs::Permissions::from_mode(mode as _);
match f {
Some(f) => f.set_permissions(perm).await,
None => fs::set_permissions(dst, perm).await,
}
}
#[cfg(windows)]
async fn _set_perms(
dst: &Path,
f: Option<&mut fs::File>,
mode: u32,
_preserve: bool,
) -> io::Result<()> {
if mode & 0o200 == 0o200 {
return Ok(());
}
match f {
Some(f) => {
let mut perm = f.metadata().await?.permissions();
perm.set_readonly(true);
f.set_permissions(perm).await
}
None => {
let mut perm = fs::metadata(dst).await?.permissions();
perm.set_readonly(true);
fs::set_permissions(dst, perm).await
}
}
}
#[cfg(target_arch = "wasm32")]
#[allow(unused_variables)]
async fn _set_perms(
dst: &Path,
f: Option<&mut fs::File>,
mode: u32,
_preserve: bool,
) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
}
#[cfg(all(unix, feature = "xattr"))]
async fn set_xattrs<R: Read + Unpin>(
me: &mut EntryFields<R>,
dst: &Path,
) -> io::Result<()> {
use std::ffi::OsStr;
use std::os::unix::prelude::*;
let exts = match me.pax_extensions().await {
Ok(Some(e)) => e,
_ => return Ok(()),
};
let exts = exts
.filter_map(|e| e.ok())
.filter_map(|e| {
let key = e.key_bytes();
let prefix = b"SCHILY.xattr.";
if key.starts_with(prefix) {
Some((&key[prefix.len()..], e))
} else {
None
}
})
.map(|(key, e)| (OsStr::from_bytes(key), e.value_bytes()));
for (key, value) in exts {
xattr::set(dst, key, value).map_err(|e| {
TarError::new(
&format!(
"failed to set extended \
attributes to {}. \
Xattrs: key={:?}, value={:?}.",
dst.display(),
key,
String::from_utf8_lossy(value)
),
e,
)
})?;
}
Ok(())
}
#[cfg(any(
windows,
target_os = "redox",
not(feature = "xattr"),
target_arch = "wasm32"
))]
async fn set_xattrs<R: Read + Unpin>(_: &mut EntryFields<R>, _: &Path) -> io::Result<()> {
Ok(())
}
}
async fn validate_inside_dst(&self, dst: &Path, file_dst: &Path) -> io::Result<PathBuf> {
let canon_parent = file_dst.canonicalize().await.map_err(|err| {
Error::new(
err.kind(),
format!("{} while canonicalizing {}", err, file_dst.display()),
)
})?;
let canon_target = dst.canonicalize().await.map_err(|err| {
Error::new(
err.kind(),
format!("{} while canonicalizing {}", err, dst.display()),
)
})?;
if !canon_parent.starts_with(&canon_target) {
let err = TarError::new(
&format!(
"trying to unpack outside of destination path: {}",
canon_target.display()
),
Error::new(ErrorKind::Other, "Invalid argument"),
);
return Err(err.into());
}
Ok(canon_target)
}
}
impl<R: Read + Unpin> Read for EntryFields<R> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
into: &mut [u8],
) -> Poll<io::Result<usize>> {
let mut this = self.project();
loop {
if this.read_state.is_none() {
if this.data.as_ref().is_empty() {
*this.read_state = None;
} else {
let data = &mut *this.data;
*this.read_state = Some(data.remove(0));
}
}
if let Some(ref mut io) = &mut *this.read_state {
let ret = Pin::new(io).poll_read(cx, into);
match ret {
Poll::Ready(Ok(0)) => {
*this.read_state = None;
if this.data.as_ref().is_empty() {
return Poll::Ready(Ok(0));
}
continue;
}
Poll::Ready(Ok(val)) => {
return Poll::Ready(Ok(val));
}
Poll::Ready(Err(err)) => {
return Poll::Ready(Err(err));
}
Poll::Pending => {
return Poll::Pending;
}
}
} else {
return Poll::Ready(Ok(0));
}
}
}
}
impl<R: Read + Unpin> Read for EntryIo<R> {
#[project]
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
into: &mut [u8],
) -> Poll<io::Result<usize>> {
#[project]
match self.project() {
EntryIo::Pad(io) => io.poll_read(cx, into),
EntryIo::Data(io) => io.poll_read(cx, into),
}
}
}
struct Guard<'a> {
buf: &'a mut Vec<u8>,
len: usize,
}
impl Drop for Guard<'_> {
fn drop(&mut self) {
unsafe {
self.buf.set_len(self.len);
}
}
}
fn poll_read_all_internal<R: Read + ?Sized>(
mut rd: Pin<&mut R>,
cx: &mut Context<'_>,
buf: &mut Vec<u8>,
) -> Poll<io::Result<usize>> {
let mut g = Guard {
len: buf.len(),
buf,
};
let ret;
loop {
if g.len == g.buf.len() {
unsafe {
g.buf.reserve(32);
let capacity = g.buf.capacity();
g.buf.set_len(capacity);
let buf = &mut g.buf[g.len..];
std::ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len());
}
}
match async_std::task::ready!(rd.as_mut().poll_read(cx, &mut g.buf[g.len..])) {
Ok(0) => {
ret = Poll::Ready(Ok(g.len));
break;
}
Ok(n) => g.len += n,
Err(e) => {
ret = Poll::Ready(Err(e));
break;
}
}
}
ret
}