1pub mod tar;
89
90use std::fs;
91use std::os::unix::fs::MetadataExt;
92use std::path::Path;
93use std::io::{Write, Read};
94use flate2::read::GzDecoder;
95use flate2::write::GzEncoder;
96use flate2::Compression;
97use std::io::{self, BufRead};
98
99#[cfg(unix)]
100use std::ffi::CStr;
101
102pub use tar::{read_tar, write_tar, Tar, TarEntry, TarHeader};
103
104#[cfg(unix)]
109fn get_username_from_uid(uid: u32) -> Option<String> {
111 unsafe {
112 let passwd = libc::getpwuid(uid);
113 if passwd.is_null() {
114 return None;
115 }
116 let name_ptr = (*passwd).pw_name;
117 if name_ptr.is_null() {
118 return None;
119 }
120 CStr::from_ptr(name_ptr)
121 .to_str()
122 .ok()
123 .map(|s| s.to_string())
124 }
125}
126
127#[cfg(unix)]
128fn get_groupname_from_gid(gid: u32) -> Option<String> {
130 unsafe {
131 let group = libc::getgrgid(gid);
132 if group.is_null() {
133 return None;
134 }
135 let name_ptr = (*group).gr_name;
136 if name_ptr.is_null() {
137 return None;
138 }
139 CStr::from_ptr(name_ptr)
140 .to_str()
141 .ok()
142 .map(|s| s.to_string())
143 }
144}
145
146#[cfg(not(unix))]
147fn get_username_from_uid(_uid: u32) -> Option<String> {
149 None
150}
151
152#[cfg(not(unix))]
153fn get_groupname_from_gid(_gid: u32) -> Option<String> {
155 None
156}
157
158fn is_gzipped(filename: &str) -> bool {
163 filename.ends_with(".tar.gz") || filename.ends_with(".tgz")
164}
165
166fn ungzip(filename: &str, data: Vec<u8>) -> Result<Vec<u8>, std::io::Error> {
169 if is_gzipped(filename) {
170 let mut decoder = GzDecoder::new(&data[..]);
171 let mut decompressed = Vec::new();
172 decoder.read_to_end(&mut decompressed)?;
173 Ok(decompressed)
174 } else {
175 Ok(data)
176 }
177}
178
179fn gzip(filename: &str, data: Vec<u8>) -> Result<Vec<u8>, std::io::Error> {
182 if is_gzipped(filename) {
183 let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
184 encoder.write_all(&data)?;
185 encoder.finish()
186 } else {
187 Ok(data)
188 }
189}
190
191fn add_file_to_entries(file_path: &Path, base_path: &Path, entries: &mut Vec<TarEntry>) {
196 let data = match fs::read(file_path) {
197 Ok(d) => d,
198 Err(e) => {
199 eprintln!("Error reading {}: {}", file_path.display(), e);
200 return;
201 }
202 };
203
204 let relative_path = file_path.strip_prefix(base_path)
206 .unwrap_or(file_path)
207 .to_string_lossy()
208 .to_string();
209
210 let mut header = TarHeader::new(
211 relative_path,
212 0o644,
213 data.len() as u64
214 );
215 match fs::metadata(file_path) {
217 Ok(m) => {
218 header.mode = m.mode() as u32;
219 header.mtime = m.modified().unwrap_or(std::time::SystemTime::UNIX_EPOCH)
220 .duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs();
221 header.gid = m.gid();
222 header.uid = m.uid();
223 if let Some(uname) = get_username_from_uid(m.uid()) {
225 header.uname = uname;
226 }
227 if let Some(gname) = get_groupname_from_gid(m.gid()) {
228 header.gname = gname;
229 }
230 },
231 Err(e) => {
232 eprintln!("Error getting metadata for {}: {}", file_path.display(), e);
233 return;
234 }
235 }; let header_bytes = header.to_bytes();
236
237 entries.push(TarEntry {
238 header,
239 data,
240 header_bytes,
241 });
242}
243
244fn collect_files_from_dir(dir_path: &Path, base_path: &Path, entries: &mut Vec<TarEntry>) {
246 let read_dir = match fs::read_dir(dir_path) {
247 Ok(d) => d,
248 Err(e) => {
249 eprintln!("Error reading directory {}: {}", dir_path.display(), e);
250 return;
251 }
252 };
253
254 for entry_result in read_dir {
255 let entry = match entry_result {
256 Ok(e) => e,
257 Err(e) => {
258 eprintln!("Error reading directory entry: {}", e);
259 continue;
260 }
261 };
262
263 let path = entry.path();
264
265 if path.is_dir() {
266 collect_files_from_dir(&path, base_path, entries);
268 } else if path.is_file() {
269 add_file_to_entries(&path, base_path, entries);
271 }
272 }
273}
274
275pub fn pack(tarfile: &str, files: &[&str]) {
280 let mut entries = Vec::new();
281
282 for file_path in files {
283 let path = Path::new(file_path);
284 if !path.exists() {
285 eprintln!("Warning: File not found: {}", file_path);
286 continue;
287 }
288
289 if path.is_dir() {
291 collect_files_from_dir(path, path, &mut entries);
293 } else {
294 let base = path.parent().unwrap_or_else(|| Path::new(""));
296 add_file_to_entries(path, base, &mut entries);
297 }
298 }
299
300 let tar_data = write_tar(&entries);
301
302 let result = gzip(tarfile, tar_data)
304 .and_then(|data| fs::write(tarfile, data));
305
306 match result {
307 Ok(_) => println!("Created tar archive: {}", tarfile),
308 Err(e) => {
309 eprintln!("Error writing tar file: {}", e);
310 std::process::exit(1);
311 }
312 }
313}
314
315pub fn unpack(tarfile: &str, output_dir: &str) {
317 unpack_with_options(tarfile, output_dir, false, true);
318}
319
320pub fn unpack_with_options(tarfile: &str, output_dir: &str, overwrite: bool, use_prompt: bool) {
329 let mut overwrite = overwrite;
330 let file_data = match fs::read(tarfile) {
332 Ok(d) => d,
333 Err(e) => {
334 eprintln!("Error reading tar file: {}", e);
335 std::process::exit(1);
336 }
337 };
338
339 let tar_data = match ungzip(tarfile, file_data) {
341 Ok(data) => data,
342 Err(e) => {
343 eprintln!("Error decompressing gzip: {}", e);
344 std::process::exit(1);
345 }
346 };
347
348 let entries = read_tar(&tar_data);
349
350 let output_path = Path::new(output_dir);
351 if !output_path.exists() {
352 if let Err(e) = fs::create_dir_all(output_path) {
353 eprintln!("Error creating output directory: {}", e);
354 std::process::exit(1);
355 }
356 }
357
358 for entry in entries {
359 let file_path = output_path.join(&entry.header.name);
360 let mut flag_overwrite = false;
361 if file_path.exists() {
363 if !overwrite {
364 if use_prompt {
365 println!("❓File '{}' already exists. Overwrite? ([Y]es/[N]o/[A]ll): ", entry.header.name);
367 let stdin = io::stdin();
368 let mut line = String::new();
369 stdin.lock().read_line(&mut line).unwrap_or(0);
370 let answer = line.trim().to_lowercase();
371
372 if answer == "a" || answer == "all" {
373 println!("⚡ Overwriting all files...");
375 overwrite = true;
376 } else if answer == "y" || answer == "yes" {
377 } else {
378 println!("- Skipping: {}", entry.header.name);
379 continue;
380 }
381 } else {
382 println!("- Skipping: {}", entry.header.name);
383 continue;
384 }
385 }
386 flag_overwrite = true;
387 }
388
389 if let Some(parent) = file_path.parent() {
391 if !parent.exists() {
392 if let Err(e) = fs::create_dir_all(parent) {
393 eprintln!("❌ Error creating directory {}: {}", parent.display(), e);
394 continue;
395 }
396 }
397 }
398
399 match fs::File::create(&file_path) {
400 Ok(mut file) => {
401 if let Err(e) = file.write_all(&entry.data) {
402 eprintln!("❌ Error writing {}: {}", entry.header.name, e);
403 } else {
404 let overwrite_msg = if flag_overwrite { " (overwritten)" } else { "" };
405 println!("- Extracted: {}{}", entry.header.name, overwrite_msg);
406 }
407 }
408 Err(e) => {
409 eprintln!("❌ Error creating {}: {}", entry.header.name, e);
410 }
411 }
412 }
413
414 println!("Extraction complete to: {}", output_dir);
415}
416
417pub fn list(tarfile: &str) -> Result<Vec<TarHeader>, std::io::Error> {
419 let file_data = fs::read(tarfile)?;
420
421 let tar_data = ungzip(tarfile, file_data)?;
423
424 let entries = read_tar(&tar_data);
425 let headers: Vec<TarHeader> = entries.into_iter().map(|e| e.header).collect();
426 Ok(headers)
427}
428
429pub fn list_entry(tarfile: &str) -> Result<Vec<TarEntry>, std::io::Error> {
431 let file_data = fs::read(tarfile)?;
432
433 let is_gzipped = tarfile.ends_with(".tar.gz") || tarfile.ends_with(".tgz");
435
436 let tar_data = if is_gzipped {
437 let mut decoder = GzDecoder::new(&file_data[..]);
439 let mut decompressed = Vec::new();
440 decoder.read_to_end(&mut decompressed)?;
441 decompressed
442 } else {
443 file_data
444 };
445
446 let entries = read_tar(&tar_data);
447 Ok(entries)
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453 use std::fs;
454 use std::path::Path;
455
456 #[test]
457 fn test_pack() {
458 let test_file1 = "test_file1.txt";
460 let test_file2 = "test_file2.txt";
461 let test_tar = "test_pack.tar";
462
463 fs::write(test_file1, "Hello, World!").unwrap();
464 fs::write(test_file2, "Test content 2").unwrap();
465
466 let files = vec![test_file1, test_file2];
468 pack(test_tar, &files);
469
470 assert!(Path::new(test_tar).exists());
472
473 let tar_data = fs::read(test_tar).unwrap();
475 let entries = read_tar(&tar_data);
476 assert_eq!(entries.len(), 2);
477 assert_eq!(entries[0].header.name, test_file1);
478 assert_eq!(entries[1].header.name, test_file2);
479
480 fs::remove_file(test_file1).unwrap();
482 fs::remove_file(test_file2).unwrap();
483 fs::remove_file(test_tar).unwrap();
484 }
485
486 #[test]
487 fn test_unpack() {
488 let test_file = "test_unpack_file.txt";
490 let test_content = "Unpack test content";
491 let test_tar = "test_unpack.tar";
492 let output_dir = "test_unpack_output";
493
494 fs::write(test_file, test_content).unwrap();
495
496 let files = vec![test_file];
498 pack(test_tar, &files);
499
500 unpack_with_options(test_tar, output_dir, false, false);
502
503 let extracted_file = Path::new(output_dir).join(test_file);
505 assert!(extracted_file.exists());
506
507 let content = fs::read_to_string(&extracted_file).unwrap();
509 assert_eq!(content, test_content);
510
511 fs::remove_file(test_file).unwrap();
513 fs::remove_file(test_tar).unwrap();
514 fs::remove_dir_all(output_dir).unwrap();
515 }
516
517 #[test]
518 fn test_list() {
519 let test_file1 = "test_list_file1.txt";
521 let test_file2 = "test_list_file2.txt";
522 let test_tar = "test_list.tar";
523
524 fs::write(test_file1, "Content 1").unwrap();
525 fs::write(test_file2, "Content 2 longer").unwrap();
526
527 let files = vec![test_file1, test_file2];
529 pack(test_tar, &files);
530
531 let headers = list(test_tar).unwrap();
533
534 assert_eq!(headers.len(), 2);
536 assert_eq!(headers[0].name, test_file1);
537 assert_eq!(headers[0].size, 9);
538 assert_eq!(headers[1].name, test_file2);
539 assert_eq!(headers[1].size, 16);
540
541 let tar_data = fs::read(test_tar).unwrap();
543 let entries = read_tar(&tar_data);
544 assert_eq!(entries.len(), 2);
545 assert_eq!(entries[0].header.name, test_file1);
546 assert_eq!(entries[0].header.size, 9);
547 assert_eq!(entries[1].header.name, test_file2);
548 assert_eq!(entries[1].header.size, 16);
549
550 fs::remove_file(test_file1).unwrap();
552 fs::remove_file(test_file2).unwrap();
553 fs::remove_file(test_tar).unwrap();
554 }
555
556 #[test]
557 fn test_tar_gz() {
558 let test_file1 = "test_gz_file1.txt";
560 let test_file2 = "test_gz_file2.txt";
561 let test_tar_gz = "test_pack.tar.gz";
562 let output_dir = "test_gz_output";
563
564 fs::write(test_file1, "GZ test content 1").unwrap();
565 fs::write(test_file2, "GZ test content 2 longer").unwrap();
566
567 let files = vec![test_file1, test_file2];
569 pack(test_tar_gz, &files);
570
571 assert!(Path::new(test_tar_gz).exists());
573
574 let headers = list(test_tar_gz).unwrap();
576 assert_eq!(headers.len(), 2);
577 assert_eq!(headers[0].name, test_file1);
578 assert_eq!(headers[0].size, 17);
579 assert_eq!(headers[1].name, test_file2);
580 assert_eq!(headers[1].size, 24);
581
582 unpack_with_options(test_tar_gz, output_dir, false, false);
584
585 let extracted_file1 = Path::new(output_dir).join(test_file1);
587 let extracted_file2 = Path::new(output_dir).join(test_file2);
588 assert!(extracted_file1.exists());
589 assert!(extracted_file2.exists());
590
591 let content1 = fs::read_to_string(&extracted_file1).unwrap();
593 let content2 = fs::read_to_string(&extracted_file2).unwrap();
594 assert_eq!(content1, "GZ test content 1");
595 assert_eq!(content2, "GZ test content 2 longer");
596
597 fs::remove_file(test_file1).unwrap();
599 fs::remove_file(test_file2).unwrap();
600 fs::remove_file(test_tar_gz).unwrap();
601 fs::remove_dir_all(output_dir).unwrap();
602 }
603
604 #[test]
605 fn test_pack_directory() {
606 let test_dir = "test_pack_dir";
608 let test_tar = "test_pack_dir.tar";
609
610 fs::create_dir_all(format!("{}/subdir", test_dir)).unwrap();
611 fs::write(format!("{}/file1.txt", test_dir), "File 1 content").unwrap();
612 fs::write(format!("{}/file2.txt", test_dir), "File 2 content").unwrap();
613 fs::write(format!("{}/subdir/file3.txt", test_dir), "File 3 in subdir").unwrap();
614
615 let files = vec![test_dir];
617 pack(test_tar, &files);
618
619 assert!(Path::new(test_tar).exists());
621
622 let tar_data = fs::read(test_tar).unwrap();
624 let entries = read_tar(&tar_data);
625 assert_eq!(entries.len(), 3);
626
627 let names: Vec<String> = entries.iter().map(|e| e.header.name.clone()).collect();
629 assert!(names.contains(&"file1.txt".to_string()));
630 assert!(names.contains(&"file2.txt".to_string()));
631 assert!(names.contains(&"subdir/file3.txt".to_string()));
632
633 fs::remove_dir_all(test_dir).unwrap();
635 fs::remove_file(test_tar).unwrap();
636 }
637
638 #[test]
639 fn test_pack_and_unpack_directory() {
640 let test_dir = "test_dir_full";
642 let test_tar = "test_dir_full.tar";
643 let output_dir = "test_dir_full_output";
644
645 fs::create_dir_all(format!("{}/a/b/c", test_dir)).unwrap();
646 fs::write(format!("{}/root.txt", test_dir), "Root file").unwrap();
647 fs::write(format!("{}/a/file_a.txt", test_dir), "File in a").unwrap();
648 fs::write(format!("{}/a/b/file_b.txt", test_dir), "File in b").unwrap();
649 fs::write(format!("{}/a/b/c/file_c.txt", test_dir), "File in c").unwrap();
650
651 let files = vec![test_dir];
653 pack(test_tar, &files);
654
655 unpack_with_options(test_tar, output_dir, false, false);
657
658 assert!(Path::new(output_dir).join("root.txt").exists());
660 assert!(Path::new(output_dir).join("a/file_a.txt").exists());
661 assert!(Path::new(output_dir).join("a/b/file_b.txt").exists());
662 assert!(Path::new(output_dir).join("a/b/c/file_c.txt").exists());
663
664 let content = fs::read_to_string(Path::new(output_dir).join("a/b/c/file_c.txt")).unwrap();
666 assert_eq!(content, "File in c");
667
668 fs::remove_dir_all(test_dir).unwrap();
670 fs::remove_file(test_tar).unwrap();
671 fs::remove_dir_all(output_dir).unwrap();
672 }
673
674 #[test]
675 fn test_pack_mixed_files_and_directories() {
676 let test_file = "test_mixed_file.txt";
678 let test_dir = "test_mixed_dir";
679 let test_tar = "test_mixed.tar";
680
681 fs::write(test_file, "Single file").unwrap();
682 fs::create_dir_all(format!("{}/subdir", test_dir)).unwrap();
683 fs::write(format!("{}/dir_file.txt", test_dir), "File in dir").unwrap();
684 fs::write(format!("{}/subdir/sub_file.txt", test_dir), "File in subdir").unwrap();
685
686 let files = vec![test_file, test_dir];
688 pack(test_tar, &files);
689
690 let tar_data = fs::read(test_tar).unwrap();
692 let entries = read_tar(&tar_data);
693 assert_eq!(entries.len(), 3);
694
695 let names: Vec<String> = entries.iter().map(|e| e.header.name.clone()).collect();
697 assert!(names.contains(&test_file.to_string()));
698 assert!(names.contains(&"dir_file.txt".to_string()));
699 assert!(names.contains(&"subdir/sub_file.txt".to_string()));
700
701 fs::remove_file(test_file).unwrap();
703 fs::remove_dir_all(test_dir).unwrap();
704 fs::remove_file(test_tar).unwrap();
705 }
706
707 #[test]
708 fn test_pack_directory_gzipped() {
709 let test_dir = "test_pack_dir_gz";
711 let test_tar_gz = "test_pack_dir.tar.gz";
712 let output_dir = "test_pack_dir_gz_output";
713
714 fs::create_dir_all(format!("{}/nested/deep", test_dir)).unwrap();
715 fs::write(format!("{}/file1.txt", test_dir), "First file").unwrap();
716 fs::write(format!("{}/nested/file2.txt", test_dir), "Second file").unwrap();
717 fs::write(format!("{}/nested/deep/file3.txt", test_dir), "Third file").unwrap();
718
719 let files = vec![test_dir];
721 pack(test_tar_gz, &files);
722
723 assert!(Path::new(test_tar_gz).exists());
725
726 let headers = list(test_tar_gz).unwrap();
728 assert_eq!(headers.len(), 3);
729
730 unpack_with_options(test_tar_gz, output_dir, false, false);
732 assert!(Path::new(output_dir).join("file1.txt").exists());
733 assert!(Path::new(output_dir).join("nested/file2.txt").exists());
734 assert!(Path::new(output_dir).join("nested/deep/file3.txt").exists());
735
736 let content = fs::read_to_string(Path::new(output_dir).join("nested/deep/file3.txt")).unwrap();
738 assert_eq!(content, "Third file");
739
740 fs::remove_dir_all(test_dir).unwrap();
742 fs::remove_file(test_tar_gz).unwrap();
743 fs::remove_dir_all(output_dir).unwrap();
744 }
745
746 #[test]
747 fn security_test_unpack_path_traversal() {
748 use crate::tar::{TarEntry, TarHeader};
752
753 let test_tar = "test_security_traversal.tar";
754 let output_dir = "test_security_output";
755
756 let mut entries = Vec::new();
758
759 let malicious_paths = vec![
761 "../outside.txt",
762 "../../etc/outside2.txt",
763 "subdir/../../../outside3.txt",
764 ];
765
766 for malicious_path in malicious_paths {
767 let header = TarHeader::new(malicious_path.to_string(), 0o644, 9);
768 let data = b"malicious".to_vec();
769 let header_bytes = header.to_bytes();
770 entries.push(TarEntry { header, data, header_bytes });
771 }
772
773 let tar_data = write_tar(&entries);
774 fs::write(test_tar, tar_data).unwrap();
775
776 unpack_with_options(test_tar, output_dir, false, false);
779
780 fs::remove_file(test_tar).unwrap();
782 if Path::new(output_dir).exists() {
783 fs::remove_dir_all(output_dir).ok();
784 }
785 fs::remove_file("outside.txt").ok();
787 fs::remove_file("../outside.txt").ok();
788 fs::remove_file("outside2.txt").ok();
789 fs::remove_file("outside3.txt").ok();
790 }
791
792 #[test]
793 fn security_test_unpack_absolute_path() {
794 use crate::tar::{TarEntry, TarHeader};
798
799 let test_tar = "test_security_absolute.tar";
800 let output_dir = "test_security_abs_output";
801
802 let header = TarHeader::new("/tmp/absolute_file.txt".to_string(), 0o644, 8);
804 let data = b"absolute".to_vec();
805 let header_bytes = header.to_bytes();
806 let entry = TarEntry { header, data, header_bytes };
807
808 let tar_data = write_tar(&[entry]);
809 fs::write(test_tar, tar_data).unwrap();
810
811 unpack_with_options(test_tar, output_dir, false, false);
813
814 fs::remove_file(test_tar).unwrap();
816 if Path::new(output_dir).exists() {
817 fs::remove_dir_all(output_dir).ok();
818 }
819 fs::remove_file("/tmp/absolute_file.txt").ok();
821 }
822
823 #[test]
824 fn security_test_unpack_large_file_size() {
825 use crate::tar::{TarEntry, TarHeader};
828
829 let test_tar = "test_security_large.tar";
830 let output_dir = "test_security_large_output";
831
832 let header = TarHeader::new("fake_large.txt".to_string(), 0o644, 5);
834 let data = b"small".to_vec();
835 let header_bytes = header.to_bytes();
836 let entry = TarEntry { header, data, header_bytes };
837
838 let tar_data = write_tar(&[entry]);
839 fs::write(test_tar, tar_data).unwrap();
840
841 unpack_with_options(test_tar, output_dir, false, false);
843
844 let extracted_file = Path::new(output_dir).join("fake_large.txt");
846 if extracted_file.exists() {
847 let content = fs::read(&extracted_file).unwrap();
848 assert_eq!(content.len(), 5);
849 }
850
851 fs::remove_file(test_tar).unwrap();
853 if Path::new(output_dir).exists() {
854 fs::remove_dir_all(output_dir).unwrap();
855 }
856 }
857
858 #[test]
859 fn security_test_unpack_empty_filename() {
860 use crate::tar::{TarEntry, TarHeader};
863
864 let test_tar = "test_security_empty_name.tar";
865 let output_dir = "test_security_empty_output";
866
867 let header = TarHeader::new("".to_string(), 0o644, 4);
869 let data = b"data".to_vec();
870 let header_bytes = header.to_bytes();
871 let entry = TarEntry { header, data, header_bytes };
872
873 let tar_data = write_tar(&[entry]);
874 fs::write(test_tar, tar_data).unwrap();
875
876 unpack_with_options(test_tar, output_dir, false, false);
878
879 fs::remove_file(test_tar).unwrap();
881 if Path::new(output_dir).exists() {
882 fs::remove_dir_all(output_dir).ok();
883 }
884 }
885
886 #[test]
887 fn security_test_unpack_special_characters() {
888 use crate::tar::{TarEntry, TarHeader};
891
892 let test_tar = "test_security_special.tar";
893 let output_dir = "test_security_special_output";
894
895 let special_names = vec![
897 "file\0with\0nulls.txt",
898 "file\nwith\nnewlines.txt",
899 "file;with;semicolons.txt",
900 "file|with|pipes.txt",
901 ];
902
903 let mut entries = Vec::new();
904 for name in special_names {
905 let header = TarHeader::new(name.to_string(), 0o644, 7);
906 let data = b"special".to_vec();
907 let header_bytes = header.to_bytes();
908 entries.push(TarEntry { header, data, header_bytes });
909 }
910
911 let tar_data = write_tar(&entries);
912 fs::write(test_tar, tar_data).unwrap();
913
914 unpack_with_options(test_tar, output_dir, false, false);
916
917 fs::remove_file(test_tar).unwrap();
919 if Path::new(output_dir).exists() {
920 fs::remove_dir_all(output_dir).ok();
921 }
922 }
923
924 #[test]
925 fn security_test_pack_symlink_handling() {
926 #[cfg(unix)]
930 {
931 use std::os::unix::fs::symlink;
932
933 let test_dir = "test_security_symlink_dir";
934 let test_tar = "test_security_symlink.tar";
935
936 fs::create_dir_all(test_dir).unwrap();
938 fs::write(format!("{}/target.txt", test_dir), "target content").unwrap();
939
940 let symlink_path = format!("{}/link.txt", test_dir);
942 let target_path = format!("{}/target.txt", test_dir);
943 symlink(&target_path, &symlink_path).ok(); let files = vec![test_dir];
947 pack(test_tar, &files);
948
949 assert!(Path::new(test_tar).exists());
951
952 fs::remove_file(&symlink_path).ok();
954 fs::remove_dir_all(test_dir).unwrap();
955 fs::remove_file(test_tar).unwrap();
956 }
957 }
958
959 #[test]
960 fn security_test_unpack_overwrites_existing() {
961 use crate::tar::{TarEntry, TarHeader};
965
966 let test_tar = "test_security_overwrite.tar";
967 let output_dir = "test_security_overwrite_output";
968
969 fs::create_dir_all(output_dir).unwrap();
970
971 let sensitive_file = Path::new(output_dir).join("important.txt");
973 fs::write(&sensitive_file, "SENSITIVE DATA").unwrap();
974
975 let header = TarHeader::new("important.txt".to_string(), 0o644, 9);
977 let data = b"overwrite".to_vec();
978 let header_bytes = header.to_bytes();
979 let entry = TarEntry { header, data, header_bytes };
980
981 let tar_data = write_tar(&[entry]);
982 fs::write(test_tar, tar_data).unwrap();
983
984 unpack_with_options(test_tar, output_dir, true, false);
986
987 let content = fs::read_to_string(&sensitive_file).unwrap();
989 assert_eq!(content, "overwrite");
990
991 fs::remove_file(test_tar).unwrap();
993 fs::remove_dir_all(output_dir).unwrap();
994 }
995}