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