use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use super::{FileValue, FromField, ValueError};
#[derive(Default)]
pub struct FileData {
bytes: Vec<u8>,
filename: Option<String>,
content_type: Option<String>,
}
impl fmt::Debug for FileData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FileData({} bytes)", self.bytes.len())
}
}
impl FileData {
pub(crate) fn new(
bytes: Vec<u8>,
filename: Option<String>,
content_type: Option<String>,
) -> Self {
FileData {
bytes,
filename,
content_type,
}
}
pub(crate) fn append(&mut self, chunk: &[u8]) {
crate::wipe::append_wiping(&mut self.bytes, chunk, 64 * 1024, usize::MAX);
}
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
#[must_use]
pub fn into_bytes(mut self) -> Vec<u8> {
core::mem::take(&mut self.bytes)
}
#[must_use]
pub fn len(&self) -> usize {
self.bytes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
#[must_use]
pub fn filename(&self) -> Option<&str> {
self.filename.as_deref()
}
#[must_use]
pub fn content_type(&self) -> Option<&str> {
self.content_type.as_deref()
}
}
impl Drop for FileData {
fn drop(&mut self) {
crate::wipe::wipe_vec(&mut self.bytes);
crate::wipe::wipe_opt_string(&mut self.filename);
crate::wipe::wipe_opt_string(&mut self.content_type);
}
}
impl FromField for FileData {
type Slot = Option<FileData>;
fn accept_file(slot: &mut Self::Slot, file: FileValue<'_>) -> Result<(), ValueError> {
let data = match file {
FileValue::Buffered(part) => {
let name = part.filename();
if name.is_some_and(|n| n.is_empty()) && part.is_empty() {
return Ok(());
}
let filename = match name {
Some(n) => {
Some(String::from(n.as_str().map_err(|_| {
ValueError::Invalid("file name is not UTF-8")
})?))
}
None => None,
};
let content_type = part
.content_type()
.and_then(|c| core::str::from_utf8(c.raw()).ok().map(String::from));
FileData::new(part.bytes().to_vec(), filename, content_type)
}
#[cfg(feature = "fs")]
FileValue::Stored(stored) => {
if stored.is_empty() && stored.filename().is_some_and(|n| n.is_empty()) {
return Ok(());
}
let filename = match stored.filename() {
Some(n) => {
Some(String::from(n.as_str().map_err(|_| {
ValueError::Invalid("file name is not UTF-8")
})?))
}
None => None,
};
let content_type = stored
.content_type()
.and_then(|c| core::str::from_utf8(c.raw()).ok().map(String::from));
let bytes = stored
.read_to_vec(stored.len())
.map_err(|_| ValueError::Invalid("the stored file could not be read"))?;
FileData::new(bytes, filename, content_type)
}
};
if slot.is_some() {
return Err(ValueError::Duplicate);
}
*slot = Some(data);
Ok(())
}
fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
slot.ok_or(ValueError::Missing)
}
}