rustmax 0.0.11

A collection of useful crates.
Documentation
Read and write tar archives.

- Crate [`::tar`].
- [docs.rs]https://docs.rs/tar
- [crates.io]https://crates.io/crates/tar
- [GitHub]https://github.com/composefs/tar-rs

---

`tar` reads and writes the tar archive format,
which bundles files and their metadata into a single stream.
It does not compress:
a `.tar.gz` is a tar archive passed through gzip,
so pair this crate with [`flate2`] to read or write one.

The API is abstract over [`Read`] and [`Write`]
rather than tied to files,
and it is streaming throughout —
an archive of any size can be processed
without holding it in memory.

[`Archive`] reads an existing archive,
yielding an [`Entry`] per member,
each of which is itself a [`Read`].
[`Builder`] writes a new one.
[`Header`] carries the per-entry metadata:
path, size, mode, timestamps, and [`EntryType`],
which distinguishes regular files from directories,
symlinks, and hard links.

Unpacking an untrusted archive can be insecure.
Entries can name absolute paths, paths containing `..`,
and symlinks or hard links pointing anywhere on the filesystem.
[`Archive::unpack`] and [`Entry::unpack_in`] exist to contain that,
refusing to write outside the destination directory.

## Examples

Writing an archive and reading it back, in memory:

```rust
use std::io::Read;
use tar::{Archive, Builder, Header};

let mut header = Header::new_gnu();
header.set_path("hello.txt").unwrap();
header.set_size(5);
header.set_mode(0o644);
header.set_cksum();

let mut builder = Builder::new(Vec::new());
builder.append(&header, &b"world"[..]).unwrap();
let bytes = builder.into_inner().unwrap();

let mut archive = Archive::new(&bytes[..]);
for entry in archive.entries().unwrap() {
    let mut entry = entry.unwrap();
    assert_eq!(entry.path().unwrap().to_str().unwrap(), "hello.txt");

    let mut contents = String::new();
    entry.read_to_string(&mut contents).unwrap();
    assert_eq!(contents, "world");
}
```

Making a `.tar.gz` by layering a compressor over the archive:

```rust
use flate2::Compression;
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use tar::{Archive, Builder, Header};

let mut header = Header::new_gnu();
header.set_path("notes.txt").unwrap();
header.set_size(4);
header.set_cksum();

let mut builder = Builder::new(GzEncoder::new(Vec::new(), Compression::default()));
builder.append(&header, &b"data"[..]).unwrap();
let targz = builder.into_inner().unwrap().finish().unwrap();

let mut archive = Archive::new(GzDecoder::new(&targz[..]));
assert_eq!(archive.entries().unwrap().count(), 1);
```

Unpacking to a directory:

```rust
use tar::{Archive, Builder, Header};

let mut header = Header::new_gnu();
header.set_path("hello.txt").unwrap();
header.set_size(5);
header.set_mode(0o644);
header.set_cksum();

let mut builder = Builder::new(Vec::new());
builder.append(&header, &b"world"[..]).unwrap();
let bytes = builder.into_inner().unwrap();

let dir = tempfile::tempdir().unwrap();
Archive::new(&bytes[..]).unpack(dir.path()).unwrap();

assert_eq!(std::fs::read_to_string(dir.path().join("hello.txt")).unwrap(), "world");
```

[`Archive`]: crate::tar::Archive
[`Archive::unpack`]: crate::tar::Archive::unpack
[`Builder`]: crate::tar::Builder
[`Entry`]: crate::tar::Entry
[`Entry::unpack_in`]: crate::tar::Entry::unpack_in
[`EntryType`]: crate::tar::EntryType
[`Header`]: crate::tar::Header
[`flate2`]: crate::flate2
[`Read`]: std::io::Read
[`Write`]: std::io::Write