spikard_http/testing/
multipart.rs1use std::time::{SystemTime, UNIX_EPOCH};
2
3#[derive(Debug, Clone)]
5pub struct MultipartFilePart {
6 pub field_name: String,
7 pub filename: String,
8 pub content_type: Option<String>,
9 pub content: Vec<u8>,
10}
11
12pub fn build_multipart_body(form_fields: &[(String, String)], files: &[MultipartFilePart]) -> (Vec<u8>, String) {
14 let boundary = generate_boundary();
15 let mut body = Vec::new();
16
17 for (name, value) in form_fields {
18 body.extend_from_slice(b"--");
19 body.extend_from_slice(boundary.as_bytes());
20 body.extend_from_slice(b"\r\n");
21 body.extend_from_slice(b"Content-Disposition: form-data; name=\"");
22 body.extend_from_slice(name.as_bytes());
23 body.extend_from_slice(b"\"\r\n\r\n");
24 body.extend_from_slice(value.as_bytes());
25 body.extend_from_slice(b"\r\n");
26 }
27
28 for file in files {
29 body.extend_from_slice(b"--");
30 body.extend_from_slice(boundary.as_bytes());
31 body.extend_from_slice(b"\r\n");
32 body.extend_from_slice(b"Content-Disposition: form-data; name=\"");
33 body.extend_from_slice(file.field_name.as_bytes());
34 body.extend_from_slice(b"\"");
35 if !file.filename.is_empty() {
36 body.extend_from_slice(b"; filename=\"");
37 body.extend_from_slice(file.filename.as_bytes());
38 body.extend_from_slice(b"\"");
39 }
40 body.extend_from_slice(b"\r\n");
41 if let Some(content_type) = &file.content_type {
42 body.extend_from_slice(b"Content-Type: ");
43 body.extend_from_slice(content_type.as_bytes());
44 body.extend_from_slice(b"\r\n");
45 }
46 body.extend_from_slice(b"\r\n");
47 body.extend_from_slice(&file.content);
48 body.extend_from_slice(b"\r\n");
49 }
50
51 body.extend_from_slice(b"--");
52 body.extend_from_slice(boundary.as_bytes());
53 body.extend_from_slice(b"--\r\n");
54
55 (body, boundary)
56}
57
58fn generate_boundary() -> String {
59 let nanos = SystemTime::now()
60 .duration_since(UNIX_EPOCH)
61 .map(|duration| duration.as_nanos())
62 .unwrap_or_default();
63 format!("spikard-boundary-{nanos}")
64}