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, 4096, 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 = (bool, Option<FileData>);
108
109 fn accept_file(slot: &mut Self::Slot, file: FileValue<'_>) -> Result<(), ValueError> {
110 if slot.0 {
114 return Err(ValueError::Duplicate);
115 }
116 slot.0 = true;
117 let data = match file {
118 FileValue::Buffered(part) => {
119 let name = part.filename();
120 if name.is_some_and(|n| n.is_empty()) && part.is_empty() {
121 return Ok(());
123 }
124 let filename = match name {
125 Some(n) => {
126 Some(String::from(n.as_str().map_err(|_| {
127 ValueError::Invalid("file name is not UTF-8")
128 })?))
129 }
130 None => None,
131 };
132 let content_type = part
133 .content_type()
134 .and_then(|c| core::str::from_utf8(c.raw()).ok().map(String::from));
135 FileData::new(part.bytes().to_vec(), filename, content_type)
136 }
137 #[cfg(feature = "fs")]
138 FileValue::Stored(stored) => {
139 if stored.is_empty() && stored.filename().is_some_and(|n| n.is_empty()) {
140 return Ok(());
141 }
142 let filename = match stored.filename() {
143 Some(n) => {
144 Some(String::from(n.as_str().map_err(|_| {
145 ValueError::Invalid("file name is not UTF-8")
146 })?))
147 }
148 None => None,
149 };
150 let content_type = stored
151 .content_type()
152 .and_then(|c| core::str::from_utf8(c.raw()).ok().map(String::from));
153 let bytes = stored
154 .read_to_vec(stored.len())
155 .map_err(|_| ValueError::Invalid("the stored file could not be read"))?;
156 FileData::new(bytes, filename, content_type)
157 }
158 };
159 slot.1 = Some(data);
160 Ok(())
161 }
162
163 fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
164 slot.1.ok_or(ValueError::Missing)
165 }
166}