twilight_http/request/
multipart.rs1#[derive(Clone, Debug)]
2#[must_use = "has no effect if not built into a Form"]
3pub struct Form {
4 boundary: [u8; 15],
5 buffer: Vec<u8>,
6}
7
8impl Form {
9 const APPLICATION_JSON: &'static [u8; 16] = b"application/json";
10
11 const BOUNDARY_TERMINATOR: &'static [u8; 2] = b"--";
12 const CONTENT_DISPOSITION_1: &'static [u8; 38] = b"Content-Disposition: form-data; name=\"";
13 const CONTENT_DISPOSITION_2: &'static [u8; 13] = b"\"; filename=\"";
14 const CONTENT_DISPOSITION_3: &'static [u8; 1] = b"\"";
15 const CONTENT_TYPE: &'static [u8; 14] = b"Content-Type: ";
16 const NEWLINE: &'static [u8; 2] = b"\r\n";
17
18 pub fn new() -> Self {
19 Self::default()
20 }
21
22 pub fn build(mut self) -> Vec<u8> {
24 self.buffer.extend(Self::BOUNDARY_TERMINATOR);
25
26 self.buffer
27 }
28
29 pub fn content_type(&self) -> Vec<u8> {
31 const NAME: &str = "multipart/form-data; boundary=";
32
33 let mut content_type = Vec::with_capacity(NAME.len() + self.boundary.len());
34 content_type.extend(NAME.as_bytes());
35 content_type.extend(self.boundary);
36
37 content_type
38 }
39
40 pub fn part(mut self, name: &[u8], value: &[u8]) -> Self {
41 self.buffer.extend(Self::NEWLINE);
43 self.buffer.extend(Self::CONTENT_DISPOSITION_1);
44 self.buffer.extend(name);
45 self.buffer.extend(Self::CONTENT_DISPOSITION_3);
46 self.buffer.extend(Self::NEWLINE);
47
48 self.buffer.extend(Self::NEWLINE);
51 self.buffer.extend(value);
52 self.buffer.extend(Self::NEWLINE);
53 self.buffer.extend(Self::BOUNDARY_TERMINATOR);
54 self.buffer.extend(self.boundary);
55
56 self
57 }
58
59 pub fn file_part(mut self, name: &[u8], filename: &[u8], value: &[u8]) -> Self {
60 self.buffer.extend(Self::NEWLINE);
62 self.buffer.extend(Self::CONTENT_DISPOSITION_1);
63 self.buffer.extend(name);
64 self.buffer.extend(Self::CONTENT_DISPOSITION_2);
65 self.buffer.extend(filename);
66 self.buffer.extend(Self::CONTENT_DISPOSITION_3);
67 self.buffer.extend(Self::NEWLINE);
68
69 self.buffer.extend(Self::NEWLINE);
72 self.buffer.extend(value);
73 self.buffer.extend(Self::NEWLINE);
74 self.buffer.extend(Self::BOUNDARY_TERMINATOR);
75 self.buffer.extend(self.boundary);
76
77 self
78 }
79
80 #[allow(clippy::len_without_is_empty)]
82 pub fn len(&self) -> usize {
83 self.buffer.len() + Self::BOUNDARY_TERMINATOR.len()
84 }
85
86 pub fn json_part(mut self, name: &[u8], value: &[u8]) -> Self {
87 self.buffer.extend(Self::NEWLINE);
89 self.buffer.extend(Self::CONTENT_DISPOSITION_1);
90 self.buffer.extend(name);
91 self.buffer.extend(Self::CONTENT_DISPOSITION_3);
92 self.buffer.extend(Self::NEWLINE);
93
94 self.buffer.extend(Self::CONTENT_TYPE);
96 self.buffer.extend(Self::APPLICATION_JSON);
97 self.buffer.extend(Self::NEWLINE);
98
99 self.buffer.extend(Self::NEWLINE);
102 self.buffer.extend(value);
103 self.buffer.extend(Self::NEWLINE);
104 self.buffer.extend(Self::BOUNDARY_TERMINATOR);
105 self.buffer.extend(self.boundary);
106
107 self
108 }
109}
110
111impl Default for Form {
112 fn default() -> Self {
113 let mut form = Self {
114 boundary: random_boundary(),
115 buffer: Vec::new(),
116 };
117
118 form.buffer.extend(Self::BOUNDARY_TERMINATOR);
120 form.buffer.extend(form.boundary);
121
122 form
123 }
124}
125
126pub fn random_boundary() -> [u8; 15] {
128 let mut boundary = [0; 15];
129
130 for value in &mut boundary {
131 *value = fastrand::alphanumeric() as u8;
132 }
133
134 boundary
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use std::str;
141
142 #[test]
143 fn form_builder() {
144 let form = Form::new()
145 .json_part(b"payload_json", b"json_value")
146 .file_part(b"files[0]", b"filename.jpg", b"file_value");
147
148 let boundary = str::from_utf8(&form.boundary).unwrap();
149 let expected = format!(
150 "--{boundary}\r\n\
151 Content-Disposition: form-data; name=\"payload_json\"\r\n\
152 Content-Type: application/json\r\n\
153 \r\n\
154 json_value\r\n\
155 --{boundary}\r\n\
156 Content-Disposition: form-data; name=\"files[0]\"; filename=\"filename.jpg\"\r\n\
157 \r\n\
158 file_value\r\n\
159 --{boundary}--",
160 );
161
162 let buffer_len = form.len();
163 let buffer = form.build();
164
165 assert_eq!(expected.as_bytes(), buffer);
166 assert_eq!(buffer_len, buffer.len());
167 }
168}