1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! Validated line-wrapping policy for the 2.0 codec core.
use core::num::NonZeroUsize;
/// Line ending inserted between encoded body lines.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum LineEnding {
/// Line feed (`\n`).
Lf,
/// Carriage return followed by line feed (`\r\n`).
CrLf,
}
impl LineEnding {
/// Returns the exact line-ending bytes.
#[must_use]
pub const fn as_bytes(self) -> &'static [u8] {
match self {
Self::Lf => b"\n",
Self::CrLf => b"\r\n",
}
}
/// Returns the line-ending width in bytes.
#[must_use]
pub const fn byte_len(self) -> usize {
self.as_bytes().len()
}
}
/// Failure constructing a line-wrapping policy.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum LineWrapError {
/// A zero-width line would prevent encoder progress.
ZeroWidth,
}
impl core::fmt::Display for LineWrapError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ZeroWidth => formatter.write_str("base64 line width must be non-zero"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for LineWrapError {}
/// Immutable, always-progressing Base64 body wrapping policy.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct LineWrap {
line_width: NonZeroUsize,
line_ending: LineEnding,
}
impl LineWrap {
/// MIME transfer-body wrapping: 76 columns with CRLF separators.
///
/// This is a body-layout value, not a complete MIME parser or profile.
pub(crate) const MIME_BODY_WRAP: Self = Self {
line_width: NonZeroUsize::MIN.saturating_add(75),
line_ending: LineEnding::CrLf,
};
/// PEM body wrapping: 64 columns with LF separators.
///
/// This is a body-layout value, not a complete RFC 7468 parser.
pub(crate) const PEM_BODY_LF_WRAP: Self = Self {
line_width: NonZeroUsize::MIN.saturating_add(63),
line_ending: LineEnding::Lf,
};
/// PEM body wrapping: 64 columns with CRLF separators.
///
/// This is a body-layout value, not a complete RFC 7468 parser.
pub(crate) const PEM_BODY_CRLF_WRAP: Self = Self {
line_width: NonZeroUsize::MIN.saturating_add(63),
line_ending: LineEnding::CrLf,
};
/// Constructs a validated wrapping policy.
pub const fn try_new(
line_width: usize,
line_ending: LineEnding,
) -> Result<Self, LineWrapError> {
match NonZeroUsize::new(line_width) {
Some(line_width) => Ok(Self {
line_width,
line_ending,
}),
None => Err(LineWrapError::ZeroWidth),
}
}
/// Returns the non-zero encoded body width.
#[must_use]
pub const fn line_width(self) -> NonZeroUsize {
self.line_width
}
/// Returns the separator inserted between body lines.
#[must_use]
pub const fn line_ending(self) -> LineEnding {
self.line_ending
}
/// Returns the exact wrapped size without a trailing line ending.
#[must_use]
pub const fn checked_output_len(self, payload_len: usize) -> Option<usize> {
if payload_len == 0 {
return Some(0);
}
let breaks = (payload_len - 1) / self.line_width.get();
let Some(separator_bytes) = breaks.checked_mul(self.line_ending.byte_len()) else {
return None;
};
payload_len.checked_add(separator_bytes)
}
/// Inserts line endings into an already encoded Base64 body.
///
/// The destination is unchanged when it is too small or length arithmetic
/// overflows. Successful output never has a trailing line ending.
pub fn insert_into(self, payload: &[u8], output: &mut [u8]) -> Option<usize> {
let required = self.checked_output_len(payload.len())?;
if output.len() < required {
return None;
}
let separator = self.line_ending.as_bytes();
let width = self.line_width.get();
let mut read = 0usize;
let mut write = 0usize;
let mut column = 0usize;
while read < payload.len() {
if column == width {
let end = write.checked_add(separator.len())?;
output[write..end].copy_from_slice(separator);
write = end;
column = 0;
}
output[write] = payload[read];
read += 1;
write += 1;
column += 1;
}
Some(write)
}
/// Validates wrapped body layout and returns its unwrapped payload length.
///
/// Interior lines must have exactly the configured width. A final line may
/// be shorter, and one final line ending is accepted for compatibility
/// with body formats that terminate their last line.
#[must_use]
pub fn payload_len(self, input: &[u8]) -> Option<usize> {
let separator = self.line_ending.as_bytes();
let width = self.line_width.get();
let mut index = 0usize;
let mut column = 0;
let mut payload_len = 0;
while index < input.len() {
if starts_with(input, index, separator) {
if column == 0 {
return None;
}
index = index.checked_add(separator.len())?;
if index == input.len() {
return Some(payload_len);
}
if column != width {
return None;
}
column = 0;
continue;
}
if matches!(input[index], b'\r' | b'\n') || column == width {
return None;
}
index += 1;
column += 1;
payload_len += 1;
}
Some(payload_len)
}
/// Validates and copies a wrapped body without its line endings.
///
/// The destination is unchanged when layout validation fails or the
/// destination is too small.
pub fn copy_payload_into(self, input: &[u8], output: &mut [u8]) -> Option<usize> {
let payload_len = self.payload_len(input)?;
if output.len() < payload_len {
return None;
}
let separator = self.line_ending.as_bytes();
let mut read = 0usize;
let mut write = 0usize;
while read < input.len() {
if starts_with(input, read, separator) {
read = read.checked_add(separator.len())?;
} else {
output[write] = input[read];
read += 1;
write += 1;
}
}
Some(write)
}
}
fn starts_with(input: &[u8], index: usize, needle: &[u8]) -> bool {
let Some(end) = index.checked_add(needle.len()) else {
return false;
};
end <= input.len() && &input[index..end] == needle
}