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}