1use std::{borrow::Cow, mem, ops::Range};
8
9use tokio::io::AsyncRead;
10
11use crate::{
12 ArchiveFormat, Block, FrameError, FrameErrorInner, GnuKind, PaxKeyword, PaxKind, PaxRecord,
13 PaxString, PaxValue, StreamPolicy, UstarKind,
14 header::{GNAME_RANGE, LINK_NAME_RANGE, UNAME_RANGE},
15 pax::GlobalPaxRecords,
16 stream::{DataFrame, DataOwner, Frame, HeaderFrame, State, TarStream},
17};
18
19pub use crate::{PaxExtension, PaxState};
20
21const PAYLOAD_DRAIN_CHUNK_BYTES: usize = 1024 * 1024;
22
23#[derive(Clone, Debug, Eq, PartialEq)]
25pub struct GnuMetadata {
26 pub position: u64,
28 pub payload: Vec<u8>,
30}
31
32#[derive(Clone, Debug, Eq, PartialEq)]
34pub enum MemberExtensions<'a> {
35 Pax(PaxState<'a>),
38 Gnu {
40 long_name: Option<GnuMetadata>,
42 long_link: Option<GnuMetadata>,
44 },
45}
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
53pub struct Header<'a> {
54 pub position: u64,
56 pub format: ArchiveFormat,
58 pub kind: UstarKind,
60 pub declared_size: u64,
62 pub effective_size: u64,
68 pub mode: Option<u64>,
76 pub uid: Option<u64>,
83 pub gid: Option<u64>,
90 pub mtime: Option<u64>,
97 pub uname: &'a [u8],
101 pub gname: &'a [u8],
105 header_path: &'a [u8],
106 link_name: &'a [u8],
107}
108
109#[derive(Clone, Debug, Eq, PartialEq)]
111pub struct PayloadBlock {
112 pub position: u64,
114 pub block: Block,
116 pub len: usize,
118}
119
120pub struct MemberFrame<'a, R> {
122 pub header: Header<'a>,
124 pub extensions: MemberExtensions<'a>,
126 pub payload: MemberPayload<'a, R>,
128}
129
130impl<R> MemberFrame<'_, R> {
131 pub fn effective_path(&self) -> Result<Cow<'_, [u8]>, FrameError> {
137 let path = effective_member_path(&self.header, &self.extensions)?;
138 if path.is_empty() {
139 return Err(FrameError::at(
140 self.header.position,
141 FrameErrorInner::EmptyMemberPath,
142 ));
143 }
144 reject_nul(self.header.position, "path", path.as_ref())?;
145 Ok(path)
146 }
147
148 pub fn effective_link_path(&self) -> Result<Cow<'_, [u8]>, FrameError> {
154 let path = match &self.extensions {
155 MemberExtensions::Pax(state) => resolve_pax_text(
156 self.header.position,
157 state,
158 &PaxKeyword::LinkPath,
159 "linkpath",
160 Cow::Borrowed(self.header.link_name),
161 |record| match record {
162 PaxRecord::LinkPath(value) => Some(value),
163 _ => None,
164 },
165 ),
166 MemberExtensions::Gnu { long_link, .. } => match long_link {
167 Some(metadata) => Ok(Cow::Borrowed(parse_gnu_metadata(
168 metadata,
169 GnuKind::LongLink,
170 )?)),
171 None => Ok(Cow::Borrowed(self.header.link_name)),
172 },
173 }?;
174 reject_nul(self.header.position, "link path", path.as_ref())?;
175 Ok(path)
176 }
177}
178
179pub struct MemberPayload<'a, R> {
181 reader: &'a mut PayloadReader<R>,
182}
183
184pub struct TarReader<R> {
192 global_pax_records: Option<GlobalPaxRecords>,
197 payload: PayloadReader<R>,
198 header_storage: HeaderStorage,
199 pending_extensions: PendingExtensions,
200 extension_payload: Option<ExtensionPayload>,
201}
202
203struct PayloadReader<R> {
206 stream: TarStream<R>,
207 remaining: u64,
208 drain_buffer: Vec<u8>,
209}
210
211#[derive(Default)]
213struct PendingExtensions {
214 global_pax: Vec<PaxExtension>,
215 local_pax: Option<PaxExtension>,
216 gnu_long_name: Option<GnuMetadata>,
217 gnu_long_link: Option<GnuMetadata>,
218}
219
220impl PendingExtensions {
221 fn set_gnu(&mut self, kind: GnuKind, metadata: GnuMetadata) {
222 *match kind {
223 GnuKind::LongName => &mut self.gnu_long_name,
224 GnuKind::LongLink => &mut self.gnu_long_link,
225 } = Some(metadata);
226 }
227}
228
229enum ExtensionPayload {
231 Pax {
232 position: u64,
233 kind: PaxKind,
234 },
235 Gnu {
236 position: u64,
237 kind: GnuKind,
238 remaining: u64,
239 payload: Vec<u8>,
240 },
241}
242
243#[derive(Default)]
244struct HeaderStorage {
245 path: Vec<u8>,
246 link_name: Vec<u8>,
247 uname: Vec<u8>,
248 gname: Vec<u8>,
249}
250
251impl HeaderStorage {
252 fn update<'a>(&'a mut self, frame: &HeaderFrame) -> Header<'a> {
253 frame.copy_header_path_into(&mut self.path);
254 copy_string_field_into(&frame.block, LINK_NAME_RANGE, &mut self.link_name);
255 copy_string_field_into(&frame.block, UNAME_RANGE, &mut self.uname);
256 copy_string_field_into(&frame.block, GNAME_RANGE, &mut self.gname);
257 Header {
258 position: frame.position,
259 format: frame.format,
260 kind: frame.kind,
261 declared_size: frame.declared_size,
262 effective_size: frame.effective_size,
263 mode: frame.mode,
264 uid: frame.uid,
265 gid: frame.gid,
266 mtime: frame.mtime,
267 uname: &self.uname,
268 gname: &self.gname,
269 header_path: &self.path,
270 link_name: &self.link_name,
271 }
272 }
273}
274
275fn copy_string_field_into(block: &Block, range: Range<usize>, destination: &mut Vec<u8>) {
276 let field = &block[range];
277 let len = field
278 .iter()
279 .position(|byte| *byte == 0)
280 .unwrap_or(field.len());
281 destination.clear();
282 destination.extend_from_slice(&field[..len]);
283}
284
285impl<R> TarReader<R> {
286 pub fn new(reader: R) -> Self {
288 Self {
289 global_pax_records: None,
290 payload: PayloadReader {
291 stream: TarStream::new(reader),
292 remaining: 0,
293 drain_buffer: Vec::new(),
294 },
295 header_storage: HeaderStorage::default(),
296 pending_extensions: PendingExtensions::default(),
297 extension_payload: None,
298 }
299 }
300
301 pub fn with_policy(mut self, policy: StreamPolicy) -> Self {
305 self.payload.stream = self.payload.stream.with_policy(policy);
306 self
307 }
308}
309
310impl<R: AsyncRead + Unpin> TarReader<R> {
311 pub async fn next_frame(&mut self) -> Result<Option<MemberFrame<'_, R>>, FrameError> {
320 if matches!(self.payload.stream.state, State::Failed) {
321 self.clear_extension_state();
322 return Ok(None);
323 }
324
325 if let Err(error) = self.payload.drain_payload().await {
326 self.clear_extension_state();
327 return Err(error);
328 }
329
330 loop {
331 let frame = match self.payload.stream.next_frame().await {
332 Ok(Some(frame)) => frame,
333 Err(error) => {
334 self.clear_extension_state();
335 return Err(error);
336 }
337 Ok(None) => {
338 self.clear_extension_state();
339 return Ok(None);
340 }
341 };
342 match frame {
343 Frame::Pax(frame) => {
344 self.extension_payload = Some(ExtensionPayload::Pax {
345 position: frame.position,
346 kind: frame.kind,
347 });
348 }
349 Frame::Gnu(frame) => {
350 if frame.payload_size == 0 {
351 let metadata = GnuMetadata {
352 position: frame.position,
353 payload: Vec::new(),
354 };
355 self.pending_extensions.set_gnu(frame.kind, metadata);
356 } else {
357 self.extension_payload = Some(ExtensionPayload::Gnu {
358 position: frame.position,
359 kind: frame.kind,
360 remaining: frame.payload_size,
361 payload: Vec::new(),
362 });
363 }
364 }
365 Frame::Header(header) => {
366 let pending_extensions = mem::take(&mut self.pending_extensions);
367 let extensions = match header.format {
368 ArchiveFormat::Pax => MemberExtensions::Pax(PaxState::new(
369 self.global_pax_records.as_ref(),
370 pending_extensions.global_pax,
371 pending_extensions.local_pax,
372 )),
373 ArchiveFormat::Gnu => MemberExtensions::Gnu {
374 long_name: pending_extensions.gnu_long_name,
375 long_link: pending_extensions.gnu_long_link,
376 },
377 };
378 self.payload.remaining = header.effective_size;
379 let header = self.header_storage.update(&header);
380 return Ok(Some(MemberFrame {
381 header,
382 extensions,
383 payload: MemberPayload {
384 reader: &mut self.payload,
385 },
386 }));
387 }
388 Frame::Data(frame) => {
389 if let Err(error) = self.process_extension_data(frame) {
390 self.clear_extension_state();
391 return Err(error);
392 }
393 }
394 }
395 }
396 }
397
398 fn clear_extension_state(&mut self) {
399 self.pending_extensions = PendingExtensions::default();
400 self.extension_payload = None;
401 }
402
403 fn process_extension_data(&mut self, frame: DataFrame) -> Result<(), FrameError> {
404 let Some(payload) = self.extension_payload.take() else {
405 return Err(FrameError::unexpected_order(
406 frame.position,
407 "extension header or ordinary member header",
408 "unattached payload data",
409 ));
410 };
411 match payload {
412 ExtensionPayload::Pax { position, kind } => {
413 if frame.owner != DataOwner::Pax(kind) {
414 return Err(FrameError::unexpected_order(
415 frame.position,
416 "pax extension payload",
417 "different payload data",
418 ));
419 }
420 if let Some(records) = frame.into_completed_pax_records() {
421 match kind {
422 PaxKind::Global => {
423 records.apply_global(&mut self.global_pax_records);
424 self.pending_extensions
425 .global_pax
426 .push(PaxExtension::new(position, kind, records));
427 }
428 PaxKind::Local => {
429 self.pending_extensions.local_pax =
430 Some(PaxExtension::new(position, kind, records));
431 }
432 }
433 } else {
434 self.extension_payload = Some(ExtensionPayload::Pax { position, kind });
435 }
436 }
437 ExtensionPayload::Gnu {
438 position,
439 kind,
440 mut remaining,
441 mut payload,
442 } => {
443 if frame.owner != DataOwner::Gnu(kind) {
444 return Err(FrameError::unexpected_order(
445 frame.position,
446 "GNU metadata payload",
447 "different payload data",
448 ));
449 }
450 let len = u64::try_from(frame.len).map_err(|_| {
451 FrameError::arithmetic_overflow(frame.position, "GNU metadata payload length")
452 })?;
453 remaining = remaining.checked_sub(len).ok_or_else(|| {
454 FrameError::unexpected_order(
455 frame.position,
456 "bounded GNU metadata payload",
457 "oversized GNU metadata payload",
458 )
459 })?;
460 payload.extend_from_slice(&frame.block[..frame.len]);
461 if remaining == 0 {
462 let metadata = GnuMetadata { position, payload };
463 self.pending_extensions.set_gnu(kind, metadata);
464 } else {
465 self.extension_payload = Some(ExtensionPayload::Gnu {
466 position,
467 kind,
468 remaining,
469 payload,
470 });
471 }
472 }
473 }
474 Ok(())
475 }
476}
477
478impl<R: AsyncRead + Unpin> PayloadReader<R> {
479 async fn next_payload_block(&mut self) -> Result<Option<PayloadBlock>, FrameError> {
480 if self.remaining == 0 {
481 return Ok(None);
482 }
483 let (position, block, len) = self.stream.read_member_block().await?;
484 let payload_len = u64::try_from(len)
485 .map_err(|_| FrameError::arithmetic_overflow(position, "member payload length"))?;
486 self.remaining = self.remaining.checked_sub(payload_len).ok_or_else(|| {
487 FrameError::unexpected_order(
488 position,
489 "bounded member payload",
490 "oversized member payload",
491 )
492 })?;
493 Ok(Some(PayloadBlock {
494 position,
495 block,
496 len,
497 }))
498 }
499
500 async fn next_payload_chunk(
501 &mut self,
502 buffer: &mut Vec<u8>,
503 target_len: usize,
504 ) -> Result<bool, FrameError> {
505 if self.remaining == 0 {
506 return Ok(false);
507 }
508 let len = self.stream.read_member_chunk(buffer, target_len).await?;
509 let len = u64::try_from(len).map_err(|_| {
510 FrameError::arithmetic_overflow(self.stream.position, "member payload chunk length")
511 })?;
512 self.remaining = self.remaining.checked_sub(len).ok_or_else(|| {
513 FrameError::unexpected_order(
514 self.stream.position,
515 "bounded member payload",
516 "oversized member payload chunk",
517 )
518 })?;
519 Ok(true)
520 }
521
522 async fn drain_payload(&mut self) -> Result<(), FrameError> {
523 let mut buffer = mem::take(&mut self.drain_buffer);
524 let result = loop {
525 match self
526 .next_payload_chunk(&mut buffer, PAYLOAD_DRAIN_CHUNK_BYTES)
527 .await
528 {
529 Ok(true) => {}
530 Ok(false) => break Ok(()),
531 Err(error) => break Err(error),
532 }
533 };
534 self.drain_buffer = buffer;
535 result
536 }
537}
538
539impl<R: AsyncRead + Unpin> MemberPayload<'_, R> {
540 pub async fn next_block(&mut self) -> Result<Option<PayloadBlock>, FrameError> {
542 self.reader.next_payload_block().await
543 }
544
545 pub async fn next_chunk(
557 &mut self,
558 buffer: &mut Vec<u8>,
559 target_len: usize,
560 ) -> Result<bool, FrameError> {
561 self.reader.next_payload_chunk(buffer, target_len).await
562 }
563
564 pub async fn skip(self) -> Result<(), FrameError> {
566 self.reader.drain_payload().await
567 }
568}
569
570fn effective_member_path<'a>(
571 header: &Header<'a>,
572 extensions: &'a MemberExtensions<'_>,
573) -> Result<Cow<'a, [u8]>, FrameError> {
574 match extensions {
575 MemberExtensions::Pax(state) => resolve_pax_text(
576 header.position,
577 state,
578 &PaxKeyword::Path,
579 "path",
580 Cow::Borrowed(header.header_path),
581 |record| match record {
582 PaxRecord::Path(value) => Some(value),
583 _ => None,
584 },
585 ),
586 MemberExtensions::Gnu { long_name, .. } => match long_name {
587 Some(metadata) => Ok(Cow::Borrowed(parse_gnu_metadata(
588 metadata,
589 GnuKind::LongName,
590 )?)),
591 None => Ok(Cow::Borrowed(header.header_path)),
592 },
593 }
594}
595
596fn reject_nul(position: u64, field: &'static str, value: &[u8]) -> Result<(), FrameError> {
597 if value.contains(&0) {
598 return Err(FrameError::at(
599 position,
600 FrameErrorInner::NulInMemberName { field },
601 ));
602 }
603 Ok(())
604}
605
606fn resolve_pax_text<'a>(
607 position: u64,
608 state: &'a PaxState<'_>,
609 keyword: &PaxKeyword,
610 field: &'static str,
611 header_value: Cow<'a, [u8]>,
612 select: fn(&PaxRecord) -> Option<&PaxValue<PaxString>>,
613) -> Result<Cow<'a, [u8]>, FrameError> {
614 if let Some(value) = state.effective_record(keyword).and_then(select) {
615 return pax_value(position, field, value);
616 }
617 Ok(header_value)
618}
619
620fn pax_value<'a>(
623 position: u64,
624 keyword: &'static str,
625 value: &'a PaxValue<PaxString>,
626) -> Result<Cow<'a, [u8]>, FrameError> {
627 match value {
628 PaxValue::Value(PaxString::Utf8(value)) => Ok(Cow::Borrowed(value.as_bytes())),
629 PaxValue::Value(PaxString::Binary(value)) => Ok(Cow::Borrowed(value.as_ref())),
630 PaxValue::Deleted => Err(FrameError::deleted_pax_metadata(position, keyword)),
640 }
641}
642
643fn parse_gnu_metadata(metadata: &GnuMetadata, kind: GnuKind) -> Result<&[u8], FrameError> {
644 let terminator = metadata
645 .payload
646 .iter()
647 .position(|byte| *byte == 0)
648 .ok_or_else(|| {
649 FrameError::invalid_gnu_metadata(metadata.position, kind, "value is not NUL-terminated")
650 })?;
651
652 if metadata.payload[terminator..].iter().any(|byte| *byte != 0) {
655 return Err(FrameError::invalid_gnu_metadata(
656 metadata.position,
657 kind,
658 "non-NUL bytes follow the terminator",
659 ));
660 }
661 Ok(&metadata.payload[..terminator])
662}
663
664#[cfg(test)]
665mod tests {
666 use tokio::io::AsyncRead;
667
668 use super::*;
669 use crate::{
670 BLOCK_SIZE, DEFAULT_MAX_GNU_EXTENSION_SIZE, FrameError, FrameErrorInner, PaxRecord,
671 PaxValue,
672 header::{
673 GID_RANGE, GNAME_RANGE, LINK_NAME_RANGE, MODE_RANGE, MTIME_RANGE, NAME_RANGE,
674 PREFIX_RANGE, TYPEFLAG_OFFSET, UID_RANGE, UNAME_RANGE,
675 },
676 stream::DataOwner,
677 test_support::{
678 ChunkedReader, append_block, append_gnu, append_pax, append_payload, append_terminator,
679 cancel_pending, gnu_header, header, ready, ready_ok, record, set_checksum,
680 },
681 };
682
683 fn set_field(block: &mut Block, range: std::ops::Range<usize>, value: &[u8]) {
684 block[range.clone()].fill(0);
685 block[range.start..range.start + value.len()].copy_from_slice(value);
686 }
687
688 async fn next_member<R: AsyncRead + Unpin>(
689 reader: &mut TarReader<R>,
690 ) -> Result<MemberFrame<'_, R>, FrameError> {
691 let Some(member) = reader.next_frame().await? else {
692 panic!("expected logical member");
693 };
694 Ok(member)
695 }
696
697 fn pax_state<'a, R>(member: &'a MemberFrame<'_, R>) -> Option<&'a PaxState<'a>> {
698 if let MemberExtensions::Pax(state) = &member.extensions {
699 Some(state)
700 } else {
701 None
702 }
703 }
704
705 fn member_followed_by_empty_member(payload: &[u8]) -> (Vec<u8>, u64) {
706 let mut bytes = Vec::new();
707 append_pax(&mut bytes, b'0', payload);
708 let next_position = u64::try_from(bytes.len()).expect("test position should fit u64");
709 append_block(&mut bytes, &header(b'0', 0));
710 append_terminator(&mut bytes);
711 (bytes, next_position)
712 }
713
714 #[test]
715 fn exposes_ordinary_header_metadata_and_decodes_modes() {
716 let mut ustar_header = header(b'2', 0);
717 set_field(&mut ustar_header, NAME_RANGE, b"file");
718 set_field(&mut ustar_header, PREFIX_RANGE, b"dir");
719 set_field(&mut ustar_header, LINK_NAME_RANGE, b"target");
720 ustar_header[MODE_RANGE].copy_from_slice(b"0100644\0");
721 ustar_header[UID_RANGE].copy_from_slice(b"0000001\0");
722 ustar_header[GID_RANGE].copy_from_slice(b"0000002\0");
723 ustar_header[MTIME_RANGE].copy_from_slice(b"00000000003\0");
724 set_field(&mut ustar_header, UNAME_RANGE, b"user");
725 set_field(&mut ustar_header, GNAME_RANGE, b"group");
726 set_checksum(&mut ustar_header);
727
728 let mut empty_header = header(b'0', 0);
729 for range in [
730 MODE_RANGE,
731 UID_RANGE,
732 GID_RANGE,
733 MTIME_RANGE,
734 UNAME_RANGE,
735 GNAME_RANGE,
736 ] {
737 empty_header[range].fill(0);
738 }
739 set_checksum(&mut empty_header);
740
741 ready_ok(async {
742 let mut bytes = Vec::new();
743 append_block(&mut bytes, &ustar_header);
744 append_block(&mut bytes, &empty_header);
745 append_terminator(&mut bytes);
746 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
747 {
748 let member = next_member(&mut reader).await?;
749 assert_eq!(member.header.format, ArchiveFormat::Pax);
750 assert_eq!(member.header.header_path, b"dir/file");
751 assert_eq!(member.header.link_name, b"target");
752 assert_eq!(member.header.mode, Some(0o100644));
753 assert_eq!(member.header.uid, Some(1));
754 assert_eq!(member.header.gid, Some(2));
755 assert_eq!(member.header.mtime, Some(3));
756 assert_eq!(member.header.uname, b"user");
757 assert_eq!(member.header.gname, b"group");
758 assert_eq!(member.effective_path()?.as_ref(), b"dir/file");
759 assert_eq!(member.effective_link_path()?.as_ref(), b"target");
760 }
761 let member = next_member(&mut reader).await?;
762 assert_eq!(member.header.mode, None);
763 assert_eq!(member.header.uid, None);
764 assert_eq!(member.header.gid, None);
765 assert_eq!(member.header.mtime, None);
766 assert!(member.header.uname.is_empty());
767 assert!(member.header.gname.is_empty());
768 Ok(())
769 });
770
771 let mut gnu_member_header = gnu_header(b'0', 0);
772 set_field(&mut gnu_member_header, NAME_RANGE, b"name");
773 set_field(&mut gnu_member_header, PREFIX_RANGE, b"ignored");
774 gnu_member_header[MODE_RANGE].fill(0);
775 gnu_member_header[MODE_RANGE.start] = 0x80;
776 gnu_member_header[MODE_RANGE.end - 2..MODE_RANGE.end].copy_from_slice(&[0x81, 0xa4]);
777 set_checksum(&mut gnu_member_header);
778
779 let mut empty_gnu_header = gnu_header(b'0', 0);
780 for range in [MODE_RANGE, UID_RANGE, GID_RANGE, MTIME_RANGE] {
781 empty_gnu_header[range].fill(0);
782 }
783 set_checksum(&mut empty_gnu_header);
784
785 ready_ok(async {
786 let mut bytes = Vec::new();
787 append_block(&mut bytes, &gnu_member_header);
788 append_block(&mut bytes, &empty_gnu_header);
789 append_terminator(&mut bytes);
790 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
791 {
792 let member = next_member(&mut reader).await?;
793 assert_eq!(member.header.format, ArchiveFormat::Gnu);
794 assert_eq!(member.header.header_path, b"name");
795 assert_eq!(member.header.mode, Some(0o100644));
796 assert_eq!(member.header.uid, Some(0));
797 assert_eq!(member.header.gid, Some(0));
798 assert_eq!(member.header.mtime, Some(0));
799 }
800 let member = next_member(&mut reader).await?;
801 assert_eq!(member.header.mode, None);
802 assert_eq!(member.header.uid, None);
803 assert_eq!(member.header.gid, None);
804 assert_eq!(member.header.mtime, None);
805 Ok(())
806 });
807 }
808
809 #[test]
810 fn preserves_ustar_separator_when_name_is_empty() {
811 let mut ustar_header = header(b'5', 0);
812 set_field(&mut ustar_header, NAME_RANGE, b"");
813 set_field(&mut ustar_header, PREFIX_RANGE, b"victim");
814 set_checksum(&mut ustar_header);
815
816 ready_ok(async {
817 let mut bytes = Vec::new();
818 append_block(&mut bytes, &ustar_header);
819 append_terminator(&mut bytes);
820 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
821 let member = next_member(&mut reader).await?;
822 assert_eq!(member.header.header_path, b"victim/");
823 assert_eq!(member.effective_path()?.as_ref(), b"victim/");
824 Ok(())
825 });
826 }
827
828 #[test]
829 fn keeps_borrowed_header_metadata_available_while_streaming_payload() {
830 let mut member_header = header(b'0', 1);
831 set_field(&mut member_header, NAME_RANGE, b"file");
832 set_field(&mut member_header, PREFIX_RANGE, b"dir");
833 set_field(&mut member_header, LINK_NAME_RANGE, b"target");
834 member_header[MODE_RANGE].copy_from_slice(b"0000755\0");
835 set_checksum(&mut member_header);
836
837 ready_ok(async {
838 let mut bytes = Vec::new();
839 append_block(&mut bytes, &member_header);
840 append_payload(&mut bytes, b"x");
841 append_terminator(&mut bytes);
842 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
843 let mut member = next_member(&mut reader).await?;
844
845 assert!(member.payload.next_block().await?.is_some());
846 assert_eq!(member.header.header_path, b"dir/file");
847 assert_eq!(member.header.link_name, b"target");
848 assert_eq!(member.header.mode, Some(0o755));
849 assert_eq!(member.effective_path()?.as_ref(), b"dir/file");
850 assert_eq!(member.effective_link_path()?.as_ref(), b"target");
851 Ok(())
852 });
853 }
854
855 #[test]
856 fn resolves_pax_path_precedence_and_deletions() {
857 let mut global = record("path", "global");
858 global.extend_from_slice(&record("linkpath", "global-link"));
859 let mut local = record("path", "local");
860 local.extend_from_slice(&record("linkpath", ""));
861 let mut bytes = Vec::new();
862 append_pax(&mut bytes, b'g', &global);
863 append_pax(&mut bytes, b'x', &local);
864 append_block(&mut bytes, &header(b'2', 0));
865 append_block(&mut bytes, &header(b'2', 0));
866 append_terminator(&mut bytes);
867
868 ready_ok(async {
869 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
870 {
871 let member = next_member(&mut reader).await?;
872 assert_eq!(member.effective_path()?.as_ref(), b"local");
873 assert!(matches!(
874 member.effective_link_path(),
875 Err(FrameError {
876 position: 2048,
877 inner: FrameErrorInner::DeletedPaxMetadata {
878 keyword: "linkpath"
879 },
880 })
881 ));
882 }
883 let member = next_member(&mut reader).await?;
884 assert_eq!(member.effective_path()?.as_ref(), b"global");
885 assert_eq!(member.effective_link_path()?.as_ref(), b"global-link");
886 Ok(())
887 });
888 }
889
890 #[test]
891 fn rejects_empty_effective_member_paths() {
892 for (case, mut bytes) in [
893 ("pax-header", {
894 let mut bytes = Vec::new();
895 let mut member = header(b'0', 0);
896 set_field(&mut member, NAME_RANGE, b"");
897 set_field(&mut member, PREFIX_RANGE, b"");
898 set_checksum(&mut member);
899 append_block(&mut bytes, &member);
900 bytes
901 }),
902 ("gnu-header", {
903 let mut bytes = Vec::new();
904 let mut member = gnu_header(b'0', 0);
905 set_field(&mut member, NAME_RANGE, b"");
906 set_checksum(&mut member);
907 append_block(&mut bytes, &member);
908 bytes
909 }),
910 ("gnu-long-name", {
911 let mut bytes = Vec::new();
912 append_gnu(&mut bytes, b'L', b"\0");
913 let mut member = gnu_header(b'0', 0);
914 set_field(&mut member, NAME_RANGE, b"physical");
915 set_checksum(&mut member);
916 append_block(&mut bytes, &member);
917 bytes
918 }),
919 ] {
920 append_terminator(&mut bytes);
921 let result: Result<(), FrameError> = ready(async {
922 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
923 let member = next_member(&mut reader).await?;
924 member.effective_path().map(|_| ())
925 });
926 assert!(
927 matches!(
928 result,
929 Err(FrameError {
930 inner: FrameErrorInner::EmptyMemberPath,
931 ..
932 })
933 ),
934 "{case}: {result:?}"
935 );
936 }
937 }
938
939 #[test]
940 fn rejects_nul_in_effective_member_names() {
941 for (field, mut bytes) in [
942 ("path", {
943 let mut bytes = Vec::new();
944 append_pax(&mut bytes, b'x', &record("path", "bad\0name"));
945 append_block(&mut bytes, &header(b'0', 0));
946 bytes
947 }),
948 ("link path", {
949 let mut bytes = Vec::new();
950 append_pax(&mut bytes, b'x', &record("linkpath", "bad\0target"));
951 append_block(&mut bytes, &header(b'2', 0));
952 bytes
953 }),
954 ] {
955 append_terminator(&mut bytes);
956 let result: Result<(), FrameError> = ready(async {
957 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
958 let member = next_member(&mut reader).await?;
959 if field == "path" {
960 member.effective_path().map(|_| ())
961 } else {
962 member.effective_link_path().map(|_| ())
963 }
964 });
965 assert!(
966 matches!(
967 result,
968 Err(FrameError {
969 inner: FrameErrorInner::NulInMemberName { field: found },
970 ..
971 }) if found == field
972 ),
973 "{field}: {result:?}"
974 );
975 }
976 }
977
978 #[test]
979 fn ignores_nul_in_overridden_pax_member_names() {
980 let mut global = record("path", "bad\0name");
981 global.extend_from_slice(&record("linkpath", "bad\0target"));
982 let mut local = record("path", "good-name");
983 local.extend_from_slice(&record("linkpath", "good-target"));
984 let mut bytes = Vec::new();
985 append_pax(&mut bytes, b'g', &global);
986 append_pax(&mut bytes, b'x', &local);
987 append_block(&mut bytes, &header(b'2', 0));
988 append_terminator(&mut bytes);
989
990 ready_ok(async {
991 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
992 let member = next_member(&mut reader).await?;
993 assert_eq!(member.effective_path()?.as_ref(), b"good-name");
994 assert_eq!(member.effective_link_path()?.as_ref(), b"good-target");
995 Ok(())
996 });
997 }
998
999 #[test]
1000 fn accepts_nonempty_extension_paths_over_empty_header_names() {
1001 for (case, mut bytes, expected) in [
1002 (
1003 "pax",
1004 {
1005 let mut bytes = Vec::new();
1006 append_pax(&mut bytes, b'x', &record("path", "pax-name"));
1007 let mut member = header(b'0', 0);
1008 set_field(&mut member, NAME_RANGE, b"");
1009 set_field(&mut member, PREFIX_RANGE, b"");
1010 set_checksum(&mut member);
1011 append_block(&mut bytes, &member);
1012 bytes
1013 },
1014 b"pax-name".as_slice(),
1015 ),
1016 (
1017 "gnu",
1018 {
1019 let mut bytes = Vec::new();
1020 append_gnu(&mut bytes, b'L', b"gnu-name\0");
1021 let mut member = gnu_header(b'0', 0);
1022 set_field(&mut member, NAME_RANGE, b"");
1023 set_checksum(&mut member);
1024 append_block(&mut bytes, &member);
1025 bytes
1026 },
1027 b"gnu-name".as_slice(),
1028 ),
1029 ] {
1030 append_terminator(&mut bytes);
1031 ready_ok(async {
1032 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
1033 let member = next_member(&mut reader).await?;
1034 assert_eq!(member.effective_path()?.as_ref(), expected, "{case}");
1035 Ok(())
1036 });
1037 }
1038 }
1039
1040 #[test]
1041 fn global_path_deletion_suppresses_the_physical_header_path() {
1042 let mut physical_header = header(b'0', 0);
1043 set_field(&mut physical_header, NAME_RANGE, b"physical");
1044 set_checksum(&mut physical_header);
1045
1046 let mut bytes = Vec::new();
1047 append_pax(&mut bytes, b'g', &record("path", "global"));
1048 append_block(&mut bytes, &header(b'0', 0));
1049 append_pax(&mut bytes, b'g', &record("path", ""));
1050 append_block(&mut bytes, &physical_header);
1051 append_terminator(&mut bytes);
1052
1053 ready_ok(async {
1054 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
1055 {
1056 let member = next_member(&mut reader).await?;
1057 assert_eq!(member.effective_path()?.as_ref(), b"global");
1058 }
1059
1060 let member = next_member(&mut reader).await?;
1061 assert!(matches!(
1062 member.effective_path(),
1063 Err(FrameError {
1064 inner: FrameErrorInner::DeletedPaxMetadata { keyword: "path" },
1065 ..
1066 })
1067 ));
1068 let state = pax_state(&member).expect("expected pax member metadata");
1069 assert_eq!(
1070 state.effective_record(&PaxKeyword::Path),
1071 Some(&PaxRecord::Path(PaxValue::Deleted))
1072 );
1073 let extensions = state.extensions().collect::<Vec<_>>();
1074 assert_eq!(extensions.len(), 1);
1075 assert!(matches!(
1076 extensions[0].records(),
1077 [PaxRecord::Path(PaxValue::Deleted)]
1078 ));
1079 Ok(())
1080 });
1081 }
1082
1083 #[test]
1084 fn resolves_and_validates_gnu_metadata_lazily() {
1085 let mut bytes = Vec::new();
1086 append_block(&mut bytes, &gnu_header(b'L', 5));
1087 append_payload(&mut bytes, b"name\0");
1088 append_block(&mut bytes, &gnu_header(b'K', 5));
1089 append_payload(&mut bytes, b"link\0");
1090 append_block(&mut bytes, &gnu_header(b'2', 0));
1091 append_terminator(&mut bytes);
1092
1093 ready_ok(async {
1094 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
1095 let member = next_member(&mut reader).await?;
1096 assert_eq!(member.effective_path()?.as_ref(), b"name");
1097 assert_eq!(member.effective_link_path()?.as_ref(), b"link");
1098 Ok(())
1099 });
1100
1101 for (typeflag, payload, kind) in [
1102 (b'L', b"no-nul".as_slice(), GnuKind::LongName),
1103 (b'K', b"link\0bad".as_slice(), GnuKind::LongLink),
1104 ] {
1105 let mut bytes = Vec::new();
1106 append_gnu(&mut bytes, typeflag, payload);
1107 append_block(&mut bytes, &gnu_header(b'2', 0));
1108 append_terminator(&mut bytes);
1109 let result: Result<(), FrameError> = ready(async {
1110 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
1111 let member = next_member(&mut reader).await?;
1112 match kind {
1113 GnuKind::LongName => member.effective_path().map(|_| ()),
1114 GnuKind::LongLink => member.effective_link_path().map(|_| ()),
1115 }
1116 });
1117 assert!(matches!(
1118 result,
1119 Err(FrameError {
1120 position: 0,
1121 inner: FrameErrorInner::InvalidGnuMetadata { kind: found, .. },
1122 }) if found == kind
1123 ));
1124 }
1125 }
1126
1127 #[test]
1128 fn groups_pax_metadata_and_streams_member_payload() {
1129 let mut global = record("comment", "first");
1130 global.extend_from_slice(&record("comment", "last"));
1131 let mut local = record("path", "renamed");
1132 local.extend_from_slice(&record("size", "513"));
1133 let mut bytes = Vec::new();
1134 append_pax(&mut bytes, b'g', &global);
1135 append_pax(&mut bytes, b'x', &local);
1136 append_block(&mut bytes, &header(b'0', 1));
1137 append_payload(&mut bytes, &[b'a'; BLOCK_SIZE]);
1138 append_payload(&mut bytes, b"b");
1139 append_terminator(&mut bytes);
1140
1141 ready_ok(async {
1142 let mut reader = TarReader::new(ChunkedReader::new(bytes, 17));
1143 {
1144 let mut member = next_member(&mut reader).await?;
1145 assert_eq!(member.header.effective_size, 513);
1146 let state = pax_state(&member).expect("expected pax member metadata");
1147 let extensions = state.extensions().collect::<Vec<_>>();
1148 assert_eq!(extensions.len(), 2);
1149 assert_eq!(extensions[0].position, 0);
1150 assert_eq!(extensions[0].kind, PaxKind::Global);
1151 assert_eq!(
1152 extensions[0].records(),
1153 [
1154 PaxRecord::Comment(PaxValue::Value("first".into())),
1155 PaxRecord::Comment(PaxValue::Value("last".into())),
1156 ]
1157 );
1158 assert_eq!(extensions[1].position, (BLOCK_SIZE * 2) as u64);
1159 assert_eq!(extensions[1].kind, PaxKind::Local);
1160 assert_eq!(
1161 state.effective_record(&PaxKeyword::Size),
1162 Some(&PaxRecord::Size(PaxValue::Value(513)))
1163 );
1164 assert_eq!(
1165 state.effective_record(&PaxKeyword::Comment),
1166 Some(&PaxRecord::Comment(PaxValue::Value("last".into())))
1167 );
1168 let Some(first) = member.payload.next_block().await? else {
1169 panic!("expected first member payload block");
1170 };
1171 let Some(last) = member.payload.next_block().await? else {
1172 panic!("expected last member payload block");
1173 };
1174 assert_eq!(first.len, BLOCK_SIZE);
1175 assert_eq!(last.len, 1);
1176 assert!(member.payload.next_block().await?.is_none());
1177 }
1178 assert!(reader.next_frame().await?.is_none());
1179 Ok(())
1180 });
1181 }
1182
1183 #[test]
1184 fn bounds_cumulative_global_pax_extension_payloads() {
1185 let payload = record("comment", "metadata");
1186 let payload_size = u64::try_from(payload.len()).expect("payload size should fit u64");
1187 let limit = payload_size
1188 .checked_mul(2)
1189 .expect("test payload total should fit u64");
1190
1191 let mut rejected = Vec::new();
1192 append_pax(&mut rejected, b'g', &payload);
1193 append_pax(&mut rejected, b'g', &payload);
1194 let rejected_position =
1195 u64::try_from(rejected.len()).expect("test position should fit u64");
1196 append_block(&mut rejected, &header(b'g', payload_size));
1197 let error: Result<(), FrameError> = ready(async {
1198 let mut reader = TarReader::new(ChunkedReader::new(rejected, BLOCK_SIZE))
1199 .with_policy(StreamPolicy::default().max_global_pax_extensions_size(limit));
1200 reader.next_frame().await.map(|_| ())
1201 });
1202 assert!(matches!(
1203 error,
1204 Err(FrameError {
1205 position,
1206 inner: FrameErrorInner::GlobalPaxExtensionsTooLarge {
1207 size,
1208 limit: found_limit,
1209 },
1210 }) if position == rejected_position
1211 && size == payload_size * 3
1212 && found_limit == limit
1213 ));
1214
1215 let mut accepted = Vec::new();
1216 for _ in 0..2 {
1217 for _ in 0..3 {
1218 append_pax(&mut accepted, b'g', &payload);
1219 }
1220 append_block(&mut accepted, &header(b'0', 0));
1221 }
1222 append_terminator(&mut accepted);
1223 ready_ok(async {
1224 let mut reader = TarReader::new(ChunkedReader::new(accepted, BLOCK_SIZE)).with_policy(
1225 StreamPolicy::default().max_global_pax_extensions_size(payload_size * 3),
1226 );
1227 for _ in 0..2 {
1228 let member = next_member(&mut reader).await?;
1229 assert_eq!(
1230 pax_state(&member)
1231 .expect("expected pax member metadata")
1232 .extensions()
1233 .count(),
1234 3
1235 );
1236 }
1237 Ok(())
1238 });
1239 }
1240
1241 #[test]
1242 fn retains_global_pax_extension_across_cancelled_reads() {
1243 let mut bytes = Vec::new();
1244 append_pax(&mut bytes, b'g', &record("comment", "metadata"));
1245 let after_extension_header = BLOCK_SIZE;
1246 let after_extension_payload = bytes.len();
1247 append_block(&mut bytes, &header(b'0', 0));
1248 append_terminator(&mut bytes);
1249
1250 for pending_at in [after_extension_header, after_extension_payload] {
1251 let mut reader = TarReader::new(ChunkedReader::pending_once(bytes.clone(), pending_at));
1252 cancel_pending(reader.next_frame());
1253
1254 ready_ok(async {
1255 let member = next_member(&mut reader).await?;
1256 let state = pax_state(&member).expect("expected pax member metadata");
1257 let extensions = state.extensions().collect::<Vec<_>>();
1258 assert_eq!(extensions.len(), 1);
1259 assert_eq!(extensions[0].position, 0);
1260 assert_eq!(extensions[0].kind, PaxKind::Global);
1261 assert_eq!(
1262 extensions[0].records(),
1263 &[PaxRecord::Comment(PaxValue::Value("metadata".into()))]
1264 );
1265 Ok(())
1266 });
1267 }
1268 }
1269
1270 #[test]
1271 fn retains_gnu_metadata_across_cancelled_reads() {
1272 let expected_name = vec![b'n'; BLOCK_SIZE + 10];
1273 let mut long_name = expected_name.clone();
1274 long_name.push(0);
1275
1276 let mut bytes = Vec::new();
1277 append_gnu(&mut bytes, b'L', &long_name);
1278 let after_first_payload_block = BLOCK_SIZE * 2;
1279 append_block(&mut bytes, &gnu_header(b'0', 0));
1280 append_terminator(&mut bytes);
1281
1282 let mut reader = TarReader::new(ChunkedReader::pending_once(
1283 bytes,
1284 after_first_payload_block,
1285 ));
1286 cancel_pending(reader.next_frame());
1287
1288 ready_ok(async {
1289 let member = next_member(&mut reader).await?;
1290 assert_eq!(member.effective_path()?.as_ref(), expected_name);
1291 Ok(())
1292 });
1293
1294 let mut bytes = Vec::new();
1295 append_gnu(&mut bytes, b'L', &[]);
1296 let after_extension_header = bytes.len();
1297 append_block(&mut bytes, &gnu_header(b'0', 0));
1298 append_terminator(&mut bytes);
1299 let mut reader = TarReader::new(ChunkedReader::pending_once(bytes, after_extension_header));
1300 cancel_pending(reader.next_frame());
1301
1302 ready_ok(async {
1303 let member = next_member(&mut reader).await?;
1304 assert!(matches!(
1305 &member.extensions,
1306 MemberExtensions::Gnu {
1307 long_name: Some(GnuMetadata { payload, .. }),
1308 ..
1309 } if payload.is_empty()
1310 ));
1311 Ok(())
1312 });
1313 }
1314
1315 #[test]
1316 fn applies_global_pax_updates_to_each_borrowed_state() {
1317 let first = record("comment", "first");
1318 let second = record("gname", "second");
1319 let replacement = record("comment", "replacement");
1320 let mut bytes = Vec::new();
1321 append_pax(&mut bytes, b'g', &first);
1322 append_pax(&mut bytes, b'g', &second);
1323 append_block(&mut bytes, &header(b'0', 0));
1324 append_block(&mut bytes, &header(b'0', 0));
1325 append_pax(&mut bytes, b'g', &replacement);
1326 append_block(&mut bytes, &header(b'0', 0));
1327 append_terminator(&mut bytes);
1328
1329 ready_ok(async {
1330 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
1331 {
1332 let member = next_member(&mut reader).await?;
1333 let state = pax_state(&member).expect("expected pax member metadata");
1334 let extensions = state.extensions().collect::<Vec<_>>();
1335 assert_eq!(extensions.len(), 2);
1336 assert_eq!(extensions[0].position, 0);
1337 assert_eq!(extensions[1].position, (BLOCK_SIZE * 2) as u64);
1338 assert_eq!(
1339 state.effective_record(&PaxKeyword::Comment),
1340 Some(&PaxRecord::Comment(PaxValue::Value("first".into())))
1341 );
1342 }
1343 {
1344 let member = next_member(&mut reader).await?;
1345 let state = pax_state(&member).expect("expected pax member metadata");
1346 assert_eq!(state.extensions().count(), 0);
1347 assert_eq!(
1348 state.effective_record(&PaxKeyword::Comment),
1349 Some(&PaxRecord::Comment(PaxValue::Value("first".into())))
1350 );
1351 }
1352
1353 let member = next_member(&mut reader).await?;
1354 let state = pax_state(&member).expect("expected pax member metadata");
1355 let extensions = state.extensions().collect::<Vec<_>>();
1356 assert_eq!(extensions.len(), 1);
1357 assert_eq!(extensions[0].kind, PaxKind::Global);
1358 assert_eq!(
1359 state.effective_record(&PaxKeyword::Comment),
1360 Some(&PaxRecord::Comment(PaxValue::Value("replacement".into())))
1361 );
1362 Ok(())
1363 });
1364 }
1365
1366 #[test]
1367 fn streams_member_payload_in_reusable_chunks() {
1368 let payload = (0..BLOCK_SIZE * 3 + 7)
1369 .map(|index| u8::try_from(index % 251).unwrap())
1370 .collect::<Vec<_>>();
1371 let mut bytes = Vec::new();
1372 append_pax(&mut bytes, b'0', &payload);
1373 append_terminator(&mut bytes);
1374
1375 ready_ok(async {
1376 let mut reader = TarReader::new(ChunkedReader::new(bytes, 17));
1377 let mut member = next_member(&mut reader).await?;
1378 let mut chunk = vec![b'x'; BLOCK_SIZE * 2];
1379 assert!(
1380 member
1381 .payload
1382 .next_chunk(&mut chunk, BLOCK_SIZE + 1)
1383 .await?
1384 );
1385 let allocation = chunk.as_ptr();
1386 assert_eq!(chunk, payload[..BLOCK_SIZE * 2]);
1387 assert!(
1388 member
1389 .payload
1390 .next_chunk(&mut chunk, BLOCK_SIZE + 1)
1391 .await?
1392 );
1393 assert_eq!(chunk.as_ptr(), allocation);
1394 assert_eq!(chunk, payload[BLOCK_SIZE * 2..]);
1395 assert!(
1396 !member
1397 .payload
1398 .next_chunk(&mut chunk, BLOCK_SIZE + 1)
1399 .await?
1400 );
1401 assert_eq!(chunk, payload[BLOCK_SIZE * 2..]);
1402 assert!(reader.next_frame().await?.is_none());
1403 Ok(())
1404 });
1405 }
1406
1407 #[test]
1408 fn resumes_cancelled_member_payload_chunk_with_either_read_api() {
1409 let payload = (0..BLOCK_SIZE * 2 + 17)
1410 .map(|index| u8::try_from(index % 251).expect("test byte should fit"))
1411 .collect::<Vec<_>>();
1412 let (bytes, next_member_position) = member_followed_by_empty_member(&payload);
1413
1414 ready_ok(async {
1415 let mut reader = TarReader::new(ChunkedReader::pending_once(bytes, BLOCK_SIZE + 73));
1416 {
1417 let mut member = next_member(&mut reader).await?;
1418 let mut cancelled_buffer = vec![b'x'; 17];
1419 cancel_pending(
1420 member
1421 .payload
1422 .next_chunk(&mut cancelled_buffer, payload.len()),
1423 );
1424 assert!(cancelled_buffer.is_empty());
1425
1426 let first = member
1427 .payload
1428 .next_block()
1429 .await?
1430 .expect("cancelled chunk should resume as a payload block");
1431 let mut resumed_buffer = vec![b'y'; 23];
1432 assert!(member.payload.next_chunk(&mut resumed_buffer, 1).await?);
1433 let mut observed = first.block[..first.len].to_vec();
1434 observed.extend_from_slice(&resumed_buffer);
1435 assert_eq!(observed, payload);
1436 assert!(!member.payload.next_chunk(&mut resumed_buffer, 1).await?);
1437 }
1438
1439 let member = next_member(&mut reader).await?;
1440 assert_eq!(member.header.position, next_member_position);
1441 Ok(())
1442 });
1443 }
1444
1445 #[test]
1446 fn resumes_cancelled_member_payload_block_during_automatic_drain() {
1447 let payload = vec![b'x'; BLOCK_SIZE * 2 + 17];
1448 let (bytes, next_member_position) = member_followed_by_empty_member(&payload);
1449
1450 ready_ok(async {
1451 let mut reader = TarReader::new(ChunkedReader::pending_once(bytes, BLOCK_SIZE + 73));
1452 {
1453 let mut member = next_member(&mut reader).await?;
1454 cancel_pending(member.payload.next_block());
1455 }
1456
1457 let member = next_member(&mut reader).await?;
1458 assert_eq!(member.header.position, next_member_position);
1459 drop(member);
1460 assert!(reader.next_frame().await?.is_none());
1461 Ok(())
1462 });
1463 }
1464
1465 #[test]
1466 fn resumes_cancelled_automatic_payload_drain() {
1467 let payload = vec![b'x'; BLOCK_SIZE * 2 + 17];
1468 let (bytes, next_member_position) = member_followed_by_empty_member(&payload);
1469
1470 ready_ok(async {
1471 let mut reader = TarReader::new(ChunkedReader::pending_once(bytes, BLOCK_SIZE + 73));
1472 drop(next_member(&mut reader).await?);
1473 cancel_pending(reader.next_frame());
1474
1475 let member = next_member(&mut reader).await?;
1476 assert_eq!(member.header.position, next_member_position);
1477 drop(member);
1478 assert!(reader.next_frame().await?.is_none());
1479 Ok(())
1480 });
1481 }
1482
1483 #[test]
1484 fn reports_cancelled_chunk_errors_at_physical_block_boundaries() {
1485 #[derive(Clone, Copy, Debug)]
1486 enum ExpectedError {
1487 TruncatedPayload,
1488 IncompleteBlock,
1489 }
1490
1491 for (expected, trailing_byte) in [
1492 (ExpectedError::TruncatedPayload, None),
1493 (ExpectedError::IncompleteBlock, Some(b'x')),
1494 ] {
1495 let mut bytes = Vec::new();
1496 append_block(&mut bytes, &header(b'0', (BLOCK_SIZE + 1) as u64));
1497 append_payload(&mut bytes, b"payload");
1498 if let Some(trailing_byte) = trailing_byte {
1499 bytes.push(trailing_byte);
1500 }
1501 let error = ready(async {
1502 let mut reader =
1503 TarReader::new(ChunkedReader::pending_once(bytes, BLOCK_SIZE + 73));
1504 let Ok(Some(mut member)) = reader.next_frame().await else {
1505 panic!("expected member");
1506 };
1507 let mut buffer = Vec::new();
1508 cancel_pending(member.payload.next_chunk(&mut buffer, BLOCK_SIZE * 2));
1509 member.payload.next_chunk(&mut buffer, BLOCK_SIZE * 2).await
1510 });
1511 let Err(FrameError { position, inner }) = &error else {
1512 panic!("{expected:?}: expected error, got {error:?}");
1513 };
1514 assert_eq!(*position, (BLOCK_SIZE * 2) as u64, "{expected:?}");
1515 assert!(
1516 matches!(
1517 (expected, inner),
1518 (
1519 ExpectedError::TruncatedPayload,
1520 FrameErrorInner::TruncatedPayload {
1521 owner: DataOwner::Member,
1522 remaining: 1,
1523 },
1524 ) | (
1525 ExpectedError::IncompleteBlock,
1526 FrameErrorInner::IncompleteBlock { read: 1 },
1527 )
1528 ),
1529 "{expected:?}: {error:?}"
1530 );
1531 }
1532 }
1533
1534 #[test]
1535 fn groups_gnu_metadata_with_its_member() {
1536 let mut bytes = Vec::new();
1537 append_block(&mut bytes, &gnu_header(b'L', 5));
1538 append_payload(&mut bytes, b"name\0");
1539 append_block(&mut bytes, &gnu_header(b'K', 5));
1540 append_payload(&mut bytes, b"link\0");
1541 append_block(&mut bytes, &gnu_header(b'2', 0));
1542 append_terminator(&mut bytes);
1543
1544 ready_ok(async {
1545 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
1546 let mut member = next_member(&mut reader).await?;
1547 let MemberExtensions::Gnu {
1548 long_name: Some(long_name),
1549 long_link: Some(long_link),
1550 } = &member.extensions
1551 else {
1552 panic!("expected GNU extensions");
1553 };
1554 assert_eq!(long_name.payload, b"name\0");
1555 assert_eq!(long_link.payload, b"link\0");
1556 assert!(member.payload.next_block().await?.is_none());
1557 Ok(())
1558 });
1559 }
1560
1561 #[test]
1562 fn rejects_oversized_gnu_extensions_before_consuming_payload() {
1563 let declared_size = 9;
1564 for (case, typeflag) in [("long-name", b'L'), ("long-link", b'K')] {
1565 let mut reader = TarReader::new(ChunkedReader::new(
1566 gnu_header(typeflag, declared_size).to_vec(),
1567 BLOCK_SIZE,
1568 ))
1569 .with_policy(StreamPolicy::default().max_gnu_extension_size(declared_size - 1));
1570 assert!(
1571 matches!(
1572 ready(reader.next_frame()),
1573 Err(FrameError {
1574 position: 0,
1575 inner: FrameErrorInner::ExtensionTooLarge {
1576 format: ArchiveFormat::Gnu,
1577 size,
1578 limit,
1579 },
1580 }) if size == declared_size && limit == declared_size - 1
1581 ),
1582 "{case}"
1583 );
1584 }
1585
1586 let mut reader = TarReader::new(ChunkedReader::new(
1587 gnu_header(b'L', DEFAULT_MAX_GNU_EXTENSION_SIZE + 1).to_vec(),
1588 BLOCK_SIZE,
1589 ));
1590 assert!(matches!(
1591 ready(reader.next_frame()),
1592 Err(FrameError {
1593 position: 0,
1594 inner: FrameErrorInner::ExtensionTooLarge {
1595 format: ArchiveFormat::Gnu,
1596 size,
1597 limit: DEFAULT_MAX_GNU_EXTENSION_SIZE,
1598 },
1599 }) if size == DEFAULT_MAX_GNU_EXTENSION_SIZE + 1
1600 ));
1601 }
1602
1603 #[test]
1604 fn logical_reader_is_fused_after_oversized_gnu_extension() {
1605 let payload = b"renamed\0";
1606 let payload_size = u64::try_from(payload.len()).expect("payload size should fit u64");
1607 let mut bytes = Vec::new();
1608 append_gnu(&mut bytes, b'L', payload);
1609 append_block(&mut bytes, &gnu_header(b'0', 0));
1610 append_terminator(&mut bytes);
1611
1612 ready_ok(async {
1613 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE))
1614 .with_policy(StreamPolicy::default().max_gnu_extension_size(payload_size - 1));
1615 assert!(matches!(
1616 reader.next_frame().await,
1617 Err(FrameError {
1618 position: 0,
1619 inner: FrameErrorInner::ExtensionTooLarge {
1620 format: ArchiveFormat::Gnu,
1621 size,
1622 limit,
1623 },
1624 }) if size == payload_size && limit == payload_size - 1
1625 ));
1626 assert!(reader.next_frame().await?.is_none());
1627 Ok(())
1628 });
1629 }
1630
1631 #[test]
1632 fn preserves_multiblock_gnu_metadata_payloads() {
1633 let mut long_name = vec![b'n'; BLOCK_SIZE * 2 + 37];
1634 long_name.push(0);
1635 let mut long_link = vec![b'l'; BLOCK_SIZE + 19];
1636 long_link.push(0);
1637
1638 let mut bytes = Vec::new();
1639 append_gnu(&mut bytes, b'L', &long_name);
1640 append_gnu(&mut bytes, b'K', &long_link);
1641 append_block(&mut bytes, &gnu_header(b'2', 0));
1642 append_terminator(&mut bytes);
1643
1644 ready_ok(async {
1645 let mut reader = TarReader::new(ChunkedReader::new(bytes, 19));
1646 let member = next_member(&mut reader).await?;
1647 let MemberExtensions::Gnu {
1648 long_name: Some(name_metadata),
1649 long_link: Some(link_metadata),
1650 } = &member.extensions
1651 else {
1652 panic!("expected GNU extensions");
1653 };
1654 assert_eq!(name_metadata.position, 0);
1655 assert_eq!(name_metadata.payload, long_name);
1656 assert_eq!(link_metadata.position, (BLOCK_SIZE * 4) as u64);
1657 assert_eq!(link_metadata.payload, long_link);
1658 member.payload.skip().await?;
1659 assert!(reader.next_frame().await?.is_none());
1660 Ok(())
1661 });
1662 }
1663
1664 #[test]
1665 fn handles_empty_archives_and_trailing_global_pax() {
1666 let mut empty = Vec::new();
1667 append_terminator(&mut empty);
1668 ready_ok(async {
1669 let mut reader = TarReader::new(ChunkedReader::new(empty, BLOCK_SIZE));
1670 assert!(reader.next_frame().await?.is_none());
1671 Ok(())
1672 });
1673
1674 for header in [
1675 header(b'x', record("path", "name").len() as u64),
1676 gnu_header(b'L', 0),
1677 ] {
1678 let mut bytes = Vec::new();
1679 append_block(&mut bytes, &header);
1680 if header[TYPEFLAG_OFFSET] == b'x' {
1681 append_payload(&mut bytes, &record("path", "name"));
1682 }
1683 let error: Result<(), FrameError> = ready(async {
1684 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
1685 reader.next_frame().await.map(|_| ())
1686 });
1687 assert!(matches!(
1688 error,
1689 Err(FrameError {
1690 inner: FrameErrorInner::UnexpectedEof { .. },
1691 ..
1692 })
1693 ));
1694 }
1695
1696 let mut global = Vec::new();
1697 append_pax(&mut global, b'g', &record("comment", "metadata"));
1698 append_pax(&mut global, b'g', &record("gname", "group"));
1699 append_terminator(&mut global);
1700 ready_ok(async {
1701 let mut reader = TarReader::new(ChunkedReader::new(global, BLOCK_SIZE));
1702 assert!(reader.next_frame().await?.is_none());
1703 Ok(())
1704 });
1705
1706 let mut malformed_global = Vec::new();
1707 append_pax(&mut malformed_global, b'g', b"invalid");
1708 append_terminator(&mut malformed_global);
1709 let error: Result<(), FrameError> = ready(async {
1710 let mut reader = TarReader::new(ChunkedReader::new(malformed_global, BLOCK_SIZE));
1711 reader.next_frame().await.map(|_| ())
1712 });
1713 assert!(matches!(
1714 error,
1715 Err(FrameError {
1716 position: 0,
1717 inner: FrameErrorInner::InvalidPaxRecord { .. },
1718 })
1719 ));
1720 }
1721
1722 #[test]
1723 fn skips_unread_payload_before_advancing() {
1724 for payload_len in [BLOCK_SIZE + 1, PAYLOAD_DRAIN_CHUNK_BYTES + 7] {
1725 let payload = vec![b'a'; payload_len];
1726 let mut bytes = Vec::new();
1727 append_pax(&mut bytes, b'0', &payload);
1728 append_block(&mut bytes, &header(b'0', 0));
1729 append_terminator(&mut bytes);
1730
1731 ready_ok(async {
1732 let mut reader = TarReader::new(ChunkedReader::new(bytes, BLOCK_SIZE));
1733 {
1734 let member = next_member(&mut reader).await?;
1735 member.payload.skip().await?;
1736 }
1737 let member = next_member(&mut reader).await?;
1738 assert_eq!(member.header.effective_size, 0);
1739 drop(member);
1740 assert!(reader.next_frame().await?.is_none());
1741 Ok(())
1742 });
1743 }
1744
1745 let mut auto_bytes = Vec::new();
1746 append_block(&mut auto_bytes, &header(b'0', 1));
1747 append_payload(&mut auto_bytes, b"a");
1748 append_block(&mut auto_bytes, &header(b'0', 0));
1749 append_terminator(&mut auto_bytes);
1750 ready_ok(async {
1751 let mut reader = TarReader::new(ChunkedReader::new(auto_bytes, BLOCK_SIZE));
1752 let first = next_member(&mut reader).await?;
1753 drop(first);
1754 assert!(reader.next_frame().await?.is_some());
1755 Ok(())
1756 });
1757 }
1758
1759 #[test]
1760 fn reports_truncated_payload_when_read_or_skipped() {
1761 #[derive(Clone, Copy, Debug)]
1762 enum Operation {
1763 ReadBlock,
1764 ReadChunk,
1765 ExplicitSkip,
1766 AutomaticSkip,
1767 }
1768
1769 for operation in [
1770 Operation::ReadBlock,
1771 Operation::ReadChunk,
1772 Operation::ExplicitSkip,
1773 Operation::AutomaticSkip,
1774 ] {
1775 ready_ok(async {
1776 let mut reader =
1777 TarReader::new(ChunkedReader::new(header(b'0', 1).to_vec(), BLOCK_SIZE));
1778 let result = {
1779 let mut member = next_member(&mut reader).await?;
1780 match operation {
1781 Operation::ReadBlock => member.payload.next_block().await.map(|_| ()),
1782 Operation::ReadChunk => {
1783 let mut chunk = Vec::new();
1784 member.payload.next_chunk(&mut chunk, 1).await.map(|_| ())
1785 }
1786 Operation::ExplicitSkip => member.payload.skip().await,
1787 Operation::AutomaticSkip => {
1788 drop(member);
1789 reader.next_frame().await.map(|_| ())
1790 }
1791 }
1792 };
1793
1794 assert!(
1795 matches!(
1796 result,
1797 Err(FrameError {
1798 inner: FrameErrorInner::TruncatedPayload {
1799 owner: DataOwner::Member,
1800 ..
1801 },
1802 ..
1803 })
1804 ),
1805 "{operation:?}"
1806 );
1807
1808 for attempt in 1..=2 {
1809 assert!(
1810 reader.next_frame().await?.is_none(),
1811 "{operation:?}, iteration {attempt}"
1812 );
1813 }
1814
1815 Ok(())
1816 });
1817 }
1818 }
1819}