trunk_recorder_plugin/
multipart.rs1use std::sync::atomic::{AtomicU64, Ordering};
15use std::time::{SystemTime, UNIX_EPOCH};
16
17pub struct Multipart {
18 boundary: String,
19 body: Vec<u8>,
20}
21
22impl Default for Multipart {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl Multipart {
29 pub fn new() -> Multipart {
30 static N: AtomicU64 = AtomicU64::new(0);
31 let t = SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |d| d.as_nanos() as u64);
32 let boundary = format!("----trunk-recorder-{t:x}{:x}", N.fetch_add(1, Ordering::Relaxed));
33 Multipart { boundary, body: Vec::new() }
34 }
35
36 pub fn text(mut self, name: &str, value: impl AsRef<str>) -> Multipart {
38 self.part(&format!("Content-Disposition: form-data; name=\"{}\"", quote(name)), value.as_ref().as_bytes());
39 self
40 }
41
42 pub fn file(mut self, name: &str, file_name: &str, content_type: &str, bytes: &[u8]) -> Multipart {
44 let head = format!("Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\nContent-Type: {content_type}", quote(name), quote(file_name));
45 self.part(&head, bytes);
46 self
47 }
48
49 pub fn finish(mut self) -> (Vec<u8>, String) {
51 self.body.extend_from_slice(format!("--{}--\r\n", self.boundary).as_bytes());
52 (self.body, format!("multipart/form-data; boundary={}", self.boundary))
53 }
54
55 fn part(&mut self, head: &str, bytes: &[u8]) {
56 self.body.extend_from_slice(format!("--{}\r\n{head}\r\n\r\n", self.boundary).as_bytes());
57 self.body.extend_from_slice(bytes);
58 self.body.extend_from_slice(b"\r\n");
59 }
60}
61
62fn quote(s: &str) -> String {
63 s.replace('"', "%22").replace(['\r', '\n'], " ")
64}
65
66#[cfg(all(test, feature = "sdk"))]
67mod tests {
68 use super::*;
69 use crate::testing::Request;
70
71 #[test]
72 fn reads_back() {
73 let (body, ct) = Multipart::new().text("a", "1").file("call", "x.m4a", "audio/mp4", b"\0\x01bytes\r\n--").text("b", "two").finish();
74 let r = Request { headers: vec![("content-type".into(), ct)], body, ..Default::default() };
75 assert_eq!(r.form_field("a").unwrap(), b"1");
76 assert_eq!(r.form_field("b").unwrap(), b"two");
77 assert_eq!(r.form_field("call").unwrap(), b"\0\x01bytes\r\n--");
78 assert_eq!(r.form_file_name("call").unwrap(), "x.m4a");
79 }
80}