Skip to content

Commit 4cf17c2

Browse files
authored
Revert "Apply PAX size overrides to entry headers" (#114)
Signed-off-by: William Woodruff <william@yossarian.net>
1 parent c6a7d4f commit 4cf17c2

9 files changed

Lines changed: 123 additions & 70 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## 0.7.0
4+
5+
* Remove all unsafe code from the crate
6+
* New API: `Entry::effective_size` returns the effective size of an archive entry,
7+
including any pax `size` records that apply to it
8+
* Changed: `Header::entry_size` is now `Header::raw_entry_size`
9+
* Changed: `Header::size` is now `Header::raw_file_size`
10+
311
## 0.5.6
412

513
* Fixed a parser desynchronization vulnerability when reading tar archives that

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "astral-tokio-tar"
3-
version = "0.6.4"
3+
version = "0.7.0"
44
authors = [
55
"Alex Crichton <alex@alexcrichton.com>",
66
"dignifiedquire <me@dignifiequire.com>",

examples/raw_list.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ fn main() {
1818
println!("-------------------------- Entry {}", i);
1919
let mut f = file.unwrap();
2020
println!("path: {}", f.path().unwrap().display());
21-
println!("size: {}", f.header().size().unwrap());
22-
println!("entry size: {}", f.header().entry_size().unwrap());
21+
println!("raw size: {}", f.header().raw_file_size().unwrap());
22+
println!("raw entry size: {}", f.header().raw_entry_size().unwrap());
2323
println!("link name: {:?}", f.link_name().unwrap());
2424
println!("file type: {:#x}", f.header().entry_type().as_byte());
2525
println!("mode: {:#o}", f.header().mode().unwrap());

src/archive.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@ impl<R: Read + Unpin> Archive<R> {
236236
/// sequence. If entries are processed out of sequence (from what the
237237
/// stream returns), then the contents read for each entry may be
238238
/// corrupted.
239+
///
240+
/// **IMPORTANT**: Most users want [`Self::entries`], not this API.
241+
/// This API returns every *physical* entry in the archive, rather
242+
/// than their composed "logical" entries. It should only be used
243+
/// for low-level diagnostic parsing.
239244
pub fn entries_raw(&mut self) -> io::Result<RawEntries<R>> {
240245
if self.inner.pax_only {
241246
return Err(other("raw entries are not supported by pax-only mode"));
@@ -721,7 +726,7 @@ fn poll_next_raw<R: Read + Unpin>(
721726
let mut header = current_header.take().unwrap();
722727

723728
// note when pax extensions are available, the size from the header will be ignored
724-
let mut size = header.entry_size()?;
729+
let mut size = header.raw_entry_size()?;
725730
let mut pax_username = None;
726731
let mut pax_groupname = None;
727732

@@ -761,7 +766,6 @@ fn poll_next_raw<R: Read + Unpin>(
761766
.value()
762767
.map_err(|_e| other("failed to parse pax size as string"))?;
763768
size = parse_pax_decimal(size_str, "failed to parse pax size")?;
764-
header.set_size(size);
765769
}
766770

767771
"uid" => {

src/entry.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,14 @@ impl<R: Read + Unpin> Entry<R> {
342342
&self.fields.header
343343
}
344344

345+
/// Returns the total size of this entry's data in bytes.
346+
///
347+
/// Unlike [`Header::raw_entry_size`], this includes any `size` override from PAX
348+
/// extensions describing this entry.
349+
pub fn effective_size(&self) -> u64 {
350+
self.fields.size
351+
}
352+
345353
/// Returns the starting position, in bytes, of the header of this entry in
346354
/// the archive.
347355
///

src/header.rs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -315,13 +315,17 @@ impl Header {
315315

316316
/// Returns the size of entry's data this header represents.
317317
///
318-
/// This is different from `Header::size` for sparse files, which have
319-
/// some longer `size()` but shorter `entry_size()`. The `entry_size()`
320-
/// listed here should be the number of bytes in the archive this header
321-
/// describes.
318+
/// This is different from [`Header::raw_size`] for sparse files, which have
319+
/// some longer [`Header::raw_size`] but shorter [`Header::raw_entry_size`].
320+
/// The [`Header::raw_entry_size`] listed here should be the number of bytes in the
321+
/// archive this header describes.
322322
///
323323
/// May return an error if the field is corrupted.
324-
pub fn entry_size(&self) -> io::Result<u64> {
324+
///
325+
/// **IMPORTANT**: This does not account for PAX `size` overrides.
326+
/// Use [`Entry::effective_size`](crate::Entry::effective_size) to obtain
327+
/// the effective size of an archive entry.
328+
pub fn raw_entry_size(&self) -> io::Result<u64> {
325329
num_field_wrapper_from(&self.as_old().size).map_err(|err| {
326330
io::Error::new(
327331
err.kind(),
@@ -330,16 +334,21 @@ impl Header {
330334
})
331335
}
332336

333-
/// Returns the file size this header represents.
337+
/// Returns the file size this header represents, adjusted for any "real size"
338+
/// if the header is a GNU sparse header.
334339
///
335340
/// May return an error if the field is corrupted.
336-
pub fn size(&self) -> io::Result<u64> {
341+
///
342+
/// **IMPORTANT**: This does not account for PAX `size` overrides. Use
343+
/// [`Entry::effective_size`](crate::Entry::effective_size) to obtain the effective size of an
344+
/// archive entry.
345+
pub fn raw_file_size(&self) -> io::Result<u64> {
337346
if self.entry_type().is_gnu_sparse() {
338347
self.as_gnu()
339348
.ok_or_else(|| other("sparse header was not a gnu header"))
340349
.and_then(|h| h.real_size())
341350
} else {
342-
self.entry_size()
351+
self.raw_entry_size()
343352
}
344353
}
345354

@@ -871,10 +880,10 @@ impl Header {
871880
}
872881

873882
fn debug_fields(&self, b: &mut fmt::DebugStruct) {
874-
if let Ok(entry_size) = self.entry_size() {
883+
if let Ok(entry_size) = self.raw_entry_size() {
875884
b.field("entry_size", &entry_size);
876885
}
877-
if let Ok(size) = self.size() {
886+
if let Ok(size) = self.raw_file_size() {
878887
b.field("size", &size);
879888
}
880889
if let Ok(path) = self.path() {

tests/all.rs

Lines changed: 75 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ async fn writing_files() {
194194
let mut f = t!(entries.next().await.unwrap());
195195

196196
assert_eq!(&*f.header().path_bytes(), b"test2");
197-
assert_eq!(f.header().size().unwrap(), 4);
197+
assert_eq!(f.header().raw_file_size().unwrap(), 4);
198198
let mut s = String::new();
199199
t!(f.read_to_string(&mut s).await);
200200
assert_eq!(s, "test");
@@ -231,15 +231,15 @@ async fn large_filename() {
231231
// The short entry added with `append`
232232
let mut f = entries.next().await.unwrap().unwrap();
233233
assert_eq!(&*f.header().path_bytes(), filename.as_bytes());
234-
assert_eq!(f.header().size().unwrap(), 4);
234+
assert_eq!(f.header().raw_file_size().unwrap(), 4);
235235
let mut s = String::new();
236236
t!(f.read_to_string(&mut s).await);
237237
assert_eq!(s, "test");
238238

239239
// The long entry added with `append_file`
240240
let mut f = entries.next().await.unwrap().unwrap();
241241
assert_eq!(&*t!(f.path_bytes()), too_long.as_bytes());
242-
assert_eq!(f.header().size().unwrap(), 4);
242+
assert_eq!(f.header().raw_file_size().unwrap(), 4);
243243
let mut s = String::new();
244244
t!(f.read_to_string(&mut s).await);
245245
assert_eq!(s, "test");
@@ -248,7 +248,7 @@ async fn large_filename() {
248248
let mut f = entries.next().await.unwrap().unwrap();
249249
assert!(f.header().path_bytes().len() < too_long.len());
250250
assert_eq!(&*t!(f.path_bytes()), too_long.as_bytes());
251-
assert_eq!(f.header().size().unwrap(), 4);
251+
assert_eq!(f.header().raw_file_size().unwrap(), 4);
252252
let mut s = String::new();
253253
t!(f.read_to_string(&mut s).await);
254254
assert_eq!(s, "test");
@@ -280,7 +280,7 @@ async fn large_filename_with_dot_dot_at_100_byte_mark() {
280280

281281
let mut f = t!(entries.next().await.unwrap());
282282
assert_eq!(&*t!(f.path_bytes()), long_name_with_dot_dot.as_bytes());
283-
assert_eq!(f.header().size().unwrap(), 4);
283+
assert_eq!(f.header().raw_file_size().unwrap(), 4);
284284
let mut s = String::new();
285285
t!(f.read_to_string(&mut s).await);
286286
assert_eq!(s, "test");
@@ -784,7 +784,7 @@ async fn octal_spaces() {
784784
assert_eq!(entry.header().mode().unwrap() & 0o777, 0o777);
785785
assert_eq!(entry.header().uid().unwrap(), 0);
786786
assert_eq!(entry.header().gid().unwrap(), 0);
787-
assert_eq!(entry.header().size().unwrap(), 2);
787+
assert_eq!(entry.header().raw_file_size().unwrap(), 2);
788788
assert_eq!(entry.header().mtime().unwrap(), 0o12_440_016_664);
789789
assert_eq!(entry.header().cksum().unwrap(), 0o4253);
790790
}
@@ -1318,6 +1318,68 @@ async fn pax_precedence() {
13181318
assert!(entries.next().await.is_none());
13191319
}
13201320

1321+
#[tokio::test]
1322+
async fn pax_size_overrides_header() {
1323+
let mut ar = Archive::new(tar!("pax-header-precedence.tar"));
1324+
let mut entries = t!(ar.entries());
1325+
1326+
let first = t!(entries.next().await.unwrap());
1327+
assert!(t!(first.path()).ends_with("normal.txt"));
1328+
assert_eq!(first.effective_size(), 6);
1329+
assert_eq!(first.effective_size(), t!(first.header().raw_file_size()));
1330+
1331+
let mut second = t!(entries.next().await.unwrap());
1332+
assert!(t!(second.path()).ends_with("blob.bin"));
1333+
assert_eq!(second.effective_size(), 1024);
1334+
assert_eq!(t!(second.header().raw_file_size()), 0);
1335+
1336+
let mut byte = [0];
1337+
t!(second.read_exact(&mut byte).await);
1338+
assert_eq!(second.effective_size(), 1024);
1339+
1340+
let mut contents = Vec::new();
1341+
t!(second.read_to_end(&mut contents).await);
1342+
assert_eq!(contents.len(), 1023);
1343+
assert_eq!(second.effective_size(), 1024);
1344+
assert_eq!(t!(second.header().raw_file_size()), 0);
1345+
1346+
let third = t!(entries.next().await.unwrap());
1347+
assert!(t!(third.path()).ends_with("marker.txt"));
1348+
assert_eq!(third.effective_size(), 7);
1349+
assert_eq!(third.effective_size(), t!(third.header().raw_file_size()));
1350+
assert!(entries.next().await.is_none());
1351+
}
1352+
1353+
#[tokio::test]
1354+
async fn last_pax_size_wins() {
1355+
let mut sizes = pax_record("size", b"3");
1356+
sizes.extend(pax_record("size", b"4"));
1357+
1358+
let mut builder = Builder::new(Vec::new());
1359+
let mut extension = Header::new_ustar();
1360+
extension.set_size(sizes.len() as u64);
1361+
extension.set_entry_type(EntryType::new(b'x'));
1362+
t!(builder.append_data(&mut extension, "pax", &sizes[..]).await);
1363+
1364+
// PAX determines the payload size; keep the raw header size different.
1365+
let mut file = Header::new_ustar();
1366+
file.set_size(1);
1367+
t!(builder.append_data(&mut file, "file", &b"DATA"[..]).await);
1368+
1369+
let bytes = t!(builder.into_inner().await);
1370+
let mut archive = Archive::new(&bytes[..]);
1371+
let mut entries = t!(archive.entries());
1372+
1373+
let mut file = t!(entries.next().await.unwrap());
1374+
assert_eq!(file.effective_size(), 4);
1375+
assert_eq!(t!(file.header().raw_file_size()), 1);
1376+
1377+
let mut contents = Vec::new();
1378+
t!(file.read_to_end(&mut contents).await);
1379+
assert_eq!(contents, b"DATA");
1380+
assert!(entries.next().await.is_none());
1381+
}
1382+
13211383
async fn pax_numeric_override_archive(pax: &[u8]) -> Vec<u8> {
13221384
let mut builder = Builder::new(Vec::new());
13231385

@@ -1344,7 +1406,7 @@ async fn pax_numeric_overrides_accept_decimal_digits() {
13441406
let mut entries = t!(archive.entries());
13451407

13461408
let entry = t!(entries.next().await.unwrap());
1347-
assert_eq!(t!(entry.header().size()), 2);
1409+
assert_eq!(t!(entry.header().raw_file_size()), 2);
13481410
assert_eq!(t!(entry.header().uid()), 42);
13491411
assert_eq!(t!(entry.header().gid()), 43);
13501412
assert!(entries.next().await.is_none());
@@ -1619,47 +1681,6 @@ async fn pax_non_utf8_owner_names_are_exposed_as_bytes() {
16191681
assert!(entries.next().await.is_none());
16201682
}
16211683

1622-
#[tokio::test]
1623-
async fn pax_size_updates_header() {
1624-
let mut ar = Archive::new(tar!("pax-header-precedence.tar"));
1625-
let mut entries = t!(ar.entries());
1626-
1627-
let first = t!(entries.next().await.unwrap());
1628-
assert!(first.path().unwrap().ends_with("normal.txt"));
1629-
1630-
let second = t!(entries.next().await.unwrap());
1631-
assert!(second.path().unwrap().ends_with("blob.bin"));
1632-
assert_eq!(second.header().size().unwrap(), 1024);
1633-
}
1634-
1635-
#[tokio::test]
1636-
async fn last_pax_size_wins() {
1637-
let mut sizes = pax_record("size", b"3");
1638-
sizes.extend(pax_record("size", b"4"));
1639-
1640-
let mut builder = Builder::new(Vec::new());
1641-
let mut extension = Header::new_ustar();
1642-
extension.set_size(sizes.len() as u64);
1643-
extension.set_entry_type(EntryType::new(b'x'));
1644-
t!(builder.append_data(&mut extension, "pax", &sizes[..]).await);
1645-
1646-
let mut file = Header::new_ustar();
1647-
file.set_size(4);
1648-
t!(builder.append_data(&mut file, "file", &b"DATA"[..]).await);
1649-
1650-
let bytes = t!(builder.into_inner().await);
1651-
let mut archive = Archive::new(&bytes[..]);
1652-
let mut entries = t!(archive.entries());
1653-
1654-
let mut file = t!(entries.next().await.unwrap());
1655-
assert_eq!(t!(file.header().size()), 4);
1656-
1657-
let mut contents = Vec::new();
1658-
t!(file.read_to_end(&mut contents).await);
1659-
assert_eq!(contents, b"DATA");
1660-
assert!(entries.next().await.is_none());
1661-
}
1662-
16631684
#[tokio::test]
16641685
async fn solaris_extended_header_applies_pax_path() {
16651686
let mut builder = Builder::new(Vec::new());
@@ -1969,6 +1990,7 @@ async fn large_sparse() {
19691990
let a = t!(entries.next().await.unwrap());
19701991
let h = a.header().as_gnu().unwrap();
19711992
assert_eq!(h.real_size().unwrap(), 12626929280);
1993+
assert_eq!(a.effective_size(), 12626929280);
19721994
}
19731995

19741996
#[tokio::test]
@@ -1977,9 +1999,11 @@ async fn sparse_with_trailing() {
19771999
let mut ar = Archive::new(rdr);
19782000
let mut entries = t!(ar.entries());
19792001
let mut a = t!(entries.next().await.unwrap());
2002+
assert_eq!(a.effective_size(), 0x100_00c);
19802003
let mut s = String::new();
19812004
t!(a.read_to_string(&mut s).await);
19822005
assert_eq!(0x100_00c, s.len());
2006+
assert_eq!(a.effective_size(), s.len() as u64);
19832007
assert_eq!(&s[..0xc], "0MB through\n");
19842008
assert!(s[0xc..0x100_000].chars().all(|x| x == '\u{0}'));
19852009
assert_eq!(&s[0x100_000..], "1MB through\n");
@@ -2080,23 +2104,23 @@ async fn append_path_symlink() {
20802104
t!(entry.link_name()),
20812105
Some(Cow::from(Path::new("testdest")))
20822106
);
2083-
assert_eq!(t!(entry.header().size()), 0);
2107+
assert_eq!(t!(entry.header().raw_file_size()), 0);
20842108

20852109
let entry = t!(entries.next().await.unwrap());
20862110
assert_eq!(t!(entry.path()), Path::new("test2"));
20872111
assert_eq!(
20882112
t!(entry.link_name()),
20892113
Some(Cow::from(Path::new(&long_linkname)))
20902114
);
2091-
assert_eq!(t!(entry.header().size()), 0);
2115+
assert_eq!(t!(entry.header().raw_file_size()), 0);
20922116

20932117
let entry = t!(entries.next().await.unwrap());
20942118
assert_eq!(t!(entry.path()), Path::new(&long_pathname));
20952119
assert_eq!(
20962120
t!(entry.link_name()),
20972121
Some(Cow::from(Path::new(&long_linkname)))
20982122
);
2099-
assert_eq!(t!(entry.header().size()), 0);
2123+
assert_eq!(t!(entry.header().raw_file_size()), 0);
21002124

21012125
assert!(entries.next().await.is_none());
21022126
}

tests/header/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ fn set_metadata_deterministic() {
213213
let two = mk_header(tmppath.as_path(), true);
214214

215215
// Always expected to match.
216-
assert_eq!(t!(one.size()), t!(two.size()));
216+
assert_eq!(t!(one.raw_file_size()), t!(two.raw_file_size()));
217217
assert_eq!(t!(one.path()), t!(two.path()));
218218
assert_eq!(t!(one.mode()), t!(two.mode()));
219219

@@ -236,9 +236,9 @@ fn extended_numeric_format() {
236236
h.as_header_mut().set_size(44);
237237
assert_eq!(h.size, [48, 48, 48, 48, 48, 48, 48, 48, 48, 53, 52, 0]);
238238
h.size = [0x80, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0, 0];
239-
assert_eq!(h.as_header().entry_size().unwrap(), 0x0200000000);
239+
assert_eq!(h.as_header().raw_entry_size().unwrap(), 0x0200000000);
240240
h.size = [48, 48, 48, 48, 48, 48, 48, 48, 48, 53, 51, 0];
241-
assert_eq!(h.as_header().entry_size().unwrap(), 43);
241+
assert_eq!(h.as_header().raw_entry_size().unwrap(), 43);
242242

243243
h.as_header_mut().set_gid(42);
244244
assert_eq!(h.gid, [48, 48, 48, 48, 48, 53, 50, 0]);

0 commit comments

Comments
 (0)