1 extern crate filetime;
2 extern crate tar;
3 extern crate tempdir;
4 #[cfg(all(unix, feature = "xattr"))]
5 extern crate xattr;
6
7 use std::fs::{self, File};
8 use std::io::prelude::*;
9 use std::io::{self, Cursor};
10 use std::iter::repeat;
11 use std::path::{Path, PathBuf};
12
13 use self::tempdir::TempDir;
14 use filetime::FileTime;
15 use tar::{Archive, Builder, EntryType, Header};
16
17 macro_rules! t {
18 ($e:expr) => {
19 match $e {
20 Ok(v) => v,
21 Err(e) => panic!("{} returned {}", stringify!($e), e),
22 }
23 };
24 }
25
26 macro_rules! tar {
27 ($e:expr) => {
28 &include_bytes!(concat!("archives/", $e))[..]
29 };
30 }
31
32 mod header;
33
34 /// test that we can concatenate the simple.tar archive and extract the same entries twice when we
35 /// use the ignore_zeros option.
36 #[test]
simple_concat()37 fn simple_concat() {
38 let bytes = tar!("simple.tar");
39 let mut archive_bytes = Vec::new();
40 archive_bytes.extend(bytes);
41
42 let original_names: Vec<String> = decode_names(&mut Archive::new(Cursor::new(&archive_bytes)));
43 let expected: Vec<&str> = original_names.iter().map(|n| n.as_str()).collect();
44
45 // concat two archives (with null in-between);
46 archive_bytes.extend(bytes);
47
48 // test now that when we read the archive, it stops processing at the first zero header.
49 let actual = decode_names(&mut Archive::new(Cursor::new(&archive_bytes)));
50 assert_eq!(expected, actual);
51
52 // extend expected by itself.
53 let expected: Vec<&str> = {
54 let mut o = Vec::new();
55 o.extend(&expected);
56 o.extend(&expected);
57 o
58 };
59
60 let mut ar = Archive::new(Cursor::new(&archive_bytes));
61 ar.set_ignore_zeros(true);
62
63 let actual = decode_names(&mut ar);
64 assert_eq!(expected, actual);
65
66 fn decode_names<R>(ar: &mut Archive<R>) -> Vec<String>
67 where
68 R: Read,
69 {
70 let mut names = Vec::new();
71
72 for entry in t!(ar.entries()) {
73 let e = t!(entry);
74 names.push(t!(::std::str::from_utf8(&e.path_bytes())).to_string());
75 }
76
77 names
78 }
79 }
80
81 #[test]
header_impls()82 fn header_impls() {
83 let mut ar = Archive::new(Cursor::new(tar!("simple.tar")));
84 let hn = Header::new_old();
85 let hnb = hn.as_bytes();
86 for file in t!(ar.entries()) {
87 let file = t!(file);
88 let h1 = file.header();
89 let h1b = h1.as_bytes();
90 let h2 = h1.clone();
91 let h2b = h2.as_bytes();
92 assert!(h1b[..] == h2b[..] && h2b[..] != hnb[..])
93 }
94 }
95
96 #[test]
header_impls_missing_last_header()97 fn header_impls_missing_last_header() {
98 let mut ar = Archive::new(Cursor::new(tar!("simple_missing_last_header.tar")));
99 let hn = Header::new_old();
100 let hnb = hn.as_bytes();
101 for file in t!(ar.entries()) {
102 let file = t!(file);
103 let h1 = file.header();
104 let h1b = h1.as_bytes();
105 let h2 = h1.clone();
106 let h2b = h2.as_bytes();
107 assert!(h1b[..] == h2b[..] && h2b[..] != hnb[..])
108 }
109 }
110
111 #[test]
reading_files()112 fn reading_files() {
113 let rdr = Cursor::new(tar!("reading_files.tar"));
114 let mut ar = Archive::new(rdr);
115 let mut entries = t!(ar.entries());
116
117 let mut a = t!(entries.next().unwrap());
118 assert_eq!(&*a.header().path_bytes(), b"a");
119 let mut s = String::new();
120 t!(a.read_to_string(&mut s));
121 assert_eq!(s, "a\na\na\na\na\na\na\na\na\na\na\n");
122
123 let mut b = t!(entries.next().unwrap());
124 assert_eq!(&*b.header().path_bytes(), b"b");
125 s.truncate(0);
126 t!(b.read_to_string(&mut s));
127 assert_eq!(s, "b\nb\nb\nb\nb\nb\nb\nb\nb\nb\nb\n");
128
129 assert!(entries.next().is_none());
130 }
131
132 #[test]
writing_files()133 fn writing_files() {
134 let mut ar = Builder::new(Vec::new());
135 let td = t!(TempDir::new("tar-rs"));
136
137 let path = td.path().join("test");
138 t!(t!(File::create(&path)).write_all(b"test"));
139
140 t!(ar.append_file("test2", &mut t!(File::open(&path))));
141
142 let data = t!(ar.into_inner());
143 let mut ar = Archive::new(Cursor::new(data));
144 let mut entries = t!(ar.entries());
145 let mut f = t!(entries.next().unwrap());
146
147 assert_eq!(&*f.header().path_bytes(), b"test2");
148 assert_eq!(f.header().size().unwrap(), 4);
149 let mut s = String::new();
150 t!(f.read_to_string(&mut s));
151 assert_eq!(s, "test");
152
153 assert!(entries.next().is_none());
154 }
155
156 #[test]
large_filename()157 fn large_filename() {
158 let mut ar = Builder::new(Vec::new());
159 let td = t!(TempDir::new("tar-rs"));
160
161 let path = td.path().join("test");
162 t!(t!(File::create(&path)).write_all(b"test"));
163
164 let filename = repeat("abcd/").take(50).collect::<String>();
165 let mut header = Header::new_ustar();
166 header.set_path(&filename).unwrap();
167 header.set_metadata(&t!(fs::metadata(&path)));
168 header.set_cksum();
169 t!(ar.append(&header, &b"test"[..]));
170 let too_long = repeat("abcd").take(200).collect::<String>();
171 t!(ar.append_file(&too_long, &mut t!(File::open(&path))));
172 t!(ar.append_data(&mut header, &too_long, &b"test"[..]));
173
174 let rd = Cursor::new(t!(ar.into_inner()));
175 let mut ar = Archive::new(rd);
176 let mut entries = t!(ar.entries());
177
178 // The short entry added with `append`
179 let mut f = entries.next().unwrap().unwrap();
180 assert_eq!(&*f.header().path_bytes(), filename.as_bytes());
181 assert_eq!(f.header().size().unwrap(), 4);
182 let mut s = String::new();
183 t!(f.read_to_string(&mut s));
184 assert_eq!(s, "test");
185
186 // The long entry added with `append_file`
187 let mut f = entries.next().unwrap().unwrap();
188 assert_eq!(&*f.path_bytes(), too_long.as_bytes());
189 assert_eq!(f.header().size().unwrap(), 4);
190 let mut s = String::new();
191 t!(f.read_to_string(&mut s));
192 assert_eq!(s, "test");
193
194 // The long entry added with `append_data`
195 let mut f = entries.next().unwrap().unwrap();
196 assert!(f.header().path_bytes().len() < too_long.len());
197 assert_eq!(&*f.path_bytes(), too_long.as_bytes());
198 assert_eq!(f.header().size().unwrap(), 4);
199 let mut s = String::new();
200 t!(f.read_to_string(&mut s));
201 assert_eq!(s, "test");
202
203 assert!(entries.next().is_none());
204 }
205
206 #[test]
reading_entries()207 fn reading_entries() {
208 let rdr = Cursor::new(tar!("reading_files.tar"));
209 let mut ar = Archive::new(rdr);
210 let mut entries = t!(ar.entries());
211 let mut a = t!(entries.next().unwrap());
212 assert_eq!(&*a.header().path_bytes(), b"a");
213 let mut s = String::new();
214 t!(a.read_to_string(&mut s));
215 assert_eq!(s, "a\na\na\na\na\na\na\na\na\na\na\n");
216 s.truncate(0);
217 t!(a.read_to_string(&mut s));
218 assert_eq!(s, "");
219 let mut b = t!(entries.next().unwrap());
220
221 assert_eq!(&*b.header().path_bytes(), b"b");
222 s.truncate(0);
223 t!(b.read_to_string(&mut s));
224 assert_eq!(s, "b\nb\nb\nb\nb\nb\nb\nb\nb\nb\nb\n");
225 assert!(entries.next().is_none());
226 }
227
check_dirtree(td: &TempDir)228 fn check_dirtree(td: &TempDir) {
229 let dir_a = td.path().join("a");
230 let dir_b = td.path().join("a/b");
231 let file_c = td.path().join("a/c");
232 assert!(fs::metadata(&dir_a).map(|m| m.is_dir()).unwrap_or(false));
233 assert!(fs::metadata(&dir_b).map(|m| m.is_dir()).unwrap_or(false));
234 assert!(fs::metadata(&file_c).map(|m| m.is_file()).unwrap_or(false));
235 }
236
237 #[test]
extracting_directories()238 fn extracting_directories() {
239 let td = t!(TempDir::new("tar-rs"));
240 let rdr = Cursor::new(tar!("directory.tar"));
241 let mut ar = Archive::new(rdr);
242 t!(ar.unpack(td.path()));
243 check_dirtree(&td);
244 }
245
246 #[test]
247 #[cfg(all(unix, feature = "xattr"))]
xattrs()248 fn xattrs() {
249 // If /tmp is a tmpfs, xattr will fail
250 // The xattr crate's unit tests also use /var/tmp for this reason
251 let td = t!(TempDir::new_in("/var/tmp", "tar-rs"));
252 let rdr = Cursor::new(tar!("xattrs.tar"));
253 let mut ar = Archive::new(rdr);
254 ar.set_unpack_xattrs(true);
255 t!(ar.unpack(td.path()));
256
257 let val = xattr::get(td.path().join("a/b"), "user.pax.flags").unwrap();
258 assert_eq!(val.unwrap(), "epm".as_bytes());
259 }
260
261 #[test]
262 #[cfg(all(unix, feature = "xattr"))]
no_xattrs()263 fn no_xattrs() {
264 // If /tmp is a tmpfs, xattr will fail
265 // The xattr crate's unit tests also use /var/tmp for this reason
266 let td = t!(TempDir::new_in("/var/tmp", "tar-rs"));
267 let rdr = Cursor::new(tar!("xattrs.tar"));
268 let mut ar = Archive::new(rdr);
269 ar.set_unpack_xattrs(false);
270 t!(ar.unpack(td.path()));
271
272 assert_eq!(
273 xattr::get(td.path().join("a/b"), "user.pax.flags").unwrap(),
274 None
275 );
276 }
277
278 #[test]
writing_and_extracting_directories()279 fn writing_and_extracting_directories() {
280 let td = t!(TempDir::new("tar-rs"));
281
282 let mut ar = Builder::new(Vec::new());
283 let tmppath = td.path().join("tmpfile");
284 t!(t!(File::create(&tmppath)).write_all(b"c"));
285 t!(ar.append_dir("a", "."));
286 t!(ar.append_dir("a/b", "."));
287 t!(ar.append_file("a/c", &mut t!(File::open(&tmppath))));
288 t!(ar.finish());
289
290 let rdr = Cursor::new(t!(ar.into_inner()));
291 let mut ar = Archive::new(rdr);
292 t!(ar.unpack(td.path()));
293 check_dirtree(&td);
294 }
295
296 #[test]
writing_directories_recursively()297 fn writing_directories_recursively() {
298 let td = t!(TempDir::new("tar-rs"));
299
300 let base_dir = td.path().join("base");
301 t!(fs::create_dir(&base_dir));
302 t!(t!(File::create(base_dir.join("file1"))).write_all(b"file1"));
303 let sub_dir = base_dir.join("sub");
304 t!(fs::create_dir(&sub_dir));
305 t!(t!(File::create(sub_dir.join("file2"))).write_all(b"file2"));
306
307 let mut ar = Builder::new(Vec::new());
308 t!(ar.append_dir_all("foobar", base_dir));
309 let data = t!(ar.into_inner());
310
311 let mut ar = Archive::new(Cursor::new(data));
312 t!(ar.unpack(td.path()));
313 let base_dir = td.path().join("foobar");
314 assert!(fs::metadata(&base_dir).map(|m| m.is_dir()).unwrap_or(false));
315 let file1_path = base_dir.join("file1");
316 assert!(fs::metadata(&file1_path)
317 .map(|m| m.is_file())
318 .unwrap_or(false));
319 let sub_dir = base_dir.join("sub");
320 assert!(fs::metadata(&sub_dir).map(|m| m.is_dir()).unwrap_or(false));
321 let file2_path = sub_dir.join("file2");
322 assert!(fs::metadata(&file2_path)
323 .map(|m| m.is_file())
324 .unwrap_or(false));
325 }
326
327 #[test]
append_dir_all_blank_dest()328 fn append_dir_all_blank_dest() {
329 let td = t!(TempDir::new("tar-rs"));
330
331 let base_dir = td.path().join("base");
332 t!(fs::create_dir(&base_dir));
333 t!(t!(File::create(base_dir.join("file1"))).write_all(b"file1"));
334 let sub_dir = base_dir.join("sub");
335 t!(fs::create_dir(&sub_dir));
336 t!(t!(File::create(sub_dir.join("file2"))).write_all(b"file2"));
337
338 let mut ar = Builder::new(Vec::new());
339 t!(ar.append_dir_all("", base_dir));
340 let data = t!(ar.into_inner());
341
342 let mut ar = Archive::new(Cursor::new(data));
343 t!(ar.unpack(td.path()));
344 let base_dir = td.path();
345 assert!(fs::metadata(&base_dir).map(|m| m.is_dir()).unwrap_or(false));
346 let file1_path = base_dir.join("file1");
347 assert!(fs::metadata(&file1_path)
348 .map(|m| m.is_file())
349 .unwrap_or(false));
350 let sub_dir = base_dir.join("sub");
351 assert!(fs::metadata(&sub_dir).map(|m| m.is_dir()).unwrap_or(false));
352 let file2_path = sub_dir.join("file2");
353 assert!(fs::metadata(&file2_path)
354 .map(|m| m.is_file())
355 .unwrap_or(false));
356 }
357
358 #[test]
append_dir_all_does_not_work_on_non_directory()359 fn append_dir_all_does_not_work_on_non_directory() {
360 let td = t!(TempDir::new("tar-rs"));
361 let path = td.path().join("test");
362 t!(t!(File::create(&path)).write_all(b"test"));
363
364 let mut ar = Builder::new(Vec::new());
365 let result = ar.append_dir_all("test", path);
366 assert!(result.is_err());
367 }
368
369 #[test]
extracting_duplicate_dirs()370 fn extracting_duplicate_dirs() {
371 let td = t!(TempDir::new("tar-rs"));
372 let rdr = Cursor::new(tar!("duplicate_dirs.tar"));
373 let mut ar = Archive::new(rdr);
374 t!(ar.unpack(td.path()));
375
376 let some_dir = td.path().join("some_dir");
377 assert!(fs::metadata(&some_dir).map(|m| m.is_dir()).unwrap_or(false));
378 }
379
380 #[test]
unpack_old_style_bsd_dir()381 fn unpack_old_style_bsd_dir() {
382 let td = t!(TempDir::new("tar-rs"));
383
384 let mut ar = Builder::new(Vec::new());
385
386 let mut header = Header::new_old();
387 header.set_entry_type(EntryType::Regular);
388 t!(header.set_path("testdir/"));
389 header.set_size(0);
390 header.set_cksum();
391 t!(ar.append(&header, &mut io::empty()));
392
393 // Extracting
394 let rdr = Cursor::new(t!(ar.into_inner()));
395 let mut ar = Archive::new(rdr);
396 t!(ar.unpack(td.path()));
397
398 // Iterating
399 let rdr = Cursor::new(ar.into_inner().into_inner());
400 let mut ar = Archive::new(rdr);
401 assert!(t!(ar.entries()).all(|fr| fr.is_ok()));
402
403 assert!(td.path().join("testdir").is_dir());
404 }
405
406 #[test]
handling_incorrect_file_size()407 fn handling_incorrect_file_size() {
408 let td = t!(TempDir::new("tar-rs"));
409
410 let mut ar = Builder::new(Vec::new());
411
412 let path = td.path().join("tmpfile");
413 t!(File::create(&path));
414 let mut file = t!(File::open(&path));
415 let mut header = Header::new_old();
416 t!(header.set_path("somepath"));
417 header.set_metadata(&t!(file.metadata()));
418 header.set_size(2048); // past the end of file null blocks
419 header.set_cksum();
420 t!(ar.append(&header, &mut file));
421
422 // Extracting
423 let rdr = Cursor::new(t!(ar.into_inner()));
424 let mut ar = Archive::new(rdr);
425 assert!(ar.unpack(td.path()).is_err());
426
427 // Iterating
428 let rdr = Cursor::new(ar.into_inner().into_inner());
429 let mut ar = Archive::new(rdr);
430 assert!(t!(ar.entries()).any(|fr| fr.is_err()));
431 }
432
433 #[test]
extracting_malicious_tarball()434 fn extracting_malicious_tarball() {
435 let td = t!(TempDir::new("tar-rs"));
436
437 let mut evil_tar = Vec::new();
438
439 {
440 let mut a = Builder::new(&mut evil_tar);
441 let mut append = |path: &str| {
442 let mut header = Header::new_gnu();
443 assert!(header.set_path(path).is_err(), "was ok: {:?}", path);
444 {
445 let h = header.as_gnu_mut().unwrap();
446 for (a, b) in h.name.iter_mut().zip(path.as_bytes()) {
447 *a = *b;
448 }
449 }
450 header.set_size(1);
451 header.set_cksum();
452 t!(a.append(&header, io::repeat(1).take(1)));
453 };
454 append("/tmp/abs_evil.txt");
455 append("//tmp/abs_evil2.txt");
456 append("///tmp/abs_evil3.txt");
457 append("/./tmp/abs_evil4.txt");
458 append("//./tmp/abs_evil5.txt");
459 append("///./tmp/abs_evil6.txt");
460 append("/../tmp/rel_evil.txt");
461 append("../rel_evil2.txt");
462 append("./../rel_evil3.txt");
463 append("some/../../rel_evil4.txt");
464 append("");
465 append("././//./..");
466 append("..");
467 append("/////////..");
468 append("/////////");
469 }
470
471 let mut ar = Archive::new(&evil_tar[..]);
472 t!(ar.unpack(td.path()));
473
474 assert!(fs::metadata("/tmp/abs_evil.txt").is_err());
475 assert!(fs::metadata("/tmp/abs_evil.txt2").is_err());
476 assert!(fs::metadata("/tmp/abs_evil.txt3").is_err());
477 assert!(fs::metadata("/tmp/abs_evil.txt4").is_err());
478 assert!(fs::metadata("/tmp/abs_evil.txt5").is_err());
479 assert!(fs::metadata("/tmp/abs_evil.txt6").is_err());
480 assert!(fs::metadata("/tmp/rel_evil.txt").is_err());
481 assert!(fs::metadata("/tmp/rel_evil.txt").is_err());
482 assert!(fs::metadata(td.path().join("../tmp/rel_evil.txt")).is_err());
483 assert!(fs::metadata(td.path().join("../rel_evil2.txt")).is_err());
484 assert!(fs::metadata(td.path().join("../rel_evil3.txt")).is_err());
485 assert!(fs::metadata(td.path().join("../rel_evil4.txt")).is_err());
486
487 // The `some` subdirectory should not be created because the only
488 // filename that references this has '..'.
489 assert!(fs::metadata(td.path().join("some")).is_err());
490
491 // The `tmp` subdirectory should be created and within this
492 // subdirectory, there should be files named `abs_evil.txt` through
493 // `abs_evil6.txt`.
494 assert!(fs::metadata(td.path().join("tmp"))
495 .map(|m| m.is_dir())
496 .unwrap_or(false));
497 assert!(fs::metadata(td.path().join("tmp/abs_evil.txt"))
498 .map(|m| m.is_file())
499 .unwrap_or(false));
500 assert!(fs::metadata(td.path().join("tmp/abs_evil2.txt"))
501 .map(|m| m.is_file())
502 .unwrap_or(false));
503 assert!(fs::metadata(td.path().join("tmp/abs_evil3.txt"))
504 .map(|m| m.is_file())
505 .unwrap_or(false));
506 assert!(fs::metadata(td.path().join("tmp/abs_evil4.txt"))
507 .map(|m| m.is_file())
508 .unwrap_or(false));
509 assert!(fs::metadata(td.path().join("tmp/abs_evil5.txt"))
510 .map(|m| m.is_file())
511 .unwrap_or(false));
512 assert!(fs::metadata(td.path().join("tmp/abs_evil6.txt"))
513 .map(|m| m.is_file())
514 .unwrap_or(false));
515 }
516
517 #[test]
octal_spaces()518 fn octal_spaces() {
519 let rdr = Cursor::new(tar!("spaces.tar"));
520 let mut ar = Archive::new(rdr);
521
522 let entry = ar.entries().unwrap().next().unwrap().unwrap();
523 assert_eq!(entry.header().mode().unwrap() & 0o777, 0o777);
524 assert_eq!(entry.header().uid().unwrap(), 0);
525 assert_eq!(entry.header().gid().unwrap(), 0);
526 assert_eq!(entry.header().size().unwrap(), 2);
527 assert_eq!(entry.header().mtime().unwrap(), 0o12440016664);
528 assert_eq!(entry.header().cksum().unwrap(), 0o4253);
529 }
530
531 #[test]
extracting_malformed_tar_null_blocks()532 fn extracting_malformed_tar_null_blocks() {
533 let td = t!(TempDir::new("tar-rs"));
534
535 let mut ar = Builder::new(Vec::new());
536
537 let path1 = td.path().join("tmpfile1");
538 let path2 = td.path().join("tmpfile2");
539 t!(File::create(&path1));
540 t!(File::create(&path2));
541 t!(ar.append_file("tmpfile1", &mut t!(File::open(&path1))));
542 let mut data = t!(ar.into_inner());
543 let amt = data.len();
544 data.truncate(amt - 512);
545 let mut ar = Builder::new(data);
546 t!(ar.append_file("tmpfile2", &mut t!(File::open(&path2))));
547 t!(ar.finish());
548
549 let data = t!(ar.into_inner());
550 let mut ar = Archive::new(&data[..]);
551 assert!(ar.unpack(td.path()).is_ok());
552 }
553
554 #[test]
empty_filename()555 fn empty_filename() {
556 let td = t!(TempDir::new("tar-rs"));
557 let rdr = Cursor::new(tar!("empty_filename.tar"));
558 let mut ar = Archive::new(rdr);
559 assert!(ar.unpack(td.path()).is_ok());
560 }
561
562 #[test]
file_times()563 fn file_times() {
564 let td = t!(TempDir::new("tar-rs"));
565 let rdr = Cursor::new(tar!("file_times.tar"));
566 let mut ar = Archive::new(rdr);
567 t!(ar.unpack(td.path()));
568
569 let meta = fs::metadata(td.path().join("a")).unwrap();
570 let mtime = FileTime::from_last_modification_time(&meta);
571 let atime = FileTime::from_last_access_time(&meta);
572 assert_eq!(mtime.unix_seconds(), 1000000000);
573 assert_eq!(mtime.nanoseconds(), 0);
574 assert_eq!(atime.unix_seconds(), 1000000000);
575 assert_eq!(atime.nanoseconds(), 0);
576 }
577
578 #[test]
backslash_treated_well()579 fn backslash_treated_well() {
580 // Insert a file into an archive with a backslash
581 let td = t!(TempDir::new("tar-rs"));
582 let mut ar = Builder::new(Vec::<u8>::new());
583 t!(ar.append_dir("foo\\bar", td.path()));
584 let mut ar = Archive::new(Cursor::new(t!(ar.into_inner())));
585 let f = t!(t!(ar.entries()).next().unwrap());
586 if cfg!(unix) {
587 assert_eq!(t!(f.header().path()).to_str(), Some("foo\\bar"));
588 } else {
589 assert_eq!(t!(f.header().path()).to_str(), Some("foo/bar"));
590 }
591
592 // Unpack an archive with a backslash in the name
593 let mut ar = Builder::new(Vec::<u8>::new());
594 let mut header = Header::new_gnu();
595 header.set_metadata(&t!(fs::metadata(td.path())));
596 header.set_size(0);
597 for (a, b) in header.as_old_mut().name.iter_mut().zip(b"foo\\bar\x00") {
598 *a = *b;
599 }
600 header.set_cksum();
601 t!(ar.append(&header, &mut io::empty()));
602 let data = t!(ar.into_inner());
603 let mut ar = Archive::new(&data[..]);
604 let f = t!(t!(ar.entries()).next().unwrap());
605 assert_eq!(t!(f.header().path()).to_str(), Some("foo\\bar"));
606
607 let mut ar = Archive::new(&data[..]);
608 t!(ar.unpack(td.path()));
609 assert!(fs::metadata(td.path().join("foo\\bar")).is_ok());
610 }
611
612 #[cfg(unix)]
613 #[test]
nul_bytes_in_path()614 fn nul_bytes_in_path() {
615 use std::ffi::OsStr;
616 use std::os::unix::prelude::*;
617
618 let nul_path = OsStr::from_bytes(b"foo\0");
619 let td = t!(TempDir::new("tar-rs"));
620 let mut ar = Builder::new(Vec::<u8>::new());
621 let err = ar.append_dir(nul_path, td.path()).unwrap_err();
622 assert!(err.to_string().contains("contains a nul byte"));
623 }
624
625 #[test]
links()626 fn links() {
627 let mut ar = Archive::new(Cursor::new(tar!("link.tar")));
628 let mut entries = t!(ar.entries());
629 let link = t!(entries.next().unwrap());
630 assert_eq!(
631 t!(link.header().link_name()).as_ref().map(|p| &**p),
632 Some(Path::new("file"))
633 );
634 let other = t!(entries.next().unwrap());
635 assert!(t!(other.header().link_name()).is_none());
636 }
637
638 #[test]
639 #[cfg(unix)] // making symlinks on windows is hard
unpack_links()640 fn unpack_links() {
641 let td = t!(TempDir::new("tar-rs"));
642 let mut ar = Archive::new(Cursor::new(tar!("link.tar")));
643 t!(ar.unpack(td.path()));
644
645 let md = t!(fs::symlink_metadata(td.path().join("lnk")));
646 assert!(md.file_type().is_symlink());
647 assert_eq!(
648 &*t!(fs::read_link(td.path().join("lnk"))),
649 Path::new("file")
650 );
651 t!(File::open(td.path().join("lnk")));
652 }
653
654 #[test]
pax_simple()655 fn pax_simple() {
656 let mut ar = Archive::new(tar!("pax.tar"));
657 let mut entries = t!(ar.entries());
658
659 let mut first = t!(entries.next().unwrap());
660 let mut attributes = t!(first.pax_extensions()).unwrap();
661 let first = t!(attributes.next().unwrap());
662 let second = t!(attributes.next().unwrap());
663 let third = t!(attributes.next().unwrap());
664 assert!(attributes.next().is_none());
665
666 assert_eq!(first.key(), Ok("mtime"));
667 assert_eq!(first.value(), Ok("1453146164.953123768"));
668 assert_eq!(second.key(), Ok("atime"));
669 assert_eq!(second.value(), Ok("1453251915.24892486"));
670 assert_eq!(third.key(), Ok("ctime"));
671 assert_eq!(third.value(), Ok("1453146164.953123768"));
672 }
673
674 #[test]
pax_path()675 fn pax_path() {
676 let mut ar = Archive::new(tar!("pax2.tar"));
677 let mut entries = t!(ar.entries());
678
679 let first = t!(entries.next().unwrap());
680 assert!(first.path().unwrap().ends_with("aaaaaaaaaaaaaaa"));
681 }
682
683 #[test]
long_name_trailing_nul()684 fn long_name_trailing_nul() {
685 let mut b = Builder::new(Vec::<u8>::new());
686
687 let mut h = Header::new_gnu();
688 t!(h.set_path("././@LongLink"));
689 h.set_size(4);
690 h.set_entry_type(EntryType::new(b'L'));
691 h.set_cksum();
692 t!(b.append(&h, "foo\0".as_bytes()));
693 let mut h = Header::new_gnu();
694
695 t!(h.set_path("bar"));
696 h.set_size(6);
697 h.set_entry_type(EntryType::file());
698 h.set_cksum();
699 t!(b.append(&h, "foobar".as_bytes()));
700
701 let contents = t!(b.into_inner());
702 let mut a = Archive::new(&contents[..]);
703
704 let e = t!(t!(a.entries()).next().unwrap());
705 assert_eq!(&*e.path_bytes(), b"foo");
706 }
707
708 #[test]
long_linkname_trailing_nul()709 fn long_linkname_trailing_nul() {
710 let mut b = Builder::new(Vec::<u8>::new());
711
712 let mut h = Header::new_gnu();
713 t!(h.set_path("././@LongLink"));
714 h.set_size(4);
715 h.set_entry_type(EntryType::new(b'K'));
716 h.set_cksum();
717 t!(b.append(&h, "foo\0".as_bytes()));
718 let mut h = Header::new_gnu();
719
720 t!(h.set_path("bar"));
721 h.set_size(6);
722 h.set_entry_type(EntryType::file());
723 h.set_cksum();
724 t!(b.append(&h, "foobar".as_bytes()));
725
726 let contents = t!(b.into_inner());
727 let mut a = Archive::new(&contents[..]);
728
729 let e = t!(t!(a.entries()).next().unwrap());
730 assert_eq!(&*e.link_name_bytes().unwrap(), b"foo");
731 }
732
733 #[test]
encoded_long_name_has_trailing_nul()734 fn encoded_long_name_has_trailing_nul() {
735 let td = t!(TempDir::new("tar-rs"));
736 let path = td.path().join("foo");
737 t!(t!(File::create(&path)).write_all(b"test"));
738
739 let mut b = Builder::new(Vec::<u8>::new());
740 let long = repeat("abcd").take(200).collect::<String>();
741
742 t!(b.append_file(&long, &mut t!(File::open(&path))));
743
744 let contents = t!(b.into_inner());
745 let mut a = Archive::new(&contents[..]);
746
747 let mut e = t!(t!(a.entries()).raw(true).next().unwrap());
748 let mut name = Vec::new();
749 t!(e.read_to_end(&mut name));
750 assert_eq!(name[name.len() - 1], 0);
751
752 let header_name = &e.header().as_gnu().unwrap().name;
753 assert!(header_name.starts_with(b"././@LongLink\x00"));
754 }
755
756 #[test]
reading_sparse()757 fn reading_sparse() {
758 let rdr = Cursor::new(tar!("sparse.tar"));
759 let mut ar = Archive::new(rdr);
760 let mut entries = t!(ar.entries());
761
762 let mut a = t!(entries.next().unwrap());
763 let mut s = String::new();
764 assert_eq!(&*a.header().path_bytes(), b"sparse_begin.txt");
765 t!(a.read_to_string(&mut s));
766 assert_eq!(&s[..5], "test\n");
767 assert!(s[5..].chars().all(|x| x == '\u{0}'));
768
769 let mut a = t!(entries.next().unwrap());
770 let mut s = String::new();
771 assert_eq!(&*a.header().path_bytes(), b"sparse_end.txt");
772 t!(a.read_to_string(&mut s));
773 assert!(s[..s.len() - 9].chars().all(|x| x == '\u{0}'));
774 assert_eq!(&s[s.len() - 9..], "test_end\n");
775
776 let mut a = t!(entries.next().unwrap());
777 let mut s = String::new();
778 assert_eq!(&*a.header().path_bytes(), b"sparse_ext.txt");
779 t!(a.read_to_string(&mut s));
780 assert!(s[..0x1000].chars().all(|x| x == '\u{0}'));
781 assert_eq!(&s[0x1000..0x1000 + 5], "text\n");
782 assert!(s[0x1000 + 5..0x3000].chars().all(|x| x == '\u{0}'));
783 assert_eq!(&s[0x3000..0x3000 + 5], "text\n");
784 assert!(s[0x3000 + 5..0x5000].chars().all(|x| x == '\u{0}'));
785 assert_eq!(&s[0x5000..0x5000 + 5], "text\n");
786 assert!(s[0x5000 + 5..0x7000].chars().all(|x| x == '\u{0}'));
787 assert_eq!(&s[0x7000..0x7000 + 5], "text\n");
788 assert!(s[0x7000 + 5..0x9000].chars().all(|x| x == '\u{0}'));
789 assert_eq!(&s[0x9000..0x9000 + 5], "text\n");
790 assert!(s[0x9000 + 5..0xb000].chars().all(|x| x == '\u{0}'));
791 assert_eq!(&s[0xb000..0xb000 + 5], "text\n");
792
793 let mut a = t!(entries.next().unwrap());
794 let mut s = String::new();
795 assert_eq!(&*a.header().path_bytes(), b"sparse.txt");
796 t!(a.read_to_string(&mut s));
797 assert!(s[..0x1000].chars().all(|x| x == '\u{0}'));
798 assert_eq!(&s[0x1000..0x1000 + 6], "hello\n");
799 assert!(s[0x1000 + 6..0x2fa0].chars().all(|x| x == '\u{0}'));
800 assert_eq!(&s[0x2fa0..0x2fa0 + 6], "world\n");
801 assert!(s[0x2fa0 + 6..0x4000].chars().all(|x| x == '\u{0}'));
802
803 assert!(entries.next().is_none());
804 }
805
806 #[test]
extract_sparse()807 fn extract_sparse() {
808 let rdr = Cursor::new(tar!("sparse.tar"));
809 let mut ar = Archive::new(rdr);
810 let td = t!(TempDir::new("tar-rs"));
811 t!(ar.unpack(td.path()));
812
813 let mut s = String::new();
814 t!(t!(File::open(td.path().join("sparse_begin.txt"))).read_to_string(&mut s));
815 assert_eq!(&s[..5], "test\n");
816 assert!(s[5..].chars().all(|x| x == '\u{0}'));
817
818 s.truncate(0);
819 t!(t!(File::open(td.path().join("sparse_end.txt"))).read_to_string(&mut s));
820 assert!(s[..s.len() - 9].chars().all(|x| x == '\u{0}'));
821 assert_eq!(&s[s.len() - 9..], "test_end\n");
822
823 s.truncate(0);
824 t!(t!(File::open(td.path().join("sparse_ext.txt"))).read_to_string(&mut s));
825 assert!(s[..0x1000].chars().all(|x| x == '\u{0}'));
826 assert_eq!(&s[0x1000..0x1000 + 5], "text\n");
827 assert!(s[0x1000 + 5..0x3000].chars().all(|x| x == '\u{0}'));
828 assert_eq!(&s[0x3000..0x3000 + 5], "text\n");
829 assert!(s[0x3000 + 5..0x5000].chars().all(|x| x == '\u{0}'));
830 assert_eq!(&s[0x5000..0x5000 + 5], "text\n");
831 assert!(s[0x5000 + 5..0x7000].chars().all(|x| x == '\u{0}'));
832 assert_eq!(&s[0x7000..0x7000 + 5], "text\n");
833 assert!(s[0x7000 + 5..0x9000].chars().all(|x| x == '\u{0}'));
834 assert_eq!(&s[0x9000..0x9000 + 5], "text\n");
835 assert!(s[0x9000 + 5..0xb000].chars().all(|x| x == '\u{0}'));
836 assert_eq!(&s[0xb000..0xb000 + 5], "text\n");
837
838 s.truncate(0);
839 t!(t!(File::open(td.path().join("sparse.txt"))).read_to_string(&mut s));
840 assert!(s[..0x1000].chars().all(|x| x == '\u{0}'));
841 assert_eq!(&s[0x1000..0x1000 + 6], "hello\n");
842 assert!(s[0x1000 + 6..0x2fa0].chars().all(|x| x == '\u{0}'));
843 assert_eq!(&s[0x2fa0..0x2fa0 + 6], "world\n");
844 assert!(s[0x2fa0 + 6..0x4000].chars().all(|x| x == '\u{0}'));
845 }
846
847 #[test]
path_separators()848 fn path_separators() {
849 let mut ar = Builder::new(Vec::new());
850 let td = t!(TempDir::new("tar-rs"));
851
852 let path = td.path().join("test");
853 t!(t!(File::create(&path)).write_all(b"test"));
854
855 let short_path: PathBuf = repeat("abcd").take(2).collect();
856 let long_path: PathBuf = repeat("abcd").take(50).collect();
857
858 // Make sure UStar headers normalize to Unix path separators
859 let mut header = Header::new_ustar();
860
861 t!(header.set_path(&short_path));
862 assert_eq!(t!(header.path()), short_path);
863 assert!(!header.path_bytes().contains(&b'\\'));
864
865 t!(header.set_path(&long_path));
866 assert_eq!(t!(header.path()), long_path);
867 assert!(!header.path_bytes().contains(&b'\\'));
868
869 // Make sure GNU headers normalize to Unix path separators,
870 // including the `@LongLink` fallback used by `append_file`.
871 t!(ar.append_file(&short_path, &mut t!(File::open(&path))));
872 t!(ar.append_file(&long_path, &mut t!(File::open(&path))));
873
874 let rd = Cursor::new(t!(ar.into_inner()));
875 let mut ar = Archive::new(rd);
876 let mut entries = t!(ar.entries());
877
878 let entry = t!(entries.next().unwrap());
879 assert_eq!(t!(entry.path()), short_path);
880 assert!(!entry.path_bytes().contains(&b'\\'));
881
882 let entry = t!(entries.next().unwrap());
883 assert_eq!(t!(entry.path()), long_path);
884 assert!(!entry.path_bytes().contains(&b'\\'));
885
886 assert!(entries.next().is_none());
887 }
888
889 #[test]
890 #[cfg(unix)]
append_path_symlink()891 fn append_path_symlink() {
892 use std::borrow::Cow;
893 use std::env;
894 use std::os::unix::fs::symlink;
895
896 let mut ar = Builder::new(Vec::new());
897 ar.follow_symlinks(false);
898 let td = t!(TempDir::new("tar-rs"));
899
900 let long_linkname = repeat("abcd").take(30).collect::<String>();
901 let long_pathname = repeat("dcba").take(30).collect::<String>();
902 t!(env::set_current_dir(td.path()));
903 // "short" path name / short link name
904 t!(symlink("testdest", "test"));
905 t!(ar.append_path("test"));
906 // short path name / long link name
907 t!(symlink(&long_linkname, "test2"));
908 t!(ar.append_path("test2"));
909 // long path name / long link name
910 t!(symlink(&long_linkname, &long_pathname));
911 t!(ar.append_path(&long_pathname));
912
913 let rd = Cursor::new(t!(ar.into_inner()));
914 let mut ar = Archive::new(rd);
915 let mut entries = t!(ar.entries());
916
917 let entry = t!(entries.next().unwrap());
918 assert_eq!(t!(entry.path()), Path::new("test"));
919 assert_eq!(
920 t!(entry.link_name()),
921 Some(Cow::from(Path::new("testdest")))
922 );
923 assert_eq!(t!(entry.header().size()), 0);
924
925 let entry = t!(entries.next().unwrap());
926 assert_eq!(t!(entry.path()), Path::new("test2"));
927 assert_eq!(
928 t!(entry.link_name()),
929 Some(Cow::from(Path::new(&long_linkname)))
930 );
931 assert_eq!(t!(entry.header().size()), 0);
932
933 let entry = t!(entries.next().unwrap());
934 assert_eq!(t!(entry.path()), Path::new(&long_pathname));
935 assert_eq!(
936 t!(entry.link_name()),
937 Some(Cow::from(Path::new(&long_linkname)))
938 );
939 assert_eq!(t!(entry.header().size()), 0);
940
941 assert!(entries.next().is_none());
942 }
943
944 #[test]
name_with_slash_doesnt_fool_long_link_and_bsd_compat()945 fn name_with_slash_doesnt_fool_long_link_and_bsd_compat() {
946 let td = t!(TempDir::new("tar-rs"));
947
948 let mut ar = Builder::new(Vec::new());
949
950 let mut h = Header::new_gnu();
951 t!(h.set_path("././@LongLink"));
952 h.set_size(4);
953 h.set_entry_type(EntryType::new(b'L'));
954 h.set_cksum();
955 t!(ar.append(&h, "foo\0".as_bytes()));
956
957 let mut header = Header::new_gnu();
958 header.set_entry_type(EntryType::Regular);
959 t!(header.set_path("testdir/"));
960 header.set_size(0);
961 header.set_cksum();
962 t!(ar.append(&header, &mut io::empty()));
963
964 // Extracting
965 let rdr = Cursor::new(t!(ar.into_inner()));
966 let mut ar = Archive::new(rdr);
967 t!(ar.unpack(td.path()));
968
969 // Iterating
970 let rdr = Cursor::new(ar.into_inner().into_inner());
971 let mut ar = Archive::new(rdr);
972 assert!(t!(ar.entries()).all(|fr| fr.is_ok()));
973
974 assert!(td.path().join("foo").is_file());
975 }
976
977 #[test]
insert_local_file_different_name()978 fn insert_local_file_different_name() {
979 let mut ar = Builder::new(Vec::new());
980 let td = t!(TempDir::new("tar-rs"));
981 let path = td.path().join("directory");
982 t!(fs::create_dir(&path));
983 ar.append_path_with_name(&path, "archive/dir").unwrap();
984 let path = td.path().join("file");
985 t!(t!(File::create(&path)).write_all(b"test"));
986 ar.append_path_with_name(&path, "archive/dir/f").unwrap();
987
988 let rd = Cursor::new(t!(ar.into_inner()));
989 let mut ar = Archive::new(rd);
990 let mut entries = t!(ar.entries());
991 let entry = t!(entries.next().unwrap());
992 assert_eq!(t!(entry.path()), Path::new("archive/dir"));
993 let entry = t!(entries.next().unwrap());
994 assert_eq!(t!(entry.path()), Path::new("archive/dir/f"));
995 assert!(entries.next().is_none());
996 }
997
998 #[test]
999 #[cfg(unix)]
tar_directory_containing_symlink_to_directory()1000 fn tar_directory_containing_symlink_to_directory() {
1001 use std::os::unix::fs::symlink;
1002
1003 let td = t!(TempDir::new("tar-rs"));
1004 let dummy_src = t!(TempDir::new("dummy_src"));
1005 let dummy_dst = td.path().join("dummy_dst");
1006 let mut ar = Builder::new(Vec::new());
1007 t!(symlink(dummy_src.path().display().to_string(), &dummy_dst));
1008
1009 assert!(dummy_dst.read_link().is_ok());
1010 assert!(dummy_dst.read_link().unwrap().is_dir());
1011 ar.append_dir_all("symlinks", td.path()).unwrap();
1012 ar.finish().unwrap();
1013 }
1014