base_web_content/upload.rs
1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use crate::FieldPath;
5use base_web_media_type::MediaType;
6use std::fmt;
7
8/// A file a form submitted, with the caller's handle for where its content was stored.
9///
10/// The parser does not keep an upload's content: it reports the content to the caller as it arrives, and the caller stores it, such as in a temporary file, and gives the parser a handle of type `H` for it. The filename and media type are what the client declared, and must not be trusted: a filename can contain path separators, `..`, a tab, Unicode controls such as bidirectional overrides, and, decoded from `filename*` when [`ParameterRules::Mime`](crate::ParameterRules::Mime) apply, CR, LF, and NUL, and a media type need not describe the content. The parser never gives an upload with an empty filename, which means no file was chosen.
11pub struct Upload<H> {
12 filename: String,
13 media_type: Option<MediaType>,
14 size: u64,
15 handle: H,
16}
17
18impl<H> Upload<H> {
19 /// An upload as the parser would give it, such as for an application's own tests of what it does with its declared struct.
20 pub fn new(
21 untrusted_filename: impl Into<String>,
22 untrusted_media_type: Option<MediaType>,
23 size: u64,
24 handle: H,
25 ) -> Self {
26 Self {
27 filename: untrusted_filename.into(),
28 media_type: untrusted_media_type,
29 size,
30 handle,
31 }
32 }
33
34 pub(crate) fn from_head(head: UploadHead, size: u64, handle: H) -> Self {
35 Self::new(head.filename, head.media_type, size, handle)
36 }
37
38 /// The filename the client declared, which the parser never gives empty, since an empty filename means no file was chosen. It can contain any character, as the type's documentation describes, and must not be used as a local path, or written into another syntax, without checking or encoding it.
39 pub fn untrusted_filename(&self) -> &str {
40 &self.filename
41 }
42
43 /// The media type the client declared for the content, if any, which need not describe the content.
44 pub fn untrusted_media_type(&self) -> Option<&MediaType> {
45 self.media_type.as_ref()
46 }
47
48 /// The bytes of content the parser reported.
49 pub fn size(&self) -> u64 {
50 self.size
51 }
52
53 /// The caller's handle for the stored content.
54 pub fn handle(&self) -> &H {
55 &self.handle
56 }
57
58 /// The handle, taken from the upload.
59 pub fn into_handle(self) -> H {
60 self.handle
61 }
62}
63
64/// Shows the size only, since the filename and media type are submitted content, and the handle may name them.
65impl<H> fmt::Debug for Upload<H> {
66 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
67 formatter
68 .debug_struct("Upload")
69 .field("size", &self.size)
70 .finish_non_exhaustive()
71 }
72}
73
74/// An upload that begins: the field it is for, and what the client declared about it, which must not be trusted.
75#[derive(Clone, PartialEq, Eq)]
76pub struct UploadHead {
77 path: FieldPath,
78 filename: String,
79 media_type: Option<MediaType>,
80}
81
82impl UploadHead {
83 pub(crate) fn new(path: FieldPath, filename: String, media_type: Option<MediaType>) -> Self {
84 Self {
85 path,
86 filename,
87 media_type,
88 }
89 }
90
91 /// The declared field the upload is for, with its index when the field is an array.
92 pub fn path(&self) -> &FieldPath {
93 &self.path
94 }
95
96 /// The filename the client declared, which is not empty, since an empty filename means no file was chosen. It can contain any character, as [`Upload`]'s documentation describes, and must not be used as a local path, or written into another syntax, without checking or encoding it.
97 pub fn untrusted_filename(&self) -> &str {
98 &self.filename
99 }
100
101 /// The media type the client declared, which the field's `accept` allowed, but need not describe the content.
102 pub fn untrusted_media_type(&self) -> Option<&MediaType> {
103 self.media_type.as_ref()
104 }
105}
106
107/// Shows the path and the filename's length, since the filename and media type are submitted content.
108impl fmt::Debug for UploadHead {
109 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
110 formatter
111 .debug_struct("UploadHead")
112 .field("path", &format_args!("{}", self.path))
113 .field("filename", &format_args!("<{} bytes>", self.filename.len()))
114 .finish_non_exhaustive()
115 }
116}