1use alloc::string::String;
4use alloc::vec::Vec;
5use core::fmt;
6
7use super::{FileValue, FromField, ValueError};
8
9#[derive(Default)]
31pub struct FileData {
32 bytes: Vec<u8>,
33 filename: Option<String>,
34 content_type: Option<String>,
35}
36
37impl fmt::Debug for FileData {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(f, "FileData({} bytes)", self.bytes.len())
40 }
41}
42
43impl FileData {
44 pub(crate) fn new(
45 bytes: Vec<u8>,
46 filename: Option<String>,
47 content_type: Option<String>,
48 ) -> Self {
49 FileData {
50 bytes,
51 filename,
52 content_type,
53 }
54 }
55
56 pub(crate) fn append(&mut self, chunk: &[u8]) {
57 crate::wipe::append_wiping(&mut self.bytes, chunk, 64 * 1024, usize::MAX);
58 }
59
60 #[must_use]
62 pub fn bytes(&self) -> &[u8] {
63 &self.bytes
64 }
65
66 #[must_use]
68 pub fn into_bytes(mut self) -> Vec<u8> {
69 core::mem::take(&mut self.bytes)
70 }
71
72 #[must_use]
74 pub fn len(&self) -> usize {
75 self.bytes.len()
76 }
77
78 #[must_use]
80 pub fn is_empty(&self) -> bool {
81 self.bytes.is_empty()
82 }
83
84 #[must_use]
86 pub fn filename(&self) -> Option<&str> {
87 self.filename.as_deref()
88 }
89
90 #[must_use]
92 pub fn content_type(&self) -> Option<&str> {
93 self.content_type.as_deref()
94 }
95}
96
97impl Drop for FileData {
98 fn drop(&mut self) {
99 crate::wipe::wipe_vec(&mut self.bytes);
100 crate::wipe::wipe_opt_string(&mut self.filename);
101 crate::wipe::wipe_opt_string(&mut self.content_type);
102 }
103}
104
105impl FromField for FileData {
106 type Slot = Option<FileData>;
107
108 fn accept_file(slot: &mut Self::Slot, file: FileValue<'_>) -> Result<(), ValueError> {
109 let data = match file {
110 FileValue::Buffered(part) => {
111 let name = part.filename();
112 if name.is_some_and(|n| n.is_empty()) && part.is_empty() {
113 return Ok(());
115 }
116 let filename = match name {
117 Some(n) => {
118 Some(String::from(n.as_str().map_err(|_| {
119 ValueError::Invalid("file name is not UTF-8")
120 })?))
121 }
122 None => None,
123 };
124 let content_type = part
125 .content_type()
126 .and_then(|c| core::str::from_utf8(c.raw()).ok().map(String::from));
127 FileData::new(part.bytes().to_vec(), filename, content_type)
128 }
129 #[cfg(feature = "fs")]
130 FileValue::Stored(stored) => {
131 if stored.is_empty() && stored.filename().is_some_and(|n| n.is_empty()) {
132 return Ok(());
133 }
134 let filename = match stored.filename() {
135 Some(n) => {
136 Some(String::from(n.as_str().map_err(|_| {
137 ValueError::Invalid("file name is not UTF-8")
138 })?))
139 }
140 None => None,
141 };
142 let content_type = stored
143 .content_type()
144 .and_then(|c| core::str::from_utf8(c.raw()).ok().map(String::from));
145 let bytes = stored
146 .read_to_vec(stored.len())
147 .map_err(|_| ValueError::Invalid("the stored file could not be read"))?;
148 FileData::new(bytes, filename, content_type)
149 }
150 };
151 if slot.is_some() {
152 return Err(ValueError::Duplicate);
153 }
154 *slot = Some(data);
155 Ok(())
156 }
157
158 fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
159 slot.ok_or(ValueError::Missing)
160 }
161}