Skip to main content

tar_framing/
error.rs

1use std::io;
2
3use crate::{ArchiveFormat, BLOCK_SIZE, GnuKind, UstarKind, pax::PaxError, stream::DataOwner};
4
5/// An error encountered at an absolute position in a tar stream.
6#[derive(Debug, thiserror::Error)]
7#[error("at byte {position}: {inner}")]
8pub struct FrameError {
9    /// The absolute byte position associated with the failure.
10    pub position: u64,
11    /// The specific failure encountered at `position`.
12    #[source]
13    pub inner: FrameErrorInner,
14}
15
16impl FrameError {
17    pub(crate) fn at(position: u64, inner: FrameErrorInner) -> Self {
18        Self { position, inner }
19    }
20
21    pub(crate) fn arithmetic_overflow(position: u64, context: &'static str) -> Self {
22        Self::at(position, FrameErrorInner::ArithmeticOverflow { context })
23    }
24
25    pub(crate) fn deleted_pax_metadata(position: u64, keyword: &'static str) -> Self {
26        Self::at(position, FrameErrorInner::DeletedPaxMetadata { keyword })
27    }
28
29    pub(crate) fn invalid_gnu_metadata(position: u64, kind: GnuKind, reason: &'static str) -> Self {
30        Self::at(
31            position,
32            FrameErrorInner::InvalidGnuMetadata { kind, reason },
33        )
34    }
35
36    pub(crate) fn invalid_pax_record(position: u64, source: PaxError) -> Self {
37        Self::at(position, FrameErrorInner::InvalidPaxRecord { source })
38    }
39
40    pub(crate) fn truncated_payload(position: u64, owner: DataOwner, remaining: u64) -> Self {
41        Self::at(
42            position,
43            FrameErrorInner::TruncatedPayload { owner, remaining },
44        )
45    }
46
47    pub(crate) fn unexpected_order(
48        position: u64,
49        expected: &'static str,
50        found: &'static str,
51    ) -> Self {
52        Self::at(
53            position,
54            FrameErrorInner::UnexpectedOrder { expected, found },
55        )
56    }
57}
58
59/// Specific errors that can occur while processing tar frames.
60#[derive(Debug, thiserror::Error)]
61pub enum FrameErrorInner {
62    /// The underlying reader failed.
63    #[error("failed to read tar data")]
64    Io {
65        /// The underlying I/O failure.
66        #[source]
67        source: io::Error,
68    },
69    /// The underlying stream ended in the middle of a logical block.
70    #[error("incomplete tar block: read {read} of {BLOCK_SIZE} bytes")]
71    IncompleteBlock {
72        /// The number of bytes received for the incomplete block.
73        read: usize,
74    },
75    /// A header did not identify either supported archive family.
76    #[error("invalid tar identity: found {found:?}")]
77    InvalidIdentity {
78        /// The bytes found in the combined magic/version fields.
79        found: [u8; 8],
80    },
81    /// A header checksum was malformed or did not match its contents.
82    #[error("invalid tar checksum: stored {expected:?}, computed {actual}")]
83    InvalidChecksum {
84        /// The parsed stored checksum, or `None` if its field was malformed.
85        expected: Option<u64>,
86        /// The checksum computed from the header block.
87        actual: u64,
88    },
89    /// A header's size field was not a strict number.
90    /// The underlying format of the number might be octal or GNU-style base256.
91    #[error("invalid tar size field: found {found:?}")]
92    InvalidSize {
93        /// The bytes found in the size field.
94        found: [u8; 12],
95    },
96    /// An ordinary member header's numeric metadata field was invalid.
97    #[error("invalid tar {field} field: found {found:?}")]
98    InvalidNumericField {
99        /// The name of the invalid header field.
100        field: &'static str,
101        /// The bytes found in the invalid header field.
102        found: Vec<u8>,
103    },
104    /// An ordinary ustar member header's string field had no NUL terminator.
105    #[error("invalid ustar {field} field: missing NUL terminator")]
106    UnterminatedUstarStringField {
107        /// The name of the unterminated header field.
108        field: &'static str,
109    },
110    /// A tar type is not supported within the selected archive family.
111    #[error("unsupported tar typeflag {typeflag:?}")]
112    UnsupportedTypeflag {
113        /// The unsupported typeflag byte.
114        typeflag: u8,
115    },
116    /// A header from another archive family appeared after family detection.
117    #[error("archive format changed from {expected:?} to {found:?}")]
118    FormatMismatch {
119        /// The archive family selected by an earlier header.
120        expected: ArchiveFormat,
121        /// The archive family identified by this header.
122        found: ArchiveFormat,
123    },
124    /// A valid block appeared in a position where another block type was required.
125    #[error("unexpected tar block: expected {expected}, found {found}")]
126    UnexpectedOrder {
127        /// A description of the required block.
128        expected: &'static str,
129        /// A description of the block received.
130        found: &'static str,
131    },
132    /// A pax extended-header record could not be parsed.
133    #[error("{source}")]
134    InvalidPaxRecord {
135        /// The pax parsing failure.
136        #[source]
137        source: PaxError,
138    },
139    /// A metadata extension declares more data than the configured format limit.
140    #[error("{format} extension payload size {size} exceeds configured limit {limit}")]
141    ExtensionTooLarge {
142        /// The archive family containing the extension.
143        format: ArchiveFormat,
144        /// The extension payload size declared in its header.
145        size: u64,
146        /// The configured maximum extension payload size.
147        limit: u64,
148    },
149    /// Consecutive global pax extensions contain more metadata than the configured limit.
150    #[error("global pax extension payload total {size} exceeds configured limit {limit}")]
151    GlobalPaxExtensionsTooLarge {
152        /// The cumulative declared size including the rejected extension.
153        size: u64,
154        /// The configured maximum cumulative payload size.
155        limit: u64,
156    },
157    /// A GNU long-name or long-link metadata payload is not a valid value.
158    #[error("malformed GNU {kind:?} metadata payload: {reason}")]
159    InvalidGnuMetadata {
160        /// The GNU metadata extension being decoded.
161        kind: GnuKind,
162        /// The reason the metadata value was rejected.
163        reason: &'static str,
164    },
165    /// A pax record removed metadata required to interpret a member.
166    #[error("pax metadata {keyword:?} deletes a required member field")]
167    DeletedPaxMetadata {
168        /// The standard pax keyword that deleted its header fallback.
169        keyword: &'static str,
170    },
171    /// An ordinary member has no effective pathname after applying extensions.
172    #[error("ordinary member has an empty effective path")]
173    EmptyMemberPath,
174    /// An effective member path or link target contains an embedded NUL byte.
175    #[error("effective member {field} contains a NUL byte")]
176    NulInMemberName {
177        /// The effective metadata field containing the NUL byte.
178        field: &'static str,
179    },
180    /// A framing offset or record length overflowed.
181    #[error("arithmetic overflow while computing {context}")]
182    ArithmeticOverflow {
183        /// The computation that overflowed.
184        context: &'static str,
185    },
186    /// A member's declared or effective size is invalid for its type.
187    #[error("member type {kind:?} cannot carry payload size {size}")]
188    InvalidMemberSize {
189        /// The member type.
190        kind: UstarKind,
191        /// The rejected declared or effective payload size.
192        size: u64,
193    },
194    /// The stream ended while a payload still required bytes.
195    #[error("unexpected end of stream: {owner:?} payload needs {remaining} more bytes")]
196    TruncatedPayload {
197        /// The kind of payload being read.
198        owner: DataOwner,
199        /// The remaining unpadded payload length.
200        remaining: u64,
201    },
202    /// The stream ended while a required member header was pending.
203    #[error("unexpected end of stream: expected {expected}")]
204    UnexpectedEof {
205        /// A description of the required next input.
206        expected: &'static str,
207    },
208    /// The two-block end marker was absent or incomplete.
209    #[error("missing two-block end-of-archive marker")]
210    MissingEndMarker,
211    /// The first zero terminator block was not followed by a second zero block.
212    #[error("invalid end-of-archive marker: expected a second zero block")]
213    InvalidEndMarker,
214}