diff --git a/crates/formats/src/tarcpio.rs b/crates/formats/src/tarcpio.rs
index 7f63de2f..78afa0bc 100644
--- a/crates/formats/src/tarcpio.rs
+++ b/crates/formats/src/tarcpio.rs
@@ -177,7 +177,8 @@ pub fn tar(data: &[u8], max_members: usize) -> Option
{
}
b'g' => {}
_ => {
- let ustar = h.get(257..262) == Some(b"ustar");
+ // Old GNU headers use these bytes for timestamps and sparse-file metadata.
+ let ustar = h.get(257..263) == Some(b"ustar\0");
let base = text(h.get(0..100).unwrap_or(&[]));
let prefix = if ustar { text(h.get(345..500).unwrap_or(&[])) } else { String::new() };
let joined = if prefix.is_empty() { base } else { format!("{prefix}/{base}") };
diff --git a/crates/formats/tests/tar_gnu_names.rs b/crates/formats/tests/tar_gnu_names.rs
new file mode 100644
index 00000000..1d0b698e
--- /dev/null
+++ b/crates/formats/tests/tar_gnu_names.rs
@@ -0,0 +1,31 @@
+use craft_anything_formats::tarcpio::{member_data, tar};
+use craft_anything_testkit::firmware as fx;
+
+fn checksum(header: &mut [u8]) {
+ header[148..156].fill(b' ');
+ let sum: u32 = header.iter().map(|&b| u32::from(b)).sum();
+ header[148..156].copy_from_slice(format!("{sum:06o}\0 ").as_bytes());
+}
+
+#[test]
+fn old_gnu_timestamps_are_not_posix_path_prefixes() {
+ let mut archive = fx::tar(&[("program", b"payload")]);
+ let header = &mut archive[..512];
+ header[257..265].copy_from_slice(b"ustar \0");
+ header[345..357].copy_from_slice(b"00000000123\0");
+ header[357..369].copy_from_slice(b"00000000456\0");
+ checksum(header);
+ let listing = tar(&archive, 10).unwrap();
+ assert!(listing.complete);
+ assert_eq!(listing.members[0].name, "program");
+ assert_eq!(member_data(&archive, &listing.members[0]), Some(b"payload".as_slice()));
+}
+
+#[test]
+fn posix_ustar_prefixes_are_still_joined() {
+ let mut archive = fx::tar(&[("program", b"payload")]);
+ let header = &mut archive[..512];
+ header[345..352].copy_from_slice(b"usr/bin");
+ checksum(header);
+ assert_eq!(tar(&archive, 10).unwrap().members[0].name, "usr/bin/program");
+}