gproxy_protocol/adapt/files/
copy.rs1use crate::{
2 WireRequest,
3 adapt::{
4 JsonInvocation,
5 files::{UploadError, upload_multipart_json},
6 },
7 capability::{ResourceAccess, ResourceReference, Upstream},
8 codec::{self, CodecLimits},
9 connection::{HttpBody, MultipartPart},
10 transform::TransformError,
11 wire::DeclaredFields,
12};
13
14#[allow(clippy::too_many_arguments)] pub async fn copy_to_multipart<
19 A: ResourceAccess,
20 U: Upstream,
21 O: serde::de::DeserializeOwned + DeclaredFields,
22>(
23 access: &A,
24 source_scope: &A::Scope,
25 source: &ResourceReference,
26 upstream: &U,
27 target: &U::Target,
28 template: WireRequest<()>,
29 boundary: String,
30 mut fields: Vec<MultipartPart>,
31 filename: Option<String>,
32 limits: CodecLimits,
33) -> Result<JsonInvocation<O>, UploadError> {
34 super::path(&template.path)?;
35 let read = access
36 .read(source_scope, source)
37 .await
38 .map_err(TransformError::from)?;
39 let filename = filename
40 .or(read.metadata.filename)
41 .ok_or_else(|| TransformError::missing_metadata("file.filename"))?;
42 if filename.contains(['\r', '\n', '\0']) {
43 return Err(TransformError::shape("filename", "invalid header characters").into());
44 }
45 let mime = read
46 .metadata
47 .mime
48 .ok_or_else(|| TransformError::missing_metadata("file.mime"))?;
49 let mut read_limits = limits;
50 read_limits.max_body_bytes = read_limits
51 .max_body_bytes
52 .min(access.limits().read_bytes)
53 .min(limits.max_part_bytes);
54 read_limits.max_buffer_bytes = read_limits.max_buffer_bytes.min(read_limits.max_body_bytes);
55 let bytes = codec::read_http_body(read.body, read_limits)
56 .await
57 .map_err(|e| {
58 TransformError::new(
59 if e.kind() == codec::CodecErrorKind::Limit {
60 crate::transform::TransformErrorKind::Limit
61 } else {
62 crate::transform::TransformErrorKind::Host
63 },
64 "file.copy.read",
65 e.to_string(),
66 )
67 })?;
68 if read
69 .metadata
70 .length
71 .is_some_and(|length| length != bytes.len() as u64)
72 {
73 return Err(TransformError::invalid_result(
74 "file.copy.length",
75 "source body differs from declared resource length",
76 )
77 .into());
78 }
79 let escaped = filename.replace('\\', "\\\\").replace('"', "\\\"");
80 let mut headers = http::HeaderMap::new();
81 headers.insert(
82 http::header::CONTENT_DISPOSITION,
83 http::HeaderValue::from_str(&format!("form-data; name=\"file\"; filename=\"{escaped}\""))
84 .map_err(|e| TransformError::shape("filename", e.to_string()))?,
85 );
86 headers.insert(
87 http::header::CONTENT_TYPE,
88 http::HeaderValue::from_str(&mime)
89 .map_err(|e| TransformError::shape("mime", e.to_string()))?,
90 );
91 fields.push(MultipartPart {
92 headers,
93 body: HttpBody::Bytes(bytes),
94 });
95 upload_multipart_json(upstream, target, template, boundary, fields, limits).await
96}