use alloc::vec::Vec;
use core::fmt;
use core::ops::Range;
use crate::multipart::filename::{FileName, FileNameSource};
use crate::multipart::parser::{Charset, PartHead, PartKind};
#[derive(Clone, Default)]
pub struct OwnedPartHead {
buf: Vec<u8>,
index: u32,
block: Range<usize>,
name: Range<usize>,
name_raw: Range<usize>,
kind: Option<PartKind>,
filename: Option<(Range<usize>, FileNameSource)>,
content_type: Option<Range<usize>>,
charset: Option<Charset>,
}
impl fmt::Debug for OwnedPartHead {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.view(), f)
}
}
impl OwnedPartHead {
#[must_use]
pub fn new() -> OwnedPartHead {
OwnedPartHead::default()
}
#[must_use]
pub fn with_capacity(capacity: usize) -> OwnedPartHead {
let mut head = OwnedPartHead::default();
head.buf = Vec::with_capacity(capacity);
head
}
fn push(&mut self, bytes: &[u8]) -> Range<usize> {
let start = self.buf.len();
crate::wipe::append_wiping(&mut self.buf, bytes, bytes.len(), usize::MAX);
start..self.buf.len()
}
pub fn copy_from(&mut self, head: &PartHead<'_>) {
crate::wipe::wipe(&mut self.buf);
self.buf.clear();
let block = head.block();
let outside = |s: &[u8]| {
if within(block, s).is_some() {
0
} else {
s.len()
}
};
let needed = [
block.len(),
outside(head.name().as_bytes()),
outside(head.name_raw()),
head.filename().map_or(0, |f| outside(f.raw())),
head.content_type().map_or(0, |c| outside(c.raw())),
]
.iter()
.fold(0usize, |a, n| a.saturating_add(*n));
if needed > self.buf.capacity() {
let mut old = core::mem::take(&mut self.buf);
crate::wipe::wipe_vec(&mut old);
drop(old);
self.buf = Vec::with_capacity(needed);
}
self.index = head.index();
self.block = self.push(block);
let base = self.block.start;
let mut place = |s: &[u8]| match within(block, s) {
Some(r) => r.start.saturating_add(base)..r.end.saturating_add(base),
None => {
let start = self.buf.len();
crate::wipe::append_wiping(&mut self.buf, s, s.len(), usize::MAX);
start..self.buf.len()
}
};
self.name = place(head.name().as_bytes());
self.name_raw = place(head.name_raw());
self.kind = Some(head.kind());
self.filename = head.filename().map(|f| (place(f.raw()), f.source()));
self.content_type = head.content_type().map(|c| place(c.raw()));
self.charset = Some(head.charset());
}
#[must_use]
pub fn view(&self) -> PartHead<'_> {
let get = |r: &Range<usize>| self.buf.get(r.clone()).unwrap_or(&[]);
PartHead::from_parts(
self.index,
get(&self.block),
core::str::from_utf8(get(&self.name)).unwrap_or(""),
get(&self.name_raw),
self.kind.unwrap_or(PartKind::Field),
self.filename
.as_ref()
.map(|(r, source)| FileName::new(get(r), *source)),
self.content_type.as_ref().map(get),
self.charset.unwrap_or(Charset::Unspecified),
)
}
}
impl Drop for OwnedPartHead {
fn drop(&mut self) {
crate::wipe::wipe(&mut self.buf);
}
}
fn within(whole: &[u8], part: &[u8]) -> Option<Range<usize>> {
let start = (part.as_ptr() as usize).checked_sub(whole.as_ptr() as usize)?;
let end = start.checked_add(part.len())?;
(end <= whole.len()).then_some(start..end)
}