1use std::{
75 future::poll_fn,
76 ops::Range,
77 pin::Pin,
78 sync::Arc,
79 task::{Context, Poll},
80};
81
82use tokio::io::{AsyncRead, ReadBuf};
83
84use crate::{
85 ArchiveFormat, BLOCK_SIZE, Block, DEFAULT_MAX_GLOBAL_PAX_EXTENSIONS_SIZE,
86 DEFAULT_MAX_GNU_EXTENSION_SIZE, DEFAULT_MAX_PAX_EXTENSION_SIZE, FrameError, FrameErrorInner,
87 GnuKind, HdrCharset, PaxError, PaxKind, PaxRecord, PaxState, PaxValue, UstarKind,
88 header::{
89 CHECKSUM_RANGE, GID_RANGE, GNAME_RANGE, GNU_IDENTITY, IDENTITY_RANGE, MODE_RANGE,
90 MTIME_RANGE, NAME_RANGE, PREFIX_RANGE, SIZE_RANGE, TYPEFLAG_OFFSET, UID_RANGE, UNAME_RANGE,
91 USTAR_IDENTITY, checksum, is_all_nul, parse_number, parse_octal,
92 },
93 pax::{GlobalPaxRecords, PaxRecords, SharedPaxRecords},
94};
95
96type PositionedBlock = (u64, Block);
97
98#[derive(Clone, Debug, Eq, PartialEq)]
100pub enum Frame {
101 Pax(PaxFrame),
103 Gnu(GnuFrame),
105 Header(HeaderFrame),
107 Data(DataFrame),
109}
110
111#[derive(Clone, Debug, Eq, PartialEq)]
113pub struct PaxFrame {
114 pub position: u64,
116 pub block: Block,
118 pub kind: PaxKind,
120 pub payload_size: u64,
122}
123
124#[derive(Clone, Debug, Eq, PartialEq)]
126pub struct GnuFrame {
127 pub position: u64,
129 pub block: Block,
131 pub kind: GnuKind,
133 pub payload_size: u64,
135}
136
137#[derive(Clone, Debug, Eq, PartialEq)]
142pub struct HeaderFrame {
143 pub position: u64,
145 pub block: Block,
147 pub format: ArchiveFormat,
149 pub kind: UstarKind,
151 pub declared_size: u64,
153 pub effective_size: u64,
159 pub(crate) mode: Option<u64>,
160 pub(crate) uid: Option<u64>,
161 pub(crate) gid: Option<u64>,
162 pub(crate) mtime: Option<u64>,
163}
164
165impl HeaderFrame {
166 fn ustar(
167 position: u64,
168 block: Block,
169 typeflag: u8,
170 declared_size: u64,
171 local_records: Option<&PaxRecords>,
172 global_records: Option<&GlobalPaxRecords>,
173 policy: &StreamPolicy,
174 ) -> Result<Self, FrameError> {
175 let kind = UstarKind::try_from_framed(position, typeflag)?;
176
177 let parse_numeric_field = |field, range: Range<usize>| {
181 Self::parse_numeric_field(
182 position,
183 ArchiveFormat::Pax,
184 field,
185 &block[range],
186 policy.allow_all_nul_numeric_fields,
187 )
188 };
189 let mode = parse_numeric_field("mode", MODE_RANGE)?;
190 let uid = parse_numeric_field("uid", UID_RANGE)?;
191 let gid = parse_numeric_field("gid", GID_RANGE)?;
192 let mtime = parse_numeric_field("mtime", MTIME_RANGE)?;
193
194 Self::validate_owner_names(position, &block, policy)?;
195
196 let effective_size = PaxState::effective_size(local_records, global_records).map_or(
202 Ok(declared_size),
203 |size| match size {
204 PaxValue::Value(size) => Ok(*size),
205 PaxValue::Deleted => Err(FrameError::deleted_pax_metadata(position, "size")),
206 },
207 )?;
208 validate_pax_member_size(position, kind, declared_size, effective_size)?;
209
210 Ok(Self {
211 position,
212 block,
213 format: ArchiveFormat::Pax,
214 kind,
215 declared_size,
216 effective_size,
217 mode,
218 uid,
219 gid,
220 mtime,
221 })
222 }
223
224 fn gnu(
225 position: u64,
226 block: Block,
227 typeflag: u8,
228 declared_size: u64,
229 require_link_kind: bool,
230 policy: &StreamPolicy,
231 ) -> Result<Self, FrameError> {
232 let kind = UstarKind::try_from_framed(position, typeflag)?;
233 if require_link_kind && !matches!(kind, UstarKind::HardLink | UstarKind::SymbolicLink) {
234 return Err(FrameError::unexpected_order(
235 position,
236 "hard-link or symbolic-link member after GNU long-link extension",
237 "non-link ordinary member",
238 ));
239 }
240 validate_gnu_member_size(position, kind, declared_size)?;
241 let parse_numeric_field = |field, range: Range<usize>| {
242 Self::parse_numeric_field(
243 position,
244 ArchiveFormat::Gnu,
245 field,
246 &block[range],
247 policy.allow_all_nul_numeric_fields,
248 )
249 };
250 let mode = parse_numeric_field("mode", MODE_RANGE)?;
251 let uid = parse_numeric_field("uid", UID_RANGE)?;
252 let gid = parse_numeric_field("gid", GID_RANGE)?;
253 let mtime = parse_numeric_field("mtime", MTIME_RANGE)?;
254 Self::validate_owner_names(position, &block, policy)?;
255
256 Ok(Self {
257 position,
258 block,
259 format: ArchiveFormat::Gnu,
260 kind,
261 declared_size,
262 effective_size: declared_size,
263 mode,
264 uid,
265 gid,
266 mtime,
267 })
268 }
269
270 fn validate_owner_names(
271 position: u64,
272 block: &Block,
273 policy: &StreamPolicy,
274 ) -> Result<(), FrameError> {
275 if policy.allow_header_uname_and_gname_without_terminator {
276 return Ok(());
277 }
278 for (field, range) in [("uname", UNAME_RANGE), ("gname", GNAME_RANGE)] {
279 if !block[range].contains(&0) {
280 return Err(FrameError::at(
281 position,
282 FrameErrorInner::UnterminatedUstarStringField { field },
283 ));
284 }
285 }
286 Ok(())
287 }
288
289 fn parse_numeric_field(
290 position: u64,
291 format: ArchiveFormat,
292 field: &'static str,
293 bytes: &[u8],
294 allow_all_nul_numeric_fields: bool,
295 ) -> Result<Option<u64>, FrameError> {
296 if allow_all_nul_numeric_fields && is_all_nul(bytes) {
297 return Ok(None);
298 }
299 parse_number(format, bytes).map(Some).ok_or_else(|| {
300 FrameError::at(
301 position,
302 FrameErrorInner::InvalidNumericField {
303 field,
304 found: bytes.to_vec(),
305 },
306 )
307 })
308 }
309
310 pub(crate) fn copy_header_path_into(&self, path: &mut Vec<u8>) {
311 path.clear();
312 let name = trim_nul(&self.block[NAME_RANGE]);
313 if self.format == ArchiveFormat::Gnu {
314 path.extend_from_slice(name);
315 return;
316 }
317 let prefix = trim_nul(&self.block[PREFIX_RANGE]);
318 if !prefix.is_empty() {
319 path.extend_from_slice(prefix);
320 path.push(b'/');
321 }
322 path.extend_from_slice(name);
323 }
324}
325
326#[derive(Clone, Copy, Debug, Eq, PartialEq)]
328pub enum DataOwner {
329 Pax(PaxKind),
331 Gnu(GnuKind),
333 Member,
335}
336
337#[derive(Clone, Debug, Eq, PartialEq)]
342pub struct DataFrame {
343 pub position: u64,
345 pub block: Block,
347 pub len: usize,
349 pub owner: DataOwner,
351 completed_pax_records: Option<SharedPaxRecords>,
356}
357
358impl DataFrame {
359 pub fn completed_pax_records(&self) -> Option<&[PaxRecord]> {
364 self.completed_pax_records
365 .as_deref()
366 .map(PaxRecords::as_slice)
367 }
368
369 pub(crate) fn into_completed_pax_records(self) -> Option<SharedPaxRecords> {
370 self.completed_pax_records
371 }
372}
373
374#[derive(Debug)]
376pub(super) enum State {
377 AwaitingHeader,
379 ReadingPax {
381 kind: PaxKind,
382 header_position: u64,
383 remaining: u64,
384 payload: Vec<u8>,
385 },
386 AwaitingUstarHeader { records: SharedPaxRecords },
388 ReadingGnu {
390 kind: GnuKind,
391 remaining: u64,
392 pending: PendingGnu,
393 },
394 AwaitingGnuMember { pending: PendingGnu },
396 ReadingMember { remaining: u64 },
398 AwaitingSecondZero,
400 Complete,
402 Failed,
404}
405
406#[derive(Clone, Copy, Debug, Default)]
407pub(super) struct PendingGnu {
408 pub(super) long_name: bool,
409 pub(super) long_link: bool,
410}
411
412#[derive(Default)]
414struct MemberChunk {
415 buffer: Vec<u8>,
416 start_position: u64,
417 physical_len: usize,
418 meaningful_len: usize,
419 state: Option<MemberChunkState>,
420}
421
422#[derive(Clone, Copy)]
423enum MemberChunkState {
424 Reading {
425 member_remaining: u64,
426 filled: usize,
427 },
428 Ready {
429 delivered: usize,
430 },
431}
432
433#[derive(Clone, Copy, Debug, Eq, PartialEq)]
435pub struct StreamPolicy {
436 max_pax_extension_size: u64,
437 max_global_pax_extensions_size: u64,
438 allow_all_nul_numeric_fields: bool,
439 allow_header_uname_and_gname_without_terminator: bool,
440 max_gnu_extension_size: u64,
441}
442
443impl Default for StreamPolicy {
444 fn default() -> Self {
445 Self {
446 max_pax_extension_size: DEFAULT_MAX_PAX_EXTENSION_SIZE,
447 max_global_pax_extensions_size: DEFAULT_MAX_GLOBAL_PAX_EXTENSIONS_SIZE,
448 allow_all_nul_numeric_fields: true,
449 allow_header_uname_and_gname_without_terminator: false,
450 max_gnu_extension_size: DEFAULT_MAX_GNU_EXTENSION_SIZE,
451 }
452 }
453}
454
455impl StreamPolicy {
456 pub fn max_pax_extension_size(mut self, max_pax_extension_size: u64) -> Self {
464 self.max_pax_extension_size = max_pax_extension_size;
465 self
466 }
467
468 pub fn max_global_pax_extensions_size(mut self, max_global_pax_extensions_size: u64) -> Self {
477 self.max_global_pax_extensions_size = max_global_pax_extensions_size;
478 self
479 }
480
481 pub fn allow_all_nul_numeric_fields(mut self, allow: bool) -> Self {
488 self.allow_all_nul_numeric_fields = allow;
489 self
490 }
491
492 pub fn allow_header_uname_and_gname_without_terminator(mut self, allow: bool) -> Self {
498 self.allow_header_uname_and_gname_without_terminator = allow;
499 self
500 }
501
502 pub fn max_gnu_extension_size(mut self, max_gnu_extension_size: u64) -> Self {
508 self.max_gnu_extension_size = max_gnu_extension_size;
509 self
510 }
511}
512
513pub struct TarStream<R> {
515 pub(super) position: u64,
517 pub(super) inner: R,
519 pub(super) block: Block,
520 pub(super) block_len: usize,
521 pub(super) format: Option<ArchiveFormat>,
522 pub(super) global_pax_records: Option<GlobalPaxRecords>,
524 policy: StreamPolicy,
525 global_pax_extensions_size: u64,
526 member_chunk: MemberChunk,
527 pub(super) state: State,
528}
529
530impl<R> TarStream<R> {
531 pub fn new(reader: R) -> Self {
533 Self {
534 position: 0,
535 inner: reader,
536 block: [0; BLOCK_SIZE],
537 block_len: 0,
538 format: None,
539 global_pax_records: None,
540 policy: StreamPolicy::default(),
541 global_pax_extensions_size: 0,
542 member_chunk: MemberChunk::default(),
543 state: State::AwaitingHeader,
544 }
545 }
546
547 pub fn with_policy(mut self, policy: StreamPolicy) -> Self {
551 self.policy = policy;
552 self
553 }
554
555 pub fn format(&self) -> Option<ArchiveFormat> {
557 self.format
558 }
559}
560
561impl<R: AsyncRead + Unpin> TarStream<R> {
562 pub async fn next_frame(&mut self) -> Result<Option<Frame>, FrameError> {
568 let result = poll_fn(|context| self.poll_next_frame(context)).await;
569 self.fail_on_error(result)
570 }
571
572 pub(crate) async fn read_member_block(&mut self) -> Result<(u64, Block, usize), FrameError> {
576 let result = self.read_member_block_inner().await;
577 self.fail_on_error(result)
578 }
579
580 async fn read_member_block_inner(&mut self) -> Result<(u64, Block, usize), FrameError> {
581 if self.member_chunk.state.is_some() {
582 self.complete_member_chunk().await?;
583 return self.take_member_block_from_chunk();
584 }
585 let remaining = match &self.state {
586 State::ReadingMember { remaining } => *remaining,
587 _ => {
588 return Err(FrameError::unexpected_order(
589 self.position,
590 "ordinary member payload",
591 "parser state without member payload",
592 ));
593 }
594 };
595 let (position, block) = match poll_fn(|context| self.poll_read_block(context)).await {
596 Ok(Some(block)) => block,
597 Ok(None) => return Err(self.handle_eof()),
598 Err(error) => return Err(error),
599 };
600 let meaningful_len = remaining.min(BLOCK_SIZE as u64) as usize;
601 self.state = member_payload_state(remaining - meaningful_len as u64);
602 Ok((position, block, meaningful_len))
603 }
604
605 pub(crate) async fn read_member_chunk(
610 &mut self,
611 buffer: &mut Vec<u8>,
612 target_len: usize,
613 ) -> Result<usize, FrameError> {
614 if self.member_chunk.state.is_none() && self.block_len != 0 {
617 let (_, block, meaningful_len) = self.read_member_block().await?;
618 buffer.clear();
619 buffer.extend_from_slice(&block[..meaningful_len]);
620 return Ok(meaningful_len);
621 }
622 if self.member_chunk.state.is_none() {
623 let result = self.start_member_chunk(buffer, target_len);
624 self.fail_on_error(result)?;
625 }
626 let result = self.complete_member_chunk().await;
627 self.fail_on_error(result)?;
628 let result = self.take_member_chunk(buffer);
629 self.fail_on_error(result)
630 }
631
632 fn fail_on_error<T>(&mut self, result: Result<T, FrameError>) -> Result<T, FrameError> {
633 if result.is_err() {
634 self.state = State::Failed;
635 }
636 result
637 }
638
639 fn start_member_chunk(
640 &mut self,
641 buffer: &mut Vec<u8>,
642 target_len: usize,
643 ) -> Result<(), FrameError> {
644 let member_remaining = match &self.state {
645 State::ReadingMember { remaining } => *remaining,
646 _ => {
647 return Err(FrameError::unexpected_order(
648 self.position,
649 "ordinary member payload",
650 "parser state without member payload",
651 ));
652 }
653 };
654 if self.block_len != 0 {
655 return Err(FrameError::unexpected_order(
656 self.position,
657 "aligned ordinary member payload",
658 "partially buffered physical block",
659 ));
660 }
661
662 let target_len = u64::try_from(target_len.max(BLOCK_SIZE)).map_err(|_| {
663 FrameError::arithmetic_overflow(self.position, "member payload chunk target length")
664 })?;
665 let physical_len = member_remaining
666 .min(target_len)
667 .div_ceil(BLOCK_SIZE as u64)
668 .checked_mul(BLOCK_SIZE as u64)
669 .ok_or_else(|| {
670 FrameError::arithmetic_overflow(
671 self.position,
672 "member payload chunk physical length",
673 )
674 })?;
675 let meaningful_len = member_remaining.min(physical_len);
676 let physical_len = usize::try_from(physical_len).map_err(|_| {
677 FrameError::arithmetic_overflow(self.position, "member payload chunk physical length")
678 })?;
679 let meaningful_len = usize::try_from(meaningful_len).map_err(|_| {
680 FrameError::arithmetic_overflow(self.position, "member payload chunk meaningful length")
681 })?;
682
683 self.member_chunk.buffer.clear();
686 std::mem::swap(buffer, &mut self.member_chunk.buffer);
687 if self.member_chunk.buffer.len() != physical_len {
688 self.member_chunk.buffer.resize(physical_len, 0);
689 }
690 self.member_chunk.start_position = self.position;
691 self.member_chunk.physical_len = physical_len;
692 self.member_chunk.meaningful_len = meaningful_len;
693 self.member_chunk.state = Some(MemberChunkState::Reading {
694 member_remaining,
695 filled: 0,
696 });
697 Ok(())
698 }
699
700 async fn complete_member_chunk(&mut self) -> Result<(), FrameError> {
701 loop {
702 let (member_remaining, filled) = match self.member_chunk.state {
703 Some(MemberChunkState::Reading {
704 member_remaining,
705 filled,
706 }) => (member_remaining, filled),
707 Some(MemberChunkState::Ready { .. }) => return Ok(()),
708 None => {
709 return Err(FrameError::unexpected_order(
710 self.position,
711 "pending member payload chunk",
712 "parser state without a pending chunk",
713 ));
714 }
715 };
716 let start_position = self.member_chunk.start_position;
717 let physical_len = self.member_chunk.physical_len;
718 let meaningful_len = self.member_chunk.meaningful_len;
719 if filled == physical_len {
720 self.position =
721 checked_position(start_position, physical_len).inspect_err(|_| {
722 self.member_chunk.state = None;
723 })?;
724 let remaining = member_remaining
725 .checked_sub(meaningful_len as u64)
726 .ok_or_else(|| {
727 self.member_chunk.state = None;
728 FrameError::arithmetic_overflow(
729 start_position,
730 "remaining member payload length",
731 )
732 })?;
733 self.state = member_payload_state(remaining);
734 self.member_chunk.state = Some(MemberChunkState::Ready { delivered: 0 });
735 return Ok(());
736 }
737
738 let read = match poll_fn(|context| {
739 let mut read_buffer =
740 ReadBuf::new(&mut self.member_chunk.buffer[filled..physical_len]);
741 match Pin::new(&mut self.inner).poll_read(context, &mut read_buffer) {
742 Poll::Pending => Poll::Pending,
743 Poll::Ready(Ok(())) => Poll::Ready(Ok(read_buffer.filled().len())),
744 Poll::Ready(Err(source)) => Poll::Ready(Err(source)),
745 }
746 })
747 .await
748 {
749 Ok(read) => read,
750 Err(source) => {
751 self.member_chunk.state = None;
752 let error_position = checked_position(start_position, filled)?;
753 self.position = checked_position(start_position, filled - filled % BLOCK_SIZE)?;
754 return Err(FrameError::at(
755 error_position,
756 FrameErrorInner::Io { source },
757 ));
758 }
759 };
760 if read == 0 {
761 self.member_chunk.state = None;
762 let partial_len = filled % BLOCK_SIZE;
763 let completed_len = filled - partial_len;
764 self.position = checked_position(start_position, completed_len)?;
765 if partial_len != 0 {
766 return Err(FrameError::at(
767 self.position,
768 FrameErrorInner::IncompleteBlock { read: partial_len },
769 ));
770 }
771 let completed_len = u64::try_from(completed_len).map_err(|_| {
772 FrameError::arithmetic_overflow(
773 self.position,
774 "completed member payload chunk length",
775 )
776 })?;
777 return Err(FrameError::truncated_payload(
778 self.position,
779 DataOwner::Member,
780 member_remaining - member_remaining.min(completed_len),
781 ));
782 }
783 if let Some(MemberChunkState::Reading { filled, .. }) = &mut self.member_chunk.state {
784 *filled += read;
785 }
786 }
787 }
788
789 fn take_member_chunk(&mut self, buffer: &mut Vec<u8>) -> Result<usize, FrameError> {
790 let Some(MemberChunkState::Ready { delivered }) = self.member_chunk.state.take() else {
791 return Err(FrameError::unexpected_order(
792 self.position,
793 "completed member payload chunk",
794 "incomplete member payload chunk",
795 ));
796 };
797 let meaningful_len = self.member_chunk.meaningful_len;
798 let remaining_len = meaningful_len.checked_sub(delivered).ok_or_else(|| {
799 FrameError::arithmetic_overflow(self.position, "undelivered member payload length")
800 })?;
801 if delivered != 0 {
802 self.member_chunk
803 .buffer
804 .copy_within(delivered..meaningful_len, 0);
805 }
806 self.member_chunk.buffer.truncate(remaining_len);
807 std::mem::swap(buffer, &mut self.member_chunk.buffer);
808 Ok(remaining_len)
809 }
810
811 fn take_member_block_from_chunk(&mut self) -> Result<(u64, Block, usize), FrameError> {
812 let Some(MemberChunkState::Ready { delivered }) = self.member_chunk.state else {
813 return Err(FrameError::unexpected_order(
814 self.position,
815 "completed member payload chunk",
816 "incomplete member payload chunk",
817 ));
818 };
819 let start_position = self.member_chunk.start_position;
820 let physical_len = self.member_chunk.physical_len;
821 let total_meaningful_len = self.member_chunk.meaningful_len;
822 let position = checked_position(start_position, delivered).inspect_err(|_| {
823 self.member_chunk.state = None;
824 })?;
825 let mut block = [0; BLOCK_SIZE];
826 block.copy_from_slice(&self.member_chunk.buffer[delivered..delivered + BLOCK_SIZE]);
827 let meaningful_len = total_meaningful_len
828 .checked_sub(delivered)
829 .ok_or_else(|| {
830 self.member_chunk.state = None;
831 FrameError::arithmetic_overflow(self.position, "undelivered member payload length")
832 })?
833 .min(BLOCK_SIZE);
834 let delivered = delivered + BLOCK_SIZE;
835 if delivered == physical_len {
836 self.member_chunk.state = None;
837 } else {
838 self.member_chunk.state = Some(MemberChunkState::Ready { delivered });
839 }
840 Ok((position, block, meaningful_len))
841 }
842
843 fn poll_read_block(
844 &mut self,
845 cx: &mut Context<'_>,
846 ) -> Poll<Result<Option<PositionedBlock>, FrameError>> {
847 while self.block_len < BLOCK_SIZE {
848 let mut read_buf = ReadBuf::new(&mut self.block[self.block_len..]);
849 match Pin::new(&mut self.inner).poll_read(cx, &mut read_buf) {
850 Poll::Pending => return Poll::Pending,
851 Poll::Ready(Err(source)) => {
852 return Poll::Ready(Err(FrameError::at(
853 self.position + self.block_len as u64,
854 FrameErrorInner::Io { source },
855 )));
856 }
857 Poll::Ready(Ok(())) => {
858 let read = read_buf.filled().len();
859 if read == 0 {
860 if self.block_len == 0 {
861 return Poll::Ready(Ok(None));
862 }
863 return Poll::Ready(Err(FrameError::at(
864 self.position,
865 FrameErrorInner::IncompleteBlock {
866 read: self.block_len,
867 },
868 )));
869 }
870 self.block_len += read;
871 }
872 }
873 }
874
875 let position = self.position;
876 self.position = self
877 .position
878 .checked_add(BLOCK_SIZE as u64)
879 .ok_or_else(|| FrameError::arithmetic_overflow(position, "stream position"))?;
880 self.block_len = 0;
881 let block = std::mem::replace(&mut self.block, [0; BLOCK_SIZE]);
882 Poll::Ready(Ok(Some((position, block))))
883 }
884
885 fn handle_eof(&mut self) -> FrameError {
886 let inner = match &self.state {
887 State::AwaitingHeader | State::AwaitingSecondZero => FrameErrorInner::MissingEndMarker,
888 State::ReadingPax {
889 kind, remaining, ..
890 } => FrameErrorInner::TruncatedPayload {
891 owner: DataOwner::Pax(*kind),
892 remaining: *remaining,
893 },
894 State::AwaitingUstarHeader { .. } => FrameErrorInner::UnexpectedEof {
895 expected: "ordinary ustar member header after a local pax header",
896 },
897 State::ReadingGnu {
898 kind, remaining, ..
899 } => FrameErrorInner::TruncatedPayload {
900 owner: DataOwner::Gnu(*kind),
901 remaining: *remaining,
902 },
903 State::AwaitingGnuMember { .. } => FrameErrorInner::UnexpectedEof {
904 expected: "ordinary GNU member header after a GNU metadata extension",
905 },
906 State::ReadingMember { remaining } => FrameErrorInner::TruncatedPayload {
907 owner: DataOwner::Member,
908 remaining: *remaining,
909 },
910 State::Complete | State::Failed => FrameErrorInner::UnexpectedEof {
911 expected: "no further input",
912 },
913 };
914 FrameError::at(self.position, inner)
915 }
916
917 fn process_block(&mut self, position: u64, block: Block) -> Result<Option<Frame>, FrameError> {
918 let state = std::mem::replace(&mut self.state, State::Failed);
919 match state {
920 State::AwaitingHeader => {
921 if is_zero_block(&block) {
922 self.state = State::AwaitingSecondZero;
923 Ok(None)
924 } else {
925 self.process_boundary_header(position, block).map(Some)
926 }
927 }
928 State::ReadingPax {
929 kind,
930 header_position,
931 mut remaining,
932 mut payload,
933 } => {
934 let len = remaining.min(BLOCK_SIZE as u64) as usize;
935 payload.extend_from_slice(&block[..len]);
936 remaining -= len as u64;
937 let completed_pax_records = if remaining == 0 {
938 let records = Arc::new(
939 PaxRecords::parse(
940 &payload,
941 self.global_pax_records
942 .as_ref()
943 .map_or(HdrCharset::Utf8, GlobalPaxRecords::hdrcharset),
944 )
945 .map_err(|source| {
946 FrameError::invalid_pax_record(header_position, source)
947 })?,
948 );
949 match kind {
950 PaxKind::Local => {
951 self.state = State::AwaitingUstarHeader {
952 records: records.clone(),
953 };
954 }
955 PaxKind::Global => {
956 records.apply_global(&mut self.global_pax_records);
957 self.state = State::AwaitingHeader;
958 }
959 }
960 Some(records)
961 } else {
962 self.state = State::ReadingPax {
963 kind,
964 header_position,
965 remaining,
966 payload,
967 };
968 None
969 };
970 Ok(Some(Frame::Data(DataFrame {
971 position,
972 block,
973 len,
974 owner: DataOwner::Pax(kind),
975 completed_pax_records,
976 })))
977 }
978 State::AwaitingUstarHeader { records } => {
979 if is_zero_block(&block) {
980 return Err(FrameError::unexpected_order(
981 position,
982 "ordinary ustar member header after a local pax header",
983 "end-of-archive marker",
984 ));
985 }
986 let parsed = self.parse_format_checked_header(position, &block)?;
987 if matches!(parsed.typeflag, b'x' | b'g') {
988 return Err(FrameError::unexpected_order(
989 position,
990 "ordinary ustar member header after a local pax header",
991 "another pax extended header",
992 ));
993 }
994 self.process_ustar_header(position, block, parsed, Some(records))
995 .map(Some)
996 }
997 State::ReadingGnu {
998 kind,
999 mut remaining,
1000 pending,
1001 } => {
1002 let len = remaining.min(BLOCK_SIZE as u64) as usize;
1003 remaining -= len as u64;
1004 if remaining == 0 {
1005 self.state = State::AwaitingGnuMember { pending };
1006 } else {
1007 self.state = State::ReadingGnu {
1008 kind,
1009 remaining,
1010 pending,
1011 };
1012 }
1013 Ok(Some(Frame::Data(DataFrame {
1014 position,
1015 block,
1016 len,
1017 owner: DataOwner::Gnu(kind),
1018 completed_pax_records: None,
1019 })))
1020 }
1021 State::AwaitingGnuMember { pending } => {
1022 if is_zero_block(&block) {
1023 return Err(FrameError::unexpected_order(
1024 position,
1025 "ordinary GNU member header after a GNU metadata extension",
1026 "end-of-archive marker",
1027 ));
1028 }
1029 let parsed = self.parse_format_checked_header(position, &block)?;
1030 self.process_gnu_header(position, block, parsed, pending)
1031 .map(Some)
1032 }
1033 State::ReadingMember { mut remaining } => {
1034 let len = remaining.min(BLOCK_SIZE as u64) as usize;
1035 remaining -= len as u64;
1036 self.state = member_payload_state(remaining);
1037 Ok(Some(Frame::Data(DataFrame {
1038 position,
1039 block,
1040 len,
1041 owner: DataOwner::Member,
1042 completed_pax_records: None,
1043 })))
1044 }
1045 State::AwaitingSecondZero => {
1046 if !is_zero_block(&block) {
1047 return Err(FrameError::at(position, FrameErrorInner::InvalidEndMarker));
1048 }
1049 self.state = State::Complete;
1050 Ok(None)
1051 }
1052 State::Complete => {
1053 self.state = State::Complete;
1054 Ok(None)
1055 }
1056 State::Failed => Ok(None),
1057 }
1058 }
1059
1060 fn process_boundary_header(
1061 &mut self,
1062 position: u64,
1063 block: Block,
1064 ) -> Result<Frame, FrameError> {
1065 let parsed = self.parse_format_checked_header(position, &block)?;
1066 match parsed.format {
1067 ArchiveFormat::Pax => self.process_pax_boundary_header(position, block, parsed),
1068 ArchiveFormat::Gnu => {
1069 self.process_gnu_header(position, block, parsed, PendingGnu::default())
1070 }
1071 }
1072 }
1073
1074 fn parse_format_checked_header(
1079 &mut self,
1080 position: u64,
1081 block: &Block,
1082 ) -> Result<ParsedHeader, FrameError> {
1083 let parsed = ParsedHeader::try_from_framed(position, block)?;
1084 if let Some(expected) = self.format
1085 && parsed.format != expected
1086 {
1087 return Err(FrameError::at(
1088 position,
1089 FrameErrorInner::FormatMismatch {
1090 expected,
1091 found: parsed.format,
1092 },
1093 ));
1094 }
1095 self.format.get_or_insert(parsed.format);
1096 Ok(parsed)
1097 }
1098
1099 fn process_pax_boundary_header(
1105 &mut self,
1106 position: u64,
1107 block: Block,
1108 parsed: ParsedHeader,
1109 ) -> Result<Frame, FrameError> {
1110 match parsed.typeflag {
1111 b'x' => self.process_pax_header(position, block, parsed.size, PaxKind::Local),
1112 b'g' => self.process_pax_header(position, block, parsed.size, PaxKind::Global),
1113 _ => self.process_ustar_header(position, block, parsed, None),
1114 }
1115 }
1116
1117 fn process_pax_header(
1122 &mut self,
1123 position: u64,
1124 block: Block,
1125 payload_size: u64,
1126 kind: PaxKind,
1127 ) -> Result<Frame, FrameError> {
1128 if payload_size > self.policy.max_pax_extension_size {
1129 return Err(FrameError::at(
1130 position,
1131 FrameErrorInner::ExtensionTooLarge {
1132 format: ArchiveFormat::Pax,
1133 size: payload_size,
1134 limit: self.policy.max_pax_extension_size,
1135 },
1136 ));
1137 }
1138 if kind == PaxKind::Global {
1139 let size = self
1140 .global_pax_extensions_size
1141 .checked_add(payload_size)
1142 .ok_or_else(|| {
1143 FrameError::arithmetic_overflow(position, "global pax extension payload total")
1144 })?;
1145 if size > self.policy.max_global_pax_extensions_size {
1146 return Err(FrameError::at(
1147 position,
1148 FrameErrorInner::GlobalPaxExtensionsTooLarge {
1149 size,
1150 limit: self.policy.max_global_pax_extensions_size,
1151 },
1152 ));
1153 }
1154 self.global_pax_extensions_size = size;
1155 }
1156 if payload_size == 0 {
1157 return Err(FrameError::invalid_pax_record(
1158 position,
1159 PaxError::InvalidRecords {
1160 reason: "extended header payload contains no records",
1161 },
1162 ));
1163 }
1164 self.state = State::ReadingPax {
1165 kind,
1166 header_position: position,
1167 remaining: payload_size,
1168 payload: Vec::new(),
1169 };
1170 Ok(Frame::Pax(PaxFrame {
1171 position,
1172 block,
1173 kind,
1174 payload_size,
1175 }))
1176 }
1177
1178 fn process_ustar_header(
1184 &mut self,
1185 position: u64,
1186 block: Block,
1187 parsed: ParsedHeader,
1188 local_pax_records: Option<SharedPaxRecords>,
1189 ) -> Result<Frame, FrameError> {
1190 let frame = HeaderFrame::ustar(
1191 position,
1192 block,
1193 parsed.typeflag,
1194 parsed.size,
1195 local_pax_records.as_deref(),
1196 self.global_pax_records.as_ref(),
1197 &self.policy,
1198 )?;
1199 self.state = member_payload_state(frame.effective_size);
1200 Ok(Frame::Header(frame))
1201 }
1202
1203 fn process_gnu_header(
1204 &mut self,
1205 position: u64,
1206 block: Block,
1207 parsed: ParsedHeader,
1208 mut pending: PendingGnu,
1209 ) -> Result<Frame, FrameError> {
1210 let extension = match parsed.typeflag {
1211 b'L' => Some(GnuKind::LongName),
1212 b'K' => Some(GnuKind::LongLink),
1213 _ => None,
1214 };
1215 if let Some(kind) = extension {
1216 let already_seen = match kind {
1217 GnuKind::LongName => &mut pending.long_name,
1218 GnuKind::LongLink => &mut pending.long_link,
1219 };
1220 if *already_seen {
1221 return Err(FrameError::unexpected_order(
1222 position,
1223 "ordinary GNU member header or the other GNU metadata extension",
1224 "duplicate GNU metadata extension",
1225 ));
1226 }
1227 if parsed.size > self.policy.max_gnu_extension_size {
1228 return Err(FrameError::at(
1229 position,
1230 FrameErrorInner::ExtensionTooLarge {
1231 format: ArchiveFormat::Gnu,
1232 size: parsed.size,
1233 limit: self.policy.max_gnu_extension_size,
1234 },
1235 ));
1236 }
1237 *already_seen = true;
1238 self.state = if parsed.size == 0 {
1239 State::AwaitingGnuMember { pending }
1240 } else {
1241 State::ReadingGnu {
1242 kind,
1243 remaining: parsed.size,
1244 pending,
1245 }
1246 };
1247 return Ok(Frame::Gnu(GnuFrame {
1248 position,
1249 block,
1250 kind,
1251 payload_size: parsed.size,
1252 }));
1253 }
1254
1255 let frame = HeaderFrame::gnu(
1256 position,
1257 block,
1258 parsed.typeflag,
1259 parsed.size,
1260 pending.long_link,
1261 &self.policy,
1262 )?;
1263 self.state = member_payload_state(frame.effective_size);
1264 Ok(Frame::Header(frame))
1265 }
1266
1267 fn poll_next_frame(
1268 &mut self,
1269 context: &mut Context<'_>,
1270 ) -> Poll<Result<Option<Frame>, FrameError>> {
1271 loop {
1272 if matches!(self.state, State::Complete | State::Failed) {
1273 return Poll::Ready(Ok(None));
1274 }
1275
1276 let (position, block) = match self.poll_read_block(context) {
1277 Poll::Pending => return Poll::Pending,
1278 Poll::Ready(Ok(Some(block))) => block,
1279 Poll::Ready(Ok(None)) => return Poll::Ready(Err(self.handle_eof())),
1280 Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
1281 };
1282
1283 match self.process_block(position, block) {
1284 Ok(Some(frame)) => return Poll::Ready(Ok(Some(frame))),
1285 Ok(None) => continue,
1286 Err(error) => return Poll::Ready(Err(error)),
1287 }
1288 }
1289 }
1290}
1291
1292struct ParsedHeader {
1293 format: ArchiveFormat,
1294 typeflag: u8,
1295 size: u64,
1296}
1297
1298trait TryFromFramed<T>: Sized {
1301 fn try_from_framed(position: u64, source: T) -> Result<Self, FrameError>;
1302}
1303
1304fn is_zero_block(block: &Block) -> bool {
1305 block.iter().all(|byte| *byte == 0)
1306}
1307
1308fn trim_nul(bytes: &[u8]) -> &[u8] {
1309 let end = bytes
1310 .iter()
1311 .position(|byte| *byte == 0)
1312 .unwrap_or(bytes.len());
1313 &bytes[..end]
1314}
1315
1316fn member_payload_state(remaining: u64) -> State {
1317 if remaining == 0 {
1318 State::AwaitingHeader
1319 } else {
1320 State::ReadingMember { remaining }
1321 }
1322}
1323
1324fn checked_position(position: u64, len: usize) -> Result<u64, FrameError> {
1325 let len = u64::try_from(len)
1326 .map_err(|_| FrameError::arithmetic_overflow(position, "stream position"))?;
1327 position
1328 .checked_add(len)
1329 .ok_or_else(|| FrameError::arithmetic_overflow(position, "stream position"))
1330}
1331
1332impl TryFromFramed<&Block> for ParsedHeader {
1333 fn try_from_framed(position: u64, block: &Block) -> Result<Self, FrameError> {
1334 let format = match &block[IDENTITY_RANGE] {
1335 identity if identity == USTAR_IDENTITY => ArchiveFormat::Pax,
1336 identity if identity == GNU_IDENTITY => ArchiveFormat::Gnu,
1337 identity => {
1338 return Err(FrameError::at(
1339 position,
1340 FrameErrorInner::InvalidIdentity {
1341 found: identity.try_into().expect("fixed header range"),
1342 },
1343 ));
1344 }
1345 };
1346
1347 let actual_checksum = checksum(block);
1348 let expected_checksum = parse_octal(&block[CHECKSUM_RANGE]);
1349 if expected_checksum != Some(actual_checksum) {
1350 return Err(FrameError::at(
1351 position,
1352 FrameErrorInner::InvalidChecksum {
1353 expected: expected_checksum,
1354 actual: actual_checksum,
1355 },
1356 ));
1357 }
1358
1359 let size_bytes: [u8; 12] = block[SIZE_RANGE].try_into().expect("fixed header range");
1360 let size = parse_number(format, &size_bytes).ok_or_else(|| {
1361 FrameError::at(position, FrameErrorInner::InvalidSize { found: size_bytes })
1362 })?;
1363
1364 Ok(Self {
1365 format,
1366 typeflag: block[TYPEFLAG_OFFSET],
1367 size,
1368 })
1369 }
1370}
1371
1372impl TryFromFramed<u8> for UstarKind {
1373 fn try_from_framed(position: u64, typeflag: u8) -> Result<Self, FrameError> {
1374 match typeflag {
1375 0 | b'0' => Ok(Self::Regular),
1376 b'1' => Ok(Self::HardLink),
1377 b'2' => Ok(Self::SymbolicLink),
1378 b'3' => Ok(Self::CharacterDevice),
1379 b'4' => Ok(Self::BlockDevice),
1380 b'5' => Ok(Self::Directory),
1381 b'6' => Ok(Self::Fifo),
1382 b'7' => Ok(Self::Contiguous),
1383 _ => Err(FrameError::at(
1384 position,
1385 FrameErrorInner::UnsupportedTypeflag { typeflag },
1386 )),
1387 }
1388 }
1389}
1390
1391fn validate_pax_member_size(
1392 position: u64,
1393 kind: UstarKind,
1394 declared_size: u64,
1395 effective_size: u64,
1396) -> Result<(), FrameError> {
1397 match kind {
1398 UstarKind::Regular | UstarKind::HardLink | UstarKind::Contiguous => Ok(()),
1403 UstarKind::SymbolicLink
1404 | UstarKind::CharacterDevice
1405 | UstarKind::BlockDevice
1406 | UstarKind::Directory
1407 | UstarKind::Fifo => {
1408 validate_payload_free_size(position, kind, declared_size)?;
1415 validate_payload_free_size(position, kind, effective_size)
1416 }
1417 }
1418}
1419
1420fn validate_gnu_member_size(position: u64, kind: UstarKind, size: u64) -> Result<(), FrameError> {
1421 match kind {
1422 UstarKind::Regular | UstarKind::Contiguous => Ok(()),
1423 UstarKind::HardLink
1424 | UstarKind::SymbolicLink
1425 | UstarKind::CharacterDevice
1426 | UstarKind::BlockDevice
1427 | UstarKind::Directory
1428 | UstarKind::Fifo => validate_payload_free_size(position, kind, size),
1429 }
1430}
1431
1432fn validate_payload_free_size(position: u64, kind: UstarKind, size: u64) -> Result<(), FrameError> {
1433 if size == 0 {
1434 Ok(())
1435 } else {
1436 Err(FrameError::at(
1437 position,
1438 FrameErrorInner::InvalidMemberSize { kind, size },
1439 ))
1440 }
1441}
1442
1443#[cfg(test)]
1444mod tests {
1445 use std::{
1446 cell::Cell,
1447 pin::Pin,
1448 rc::Rc,
1449 task::{Context, Poll},
1450 };
1451
1452 use tokio::io::ReadBuf;
1453
1454 use super::*;
1455 use crate::{
1456 ArchiveFormat, FrameError, FrameErrorInner, HdrCharset, PaxString, PaxValue,
1457 header::{DEVMAJOR_RANGE, DEVMINOR_RANGE},
1458 test_support::{
1459 ChunkedReader, append_block, append_gnu, append_pax, append_payload, append_terminator,
1460 collect_frames, gnu_base256_header, gnu_header, header, ready, record, set_checksum,
1461 },
1462 };
1463
1464 fn collect(bytes: Vec<u8>, max_chunk: usize) -> Vec<Result<Frame, FrameError>> {
1465 ready(collect_frames(TarStream::new(ChunkedReader::new(
1466 bytes, max_chunk,
1467 ))))
1468 }
1469
1470 fn collect_with_max_pax_extension_size(
1471 bytes: Vec<u8>,
1472 max_chunk: usize,
1473 max_pax_extension_size: u64,
1474 ) -> Vec<Result<Frame, FrameError>> {
1475 ready(collect_frames(
1476 TarStream::new(ChunkedReader::new(bytes, max_chunk)).with_policy(
1477 StreamPolicy::default().max_pax_extension_size(max_pax_extension_size),
1478 ),
1479 ))
1480 }
1481
1482 fn header_frame(frames: &[Result<Frame, FrameError>], index: usize) -> &HeaderFrame {
1483 let Ok(Frame::Header(frame)) = &frames[index] else {
1484 panic!("expected header frame");
1485 };
1486 frame
1487 }
1488
1489 fn data_frame(frames: &[Result<Frame, FrameError>], index: usize) -> &DataFrame {
1490 let Ok(Frame::Data(frame)) = &frames[index] else {
1491 panic!("expected data frame");
1492 };
1493 frame
1494 }
1495
1496 fn last_error(frames: &[Result<Frame, FrameError>]) -> &FrameError {
1497 frames
1498 .last()
1499 .expect("stream should emit an item")
1500 .as_ref()
1501 .expect_err("last item should be an error")
1502 }
1503
1504 fn last_error_inner(frames: &[Result<Frame, FrameError>]) -> &FrameErrorInner {
1505 &last_error(frames).inner
1506 }
1507
1508 struct CountingReader {
1509 bytes: Vec<u8>,
1510 position: usize,
1511 consumed: Rc<Cell<usize>>,
1512 }
1513
1514 impl AsyncRead for CountingReader {
1515 fn poll_read(
1516 mut self: Pin<&mut Self>,
1517 _context: &mut Context<'_>,
1518 buffer: &mut ReadBuf<'_>,
1519 ) -> Poll<std::io::Result<()>> {
1520 let len = buffer
1521 .remaining()
1522 .min(self.bytes.len().saturating_sub(self.position));
1523 let end = self.position + len;
1524 buffer.put_slice(&self.bytes[self.position..end]);
1525 self.position = end;
1526 self.consumed.set(self.consumed.get() + len);
1527 Poll::Ready(Ok(()))
1528 }
1529 }
1530
1531 #[derive(Clone, Copy)]
1532 enum ExpectedHeaderError {
1533 InvalidIdentity,
1534 InvalidChecksum,
1535 InvalidSize,
1536 InvalidNumericField(&'static str),
1537 UnterminatedUstarStringField(&'static str),
1538 UnsupportedTypeflag(u8),
1539 }
1540
1541 impl ExpectedHeaderError {
1542 fn matches(self, error: &FrameErrorInner) -> bool {
1543 match (self, error) {
1544 (Self::InvalidIdentity, FrameErrorInner::InvalidIdentity { .. })
1545 | (Self::InvalidChecksum, FrameErrorInner::InvalidChecksum { .. })
1546 | (Self::InvalidSize, FrameErrorInner::InvalidSize { .. }) => true,
1547 (
1548 Self::InvalidNumericField(field),
1549 FrameErrorInner::InvalidNumericField { field: found, .. },
1550 )
1551 | (
1552 Self::UnterminatedUstarStringField(field),
1553 FrameErrorInner::UnterminatedUstarStringField { field: found },
1554 ) => field == *found,
1555 (
1556 Self::UnsupportedTypeflag(typeflag),
1557 FrameErrorInner::UnsupportedTypeflag { typeflag: found },
1558 ) => typeflag == *found,
1559 _ => false,
1560 }
1561 }
1562 }
1563
1564 fn checksummed_header(mutate: impl FnOnce(&mut Block)) -> Block {
1565 let mut block = header(b'0', 0);
1566 mutate(&mut block);
1567 set_checksum(&mut block);
1568 block
1569 }
1570
1571 fn invalid_header_cases() -> Vec<(&'static str, Block, ExpectedHeaderError)> {
1572 let mut bad_magic = header(b'0', 0);
1573 bad_magic[IDENTITY_RANGE.start] = b'g';
1574 let mut bad_version = header(b'0', 0);
1575 bad_version[IDENTITY_RANGE.end - 2..IDENTITY_RANGE.end].copy_from_slice(b" ");
1576 let mut bad_checksum = header(b'0', 0);
1577 bad_checksum[0] = b'X';
1578
1579 vec![
1580 ("magic", bad_magic, ExpectedHeaderError::InvalidIdentity),
1581 ("version", bad_version, ExpectedHeaderError::InvalidIdentity),
1582 (
1583 "checksum",
1584 bad_checksum,
1585 ExpectedHeaderError::InvalidChecksum,
1586 ),
1587 (
1588 "octal size",
1589 checksummed_header(|block| {
1590 block[SIZE_RANGE].copy_from_slice(b"00000000008\0");
1591 }),
1592 ExpectedHeaderError::InvalidSize,
1593 ),
1594 (
1595 "base256 size",
1596 checksummed_header(|block| block[SIZE_RANGE.start] = 0x80),
1597 ExpectedHeaderError::InvalidSize,
1598 ),
1599 (
1600 "octal mode",
1601 checksummed_header(|block| {
1602 block[MODE_RANGE].copy_from_slice(b"0000080\0");
1603 }),
1604 ExpectedHeaderError::InvalidNumericField("mode"),
1605 ),
1606 (
1607 "uid",
1608 checksummed_header(|block| {
1609 block[UID_RANGE].copy_from_slice(b"invalid\0");
1610 }),
1611 ExpectedHeaderError::InvalidNumericField("uid"),
1612 ),
1613 (
1614 "gid",
1615 checksummed_header(|block| block[GID_RANGE.start] = b'8'),
1616 ExpectedHeaderError::InvalidNumericField("gid"),
1617 ),
1618 (
1619 "mtime",
1620 checksummed_header(|block| {
1621 block[MTIME_RANGE].copy_from_slice(b"00000000008\0");
1622 }),
1623 ExpectedHeaderError::InvalidNumericField("mtime"),
1624 ),
1625 (
1626 "uname",
1627 checksummed_header(|block| block[UNAME_RANGE].fill(b'u')),
1628 ExpectedHeaderError::UnterminatedUstarStringField("uname"),
1629 ),
1630 (
1631 "gname",
1632 checksummed_header(|block| block[GNAME_RANGE].fill(b'g')),
1633 ExpectedHeaderError::UnterminatedUstarStringField("gname"),
1634 ),
1635 (
1636 "POSIX typeflag",
1637 header(b'X', 0),
1638 ExpectedHeaderError::UnsupportedTypeflag(b'X'),
1639 ),
1640 (
1641 "GNU typeflag",
1642 header(b'L', 0),
1643 ExpectedHeaderError::UnsupportedTypeflag(b'L'),
1644 ),
1645 ]
1646 }
1647
1648 #[test]
1649 fn frames_bare_member_across_fragmented_reads() {
1650 let mut bytes = Vec::new();
1651 append_block(&mut bytes, &header(b'0', 513));
1652 append_payload(&mut bytes, &[b'a'; BLOCK_SIZE]);
1653 append_payload(&mut bytes, b"b");
1654 append_terminator(&mut bytes);
1655
1656 let frames = collect(bytes, 7);
1657 assert_eq!(frames.len(), 3);
1658 let header = header_frame(&frames, 0);
1659 assert_eq!(header.kind, UstarKind::Regular);
1660 assert_eq!(header.declared_size, 513);
1661 assert_eq!(header.effective_size, 513);
1662 let first = data_frame(&frames, 1);
1663 let last = data_frame(&frames, 2);
1664 assert_eq!(first.len, BLOCK_SIZE);
1665 assert_eq!(last.len, 1);
1666 assert_eq!(last.owner, DataOwner::Member);
1667 assert!(first.completed_pax_records().is_none());
1668 assert!(last.completed_pax_records().is_none());
1669 }
1670
1671 #[test]
1672 fn frames_multiblock_pax_records_and_applies_size_override() {
1673 let mut payload = record("comment", &"x".repeat(BLOCK_SIZE));
1674 payload.extend_from_slice(&record("size", "513"));
1675 assert!(payload.len() > BLOCK_SIZE);
1676
1677 let mut bytes = Vec::new();
1678 append_pax(&mut bytes, b'x', &payload);
1679 append_block(&mut bytes, &header(b'0', 1));
1680 append_payload(&mut bytes, &[b'a'; BLOCK_SIZE]);
1681 append_payload(&mut bytes, b"b");
1682 append_terminator(&mut bytes);
1683
1684 let frames = collect(bytes, 19);
1685 assert_eq!(frames.len(), 6);
1686 let Frame::Pax(pax) = frames[0].as_ref().unwrap() else {
1687 panic!("expected pax header");
1688 };
1689 assert_eq!(pax.kind, PaxKind::Local);
1690 assert_eq!(pax.payload_size, payload.len() as u64);
1691 let first_pax_data = data_frame(&frames, 1);
1692 assert_eq!(first_pax_data.owner, DataOwner::Pax(PaxKind::Local));
1693 assert!(first_pax_data.completed_pax_records().is_none());
1694 let final_pax_data = data_frame(&frames, 2);
1695 assert_eq!(final_pax_data.owner, DataOwner::Pax(PaxKind::Local));
1696 assert_eq!(
1697 final_pax_data
1698 .completed_pax_records()
1699 .and_then(|records| records.last()),
1700 Some(&PaxRecord::Size(PaxValue::Value(513)))
1701 );
1702 let header = header_frame(&frames, 3);
1703 assert_eq!(header.declared_size, 1);
1704 assert_eq!(header.effective_size, 513);
1705 let last = data_frame(&frames, 5);
1706 assert_eq!(last.len, 1);
1707 }
1708
1709 #[test]
1710 fn rejects_oversized_pax_extensions_before_consuming_payload() {
1711 let mut payload = record("comment", "metadata");
1712 payload.extend_from_slice(&record("mtime", "1"));
1713 let declared_size = u64::try_from(payload.len()).expect("payload size should fit u64");
1714 for (case, typeflag) in [("local", b'x'), ("global", b'g')] {
1715 let mut bytes = Vec::new();
1716 append_pax(&mut bytes, typeflag, &payload);
1717 let frames = collect_with_max_pax_extension_size(bytes, BLOCK_SIZE, declared_size - 1);
1718 assert_eq!(frames.len(), 1, "{case}");
1719 assert!(matches!(
1720 last_error(&frames),
1721 FrameError {
1722 position: 0,
1723 inner: FrameErrorInner::ExtensionTooLarge {
1724 format: ArchiveFormat::Pax,
1725 size,
1726 limit,
1727 },
1728 } if *size == declared_size && *limit == declared_size - 1
1729 ));
1730 }
1731
1732 let frames = collect(
1733 header(b'x', DEFAULT_MAX_PAX_EXTENSION_SIZE + 1).to_vec(),
1734 BLOCK_SIZE,
1735 );
1736 assert_eq!(frames.len(), 1);
1737 assert!(matches!(
1738 last_error(&frames),
1739 FrameError {
1740 position: 0,
1741 inner: FrameErrorInner::ExtensionTooLarge {
1742 format: ArchiveFormat::Pax,
1743 size,
1744 limit: DEFAULT_MAX_PAX_EXTENSION_SIZE,
1745 },
1746 } if *size == DEFAULT_MAX_PAX_EXTENSION_SIZE + 1
1747 ));
1748 }
1749
1750 #[test]
1751 fn oversized_pax_extension_does_not_read_its_payload_block() {
1752 let mut bytes = header(b'x', 1).to_vec();
1753 bytes.resize(BLOCK_SIZE * 2, 0);
1754 let consumed = Rc::new(Cell::new(0));
1755 let reader = CountingReader {
1756 bytes,
1757 position: 0,
1758 consumed: Rc::clone(&consumed),
1759 };
1760 let mut stream =
1761 TarStream::new(reader).with_policy(StreamPolicy::default().max_pax_extension_size(0));
1762
1763 assert!(matches!(
1764 ready(stream.next_frame()),
1765 Err(FrameError {
1766 position: 0,
1767 inner: FrameErrorInner::ExtensionTooLarge {
1768 format: ArchiveFormat::Pax,
1769 size: 1,
1770 limit: 0,
1771 },
1772 })
1773 ));
1774 assert_eq!(consumed.get(), BLOCK_SIZE);
1775 }
1776
1777 #[test]
1778 fn accepts_pax_extensions_at_the_configured_limit() {
1779 let mut payload = record("comment", "metadata");
1780 payload.extend_from_slice(&record("ACME.attribute", "value"));
1781 for (case, typeflag) in [("local", b'x'), ("global", b'g')] {
1782 let mut bytes = Vec::new();
1783 append_pax(&mut bytes, typeflag, &payload);
1784 if typeflag == b'x' {
1785 append_block(&mut bytes, &header(b'0', 0));
1786 }
1787 append_terminator(&mut bytes);
1788
1789 let frames = collect_with_max_pax_extension_size(
1790 bytes,
1791 7,
1792 payload
1793 .len()
1794 .try_into()
1795 .expect("payload size should fit u64"),
1796 );
1797 assert!(frames.iter().all(Result::is_ok), "{case}");
1798 }
1799 }
1800
1801 #[test]
1802 fn applies_global_pax_records_overrides_and_rejects_size_deletions() {
1803 let mut initial_global = record("comment", "old");
1804 initial_global.extend_from_slice(&record("size", "2"));
1805 let replacement_global = record("comment", "new");
1806 let mut local = record("comment", "local");
1807 local.extend_from_slice(&record("size", "3"));
1808 let mut deletion = record("comment", "");
1809 deletion.extend_from_slice(&record("size", ""));
1810
1811 let mut bytes = Vec::new();
1812 append_pax(&mut bytes, b'g', &initial_global);
1813 append_pax(&mut bytes, b'g', &replacement_global);
1814 append_block(&mut bytes, &header(b'0', 1));
1815 append_payload(&mut bytes, b"ab");
1816 append_pax(&mut bytes, b'x', &local);
1817 append_block(&mut bytes, &header(b'0', 1));
1818 append_payload(&mut bytes, b"abc");
1819 append_pax(&mut bytes, b'g', &deletion);
1820 append_block(&mut bytes, &header(b'5', 1));
1821 append_terminator(&mut bytes);
1822
1823 let frames = collect(bytes, 31);
1824 assert!(frames.iter().any(|frame| matches!(
1825 frame,
1826 Ok(Frame::Pax(PaxFrame {
1827 kind: PaxKind::Global,
1828 ..
1829 }))
1830 )));
1831 assert!(frames.iter().any(|frame| matches!(
1832 frame,
1833 Ok(Frame::Data(DataFrame {
1834 owner: DataOwner::Pax(PaxKind::Global),
1835 ..
1836 }))
1837 )));
1838 let completed_global_payloads: Vec<&[PaxRecord]> = frames
1839 .iter()
1840 .filter_map(|frame| match frame {
1841 Ok(Frame::Data(frame)) if frame.owner == DataOwner::Pax(PaxKind::Global) => {
1842 frame.completed_pax_records()
1843 }
1844 _ => None,
1845 })
1846 .collect();
1847 assert_eq!(completed_global_payloads.len(), 3);
1848 assert_eq!(
1849 completed_global_payloads[2],
1850 [
1851 PaxRecord::Comment(PaxValue::Deleted),
1852 PaxRecord::Size(PaxValue::Deleted),
1853 ]
1854 );
1855 let headers: Vec<&HeaderFrame> = frames
1856 .iter()
1857 .filter_map(|frame| match frame {
1858 Ok(Frame::Header(header)) => Some(header),
1859 _ => None,
1860 })
1861 .collect();
1862 assert_eq!(headers.len(), 2);
1863 assert_eq!(headers[0].effective_size, 2);
1864 assert_eq!(headers[1].effective_size, 3);
1865 assert!(frames.iter().any(|frame| {
1866 matches!(
1867 frame,
1868 Ok(Frame::Data(frame))
1869 if frame.owner == DataOwner::Pax(PaxKind::Local)
1870 && frame.completed_pax_records() == Some(local_records("local", 3).as_slice())
1871 )
1872 }));
1873 assert!(matches!(
1874 last_error_inner(&frames),
1875 FrameErrorInner::DeletedPaxMetadata { keyword: "size" }
1876 ));
1877 }
1878
1879 fn local_records(comment: &str, size: u64) -> Vec<PaxRecord> {
1880 vec![
1881 PaxRecord::Comment(PaxValue::Value(comment.into())),
1882 PaxRecord::Size(PaxValue::Value(size)),
1883 ]
1884 }
1885
1886 #[test]
1887 fn allows_local_size_deletion_when_a_later_record_restores_size() {
1888 let mut local = record("size", "");
1889 local.extend_from_slice(&record("size", "2"));
1890 let mut bytes = Vec::new();
1891 append_pax(&mut bytes, b'x', &local);
1892 append_block(&mut bytes, &header(b'0', 1));
1893 append_payload(&mut bytes, b"ab");
1894 append_terminator(&mut bytes);
1895
1896 let frames = collect(bytes, BLOCK_SIZE);
1897 let header = header_frame(&frames, 2);
1898 assert_eq!(header.effective_size, 2);
1899 assert_eq!(
1900 data_frame(&frames, 1).completed_pax_records(),
1901 Some(
1902 [
1903 PaxRecord::Size(PaxValue::Deleted),
1904 PaxRecord::Size(PaxValue::Value(2)),
1905 ]
1906 .as_slice()
1907 )
1908 );
1909 }
1910
1911 #[test]
1912 fn pax_records_do_not_make_malformed_ordinary_header_fields_valid() {
1913 let cases = [
1914 (
1915 "local uid",
1916 b'x',
1917 record("uid", "1"),
1918 checksummed_header(|block| block[UID_RANGE].fill(b'u')),
1919 ExpectedHeaderError::InvalidNumericField("uid"),
1920 ),
1921 (
1922 "global gid",
1923 b'g',
1924 record("gid", "2"),
1925 checksummed_header(|block| block[GID_RANGE].fill(b'g')),
1926 ExpectedHeaderError::InvalidNumericField("gid"),
1927 ),
1928 (
1929 "local mtime",
1930 b'x',
1931 record("mtime", "3"),
1932 checksummed_header(|block| block[MTIME_RANGE].fill(b'm')),
1933 ExpectedHeaderError::InvalidNumericField("mtime"),
1934 ),
1935 (
1936 "global uname",
1937 b'g',
1938 record("uname", "user"),
1939 checksummed_header(|block| block[UNAME_RANGE].fill(b'u')),
1940 ExpectedHeaderError::UnterminatedUstarStringField("uname"),
1941 ),
1942 (
1943 "local gname",
1944 b'x',
1945 record("gname", "group"),
1946 checksummed_header(|block| block[GNAME_RANGE].fill(b'g')),
1947 ExpectedHeaderError::UnterminatedUstarStringField("gname"),
1948 ),
1949 ];
1950
1951 for (case, typeflag, records, malformed, expected) in cases {
1952 let mut bytes = Vec::new();
1953 append_pax(&mut bytes, typeflag, &records);
1954 append_block(&mut bytes, &malformed);
1955 append_terminator(&mut bytes);
1956
1957 let frames = collect(bytes, BLOCK_SIZE);
1958 assert!(
1959 expected.matches(last_error_inner(&frames)),
1960 "{case}: {frames:?}"
1961 );
1962 }
1963 }
1964
1965 #[test]
1966 fn accepts_all_nul_unused_device_fields() {
1967 let block = header(b'0', 0);
1968 assert_eq!(parse_octal(&block[DEVMAJOR_RANGE]), None);
1969 assert_eq!(parse_octal(&block[DEVMINOR_RANGE]), None);
1970
1971 let mut bytes = Vec::new();
1972 append_block(&mut bytes, &block);
1973 append_terminator(&mut bytes);
1974 assert!(collect(bytes, BLOCK_SIZE).iter().all(Result::is_ok));
1975 }
1976
1977 #[test]
1978 fn rejects_local_size_deletion_for_payload_free_members() {
1979 let global = record("size", "7");
1980 let local = record("size", "");
1981 let mut bytes = Vec::new();
1982 append_pax(&mut bytes, b'g', &global);
1983 append_pax(&mut bytes, b'x', &local);
1984 append_block(&mut bytes, &header(b'5', 3));
1985 append_terminator(&mut bytes);
1986
1987 assert!(matches!(
1988 last_error_inner(&collect(bytes, BLOCK_SIZE)),
1989 FrameErrorInner::DeletedPaxMetadata { keyword: "size" }
1990 ));
1991 }
1992
1993 #[test]
1994 fn rejects_deleted_size_when_member_payload_cannot_be_framed() {
1995 let records = record("size", "");
1996 for typeflag in *b"xg" {
1997 let mut bytes = Vec::new();
1998 append_pax(&mut bytes, typeflag, &records);
1999 append_block(&mut bytes, &header(b'0', 0));
2000
2001 assert!(
2002 matches!(
2003 last_error_inner(&collect(bytes, BLOCK_SIZE)),
2004 FrameErrorInner::DeletedPaxMetadata { keyword: "size" }
2005 ),
2006 "{typeflag:?}"
2007 );
2008 }
2009 }
2010
2011 #[test]
2012 fn allows_local_size_to_restore_an_active_global_deletion() {
2013 let global = record("size", "");
2014 let local = record("size", "2");
2015 let mut bytes = Vec::new();
2016 append_pax(&mut bytes, b'g', &global);
2017 append_pax(&mut bytes, b'x', &local);
2018 append_block(&mut bytes, &header(b'0', 1));
2019 append_payload(&mut bytes, b"ab");
2020 append_terminator(&mut bytes);
2021
2022 let frames = collect(bytes, BLOCK_SIZE);
2023 let header = header_frame(&frames, 4);
2024 assert_eq!(header.effective_size, 2);
2025 assert_eq!(
2026 data_frame(&frames, 1).completed_pax_records(),
2027 Some([PaxRecord::Size(PaxValue::Deleted)].as_slice())
2028 );
2029 assert_eq!(
2030 data_frame(&frames, 3).completed_pax_records(),
2031 Some([PaxRecord::Size(PaxValue::Value(2))].as_slice())
2032 );
2033 }
2034
2035 #[test]
2036 fn frames_pax_hard_link_bodies_from_header_or_size_override() {
2037 for (case, declared_size, override_size, header_index, data_index) in [
2038 ("physical size", 3, None, 0, 1),
2039 ("pax size", 0, Some("3"), 2, 3),
2040 ("pax size overrides physical size", 1, Some("3"), 2, 3),
2041 ] {
2042 let mut bytes = Vec::new();
2043 if let Some(override_size) = override_size {
2044 append_pax(&mut bytes, b'x', &record("size", override_size));
2045 }
2046 append_block(&mut bytes, &header(b'1', declared_size));
2047 append_payload(&mut bytes, b"abc");
2048 append_terminator(&mut bytes);
2049
2050 let frames = collect(bytes, BLOCK_SIZE);
2051 let header = header_frame(&frames, header_index);
2052 assert_eq!(header.format, ArchiveFormat::Pax, "{case}");
2053 assert_eq!(header.kind, UstarKind::HardLink, "{case}");
2054 assert_eq!(header.declared_size, declared_size, "{case}");
2055 assert_eq!(header.effective_size, 3, "{case}");
2056 assert_eq!(data_frame(&frames, data_index).len, 3, "{case}");
2057 }
2058 }
2059
2060 #[test]
2061 fn zero_data_block_is_not_a_terminator() {
2062 let mut bytes = Vec::new();
2063 append_block(&mut bytes, &header(b'0', BLOCK_SIZE as u64));
2064 append_block(&mut bytes, &[0; BLOCK_SIZE]);
2065 append_terminator(&mut bytes);
2066
2067 let frames = collect(bytes, BLOCK_SIZE);
2068 assert_eq!(frames.len(), 2);
2069 assert!(matches!(frames[1], Ok(Frame::Data(_))));
2070 }
2071
2072 #[test]
2073 fn zero_filled_block_inside_pax_payload_is_data() {
2074 let payload = record("comment", &"\0".repeat(BLOCK_SIZE * 3));
2075 let mut bytes = Vec::new();
2076 append_pax(&mut bytes, b'x', &payload);
2077 append_block(&mut bytes, &header(b'0', 0));
2078 append_terminator(&mut bytes);
2079
2080 let frames = collect(bytes, BLOCK_SIZE);
2081 assert!(frames.iter().any(|frame| matches!(
2082 frame,
2083 Ok(Frame::Data(DataFrame {
2084 block,
2085 owner: DataOwner::Pax(PaxKind::Local),
2086 ..
2087 })) if is_zero_block(block)
2088 )));
2089 }
2090
2091 #[test]
2092 fn frames_gnu_long_metadata_and_base256_payloads() {
2093 let mut bytes = Vec::new();
2094 append_block(&mut bytes, &gnu_base256_header(b'L', 513));
2095 append_payload(&mut bytes, &[b'n'; BLOCK_SIZE]);
2096 append_payload(&mut bytes, b"\0");
2097 append_gnu(&mut bytes, b'K', b"link\0");
2098 append_block(&mut bytes, &gnu_header(b'2', 0));
2099 append_terminator(&mut bytes);
2100
2101 let frames = collect(bytes, 13);
2102 assert_eq!(frames.len(), 6);
2103 assert!(matches!(
2104 frames[0].as_ref().unwrap(),
2105 Frame::Gnu(GnuFrame {
2106 kind: GnuKind::LongName,
2107 payload_size: 513,
2108 ..
2109 })
2110 ));
2111 let final_name = data_frame(&frames, 2);
2112 assert_eq!(final_name.owner, DataOwner::Gnu(GnuKind::LongName));
2113 assert_eq!(final_name.len, 1);
2114 assert!(final_name.completed_pax_records().is_none());
2115 assert!(matches!(
2116 frames[3].as_ref().unwrap(),
2117 Frame::Gnu(GnuFrame {
2118 kind: GnuKind::LongLink,
2119 ..
2120 })
2121 ));
2122 let header = header_frame(&frames, 5);
2123 assert_eq!(header.kind, UstarKind::SymbolicLink);
2124 }
2125
2126 #[test]
2127 fn rejects_header_format_type_and_field_errors() {
2128 for (case, block, expected) in invalid_header_cases() {
2129 let frames = collect(block.to_vec(), BLOCK_SIZE);
2130 let error = last_error_inner(&frames);
2131 assert!(expected.matches(error), "{case}: {error:?}");
2132 }
2133 }
2134
2135 #[test]
2136 fn rejects_malformed_gnu_numeric_fields() {
2137 for (field, range) in [
2138 ("mode", MODE_RANGE),
2139 ("uid", UID_RANGE),
2140 ("gid", GID_RANGE),
2141 ("mtime", MTIME_RANGE),
2142 ] {
2143 let mut block = gnu_header(b'0', 0);
2144 block[range].fill(b'x');
2145 set_checksum(&mut block);
2146
2147 assert!(matches!(
2148 last_error_inner(&collect(block.to_vec(), BLOCK_SIZE)),
2149 FrameErrorInner::InvalidNumericField { field: found, .. } if *found == field
2150 ));
2151 }
2152 }
2153
2154 #[test]
2155 fn rejects_nonzero_physical_sizes_for_payload_free_members() {
2156 for (format, block, kind) in [
2157 (ArchiveFormat::Pax, header(b'2', 1), UstarKind::SymbolicLink),
2158 (ArchiveFormat::Gnu, gnu_header(b'1', 1), UstarKind::HardLink),
2159 (
2160 ArchiveFormat::Gnu,
2161 gnu_header(b'2', 1),
2162 UstarKind::SymbolicLink,
2163 ),
2164 (
2165 ArchiveFormat::Pax,
2166 header(b'3', 1),
2167 UstarKind::CharacterDevice,
2168 ),
2169 (
2170 ArchiveFormat::Gnu,
2171 gnu_header(b'3', 1),
2172 UstarKind::CharacterDevice,
2173 ),
2174 (ArchiveFormat::Pax, header(b'4', 1), UstarKind::BlockDevice),
2175 (
2176 ArchiveFormat::Gnu,
2177 gnu_header(b'4', 1),
2178 UstarKind::BlockDevice,
2179 ),
2180 (ArchiveFormat::Pax, header(b'5', 1), UstarKind::Directory),
2181 (
2182 ArchiveFormat::Gnu,
2183 gnu_header(b'5', 1),
2184 UstarKind::Directory,
2185 ),
2186 (ArchiveFormat::Pax, header(b'6', 1), UstarKind::Fifo),
2187 (ArchiveFormat::Gnu, gnu_header(b'6', 1), UstarKind::Fifo),
2188 ] {
2189 let frames = collect(block.to_vec(), BLOCK_SIZE);
2190 assert!(
2191 matches!(
2192 last_error_inner(&frames),
2193 FrameErrorInner::InvalidMemberSize {
2194 kind: found,
2195 size: 1,
2196 } if *found == kind
2197 ),
2198 "{format:?} {kind:?}"
2199 );
2200 }
2201 }
2202
2203 #[test]
2204 fn rejects_nonzero_declared_or_effective_pax_sizes_for_payload_free_members() {
2205 for (case, declared_size, override_size) in [("effective", 0, "1"), ("declared", 1, "0")] {
2206 for (typeflag, kind) in [
2207 (b'2', UstarKind::SymbolicLink),
2208 (b'3', UstarKind::CharacterDevice),
2209 (b'4', UstarKind::BlockDevice),
2210 (b'5', UstarKind::Directory),
2211 (b'6', UstarKind::Fifo),
2212 ] {
2213 let mut bytes = Vec::new();
2214 append_pax(&mut bytes, b'x', &record("size", override_size));
2215 append_block(&mut bytes, &header(typeflag, declared_size));
2216
2217 assert!(
2218 matches!(
2219 last_error_inner(&collect(bytes, BLOCK_SIZE)),
2220 FrameErrorInner::InvalidMemberSize {
2221 kind: found,
2222 size: 1,
2223 } if *found == kind
2224 ),
2225 "{case} {kind:?}"
2226 );
2227 }
2228 }
2229 }
2230
2231 #[test]
2232 fn header_errors_preserve_later_header_positions() {
2233 let position = BLOCK_SIZE as u64;
2234
2235 for (case, block, expected) in invalid_header_cases() {
2236 let mut bytes = Vec::new();
2237 append_block(&mut bytes, &header(b'0', 0));
2238 append_block(&mut bytes, &block);
2239 let frames = collect(bytes, BLOCK_SIZE);
2240 let error = last_error(&frames);
2241 assert_eq!(error.position, position, "{case}");
2242 assert!(expected.matches(&error.inner), "{case}: {error:?}");
2243 }
2244 }
2245
2246 #[test]
2247 fn rejects_invalid_pax_sequences() {
2248 assert!(matches!(
2249 last_error_inner(&collect(header(b'x', 0).to_vec(), BLOCK_SIZE)),
2250 FrameErrorInner::InvalidPaxRecord {
2251 source: PaxError::InvalidRecords { .. },
2252 }
2253 ));
2254
2255 let valid = record("path", "name");
2256 let mut consecutive = Vec::new();
2257 append_pax(&mut consecutive, b'x', &valid);
2258 append_block(&mut consecutive, &header(b'x', valid.len() as u64));
2259 assert!(matches!(
2260 last_error_inner(&collect(consecutive, BLOCK_SIZE)),
2261 FrameErrorInner::UnexpectedOrder { .. }
2262 ));
2263
2264 let mut missing_member = Vec::new();
2265 append_pax(&mut missing_member, b'x', &valid);
2266 assert!(matches!(
2267 last_error_inner(&collect(missing_member, BLOCK_SIZE)),
2268 FrameErrorInner::UnexpectedEof { .. }
2269 ));
2270 }
2271
2272 #[test]
2273 fn preserves_pax_parse_error_positions_in_stream() {
2274 let invalid = record("size", "bad");
2275 let mut bytes = Vec::new();
2276 append_block(&mut bytes, &header(b'0', 0));
2277 append_pax(&mut bytes, b'x', &invalid);
2278
2279 let frames = collect(bytes, BLOCK_SIZE);
2280 assert!(matches!(
2281 frames.last(),
2282 Some(Err(FrameError {
2283 position,
2284 inner: FrameErrorInner::InvalidPaxRecord {
2285 source: PaxError::InvalidInteger { .. },
2286 },
2287 })) if *position == BLOCK_SIZE as u64
2288 ));
2289 }
2290
2291 #[test]
2292 fn accepts_binary_and_rejects_unknown_pax_charsets() {
2293 let mut global = record("hdrcharset", "BINARY");
2294 global.extend_from_slice(&record("path", "global"));
2295 let local = record("path", "local");
2296 let mut bytes = Vec::new();
2297 append_pax(&mut bytes, b'g', &global);
2298 append_pax(&mut bytes, b'x', &local);
2299 append_block(&mut bytes, &header(b'0', 0));
2300 append_terminator(&mut bytes);
2301 let frames = collect(bytes, BLOCK_SIZE);
2302 let member_header = header_frame(&frames, 4);
2303 assert_eq!(member_header.kind, UstarKind::Regular);
2304 assert_eq!(
2305 data_frame(&frames, 1).completed_pax_records(),
2306 Some(
2307 [
2308 PaxRecord::HdrCharset(PaxValue::Value(HdrCharset::Binary)),
2309 PaxRecord::Path(PaxValue::Value(PaxString::Binary(
2310 b"global".to_vec().into(),
2311 ))),
2312 ]
2313 .as_slice()
2314 )
2315 );
2316 assert_eq!(
2317 data_frame(&frames, 3).completed_pax_records(),
2318 Some(
2319 [PaxRecord::Path(PaxValue::Value(PaxString::Binary(
2320 b"local".to_vec().into()
2321 )))]
2322 .as_slice()
2323 )
2324 );
2325
2326 let records = record("hdrcharset", "ISO-IR 8859 1 1998");
2327 let mut bytes = Vec::new();
2328 append_pax(&mut bytes, b'x', &records);
2329 assert!(matches!(
2330 last_error_inner(&collect(bytes, BLOCK_SIZE)),
2331 FrameErrorInner::InvalidPaxRecord {
2332 source: PaxError::UnsupportedCharset { value },
2333 } if value == "ISO-IR 8859 1 1998"
2334 ));
2335 }
2336
2337 #[test]
2338 fn rejects_invalid_gnu_sequences_and_sizes() {
2339 let mut duplicate = Vec::new();
2340 append_block(&mut duplicate, &gnu_header(b'L', 0));
2341 append_block(&mut duplicate, &gnu_header(b'L', 0));
2342 let mut long_link_for_regular = Vec::new();
2343 append_block(&mut long_link_for_regular, &gnu_header(b'K', 0));
2344 append_block(&mut long_link_for_regular, &gnu_header(b'0', 0));
2345 let mut dangling = Vec::new();
2346 append_block(&mut dangling, &gnu_header(b'L', 0));
2347 append_terminator(&mut dangling);
2348 for (case, bytes) in [
2349 ("duplicate", duplicate),
2350 ("long-link-for-regular", long_link_for_regular),
2351 ("dangling", dangling),
2352 ] {
2353 assert!(
2354 matches!(
2355 last_error_inner(&collect(bytes, BLOCK_SIZE)),
2356 FrameErrorInner::UnexpectedOrder { .. }
2357 ),
2358 "{case}"
2359 );
2360 }
2361
2362 assert!(matches!(
2363 last_error_inner(&collect(gnu_header(b'S', 0).to_vec(), BLOCK_SIZE)),
2364 FrameErrorInner::UnsupportedTypeflag { typeflag: b'S' }
2365 ));
2366
2367 let mut negative_size = gnu_header(b'0', 0);
2368 negative_size[SIZE_RANGE].fill(0xff);
2369 set_checksum(&mut negative_size);
2370 assert!(matches!(
2371 last_error_inner(&collect(negative_size.to_vec(), BLOCK_SIZE)),
2372 FrameErrorInner::InvalidSize { .. }
2373 ));
2374 }
2375
2376 #[test]
2377 fn detects_one_archive_family_and_rejects_mixing() {
2378 let mut posix_then_gnu = Vec::new();
2379 append_block(&mut posix_then_gnu, &header(b'0', 0));
2380 append_block(&mut posix_then_gnu, &gnu_header(b'0', 0));
2381 assert!(matches!(
2382 last_error_inner(&collect(posix_then_gnu, BLOCK_SIZE)),
2383 FrameErrorInner::FormatMismatch {
2384 expected: ArchiveFormat::Pax,
2385 found: ArchiveFormat::Gnu,
2386 }
2387 ));
2388
2389 let mut malformed_gnu = gnu_header(b'0', 0);
2391 malformed_gnu[0] = b'X';
2392 let mut posix_then_malformed_gnu = Vec::new();
2393 append_block(&mut posix_then_malformed_gnu, &header(b'0', 0));
2394 append_block(&mut posix_then_malformed_gnu, &malformed_gnu);
2395 assert!(matches!(
2396 last_error_inner(&collect(posix_then_malformed_gnu, BLOCK_SIZE)),
2397 FrameErrorInner::InvalidChecksum { .. }
2398 ));
2399
2400 let mut gnu_then_posix = Vec::new();
2401 append_block(&mut gnu_then_posix, &gnu_header(b'0', 0));
2402 append_block(&mut gnu_then_posix, &header(b'0', 0));
2403 assert!(matches!(
2404 last_error_inner(&collect(gnu_then_posix, BLOCK_SIZE)),
2405 FrameErrorInner::FormatMismatch {
2406 expected: ArchiveFormat::Gnu,
2407 found: ArchiveFormat::Pax,
2408 }
2409 ));
2410
2411 for typeflag in *b"xg" {
2412 assert!(
2413 matches!(
2414 last_error_inner(&collect(gnu_header(typeflag, 0).to_vec(), BLOCK_SIZE)),
2415 FrameErrorInner::UnsupportedTypeflag { typeflag: found } if *found == typeflag
2416 ),
2417 "{typeflag:?}"
2418 );
2419 }
2420
2421 let mut empty = Vec::new();
2422 append_terminator(&mut empty);
2423 let mut stream = TarStream::new(ChunkedReader::new(empty, BLOCK_SIZE));
2424 assert!(matches!(ready(stream.next_frame()), Ok(None)));
2425 assert_eq!(stream.format(), None);
2426 }
2427
2428 #[test]
2429 fn rejects_truncation_and_invalid_termination() {
2430 assert!(matches!(
2431 last_error_inner(&collect(vec![0; 3], 1)),
2432 FrameErrorInner::IncompleteBlock { read: 3 }
2433 ));
2434
2435 let mut payload_truncated = Vec::new();
2436 append_block(&mut payload_truncated, &header(b'0', 1));
2437 assert!(matches!(
2438 last_error_inner(&collect(payload_truncated, BLOCK_SIZE)),
2439 FrameErrorInner::TruncatedPayload {
2440 owner: DataOwner::Member,
2441 ..
2442 }
2443 ));
2444
2445 let mut pax_payload_truncated = Vec::new();
2446 append_block(&mut pax_payload_truncated, &header(b'x', 513));
2447 append_payload(&mut pax_payload_truncated, b"11 path=x\n");
2448 assert!(matches!(
2449 last_error_inner(&collect(pax_payload_truncated, BLOCK_SIZE)),
2450 FrameErrorInner::TruncatedPayload {
2451 owner: DataOwner::Pax(PaxKind::Local),
2452 ..
2453 }
2454 ));
2455
2456 let mut missing_second_zero = Vec::new();
2457 append_block(&mut missing_second_zero, &header(b'0', 0));
2458 append_block(&mut missing_second_zero, &[0; BLOCK_SIZE]);
2459 assert!(matches!(
2460 last_error_inner(&collect(missing_second_zero, BLOCK_SIZE)),
2461 FrameErrorInner::MissingEndMarker
2462 ));
2463
2464 let mut bad_second_zero = Vec::new();
2465 append_block(&mut bad_second_zero, &header(b'0', 0));
2466 append_block(&mut bad_second_zero, &[0; BLOCK_SIZE]);
2467 append_block(&mut bad_second_zero, &header(b'0', 0));
2468 assert!(matches!(
2469 last_error_inner(&collect(bad_second_zero, BLOCK_SIZE)),
2470 FrameErrorInner::InvalidEndMarker
2471 ));
2472 }
2473
2474 #[test]
2475 fn stream_is_fused_after_first_error() {
2476 let mut stream = TarStream::new(ChunkedReader::new(header(b'L', 0).to_vec(), BLOCK_SIZE));
2477 assert!(matches!(
2478 ready(stream.next_frame()),
2479 Err(FrameError {
2480 position: 0,
2481 inner: FrameErrorInner::UnsupportedTypeflag { typeflag: b'L' },
2482 })
2483 ));
2484 assert!(matches!(ready(stream.next_frame()), Ok(None)));
2485 }
2486}