1use crate::{
8 BLOCK_SIZE, Block, PaxKeyword, UstarKind,
9 header::{
10 DEVMAJOR_RANGE, DEVMINOR_RANGE, GID_RANGE, IDENTITY_RANGE, LINK_NAME_RANGE, MODE_RANGE,
11 MTIME_RANGE, NAME_RANGE, PREFIX_RANGE, SIZE_RANGE, TYPEFLAG_OFFSET, UID_RANGE,
12 USTAR_IDENTITY, encode_checksum, encode_octal,
13 },
14};
15
16const MAX_DECIMAL_U64_BYTES: usize = 20;
19const _: () = assert!(MAX_DECIMAL_U64_BYTES == (u64::MAX.ilog10() as usize) + 1);
20
21const MAX_SEQUENCE_NAME_BYTES: usize = b"PaxHeaders/".len() + MAX_DECIMAL_U64_BYTES;
22
23const DECIMAL_PAIRS: &[u8; 200] = b"\
28 0001020304050607080910111213141516171819\
29 2021222324252627282930313233343536373839\
30 4041424344454647484950515253545556575859\
31 6061626364656667686970717273747576777879\
32 8081828384858687888990919293949596979899";
33const ZERO_BLOCK: Block = [0; BLOCK_SIZE];
34const END_MARKER_BYTES: [u8; BLOCK_SIZE * 2] = [0; BLOCK_SIZE * 2];
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38pub struct PaxMember<'a> {
39 pub path: &'a str,
43 pub kind: UstarKind,
45 pub size: u64,
47 pub link_path: Option<&'a str>,
49 pub executable: bool,
51}
52
53#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
55pub enum FramingWriteError {
56 #[error("cannot encode unsupported member type {kind:?}")]
58 UnsupportedMemberKind {
59 kind: UstarKind,
61 },
62 #[error("member type {kind:?} cannot carry payload size {size}")]
64 InvalidMemberSize {
65 kind: UstarKind,
67 size: u64,
69 },
70 #[error("symbolic-link member is missing its link path")]
72 MissingLinkPath,
73 #[error("member type {kind:?} cannot carry a link path")]
75 UnexpectedLinkPath {
76 kind: UstarKind,
78 },
79 #[error("invalid pax {field}: values must be non-empty and cannot contain NUL bytes")]
81 InvalidText {
82 field: &'static str,
84 },
85 #[error("pax record keywords must be non-empty and cannot contain '='")]
87 InvalidPaxRecordKeyword,
88 #[error("member type {kind:?} cannot have a directory-required path suffix")]
90 DirectoryRequiredPathSuffix {
91 kind: UstarKind,
93 },
94 #[error("pax extended header payload is too large: {size} bytes")]
96 ExtendedHeaderTooLarge {
97 size: u64,
99 },
100 #[error("arithmetic overflow while constructing {context}")]
102 ArithmeticOverflow {
103 context: &'static str,
105 },
106}
107
108pub fn append_pax_record(
113 output: &mut Vec<u8>,
114 keyword: &PaxKeyword,
115 value: &[u8],
116) -> Result<(), FramingWriteError> {
117 let (namespace, name) = keyword.components();
118 if namespace.is_empty()
119 || namespace.contains('=')
120 || name.is_some_and(|name| name.is_empty() || name.contains('='))
121 {
122 return Err(FramingWriteError::InvalidPaxRecordKeyword);
123 }
124 let len = record_len(keyword, value)?;
125 output
126 .len()
127 .checked_add(len)
128 .ok_or(FramingWriteError::ArithmeticOverflow {
129 context: "pax record output length",
130 })?;
131 output.reserve(len);
132 append_record_with_len(output, keyword, value, len);
133 Ok(())
134}
135
136pub fn frame_pax_member_into(
143 sequence: u64,
144 member: PaxMember<'_>,
145 buffer: &mut Vec<u8>,
146) -> Result<(), FramingWriteError> {
147 validate_member(member)?;
148
149 let mut size_buffer = [0; MAX_DECIMAL_U64_BYTES];
150 let size = decimal_u64(member.size, &mut size_buffer);
151 let link_path = member.link_path.map(str::as_bytes);
152 buffer.clear();
153 buffer.resize(BLOCK_SIZE, 0);
154 append_pax_record(buffer, &PaxKeyword::Path, member.path.as_bytes())?;
155 append_pax_record(buffer, &PaxKeyword::Size, size)?;
156 if let Some(link_path) = link_path {
157 append_pax_record(buffer, &PaxKeyword::LinkPath, link_path)?;
158 }
159 let payload_len = buffer.len() - BLOCK_SIZE;
160 let payload_size =
161 u64::try_from(payload_len).map_err(|_| FramingWriteError::ArithmeticOverflow {
162 context: "pax payload length",
163 })?;
164 let padded_payload_len = padded_payload_len(payload_len)?;
165
166 let mut sequence_name_buffer = [0; MAX_SEQUENCE_NAME_BYTES];
167 let extended_name = prefixed_decimal_name(b"PaxHeaders/", sequence, &mut sequence_name_buffer);
168 let sequence_name_len = extended_name.len();
169 let fallback_size = if fits_octal(SIZE_RANGE.len(), member.size) {
170 member.size
171 } else {
172 0
173 };
174 let (mode, typeflag) = match member.kind {
175 UstarKind::Regular => (if member.executable { 0o755 } else { 0o644 }, b'0'),
176 UstarKind::Directory => (0o755, b'5'),
177 UstarKind::SymbolicLink => (0o777, b'2'),
178 _ => {
179 return Err(FramingWriteError::UnsupportedMemberKind { kind: member.kind });
180 }
181 };
182 let framing_len = padded_payload_len.checked_add(BLOCK_SIZE * 2).ok_or(
183 FramingWriteError::ArithmeticOverflow {
184 context: "pax framing length",
185 },
186 )?;
187 buffer.resize(framing_len, 0);
188 let (extended_header, rest) = buffer.split_at_mut(BLOCK_SIZE);
189 let (_, member_header) = rest.split_at_mut(padded_payload_len);
190 build_header_into(
191 extended_header,
192 (&[], extended_name),
193 0o644,
194 payload_size,
195 b'x',
196 b"",
197 )?;
198 let member_path: (&[u8], &[u8]) =
199 if let Some(member_path) = split_ustar_path(member.path.as_bytes()) {
200 member_path
201 } else {
202 sequence_name_buffer[..b"PaxEntries/".len()].copy_from_slice(b"PaxEntries/");
205 (&[], &sequence_name_buffer[..sequence_name_len])
206 };
207 build_header_into(
208 member_header,
209 member_path,
210 mode,
211 fallback_size,
212 typeflag,
213 member.link_path.unwrap_or_default().as_bytes(),
214 )?;
215 Ok(())
216}
217
218pub fn end_marker_bytes() -> &'static [u8] {
220 &END_MARKER_BYTES
221}
222
223#[inline]
225pub fn payload_padding(size: u64) -> &'static [u8] {
226 let padding = size.wrapping_neg() & (BLOCK_SIZE as u64 - 1);
229 &ZERO_BLOCK[..padding as usize]
230}
231
232fn validate_member(member: PaxMember<'_>) -> Result<(), FramingWriteError> {
233 validate_text("path", member.path)?;
234 if !matches!(member.kind, UstarKind::Directory)
238 && (member.path.ends_with('/')
239 || member
240 .path
241 .rsplit('/')
242 .next()
243 .is_some_and(|component| matches!(component, "." | "..")))
244 {
245 return Err(FramingWriteError::DirectoryRequiredPathSuffix { kind: member.kind });
246 }
247 match member.kind {
248 UstarKind::Regular | UstarKind::Directory if member.link_path.is_some() => {
249 Err(FramingWriteError::UnexpectedLinkPath { kind: member.kind })
250 }
251 UstarKind::Directory | UstarKind::SymbolicLink if member.size != 0 => {
252 Err(FramingWriteError::InvalidMemberSize {
253 kind: member.kind,
254 size: member.size,
255 })
256 }
257 UstarKind::Regular | UstarKind::Directory => Ok(()),
258 UstarKind::SymbolicLink => validate_text(
259 "linkpath",
260 member.link_path.ok_or(FramingWriteError::MissingLinkPath)?,
261 ),
262 _ => Err(FramingWriteError::UnsupportedMemberKind { kind: member.kind }),
263 }
264}
265
266fn validate_text(field: &'static str, value: &str) -> Result<(), FramingWriteError> {
267 if value.is_empty() || value.contains('\0') {
268 return Err(FramingWriteError::InvalidText { field });
269 }
270 Ok(())
271}
272
273fn record_len(keyword: &PaxKeyword, value: &[u8]) -> Result<usize, FramingWriteError> {
274 let (namespace, name) = keyword.components();
275 let keyword_len = name
276 .map_or(Some(namespace.len()), |name| {
277 namespace
278 .len()
279 .checked_add(1)
280 .and_then(|len| len.checked_add(name.len()))
281 })
282 .ok_or(FramingWriteError::ArithmeticOverflow {
283 context: "pax record keyword length",
284 })?;
285 let suffix_len = keyword_len
286 .checked_add(value.len())
287 .and_then(|len| len.checked_add(3))
288 .ok_or(FramingWriteError::ArithmeticOverflow {
289 context: "pax record length",
290 })?;
291 let tentative_len = (suffix_len.ilog10() as usize + 1)
292 .checked_add(suffix_len)
293 .ok_or(FramingWriteError::ArithmeticOverflow {
294 context: "pax record length",
295 })?;
296 (tentative_len.ilog10() as usize + 1)
297 .checked_add(suffix_len)
298 .ok_or(FramingWriteError::ArithmeticOverflow {
299 context: "pax record length",
300 })
301}
302
303fn append_record_with_len(payload: &mut Vec<u8>, keyword: &PaxKeyword, value: &[u8], len: usize) {
304 append_decimal_usize(payload, len);
305 payload.push(b' ');
306 let (namespace, name) = keyword.components();
307 payload.extend_from_slice(namespace.as_bytes());
308 if let Some(name) = name {
309 payload.push(b'.');
310 payload.extend_from_slice(name.as_bytes());
311 }
312 payload.push(b'=');
313 payload.extend_from_slice(value);
314 payload.push(b'\n');
315}
316
317fn build_header_into(
318 block: &mut [u8],
319 (prefix, name): (&[u8], &[u8]),
320 mode: u64,
321 size: u64,
322 typeflag: u8,
323 link_path: &[u8],
324) -> Result<(), FramingWriteError> {
325 let block: &mut Block =
326 block
327 .try_into()
328 .map_err(|_| FramingWriteError::ArithmeticOverflow {
329 context: "ustar header block length",
330 })?;
331 block[NAME_RANGE.start..NAME_RANGE.start + name.len()].copy_from_slice(name);
332 block[PREFIX_RANGE.start..PREFIX_RANGE.start + prefix.len()].copy_from_slice(prefix);
333 if link_path.len() <= LINK_NAME_RANGE.len() {
334 block[LINK_NAME_RANGE.start..LINK_NAME_RANGE.start + link_path.len()]
335 .copy_from_slice(link_path);
336 }
337 if !encode_octal(&mut block[MODE_RANGE], mode)
338 || !encode_octal(&mut block[UID_RANGE], 0)
339 || !encode_octal(&mut block[GID_RANGE], 0)
340 || !encode_octal(&mut block[SIZE_RANGE], size)
341 || !encode_octal(&mut block[MTIME_RANGE], 0)
342 || !encode_octal(&mut block[DEVMAJOR_RANGE], 0)
346 || !encode_octal(&mut block[DEVMINOR_RANGE], 0)
347 {
348 return Err(FramingWriteError::ExtendedHeaderTooLarge { size });
349 }
350 block[TYPEFLAG_OFFSET] = typeflag;
351 block[IDENTITY_RANGE].copy_from_slice(USTAR_IDENTITY);
352 encode_checksum(block);
353 Ok(())
354}
355
356fn fits_octal(field_len: usize, value: u64) -> bool {
357 value.checked_ilog(8).map_or(1, |log| log + 1) < field_len as u32
358}
359
360fn split_ustar_path(path: &[u8]) -> Option<(&[u8], &[u8])> {
361 if path.len() <= NAME_RANGE.len() {
362 return Some((&[], path));
363 }
364 path.iter()
365 .enumerate()
366 .rev()
367 .filter(|(_, byte)| **byte == b'/')
368 .find_map(|(separator, _)| {
369 let prefix = &path[..separator];
370 let name = &path[separator + 1..];
371 if !prefix.is_empty()
372 && prefix.len() <= PREFIX_RANGE.len()
373 && !name.is_empty()
374 && name.len() <= NAME_RANGE.len()
375 {
376 Some((prefix, name))
377 } else {
378 None
379 }
380 })
381}
382
383fn padded_payload_len(len: usize) -> Result<usize, FramingWriteError> {
384 len.checked_next_multiple_of(BLOCK_SIZE)
385 .ok_or(FramingWriteError::ArithmeticOverflow {
386 context: "padded pax payload length",
387 })
388}
389
390fn prefixed_decimal_name<'a>(
391 prefix: &[u8; b"PaxHeaders/".len()],
392 value: u64,
393 buffer: &'a mut [u8; MAX_SEQUENCE_NAME_BYTES],
394) -> &'a [u8] {
395 let mut digits_buffer = [0; MAX_DECIMAL_U64_BYTES];
396 let digits = decimal_u64(value, &mut digits_buffer);
397 let len = prefix.len() + digits.len();
398 buffer[..prefix.len()].copy_from_slice(prefix);
399 buffer[prefix.len()..len].copy_from_slice(digits);
400 &buffer[..len]
401}
402
403#[inline]
406fn decimal_u64(value: u64, buffer: &mut [u8; MAX_DECIMAL_U64_BYTES]) -> &[u8] {
407 if value < 10 {
414 buffer[MAX_DECIMAL_U64_BYTES - 1] = b'0' + value as u8;
415 return &buffer[MAX_DECIMAL_U64_BYTES - 1..];
416 }
417 if value < 100 {
418 let value = value as u8;
419 let tens = value / 10;
420 buffer[MAX_DECIMAL_U64_BYTES - 2] = b'0' + tens;
421 buffer[MAX_DECIMAL_U64_BYTES - 1] = b'0' + value - tens * 10;
422 return &buffer[MAX_DECIMAL_U64_BYTES - 2..];
423 }
424 if value < 1_000 {
425 let value = value as u16;
426 let hundreds = value / 100;
427 let remainder = (value - hundreds * 100) as u8;
428 let tens = remainder / 10;
429 buffer[MAX_DECIMAL_U64_BYTES - 3] = b'0' + hundreds as u8;
430 buffer[MAX_DECIMAL_U64_BYTES - 2] = b'0' + tens;
431 buffer[MAX_DECIMAL_U64_BYTES - 1] = b'0' + remainder - tens * 10;
432 return &buffer[MAX_DECIMAL_U64_BYTES - 3..];
433 }
434
435 decimal_u64_large(value, buffer)
436}
437
438#[inline(never)]
442fn decimal_u64_large(value: u64, buffer: &mut [u8; MAX_DECIMAL_U64_BYTES]) -> &[u8] {
443 let mut remaining = value;
444 let mut start = buffer.len();
445
446 while remaining >= 10_000 {
449 start -= 4;
450 let quad = (remaining % 10_000) as usize;
451 remaining /= 10_000;
452 let first_pair = quad / 100 * 2;
453 let second_pair = quad % 100 * 2;
454 buffer[start] = DECIMAL_PAIRS[first_pair];
455 buffer[start + 1] = DECIMAL_PAIRS[first_pair + 1];
456 buffer[start + 2] = DECIMAL_PAIRS[second_pair];
457 buffer[start + 3] = DECIMAL_PAIRS[second_pair + 1];
458 }
459
460 if remaining >= 100 {
461 start -= 2;
462 let pair = remaining as usize % 100 * 2;
463 remaining /= 100;
464 buffer[start] = DECIMAL_PAIRS[pair];
465 buffer[start + 1] = DECIMAL_PAIRS[pair + 1];
466 }
467
468 if remaining >= 10 {
469 start -= 2;
470 let pair = remaining as usize * 2;
471 buffer[start] = DECIMAL_PAIRS[pair];
472 buffer[start + 1] = DECIMAL_PAIRS[pair + 1];
473 } else {
474 start -= 1;
475 buffer[start] = b'0' + remaining as u8;
476 }
477
478 &buffer[start..]
479}
480
481fn append_decimal_usize(output: &mut Vec<u8>, value: usize) {
482 let mut buffer = [0; MAX_DECIMAL_U64_BYTES];
483 output.extend_from_slice(decimal_u64(value as u64, &mut buffer));
484}
485
486#[cfg(test)]
487mod tests {
488 use std::sync::Arc;
489
490 use super::*;
491 use crate::{
492 PaxKind, PaxRecord, PaxString, PaxValue,
493 header::parse_octal,
494 stream::{Frame, TarStream},
495 test_support::{ChunkedReader, collect_frames, ready},
496 };
497
498 fn pax_member<'a>(
499 path: &'a str,
500 kind: UstarKind,
501 size: u64,
502 link_path: Option<&'a str>,
503 executable: bool,
504 ) -> PaxMember<'a> {
505 PaxMember {
506 path,
507 kind,
508 size,
509 link_path,
510 executable,
511 }
512 }
513
514 fn frame_archive(
515 sequence: u64,
516 member: PaxMember<'_>,
517 payload: &[u8],
518 ) -> Result<Vec<u8>, FramingWriteError> {
519 let mut bytes = Vec::new();
520 frame_pax_member_into(sequence, member, &mut bytes)?;
521 bytes.extend_from_slice(payload);
522 bytes.extend_from_slice(payload_padding(member.size));
523 bytes.extend_from_slice(end_marker_bytes());
524 Ok(bytes)
525 }
526
527 fn assert_decimal_u64_matches_standard(value: u64) {
528 let mut buffer = [b'?'; MAX_DECIMAL_U64_BYTES];
529 assert_eq!(
530 decimal_u64(value, &mut buffer),
531 value.to_string().as_bytes(),
532 "value: {value}"
533 );
534 }
535
536 #[test]
537 fn frames_regular_directory_and_symbolic_link_members() {
538 let members = [
539 pax_member("bin/tool", UstarKind::Regular, 3, None, true),
540 pax_member("bin", UstarKind::Directory, 0, None, false),
541 pax_member("alias", UstarKind::SymbolicLink, 0, Some("bin/tool"), false),
542 ];
543 for (sequence, member) in members.into_iter().enumerate() {
544 let payload: &[u8] = if member.kind == UstarKind::Regular {
545 b"run"
546 } else {
547 b""
548 };
549 let bytes = frame_archive(sequence as u64, member, payload).expect("valid member");
550 let frames = ready(collect_frames(TarStream::new(ChunkedReader::new(
551 bytes, 19,
552 ))));
553 assert!(matches!(
554 &frames[0],
555 Ok(Frame::Pax(frame)) if frame.kind == PaxKind::Local
556 ));
557 let header = frames
558 .iter()
559 .find_map(|frame| match frame {
560 Ok(Frame::Header(header)) => Some(header),
561 _ => None,
562 })
563 .expect("member header");
564 assert_eq!(header.kind, member.kind);
565 assert_eq!(header.effective_size, member.size);
566 let records = frames
567 .iter()
568 .find_map(|frame| match frame {
569 Ok(Frame::Data(data)) => data.completed_pax_records(),
570 _ => None,
571 })
572 .expect("local pax records");
573 assert!(
574 records.contains(&PaxRecord::Path(PaxValue::Value(PaxString::Utf8(
575 member.path.to_owned().into()
576 ))))
577 );
578 }
579 }
580
581 #[test]
582 fn frames_members_into_a_reusable_buffer() {
583 let member = pax_member("bin/tool", UstarKind::Regular, 3, None, true);
584 let mut bytes = Vec::with_capacity(BLOCK_SIZE * 3);
585 bytes.extend_from_slice(b"stale bytes");
586 frame_pax_member_into(7, member, &mut bytes).expect("valid member");
587 assert_eq!(bytes.len(), BLOCK_SIZE * 3);
588 let capacity = bytes.capacity();
589
590 frame_pax_member_into(8, member, &mut bytes).expect("valid member");
591 assert_eq!(bytes.len(), BLOCK_SIZE * 3);
592 assert_eq!(bytes.capacity(), capacity);
593
594 bytes.extend_from_slice(b"run");
595 bytes.resize(bytes.len() + BLOCK_SIZE - 3, 0);
596 bytes.extend_from_slice(end_marker_bytes());
597 let frames = ready(collect_frames(TarStream::new(ChunkedReader::new(
598 bytes, 19,
599 ))));
600 assert!(frames.iter().all(Result::is_ok));
601 }
602
603 #[test]
604 fn encodes_unused_device_fields_as_octal_zero() {
605 let mut bytes = Vec::new();
606 frame_pax_member_into(
607 0,
608 pax_member("file", UstarKind::Regular, 0, None, false),
609 &mut bytes,
610 )
611 .expect("valid member");
612
613 for (kind, header) in [
614 ("pax", &bytes[..BLOCK_SIZE]),
615 ("member", &bytes[bytes.len() - BLOCK_SIZE..]),
616 ] {
617 assert_eq!(parse_octal(&header[DEVMAJOR_RANGE]), Some(0), "{kind}");
618 assert_eq!(parse_octal(&header[DEVMINOR_RANGE]), Some(0), "{kind}");
619 }
620 }
621
622 #[test]
623 fn returns_payload_padding_and_contiguous_end_marker_bytes() {
624 for (size, expected) in [
625 (0, &[] as &[u8]),
626 (BLOCK_SIZE as u64, &[]),
627 (1, &[0; BLOCK_SIZE - 1]),
628 ((BLOCK_SIZE + 7) as u64, &[0; BLOCK_SIZE - 7]),
629 (u64::MAX - (BLOCK_SIZE as u64 - 1), &[]),
630 (u64::MAX - (BLOCK_SIZE as u64 - 2), &[0; BLOCK_SIZE - 1]),
631 (u64::MAX, &[0; 1]),
632 ] {
633 assert_eq!(payload_padding(size), expected, "{size}");
634 }
635
636 assert_eq!(end_marker_bytes().len(), BLOCK_SIZE * 2);
637 assert!(end_marker_bytes().iter().all(|byte| *byte == 0));
638 }
639
640 #[test]
641 fn appends_standalone_pax_records_across_decimal_boundaries() {
642 let mut record = Vec::new();
643 assert_eq!(
644 append_pax_record(&mut record, &PaxKeyword::Path, b"b"),
645 Ok(())
646 );
647 assert_eq!(record, b"9 path=b\n");
648 record.clear();
649 assert_eq!(
650 append_pax_record(&mut record, &PaxKeyword::Atime, b"x"),
651 Ok(())
652 );
653 assert_eq!(record, b"11 atime=x\n");
654 for keyword in [
655 PaxKeyword::Realtime(Arc::from("")),
656 PaxKeyword::Vendor {
657 vendor: Arc::from("invalid=vendor"),
658 name: Arc::from("attribute"),
659 },
660 ] {
661 assert_eq!(
662 append_pax_record(&mut Vec::new(), &keyword, b"value"),
663 Err(FramingWriteError::InvalidPaxRecordKeyword)
664 );
665 }
666 }
667
668 #[test]
669 fn formats_u64_values_across_decimal_boundaries() {
670 for (value, expected) in [
671 (0, "0"),
672 (9, "9"),
673 (10, "10"),
674 (99, "99"),
675 (100, "100"),
676 (999, "999"),
677 (1_000, "1000"),
678 (1_001, "1001"),
679 (9_999, "9999"),
680 (10_000, "10000"),
681 (1_000_001, "1000001"),
682 (u64::MAX, "18446744073709551615"),
683 ] {
684 let mut buffer = [0; MAX_DECIMAL_U64_BYTES];
685 assert_eq!(decimal_u64(value, &mut buffer), expected.as_bytes());
686 }
687 }
688
689 #[test]
690 fn matches_standard_formatting_across_the_u64_range() {
691 for value in 0..1_000_000 {
693 assert_decimal_u64_matches_standard(value);
694 }
695
696 for exponent in 6..=19 {
698 let power = 10_u64.pow(exponent);
699 for distance in 0..=9 {
700 if let Some(value) = power.checked_sub(distance) {
701 assert_decimal_u64_matches_standard(value);
702 }
703 if distance != 0
704 && let Some(value) = power.checked_add(distance)
705 {
706 assert_decimal_u64_matches_standard(value);
707 }
708 }
709 }
710
711 let mut value = 0x4d59_5df4_d0f3_3173;
713 for _ in 0..250_000 {
714 assert_decimal_u64_matches_standard(value);
715 value = value
716 .wrapping_mul(6_364_136_223_846_793_005)
717 .wrapping_add(1_442_695_040_888_963_407);
718 }
719 }
720
721 #[test]
722 fn uses_generated_fallbacks_for_long_paths_and_links() {
723 let path = format!("{}/{}", "a".repeat(156), "b".repeat(101));
724 let link_path = "c".repeat(101);
725 let member = pax_member(&path, UstarKind::SymbolicLink, 0, Some(&link_path), false);
726 let mut bytes = Vec::new();
727 frame_pax_member_into(7, member, &mut bytes).expect("valid member");
728 let member_header = &bytes[bytes.len() - BLOCK_SIZE..];
729 assert_eq!(
730 &member_header[NAME_RANGE.start..NAME_RANGE.start + 12],
731 b"PaxEntries/7"
732 );
733 assert!(member_header[LINK_NAME_RANGE].iter().all(|byte| *byte == 0));
734
735 bytes.extend_from_slice(end_marker_bytes());
736 let frames = ready(collect_frames(TarStream::new(ChunkedReader::new(
737 bytes, 23,
738 ))));
739 let records = frames
740 .iter()
741 .find_map(|frame| match frame {
742 Ok(Frame::Data(data)) => data.completed_pax_records(),
743 _ => None,
744 })
745 .expect("local pax records");
746 assert_eq!(records.len(), 3);
747 }
748
749 #[test]
750 fn rejects_unsupported_or_inconsistent_members() {
751 for (member, expected) in [
752 (
753 pax_member("file", UstarKind::HardLink, 0, None, false),
754 FramingWriteError::UnsupportedMemberKind {
755 kind: UstarKind::HardLink,
756 },
757 ),
758 (
759 pax_member("link", UstarKind::SymbolicLink, 1, Some("file"), false),
760 FramingWriteError::InvalidMemberSize {
761 kind: UstarKind::SymbolicLink,
762 size: 1,
763 },
764 ),
765 ] {
766 assert_eq!(
767 frame_pax_member_into(0, member, &mut Vec::new()),
768 Err(expected)
769 );
770 }
771 }
772
773 #[test]
774 fn uses_zero_ustar_fallback_for_oversized_regular_payloads() {
775 let mut bytes = Vec::new();
776 frame_pax_member_into(
777 0,
778 pax_member("large", UstarKind::Regular, u64::MAX, None, false),
779 &mut bytes,
780 )
781 .expect("pax size can represent u64 values");
782 assert_eq!(
783 parse_octal(&bytes[bytes.len() - BLOCK_SIZE..][SIZE_RANGE]),
784 Some(0)
785 );
786 }
787}