1use core::fmt;
4
5const BLOCK_SIZE: usize = 512;
6const NAME_RANGE: core::ops::Range<usize> = 0..100;
7const SIZE_RANGE: core::ops::Range<usize> = 124..136;
8const CHECKSUM_RANGE: core::ops::Range<usize> = 148..156;
9const TYPE_OFFSET: usize = 156;
10const PREFIX_RANGE: core::ops::Range<usize> = 345..500;
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13enum WriteState {
14 Ready,
15 File { remaining: u64, size: u64 },
16 Finished,
17}
18
19pub struct TarWriter {
21 state: WriteState,
22}
23
24impl TarWriter {
25 pub const fn new() -> Self {
26 Self {
27 state: WriteState::Ready,
28 }
29 }
30
31 pub fn begin_file<E>(
32 &mut self,
33 path: &[u8],
34 size: u64,
35 mode: u32,
36 mut output: impl FnMut(&[u8]) -> Result<(), E>,
37 ) -> Result<(), TarWriteError<E>> {
38 if self.state != WriteState::Ready {
39 return Err(TarWriteError::InvalidState);
40 }
41 if path.is_empty()
42 || path.len() > NAME_RANGE.len()
43 || path[0] == b'/'
44 || path.contains(&0)
45 || path
46 .split(|byte| *byte == b'/')
47 .any(|component| component == b"..")
48 {
49 return Err(TarWriteError::InvalidPath);
50 }
51
52 let mut header = [0u8; BLOCK_SIZE];
53 header[..path.len()].copy_from_slice(path);
54 write_octal(&mut header[100..108], mode as u64).map_err(cast_write_error)?;
55 write_octal(&mut header[108..116], 0).map_err(cast_write_error)?;
56 write_octal(&mut header[116..124], 0).map_err(cast_write_error)?;
57 write_octal(&mut header[SIZE_RANGE], size).map_err(cast_write_error)?;
58 write_octal(&mut header[136..148], 0).map_err(cast_write_error)?;
59 header[CHECKSUM_RANGE].fill(b' ');
60 header[TYPE_OFFSET] = b'0';
61 header[257..263].copy_from_slice(b"ustar\0");
62 header[263..265].copy_from_slice(b"00");
63 let checksum = header.iter().map(|byte| *byte as u64).sum();
64 write_checksum(&mut header[CHECKSUM_RANGE], checksum).map_err(cast_write_error)?;
65 output(&header).map_err(TarWriteError::Output)?;
66 self.state = WriteState::File {
67 remaining: size,
68 size,
69 };
70 Ok(())
71 }
72
73 pub fn write_file_data<E>(
74 &mut self,
75 bytes: &[u8],
76 mut output: impl FnMut(&[u8]) -> Result<(), E>,
77 ) -> Result<(), TarWriteError<E>> {
78 let WriteState::File { remaining, size } = self.state else {
79 return Err(TarWriteError::InvalidState);
80 };
81 if bytes.len() as u64 > remaining {
82 return Err(TarWriteError::TooMuchData);
83 }
84 output(bytes).map_err(TarWriteError::Output)?;
85 self.state = WriteState::File {
86 remaining: remaining - bytes.len() as u64,
87 size,
88 };
89 Ok(())
90 }
91
92 pub fn end_file<E>(
93 &mut self,
94 mut output: impl FnMut(&[u8]) -> Result<(), E>,
95 ) -> Result<(), TarWriteError<E>> {
96 let WriteState::File { remaining, size } = self.state else {
97 return Err(TarWriteError::InvalidState);
98 };
99 if remaining != 0 {
100 return Err(TarWriteError::SizeMismatch { remaining });
101 }
102 let size_remainder = (size % BLOCK_SIZE as u64) as usize;
103 let padding = (BLOCK_SIZE - size_remainder) % BLOCK_SIZE;
104 if padding != 0 {
105 output(&[0; BLOCK_SIZE][..padding]).map_err(TarWriteError::Output)?;
106 }
107 self.state = WriteState::Ready;
108 Ok(())
109 }
110
111 pub fn finish<E>(
112 &mut self,
113 mut output: impl FnMut(&[u8]) -> Result<(), E>,
114 ) -> Result<(), TarWriteError<E>> {
115 if self.state != WriteState::Ready {
116 return Err(TarWriteError::InvalidState);
117 }
118 output(&[0; BLOCK_SIZE * 2]).map_err(TarWriteError::Output)?;
119 self.state = WriteState::Finished;
120 Ok(())
121 }
122}
123
124impl Default for TarWriter {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130#[derive(Clone, Copy, Debug, Eq, PartialEq)]
131pub enum TarWriteError<E> {
132 InvalidState,
133 InvalidPath,
134 ValueTooLarge,
135 TooMuchData,
136 SizeMismatch { remaining: u64 },
137 Output(E),
138}
139
140impl<E: fmt::Display> fmt::Display for TarWriteError<E> {
141 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
142 match self {
143 Self::InvalidState => formatter.write_str("invalid tar writer state"),
144 Self::InvalidPath => formatter.write_str("invalid tar member path"),
145 Self::ValueTooLarge => formatter.write_str("tar header value is too large"),
146 Self::TooMuchData => formatter.write_str("tar member received too much data"),
147 Self::SizeMismatch { remaining } => {
148 write!(formatter, "tar member is missing {remaining} bytes")
149 }
150 Self::Output(error) => write!(formatter, "tar writer output failed: {error}"),
151 }
152 }
153}
154
155fn write_octal(
156 field: &mut [u8],
157 mut value: u64,
158) -> Result<(), TarWriteError<core::convert::Infallible>> {
159 field.fill(b'0');
160 let digits = field
161 .len()
162 .checked_sub(1)
163 .ok_or(TarWriteError::ValueTooLarge)?;
164 field[digits] = 0;
165 for index in (0..digits).rev() {
166 field[index] = b'0' + (value & 7) as u8;
167 value >>= 3;
168 }
169 if value != 0 {
170 return Err(TarWriteError::ValueTooLarge);
171 }
172 Ok(())
173}
174
175fn write_checksum(
176 field: &mut [u8],
177 mut value: u64,
178) -> Result<(), TarWriteError<core::convert::Infallible>> {
179 field.fill(b'0');
180 field[6] = 0;
181 field[7] = b' ';
182 for index in (0..6).rev() {
183 field[index] = b'0' + (value & 7) as u8;
184 value >>= 3;
185 }
186 if value != 0 {
187 return Err(TarWriteError::ValueTooLarge);
188 }
189 Ok(())
190}
191
192fn cast_write_error<E>(error: TarWriteError<core::convert::Infallible>) -> TarWriteError<E> {
193 match error {
194 TarWriteError::InvalidState => TarWriteError::InvalidState,
195 TarWriteError::InvalidPath => TarWriteError::InvalidPath,
196 TarWriteError::ValueTooLarge => TarWriteError::ValueTooLarge,
197 TarWriteError::TooMuchData => TarWriteError::TooMuchData,
198 TarWriteError::SizeMismatch { remaining } => TarWriteError::SizeMismatch { remaining },
199 TarWriteError::Output(never) => match never {},
200 }
201}
202
203#[derive(Clone, Copy, Debug, Eq, PartialEq)]
205pub enum ExtractError<E> {
206 InvalidChecksum,
208 InvalidSize,
210 InvalidEndMarker,
212 Output(E),
214}
215
216impl<E: fmt::Display> fmt::Display for ExtractError<E> {
217 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
218 match self {
219 Self::InvalidChecksum => formatter.write_str("invalid tar header checksum"),
220 Self::InvalidSize => formatter.write_str("invalid tar entry size"),
221 Self::InvalidEndMarker => formatter.write_str("invalid tar end marker"),
222 Self::Output(error) => write!(formatter, "tar output failed: {error}"),
223 }
224 }
225}
226
227#[derive(Clone, Copy, Debug, Eq, PartialEq)]
229pub enum FinishError {
230 UnexpectedEof,
232 NotFound,
234}
235
236impl fmt::Display for FinishError {
237 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
238 match self {
239 Self::UnexpectedEof => formatter.write_str("unexpected end of tar stream"),
240 Self::NotFound => formatter.write_str("tar entry not found"),
241 }
242 }
243}
244
245pub struct EntryExtractor<'target> {
255 target: &'target [u8],
256 header: [u8; BLOCK_SIZE],
257 header_len: usize,
258 entry_remaining: u64,
259 padding_remaining: usize,
260 selected: bool,
261 found: bool,
262 zero_blocks: u8,
263}
264
265impl<'target> EntryExtractor<'target> {
266 pub const fn new(target: &'target [u8]) -> Self {
268 Self {
269 target,
270 header: [0; BLOCK_SIZE],
271 header_len: 0,
272 entry_remaining: 0,
273 padding_remaining: 0,
274 selected: false,
275 found: false,
276 zero_blocks: 0,
277 }
278 }
279
280 pub fn push<E>(
286 &mut self,
287 mut input: &[u8],
288 mut output: impl FnMut(&[u8]) -> Result<(), E>,
289 ) -> Result<(), ExtractError<E>> {
290 while !input.is_empty() {
291 if self.zero_blocks == 2 {
292 return Ok(());
293 }
294
295 if self.entry_remaining != 0 {
296 let length = input.len().min(u64_to_usize(self.entry_remaining));
297 let bytes = &input[..length];
298 if self.selected {
299 output(bytes).map_err(ExtractError::Output)?;
300 }
301 self.entry_remaining -= length as u64;
302 input = &input[length..];
303 continue;
304 }
305
306 if self.padding_remaining != 0 {
307 let length = input.len().min(self.padding_remaining);
308 self.padding_remaining -= length;
309 input = &input[length..];
310 continue;
311 }
312
313 let length = input.len().min(BLOCK_SIZE - self.header_len);
314 self.header[self.header_len..self.header_len + length]
315 .copy_from_slice(&input[..length]);
316 self.header_len += length;
317 input = &input[length..];
318
319 if self.header_len == BLOCK_SIZE {
320 self.consume_header()?;
321 self.header_len = 0;
322 }
323 }
324
325 Ok(())
326 }
327
328 pub const fn found(&self) -> bool {
330 self.found
331 }
332
333 pub fn finish(&self) -> Result<(), FinishError> {
335 if self.zero_blocks != 2
336 || self.header_len != 0
337 || self.entry_remaining != 0
338 || self.padding_remaining != 0
339 {
340 return Err(FinishError::UnexpectedEof);
341 }
342 if !self.found {
343 return Err(FinishError::NotFound);
344 }
345 Ok(())
346 }
347
348 fn consume_header<E>(&mut self) -> Result<(), ExtractError<E>> {
349 if self.header.iter().all(|byte| *byte == 0) {
350 self.zero_blocks += 1;
351 return Ok(());
352 }
353 if self.zero_blocks != 0 {
354 return Err(ExtractError::InvalidEndMarker);
355 }
356
357 verify_checksum(&self.header)?;
358 let size = parse_number(&self.header[SIZE_RANGE]).ok_or(ExtractError::InvalidSize)?;
359 let is_regular = matches!(self.header[TYPE_OFFSET], 0 | b'0');
360 self.selected = !self.found && is_regular && path_matches(&self.header, self.target);
361 self.found |= self.selected;
362 self.entry_remaining = size;
363 self.padding_remaining = padding_for(size);
364 Ok(())
365 }
366}
367
368fn verify_checksum<E>(header: &[u8; BLOCK_SIZE]) -> Result<(), ExtractError<E>> {
369 let expected = parse_octal(&header[CHECKSUM_RANGE]).ok_or(ExtractError::InvalidChecksum)?;
370 let actual = header
371 .iter()
372 .enumerate()
373 .map(|(index, byte)| {
374 if CHECKSUM_RANGE.contains(&index) {
375 u64::from(b' ')
376 } else {
377 u64::from(*byte)
378 }
379 })
380 .sum::<u64>();
381 if actual == expected {
382 Ok(())
383 } else {
384 Err(ExtractError::InvalidChecksum)
385 }
386}
387
388fn parse_number(field: &[u8]) -> Option<u64> {
389 if field.first().copied()? & 0x80 == 0 {
390 return parse_octal(field);
391 }
392
393 let mut value = u64::from(field[0] & 0x7f);
394 for byte in &field[1..] {
395 value = value.checked_mul(256)?.checked_add(u64::from(*byte))?;
396 }
397 Some(value)
398}
399
400fn parse_octal(field: &[u8]) -> Option<u64> {
401 let mut value = 0u64;
402 let mut saw_digit = false;
403 let mut saw_terminator = false;
404
405 for byte in field {
406 match *byte {
407 b'0'..=b'7' if !saw_terminator => {
408 saw_digit = true;
409 value = value.checked_mul(8)?.checked_add(u64::from(*byte - b'0'))?;
410 }
411 b' ' if !saw_digit => {}
412 0 | b' ' if saw_digit => saw_terminator = true,
413 _ => return None,
414 }
415 }
416
417 saw_digit.then_some(value)
418}
419
420fn path_matches(header: &[u8; BLOCK_SIZE], target: &[u8]) -> bool {
421 let name = nul_terminated(&header[NAME_RANGE]);
422 let prefix = nul_terminated(&header[PREFIX_RANGE]);
423 if prefix.is_empty() {
424 return target == name;
425 }
426
427 target.len() == prefix.len() + 1 + name.len()
428 && target.starts_with(prefix)
429 && target[prefix.len()] == b'/'
430 && &target[prefix.len() + 1..] == name
431}
432
433fn nul_terminated(field: &[u8]) -> &[u8] {
434 let length = field
435 .iter()
436 .position(|byte| *byte == 0)
437 .unwrap_or(field.len());
438 &field[..length]
439}
440
441fn padding_for(size: u64) -> usize {
442 ((BLOCK_SIZE as u64 - size % BLOCK_SIZE as u64) % BLOCK_SIZE as u64) as usize
443}
444
445fn u64_to_usize(value: u64) -> usize {
446 usize::try_from(value).unwrap_or(usize::MAX)
447}
448
449pub struct ArchiveBuffers<'a> {
451 pub path: &'a mut [u8],
453 pub link: &'a mut [u8],
455 pub pax: &'a mut [u8],
457}
458
459#[derive(Clone, Copy, Debug, Eq, PartialEq)]
461pub enum EntryKind {
462 Regular,
463 HardLink,
464 SymbolicLink,
465 CharacterDevice,
466 BlockDevice,
467 Directory,
468 Fifo,
469 Contiguous,
470 Other(u8),
471}
472
473#[derive(Clone, Copy)]
475pub struct Entry<'a> {
476 pub path: &'a [u8],
477 pub kind: EntryKind,
478 pub size: u64,
479 pub mode: u64,
480 pub uid: u64,
481 pub gid: u64,
482 pub mtime: u64,
483 pub link_target: Option<&'a [u8]>,
484 pub device_major: Option<u64>,
485 pub device_minor: Option<u64>,
486 pax: &'a [u8],
487}
488
489impl<'a> Entry<'a> {
490 pub fn pax_records(&self) -> PaxRecords<'a> {
492 PaxRecords::new(self.pax)
493 }
494}
495
496#[derive(Clone, Copy, Debug, Eq, PartialEq)]
498pub struct PaxRecord<'a> {
499 pub key: &'a [u8],
500 pub value: &'a [u8],
501}
502
503pub struct PaxRecords<'a> {
505 bytes: &'a [u8],
506 position: usize,
507 failed: bool,
508}
509
510impl<'a> PaxRecords<'a> {
511 const fn new(bytes: &'a [u8]) -> Self {
512 Self {
513 bytes,
514 position: 0,
515 failed: false,
516 }
517 }
518}
519
520impl<'a> Iterator for PaxRecords<'a> {
521 type Item = Result<PaxRecord<'a>, PaxError>;
522
523 fn next(&mut self) -> Option<Self::Item> {
524 if self.failed || self.position == self.bytes.len() {
525 return None;
526 }
527 match parse_pax_record(self.bytes, self.position) {
528 Ok((record, next)) => {
529 self.position = next;
530 Some(Ok(record))
531 }
532 Err(error) => {
533 self.failed = true;
534 Some(Err(error))
535 }
536 }
537 }
538}
539
540pub trait LayerEventSink {
542 type Error;
543
544 fn begin_entry(&mut self, entry: Entry<'_>) -> Result<(), Self::Error>;
545 fn entry_data(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
546 fn end_entry(&mut self) -> Result<(), Self::Error>;
547 fn whiteout(&mut self, path: &[u8]) -> Result<(), Self::Error>;
548 fn opaque_directory(&mut self, path: &[u8]) -> Result<(), Self::Error>;
549}
550
551pub trait TransactionalLayerSink: LayerEventSink {
553 fn begin_layer(&mut self) -> Result<(), Self::Error>;
554 fn commit_layer(&mut self) -> Result<(), Self::Error>;
555 fn abort_layer(&mut self);
556}
557
558#[derive(Clone, Copy, Debug, Eq, PartialEq)]
559enum ArchiveEntry {
560 None,
561 Normal,
562 LongPath,
563 LongLink,
564 Pax,
565}
566
567pub struct Archive<'a> {
569 header: [u8; BLOCK_SIZE],
570 header_len: usize,
571 entry_remaining: u64,
572 padding_remaining: usize,
573 current: ArchiveEntry,
574 entry_open: bool,
575 zero_blocks: u8,
576 path: &'a mut [u8],
577 path_len: usize,
578 link: &'a mut [u8],
579 link_len: usize,
580 pax: &'a mut [u8],
581 pax_len: usize,
582 pending_long_path: bool,
583 pending_long_link: bool,
584 pending_pax: bool,
585}
586
587impl<'a> Archive<'a> {
588 pub fn new(buffers: ArchiveBuffers<'a>) -> Self {
589 Self {
590 header: [0; BLOCK_SIZE],
591 header_len: 0,
592 entry_remaining: 0,
593 padding_remaining: 0,
594 current: ArchiveEntry::None,
595 entry_open: false,
596 zero_blocks: 0,
597 path: buffers.path,
598 path_len: 0,
599 link: buffers.link,
600 link_len: 0,
601 pax: buffers.pax,
602 pax_len: 0,
603 pending_long_path: false,
604 pending_long_link: false,
605 pending_pax: false,
606 }
607 }
608
609 pub fn push<S: LayerEventSink>(
610 &mut self,
611 mut input: &[u8],
612 sink: &mut S,
613 ) -> Result<(), ArchiveError<S::Error>> {
614 while !input.is_empty() {
615 if self.zero_blocks == 2 {
616 return Ok(());
617 }
618 if self.entry_remaining != 0 {
619 let length = input.len().min(u64_to_usize(self.entry_remaining));
620 let bytes = &input[..length];
621 match self.current {
622 ArchiveEntry::Normal => sink.entry_data(bytes).map_err(ArchiveError::Sink)?,
623 ArchiveEntry::LongPath => {
624 append_scratch(self.path, &mut self.path_len, bytes, Scratch::Path)?
625 }
626 ArchiveEntry::LongLink => {
627 append_scratch(self.link, &mut self.link_len, bytes, Scratch::Link)?
628 }
629 ArchiveEntry::Pax => {
630 append_scratch(self.pax, &mut self.pax_len, bytes, Scratch::Pax)?
631 }
632 ArchiveEntry::None => return Err(ArchiveError::InvalidState),
633 }
634 self.entry_remaining -= length as u64;
635 input = &input[length..];
636 if self.entry_remaining == 0 {
637 self.finish_content(sink)?;
638 }
639 continue;
640 }
641 if self.padding_remaining != 0 {
642 let length = input.len().min(self.padding_remaining);
643 self.padding_remaining -= length;
644 input = &input[length..];
645 if self.padding_remaining == 0 {
646 self.current = ArchiveEntry::None;
647 }
648 continue;
649 }
650
651 let length = input.len().min(BLOCK_SIZE - self.header_len);
652 self.header[self.header_len..self.header_len + length]
653 .copy_from_slice(&input[..length]);
654 self.header_len += length;
655 input = &input[length..];
656 if self.header_len == BLOCK_SIZE {
657 self.consume_header(sink)?;
658 self.header_len = 0;
659 }
660 }
661 Ok(())
662 }
663
664 pub fn finish(&self) -> Result<(), ArchiveFinishError> {
665 if self.zero_blocks != 2
666 || self.header_len != 0
667 || self.entry_remaining != 0
668 || self.padding_remaining != 0
669 || self.entry_open
670 || self.current != ArchiveEntry::None
671 {
672 return Err(ArchiveFinishError::UnexpectedEof);
673 }
674 if self.pending_long_path || self.pending_long_link || self.pending_pax {
675 return Err(ArchiveFinishError::DanglingExtension);
676 }
677 Ok(())
678 }
679
680 fn consume_header<S: LayerEventSink>(
681 &mut self,
682 sink: &mut S,
683 ) -> Result<(), ArchiveError<S::Error>> {
684 if self.header.iter().all(|byte| *byte == 0) {
685 if self.current != ArchiveEntry::None {
686 return Err(ArchiveError::InvalidState);
687 }
688 self.zero_blocks += 1;
689 return Ok(());
690 }
691 if self.zero_blocks != 0 {
692 return Err(ArchiveError::InvalidEndMarker);
693 }
694 verify_checksum::<()>(&self.header).map_err(|_| ArchiveError::InvalidChecksum)?;
695
696 let mut size = parse_number(&self.header[SIZE_RANGE]).ok_or(ArchiveError::InvalidSize)?;
697 let type_flag = self.header[TYPE_OFFSET];
698 match type_flag {
699 b'L' | b'K' | b'x' => {
700 self.current = match type_flag {
701 b'L' => {
702 self.path_len = 0;
703 ArchiveEntry::LongPath
704 }
705 b'K' => {
706 self.link_len = 0;
707 ArchiveEntry::LongLink
708 }
709 _ => {
710 self.pax_len = 0;
711 ArchiveEntry::Pax
712 }
713 };
714 self.entry_remaining = size;
715 self.padding_remaining = padding_for(size);
716 if size == 0 {
717 self.finish_content(sink)?;
718 }
719 return Ok(());
720 }
721 b'g' => return Err(ArchiveError::UnsupportedGlobalPax),
722 _ => {}
723 }
724
725 if self.pending_pax {
726 validate_pax(&self.pax[..self.pax_len])?;
727 if let Some(value) = pax_value(&self.pax[..self.pax_len], b"size")? {
728 size = decimal(value).ok_or(ArchiveError::InvalidSize)?;
729 }
730 }
731
732 if self.pending_pax {
733 if let Some(value) = pax_value(&self.pax[..self.pax_len], b"path")? {
734 self.path_len = copy_scratch(self.path, value, Scratch::Path)?;
735 } else if !self.pending_long_path {
736 self.path_len = header_path(&self.header, self.path)?;
737 }
738 } else if !self.pending_long_path {
739 self.path_len = header_path(&self.header, self.path)?;
740 }
741 trim_nul(self.path, &mut self.path_len);
742 self.path_len = normalize_path(self.path, self.path_len)?;
743
744 if self.pending_pax {
745 if let Some(value) = pax_value(&self.pax[..self.pax_len], b"linkpath")? {
746 self.link_len = copy_scratch(self.link, value, Scratch::Link)?;
747 } else if !self.pending_long_link {
748 self.link_len = copy_scratch(
749 self.link,
750 nul_terminated(&self.header[157..257]),
751 Scratch::Link,
752 )?;
753 }
754 } else if !self.pending_long_link {
755 self.link_len = copy_scratch(
756 self.link,
757 nul_terminated(&self.header[157..257]),
758 Scratch::Link,
759 )?;
760 }
761 trim_nul(self.link, &mut self.link_len);
762
763 let kind = entry_kind(type_flag);
764 match kind {
765 EntryKind::HardLink if self.link_len != 0 => {
766 self.link_len = normalize_path(self.link, self.link_len)?;
767 }
768 EntryKind::SymbolicLink if self.link_len != 0 => {
769 validate_symbolic_link(&self.path[..self.path_len], &self.link[..self.link_len])?;
770 }
771 _ => {}
772 }
773
774 self.pending_long_path = false;
775 self.pending_long_link = false;
776 self.current = ArchiveEntry::Normal;
777 self.entry_remaining = size;
778 self.padding_remaining = padding_for(size);
779
780 if let Some(whiteout) = whiteout(self.path, &mut self.path_len) {
781 if kind != EntryKind::Regular || size != 0 {
782 return Err(ArchiveError::InvalidWhiteout);
783 }
784 match whiteout {
785 Whiteout::Remove => sink
786 .whiteout(&self.path[..self.path_len])
787 .map_err(ArchiveError::Sink)?,
788 Whiteout::Opaque => sink
789 .opaque_directory(&self.path[..self.path_len])
790 .map_err(ArchiveError::Sink)?,
791 }
792 self.pending_pax = false;
793 self.pax_len = 0;
794 self.current = ArchiveEntry::None;
795 return Ok(());
796 }
797
798 let mode = parse_number(&self.header[100..108]).ok_or(ArchiveError::InvalidMetadata)?;
799 let uid = parse_number(&self.header[108..116]).ok_or(ArchiveError::InvalidMetadata)?;
800 let gid = parse_number(&self.header[116..124]).ok_or(ArchiveError::InvalidMetadata)?;
801 let mtime = parse_number(&self.header[136..148]).ok_or(ArchiveError::InvalidMetadata)?;
802 let has_device = matches!(kind, EntryKind::CharacterDevice | EntryKind::BlockDevice);
803 let device_major = has_device
804 .then(|| parse_number(&self.header[329..337]))
805 .flatten();
806 let device_minor = has_device
807 .then(|| parse_number(&self.header[337..345]))
808 .flatten();
809 if has_device && (device_major.is_none() || device_minor.is_none()) {
810 return Err(ArchiveError::InvalidMetadata);
811 }
812 let link_target = matches!(kind, EntryKind::HardLink | EntryKind::SymbolicLink)
813 .then_some(&self.link[..self.link_len]);
814 sink.begin_entry(Entry {
815 path: &self.path[..self.path_len],
816 kind,
817 size,
818 mode,
819 uid,
820 gid,
821 mtime,
822 link_target,
823 device_major,
824 device_minor,
825 pax: if self.pending_pax {
826 &self.pax[..self.pax_len]
827 } else {
828 &[]
829 },
830 })
831 .map_err(ArchiveError::Sink)?;
832 self.entry_open = true;
833 self.pending_pax = false;
834 self.pax_len = 0;
835 if size == 0 {
836 self.finish_content(sink)?;
837 }
838 Ok(())
839 }
840
841 fn finish_content<S: LayerEventSink>(
842 &mut self,
843 sink: &mut S,
844 ) -> Result<(), ArchiveError<S::Error>> {
845 match self.current {
846 ArchiveEntry::Normal => {
847 if self.entry_open {
848 sink.end_entry().map_err(ArchiveError::Sink)?;
849 self.entry_open = false;
850 }
851 }
852 ArchiveEntry::LongPath => {
853 trim_nul(self.path, &mut self.path_len);
854 self.pending_long_path = true;
855 }
856 ArchiveEntry::LongLink => {
857 trim_nul(self.link, &mut self.link_len);
858 self.pending_long_link = true;
859 }
860 ArchiveEntry::Pax => {
861 validate_pax(&self.pax[..self.pax_len])?;
862 self.pending_pax = true;
863 }
864 ArchiveEntry::None => return Err(ArchiveError::InvalidState),
865 }
866 if self.padding_remaining == 0 {
867 self.current = ArchiveEntry::None;
868 }
869 Ok(())
870 }
871}
872
873#[derive(Clone, Copy, Debug, Eq, PartialEq)]
874pub enum Scratch {
875 Path,
876 Link,
877 Pax,
878}
879
880#[derive(Debug, Eq, PartialEq)]
881pub enum ArchiveError<E> {
882 InvalidChecksum,
883 InvalidSize,
884 InvalidMetadata,
885 InvalidEndMarker,
886 InvalidPath,
887 InvalidLink,
888 InvalidWhiteout,
889 InvalidPax(PaxError),
890 UnsupportedGlobalPax,
891 BufferTooSmall(Scratch),
892 InvalidState,
893 Sink(E),
894}
895
896impl<E: fmt::Display> fmt::Display for ArchiveError<E> {
897 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
898 match self {
899 Self::InvalidChecksum => formatter.write_str("invalid tar header checksum"),
900 Self::InvalidSize => formatter.write_str("invalid tar entry size"),
901 Self::InvalidMetadata => formatter.write_str("invalid tar entry metadata"),
902 Self::InvalidEndMarker => formatter.write_str("invalid tar end marker"),
903 Self::InvalidPath => formatter.write_str("unsafe or invalid tar path"),
904 Self::InvalidLink => formatter.write_str("unsafe tar link target"),
905 Self::InvalidWhiteout => formatter.write_str("invalid OCI whiteout entry"),
906 Self::InvalidPax(error) => write!(formatter, "invalid PAX header: {error}"),
907 Self::UnsupportedGlobalPax => {
908 formatter.write_str("global PAX headers are not supported")
909 }
910 Self::BufferTooSmall(buffer) => write!(formatter, "{buffer:?} buffer is too small"),
911 Self::InvalidState => formatter.write_str("invalid tar parser state"),
912 Self::Sink(error) => write!(formatter, "layer sink failed: {error}"),
913 }
914 }
915}
916
917#[derive(Clone, Copy, Debug, Eq, PartialEq)]
918pub enum ArchiveFinishError {
919 UnexpectedEof,
920 DanglingExtension,
921}
922
923impl fmt::Display for ArchiveFinishError {
924 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
925 formatter.write_str(match self {
926 Self::UnexpectedEof => "unexpected end of tar archive",
927 Self::DanglingExtension => "tar archive ended after an extension header",
928 })
929 }
930}
931
932#[derive(Clone, Copy, Debug, Eq, PartialEq)]
933pub enum PaxError {
934 InvalidLength,
935 InvalidRecord,
936}
937
938impl fmt::Display for PaxError {
939 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
940 formatter.write_str(match self {
941 Self::InvalidLength => "invalid record length",
942 Self::InvalidRecord => "invalid key/value record",
943 })
944 }
945}
946
947impl<E> From<PaxError> for ArchiveError<E> {
948 fn from(error: PaxError) -> Self {
949 Self::InvalidPax(error)
950 }
951}
952
953#[derive(Clone, Copy)]
954enum Whiteout {
955 Remove,
956 Opaque,
957}
958
959fn entry_kind(type_flag: u8) -> EntryKind {
960 match type_flag {
961 0 | b'0' => EntryKind::Regular,
962 b'1' => EntryKind::HardLink,
963 b'2' => EntryKind::SymbolicLink,
964 b'3' => EntryKind::CharacterDevice,
965 b'4' => EntryKind::BlockDevice,
966 b'5' => EntryKind::Directory,
967 b'6' => EntryKind::Fifo,
968 b'7' => EntryKind::Contiguous,
969 other => EntryKind::Other(other),
970 }
971}
972
973fn header_path<E>(
974 header: &[u8; BLOCK_SIZE],
975 destination: &mut [u8],
976) -> Result<usize, ArchiveError<E>> {
977 let name = nul_terminated(&header[NAME_RANGE]);
978 let prefix = nul_terminated(&header[PREFIX_RANGE]);
979 let needed = name.len() + usize::from(!prefix.is_empty()) + prefix.len();
980 if needed > destination.len() {
981 return Err(ArchiveError::BufferTooSmall(Scratch::Path));
982 }
983 let mut length = 0;
984 if !prefix.is_empty() {
985 destination[..prefix.len()].copy_from_slice(prefix);
986 length += prefix.len();
987 destination[length] = b'/';
988 length += 1;
989 }
990 destination[length..length + name.len()].copy_from_slice(name);
991 Ok(length + name.len())
992}
993
994fn append_scratch<E>(
995 destination: &mut [u8],
996 length: &mut usize,
997 bytes: &[u8],
998 kind: Scratch,
999) -> Result<(), ArchiveError<E>> {
1000 let end = length
1001 .checked_add(bytes.len())
1002 .ok_or(ArchiveError::BufferTooSmall(kind))?;
1003 let output = destination
1004 .get_mut(*length..end)
1005 .ok_or(ArchiveError::BufferTooSmall(kind))?;
1006 output.copy_from_slice(bytes);
1007 *length = end;
1008 Ok(())
1009}
1010
1011fn copy_scratch<E>(
1012 destination: &mut [u8],
1013 bytes: &[u8],
1014 kind: Scratch,
1015) -> Result<usize, ArchiveError<E>> {
1016 if bytes.len() > destination.len() {
1017 return Err(ArchiveError::BufferTooSmall(kind));
1018 }
1019 destination[..bytes.len()].copy_from_slice(bytes);
1020 Ok(bytes.len())
1021}
1022
1023fn trim_nul(bytes: &[u8], length: &mut usize) {
1024 while *length != 0 && bytes[*length - 1] == 0 {
1025 *length -= 1;
1026 }
1027}
1028
1029fn normalize_path<E>(bytes: &mut [u8], length: usize) -> Result<usize, ArchiveError<E>> {
1030 if length == 0 || bytes[0] == b'/' || bytes[..length].contains(&0) {
1031 return Err(ArchiveError::InvalidPath);
1032 }
1033 let mut read = 0;
1034 let mut write = 0;
1035 while read < length {
1036 while read < length && bytes[read] == b'/' {
1037 read += 1;
1038 }
1039 let start = read;
1040 while read < length && bytes[read] != b'/' {
1041 read += 1;
1042 }
1043 let component_len = read - start;
1044 if component_len == 0 || &bytes[start..read] == b"." {
1045 continue;
1046 }
1047 if &bytes[start..read] == b".." {
1048 return Err(ArchiveError::InvalidPath);
1049 }
1050 if write != 0 {
1051 bytes[write] = b'/';
1052 write += 1;
1053 }
1054 bytes.copy_within(start..read, write);
1055 write += component_len;
1056 }
1057 if write == 0 {
1058 bytes[0] = b'.';
1059 write = 1;
1060 }
1061 Ok(write)
1062}
1063
1064fn validate_symbolic_link<E>(path: &[u8], target: &[u8]) -> Result<(), ArchiveError<E>> {
1065 if target.is_empty() || target[0] == b'/' || target.contains(&0) {
1066 return Err(ArchiveError::InvalidLink);
1067 }
1068 let mut depth = path.split(|byte| *byte == b'/').count().saturating_sub(1);
1069 for component in target.split(|byte| *byte == b'/') {
1070 match component {
1071 b"" | b"." => {}
1072 b".." => {
1073 depth = depth.checked_sub(1).ok_or(ArchiveError::InvalidLink)?;
1074 }
1075 _ => depth += 1,
1076 }
1077 }
1078 Ok(())
1079}
1080
1081fn whiteout(path: &mut [u8], length: &mut usize) -> Option<Whiteout> {
1082 let basename = path[..*length]
1083 .iter()
1084 .rposition(|byte| *byte == b'/')
1085 .map_or(0, |position| position + 1);
1086 let name = &path[basename..*length];
1087 if name == b".wh..wh..opq" {
1088 if basename == 0 {
1089 path[0] = b'.';
1090 *length = 1;
1091 } else {
1092 *length = basename - 1;
1093 }
1094 return Some(Whiteout::Opaque);
1095 }
1096 let target = name.strip_prefix(b".wh.")?;
1097 if target.is_empty() {
1098 return None;
1099 }
1100 path.copy_within(basename + 4..*length, basename);
1101 *length -= 4;
1102 Some(Whiteout::Remove)
1103}
1104
1105fn validate_pax<E>(bytes: &[u8]) -> Result<(), ArchiveError<E>> {
1106 for record in PaxRecords::new(bytes) {
1107 record?;
1108 }
1109 Ok(())
1110}
1111
1112fn pax_value<'a, E>(bytes: &'a [u8], key: &[u8]) -> Result<Option<&'a [u8]>, ArchiveError<E>> {
1113 let mut found = None;
1114 for record in PaxRecords::new(bytes) {
1115 let record = record?;
1116 if record.key == key {
1117 if found.is_some() {
1118 return Err(ArchiveError::InvalidPax(PaxError::InvalidRecord));
1119 }
1120 found = Some(record.value);
1121 }
1122 }
1123 Ok(found)
1124}
1125
1126fn parse_pax_record(bytes: &[u8], position: usize) -> Result<(PaxRecord<'_>, usize), PaxError> {
1127 let space = bytes[position..]
1128 .iter()
1129 .position(|byte| *byte == b' ')
1130 .map(|offset| position + offset)
1131 .ok_or(PaxError::InvalidLength)?;
1132 let length = decimal(&bytes[position..space]).ok_or(PaxError::InvalidLength)?;
1133 let length = usize::try_from(length).map_err(|_| PaxError::InvalidLength)?;
1134 let end = position
1135 .checked_add(length)
1136 .ok_or(PaxError::InvalidLength)?;
1137 let record = bytes.get(space + 1..end).ok_or(PaxError::InvalidLength)?;
1138 let record = record.strip_suffix(b"\n").ok_or(PaxError::InvalidRecord)?;
1139 let equals = record
1140 .iter()
1141 .position(|byte| *byte == b'=')
1142 .ok_or(PaxError::InvalidRecord)?;
1143 if equals == 0 {
1144 return Err(PaxError::InvalidRecord);
1145 }
1146 Ok((
1147 PaxRecord {
1148 key: &record[..equals],
1149 value: &record[equals + 1..],
1150 },
1151 end,
1152 ))
1153}
1154
1155fn decimal(bytes: &[u8]) -> Option<u64> {
1156 if bytes.is_empty() {
1157 return None;
1158 }
1159 bytes.iter().try_fold(0u64, |value, byte| {
1160 value.checked_mul(10)?.checked_add(u64::from(
1161 byte.checked_sub(b'0').filter(|digit| *digit <= 9)?,
1162 ))
1163 })
1164}
1165
1166#[cfg(test)]
1167mod tests {
1168 use super::{
1169 header_path, normalize_path, validate_pax, validate_symbolic_link, Archive, ArchiveBuffers,
1170 ArchiveError, Entry, EntryExtractor, EntryKind, ExtractError, FinishError, LayerEventSink,
1171 TarWriteError, TarWriter, BLOCK_SIZE,
1172 };
1173 use std::{format, string::ToString, vec::Vec};
1174
1175 type OwnedPaxRecord = (Vec<u8>, Vec<u8>);
1176 type OwnedEntry = (Vec<u8>, EntryKind, Vec<OwnedPaxRecord>);
1177
1178 #[derive(Default)]
1179 struct Events {
1180 entries: Vec<OwnedEntry>,
1181 contents: Vec<u8>,
1182 whiteouts: Vec<Vec<u8>>,
1183 opaque: Vec<Vec<u8>>,
1184 }
1185
1186 #[test]
1187 fn writes_deterministic_ustar_files() {
1188 let mut bytes = Vec::new();
1189 let mut writer = TarWriter::new();
1190 writer
1191 .begin_file(b"blobs/sha256/abc", 5, 0o644, |chunk| {
1192 bytes.extend_from_slice(chunk);
1193 Ok::<_, ()>(())
1194 })
1195 .unwrap();
1196 writer
1197 .write_file_data(b"hello", |chunk| {
1198 bytes.extend_from_slice(chunk);
1199 Ok::<_, ()>(())
1200 })
1201 .unwrap();
1202 writer
1203 .end_file(|chunk| {
1204 bytes.extend_from_slice(chunk);
1205 Ok::<_, ()>(())
1206 })
1207 .unwrap();
1208 writer
1209 .finish(|chunk| {
1210 bytes.extend_from_slice(chunk);
1211 Ok::<_, ()>(())
1212 })
1213 .unwrap();
1214
1215 assert_eq!(&bytes[..18], b"blobs/sha256/abc\0\0");
1216 assert_eq!(&bytes[BLOCK_SIZE..BLOCK_SIZE + 5], b"hello");
1217 assert_eq!(bytes.len(), BLOCK_SIZE * 4);
1218
1219 let mut invalid = TarWriter::new();
1220 assert_eq!(
1221 invalid.begin_file(b"../escape", 0, 0o644, |_| Ok::<_, ()>(())),
1222 Err(TarWriteError::InvalidPath)
1223 );
1224 }
1225
1226 impl LayerEventSink for Events {
1227 type Error = ();
1228
1229 fn begin_entry(&mut self, entry: Entry<'_>) -> Result<(), Self::Error> {
1230 let pax = entry
1231 .pax_records()
1232 .map(|record| {
1233 let record = record.unwrap();
1234 (record.key.to_vec(), record.value.to_vec())
1235 })
1236 .collect();
1237 self.entries.push((entry.path.to_vec(), entry.kind, pax));
1238 Ok(())
1239 }
1240
1241 fn entry_data(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
1242 self.contents.extend_from_slice(bytes);
1243 Ok(())
1244 }
1245
1246 fn end_entry(&mut self) -> Result<(), Self::Error> {
1247 Ok(())
1248 }
1249
1250 fn whiteout(&mut self, path: &[u8]) -> Result<(), Self::Error> {
1251 self.whiteouts.push(path.to_vec());
1252 Ok(())
1253 }
1254
1255 fn opaque_directory(&mut self, path: &[u8]) -> Result<(), Self::Error> {
1256 self.opaque.push(path.to_vec());
1257 Ok(())
1258 }
1259 }
1260
1261 #[test]
1262 fn streams_layer_events_and_whiteouts_at_every_fragment_size() {
1263 let mut bytes = Vec::new();
1264 append_entry(&mut bytes, b"./etc//config", b"value", b'0', b"");
1265 append_entry(&mut bytes, b"etc/.wh.deleted", b"", b'0', b"");
1266 append_entry(&mut bytes, b"var/lib/.wh..wh..opq", b"", b'0', b"");
1267 finish_archive(&mut bytes);
1268
1269 for fragment in 1..=BLOCK_SIZE + 1 {
1270 let mut path = [0; 256];
1271 let mut link = [0; 256];
1272 let mut pax = [0; 512];
1273 let mut archive = Archive::new(ArchiveBuffers {
1274 path: &mut path,
1275 link: &mut link,
1276 pax: &mut pax,
1277 });
1278 let mut events = Events::default();
1279 for chunk in bytes.chunks(fragment) {
1280 archive.push(chunk, &mut events).unwrap();
1281 }
1282 archive.finish().unwrap();
1283 assert_eq!(events.entries[0].0, b"etc/config");
1284 assert_eq!(events.contents, b"value");
1285 assert_eq!(events.whiteouts, [b"etc/deleted".to_vec()]);
1286 assert_eq!(events.opaque, [b"var/lib".to_vec()]);
1287 }
1288 }
1289
1290 #[test]
1291 fn applies_pax_and_gnu_long_path_overrides_without_allocating() {
1292 let mut bytes = Vec::new();
1293 let record = pax_record("path", "pax/overridden/file");
1294 append_entry(&mut bytes, b"PaxHeaders.0", &record, b'x', b"");
1295 append_entry(&mut bytes, b"ignored", b"pax", b'0', b"");
1296
1297 let long = [b'a'; 140];
1298 let mut long_nul = long.to_vec();
1299 long_nul.push(0);
1300 append_entry(&mut bytes, b"LongLink", &long_nul, b'L', b"");
1301 append_entry(&mut bytes, b"ignored", b"gnu", b'0', b"");
1302 finish_archive(&mut bytes);
1303
1304 let mut path = [0; 256];
1305 let mut link = [0; 256];
1306 let mut pax = [0; 512];
1307 let mut archive = Archive::new(ArchiveBuffers {
1308 path: &mut path,
1309 link: &mut link,
1310 pax: &mut pax,
1311 });
1312 let mut events = Events::default();
1313 archive.push(&bytes, &mut events).unwrap();
1314 archive.finish().unwrap();
1315 assert_eq!(events.entries[0].0, b"pax/overridden/file");
1316 assert_eq!(events.entries[0].2[0].0, b"path");
1317 assert_eq!(events.entries[1].0, long);
1318 assert_eq!(events.contents, b"paxgnu");
1319 }
1320
1321 #[test]
1322 fn rejects_paths_that_escape_the_root() {
1323 let mut bytes = Vec::new();
1324 append_entry(&mut bytes, b"../escape", b"", b'0', b"");
1325 let mut path = [0; 64];
1326 let mut link = [0; 64];
1327 let mut pax = [0; 64];
1328 let mut archive = Archive::new(ArchiveBuffers {
1329 path: &mut path,
1330 link: &mut link,
1331 pax: &mut pax,
1332 });
1333 assert!(archive.push(&bytes, &mut Events::default()).is_err());
1334 }
1335
1336 #[test]
1337 fn rejects_absolute_symbolic_link_targets() {
1338 let mut bytes = Vec::new();
1339 append_link(&mut bytes, b"link", b"/etc/passwd", b'2');
1340 let mut path = [0; 64];
1341 let mut link = [0; 64];
1342 let mut pax = [0; 64];
1343 let mut archive = Archive::new(ArchiveBuffers {
1344 path: &mut path,
1345 link: &mut link,
1346 pax: &mut pax,
1347 });
1348 assert_eq!(
1349 archive.push(&bytes, &mut Events::default()),
1350 Err(ArchiveError::InvalidLink)
1351 );
1352 }
1353
1354 #[test]
1355 fn rejects_symbolic_link_targets_that_escape_the_root() {
1356 let mut bytes = Vec::new();
1357 append_link(&mut bytes, b"dir/link", b"../../escape", b'2');
1358 let mut path = [0; 64];
1359 let mut link = [0; 64];
1360 let mut pax = [0; 64];
1361 let mut archive = Archive::new(ArchiveBuffers {
1362 path: &mut path,
1363 link: &mut link,
1364 pax: &mut pax,
1365 });
1366 assert_eq!(
1367 archive.push(&bytes, &mut Events::default()),
1368 Err(ArchiveError::InvalidLink)
1369 );
1370 }
1371
1372 #[test]
1373 fn resolves_symbolic_link_target_components_before_checking_depth() {
1374 assert_eq!(
1375 validate_symbolic_link::<()>(b"link", b"dir/../target"),
1376 Ok(())
1377 );
1378 assert_eq!(
1379 validate_symbolic_link::<()>(b"dir/link", b"./../../escape"),
1380 Err(ArchiveError::InvalidLink)
1381 );
1382 assert_eq!(
1383 validate_symbolic_link::<()>(b"dir/link", b"nested//../../../escape"),
1384 Err(ArchiveError::InvalidLink)
1385 );
1386 }
1387
1388 #[test]
1389 fn joins_ustar_name_and_prefix_with_exact_buffer_bounds() {
1390 let mut header = [0; BLOCK_SIZE];
1391 header[..4].copy_from_slice(b"name");
1392 header[345..351].copy_from_slice(b"prefix");
1393
1394 let mut exact = [0; 11];
1395 assert_eq!(header_path::<()>(&header, &mut exact), Ok(11));
1396 assert_eq!(&exact, b"prefix/name");
1397
1398 let mut too_small = [0; 10];
1399 assert!(header_path::<()>(&header, &mut too_small).is_err());
1400
1401 header[345..351].fill(0);
1402 let mut name_only = [0; 4];
1403 assert_eq!(header_path::<()>(&header, &mut name_only), Ok(4));
1404 assert_eq!(&name_only, b"name");
1405 }
1406
1407 #[test]
1408 fn normalizes_paths_and_rejects_invalid_inputs() {
1409 let mut path = *b"./a//b/./";
1410 let length = normalize_path::<()>(&mut path, 9).unwrap();
1411 assert_eq!(&path[..length], b"a/b");
1412
1413 let mut dot = *b".";
1414 let length = normalize_path::<()>(&mut dot, 1).unwrap();
1415 assert_eq!(&dot[..length], b".");
1416
1417 for invalid in [b"".as_slice(), b"/root", b"a\0b", b"../root"] {
1418 let mut path = [0; 16];
1419 path[..invalid.len()].copy_from_slice(invalid);
1420 assert_eq!(
1421 normalize_path::<()>(&mut path, invalid.len()),
1422 Err(ArchiveError::InvalidPath)
1423 );
1424 }
1425 }
1426
1427 #[test]
1428 fn rejects_malformed_pax_records() {
1429 assert!(validate_pax::<()>(b"garbage").is_err());
1430 }
1431
1432 #[test]
1433 fn extracts_a_regular_file_from_arbitrary_fragments() {
1434 let mut archive = Vec::new();
1435 append_entry(&mut archive, b"first", b"skip", b'0', b"");
1436 append_entry(&mut archive, b"wanted", b"contents", b'0', b"");
1437 append_entry(&mut archive, b"wanted", b"later", b'0', b"");
1438 finish_archive(&mut archive);
1439
1440 for fragment_size in 1..=BLOCK_SIZE + 1 {
1441 let mut extractor = EntryExtractor::new(b"wanted");
1442 let mut output = Vec::new();
1443 for fragment in archive.chunks(fragment_size) {
1444 extractor
1445 .push(fragment, |bytes| {
1446 output.extend_from_slice(bytes);
1447 Ok::<_, ()>(())
1448 })
1449 .unwrap();
1450 }
1451 extractor.finish().unwrap();
1452 assert_eq!(output, b"contents");
1453 }
1454 }
1455
1456 #[test]
1457 fn matches_a_ustar_prefix_without_allocating_a_path() {
1458 let mut archive = Vec::new();
1459 append_entry(
1460 &mut archive,
1461 b"settings.yaml",
1462 b"value",
1463 b'0',
1464 b"etc/datadog-agent",
1465 );
1466 finish_archive(&mut archive);
1467 let mut extractor = EntryExtractor::new(b"etc/datadog-agent/settings.yaml");
1468 let mut output = Vec::new();
1469 extractor
1470 .push(&archive, |bytes| {
1471 output.extend_from_slice(bytes);
1472 Ok::<_, ()>(())
1473 })
1474 .unwrap();
1475 extractor.finish().unwrap();
1476 assert_eq!(output, b"value");
1477 }
1478
1479 #[test]
1480 fn recognizes_an_empty_regular_file() {
1481 let mut archive = Vec::new();
1482 append_entry(&mut archive, b"empty", b"", b'0', b"");
1483 finish_archive(&mut archive);
1484 let mut extractor = EntryExtractor::new(b"empty");
1485 extractor.push(&archive, |_| Ok::<_, ()>(())).unwrap();
1486 assert!(extractor.found());
1487 extractor.finish().unwrap();
1488 }
1489
1490 #[test]
1491 fn does_not_extract_a_directory_with_the_requested_name() {
1492 let mut archive = Vec::new();
1493 append_entry(&mut archive, b"wanted", b"", b'5', b"");
1494 finish_archive(&mut archive);
1495 let mut extractor = EntryExtractor::new(b"wanted");
1496 extractor.push(&archive, |_| Ok::<_, ()>(())).unwrap();
1497 assert_eq!(extractor.finish(), Err(FinishError::NotFound));
1498 }
1499
1500 #[test]
1501 fn rejects_an_invalid_checksum() {
1502 let mut archive = Vec::new();
1503 append_entry(&mut archive, b"wanted", b"contents", b'0', b"");
1504 archive[0] ^= 1;
1505 let mut extractor = EntryExtractor::new(b"wanted");
1506 assert_eq!(
1507 extractor.push(&archive, |_| Ok::<_, ()>(())),
1508 Err(ExtractError::InvalidChecksum)
1509 );
1510 }
1511
1512 #[test]
1513 fn reports_a_truncated_stream() {
1514 let mut archive = Vec::new();
1515 append_entry(&mut archive, b"wanted", b"contents", b'0', b"");
1516 let mut extractor = EntryExtractor::new(b"wanted");
1517 extractor
1518 .push(&archive[..BLOCK_SIZE + 2], |_| Ok::<_, ()>(()))
1519 .unwrap();
1520 assert_eq!(extractor.finish(), Err(FinishError::UnexpectedEof));
1521 }
1522
1523 fn append_entry(archive: &mut Vec<u8>, name: &[u8], contents: &[u8], kind: u8, prefix: &[u8]) {
1524 let mut header = [0u8; BLOCK_SIZE];
1525 header[..name.len()].copy_from_slice(name);
1526 write_octal(&mut header[100..108], 0o644);
1527 write_octal(&mut header[108..116], 0);
1528 write_octal(&mut header[116..124], 0);
1529 write_octal(&mut header[124..136], contents.len() as u64);
1530 write_octal(&mut header[136..148], 0);
1531 header[148..156].fill(b' ');
1532 header[156] = kind;
1533 header[257..263].copy_from_slice(b"ustar\0");
1534 header[263..265].copy_from_slice(b"00");
1535 header[345..345 + prefix.len()].copy_from_slice(prefix);
1536 let checksum = header.iter().map(|byte| u64::from(*byte)).sum();
1537 write_octal(&mut header[148..156], checksum);
1538
1539 archive.extend_from_slice(&header);
1540 archive.extend_from_slice(contents);
1541 archive.resize(archive.len() + super::padding_for(contents.len() as u64), 0);
1542 }
1543
1544 fn append_link(archive: &mut Vec<u8>, name: &[u8], target: &[u8], kind: u8) {
1545 let start = archive.len();
1546 append_entry(archive, name, b"", kind, b"");
1547 let header = &mut archive[start..start + BLOCK_SIZE];
1548 header[157..157 + target.len()].copy_from_slice(target);
1549 header[148..156].fill(b' ');
1550 let checksum = header.iter().map(|byte| u64::from(*byte)).sum();
1551 write_octal(&mut header[148..156], checksum);
1552 }
1553
1554 fn finish_archive(archive: &mut Vec<u8>) {
1555 archive.resize(archive.len() + 2 * BLOCK_SIZE, 0);
1556 }
1557
1558 fn pax_record(key: &str, value: &str) -> Vec<u8> {
1559 let payload = key.len() + 1 + value.len() + 1;
1560 let mut digits = 1;
1561 loop {
1562 let length = digits + 1 + payload;
1563 let actual_digits = length.to_string().len();
1564 if actual_digits == digits {
1565 return format!("{length} {key}={value}\n").into_bytes();
1566 }
1567 digits = actual_digits;
1568 }
1569 }
1570
1571 fn write_octal(field: &mut [u8], mut value: u64) {
1572 field.fill(b'0');
1573 *field.last_mut().unwrap() = 0;
1574 let digits = field.len() - 1;
1575 for byte in field[..digits].iter_mut().rev() {
1576 *byte = b'0' + (value % 8) as u8;
1577 value /= 8;
1578 }
1579 assert_eq!(value, 0);
1580 }
1581}