diff --git a/src/api/files.rs b/src/api/files.rs index 21a847e9..056c1217 100644 --- a/src/api/files.rs +++ b/src/api/files.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize, Serializer}; use serde_with::skip_serializing_none; use crate::models::*; +use crate::multipart_form::FileMultipartData; use crate::ratectl::*; use crate::SlackClientSession; use crate::{ClientResult, SlackClientHttpConnector}; @@ -29,11 +30,14 @@ where let file_mime = mime_guess::MimeGuess::from_path(&filename).first_or_octet_stream(); file_mime.to_string() }); + let file = FileMultipartData { + name: filename.as_str(), + content_type: file_content_type.as_str(), + data: file.as_slice(), + }; self.http_session_api .http_post_multipart_form( "files.upload", - filename, - file_content_type, file, &vec![ ( diff --git a/src/client.rs b/src/client.rs index 2c2ad09d..f164f431 100644 --- a/src/client.rs +++ b/src/client.rs @@ -6,6 +6,7 @@ use crate::token::*; use crate::errors::SlackClientError; use crate::models::*; +use crate::multipart_form::FileMultipartData; use crate::ratectl::SlackApiMethodRateControlConfig; use futures_util::future::BoxFuture; use lazy_static::*; @@ -149,9 +150,7 @@ pub trait SlackClientHttpConnector { fn http_post_uri_multipart_form<'a, 'p, RS, PT, TS>( &'a self, full_uri: Url, - file_name: String, - file_content_type: String, - file_content: &'p [u8], + file: FileMultipartData<'p>, params: &'p PT, context: SlackClientApiCallContext<'a>, ) -> BoxFuture<'a, ClientResult> @@ -163,9 +162,7 @@ pub trait SlackClientHttpConnector { fn http_post_multipart_form<'a, 'p, RS, PT, TS>( &'a self, method_relative_uri: &str, - file_name: String, - file_content_type: String, - file_content: &'p [u8], + file: FileMultipartData<'p>, params: &'p PT, context: SlackClientApiCallContext<'a>, ) -> BoxFuture<'a, ClientResult> @@ -178,14 +175,7 @@ pub trait SlackClientHttpConnector { &SlackClientHttpApiUri::create_method_uri_path(method_relative_uri), ); - self.http_post_uri_multipart_form( - full_uri, - file_name, - file_content_type, - file_content, - params, - context, - ) + self.http_post_uri_multipart_form(full_uri, file, params, context) } } @@ -420,9 +410,7 @@ where pub async fn http_post_multipart_form<'p, RS, PT, TS>( &self, method_relative_uri: &str, - file_name: String, - file_content_type: String, - file_content: &'p [u8], + file: FileMultipartData<'p>, params: &'p PT, rate_control_params: Option<&'a SlackApiMethodRateControlConfig>, ) -> ClientResult @@ -441,14 +429,7 @@ where self.client .http_api .connector - .http_post_multipart_form( - method_relative_uri, - file_name, - file_content_type, - file_content, - params, - context, - ) + .http_post_multipart_form(method_relative_uri, file, params, context) .await } } diff --git a/src/hyper_tokio/connector.rs b/src/hyper_tokio/connector.rs index bc3e5351..c77415ad 100644 --- a/src/hyper_tokio/connector.rs +++ b/src/hyper_tokio/connector.rs @@ -13,6 +13,10 @@ use hyper_util::client::legacy::*; use hyper_util::rt::TokioExecutor; use rvstruct::ValueStruct; +use crate::hyper_tokio::multipart_form::{ + create_multipart_file_content, generate_multipart_boundary, +}; +use crate::multipart_form::FileMultipartData; use crate::prelude::hyper_ext::HyperExtensions; use crate::ratectl::SlackApiRateControlConfig; use std::hash::Hash; @@ -434,9 +438,7 @@ impl SlackClientHttpConnect fn http_post_uri_multipart_form<'a, 'p, RS, PT, TS>( &'a self, full_uri: Url, - file_name: String, - file_content_type: String, - file_content: &'p [u8], + file: FileMultipartData<'p>, params: &'p PT, context: SlackClientApiCallContext<'a>, ) -> BoxFuture<'a, ClientResult> @@ -446,14 +448,8 @@ impl SlackClientHttpConnect TS: AsRef + 'p + Send, { let context_token = context.token; - let boundary = HyperExtensions::generate_multipart_boundary(); - match HyperExtensions::create_multipart_file_content( - params, - boundary.as_str(), - file_name.as_str(), - file_content_type.as_str(), - file_content, - ) { + let boundary = generate_multipart_boundary(); + match create_multipart_file_content(params, boundary.as_str(), file) { Ok(file_bytes) => self .send_rate_controlled_request( move || { diff --git a/src/hyper_tokio/hyper_ext.rs b/src/hyper_tokio/hyper_ext.rs index 0b76ce57..322e1387 100644 --- a/src/hyper_tokio/hyper_ext.rs +++ b/src/hyper_tokio/hyper_ext.rs @@ -125,63 +125,4 @@ impl HyperExtensions { _ => Err(Box::new(SlackEventAbsentSignatureError::new())), } } - - pub fn generate_multipart_boundary() -> String { - format!( - "----WebKitFormBoundarySlackMorphismRust{}", - chrono::Utc::now().timestamp() - ) - } - - pub fn create_multipart_file_content<'p, PT, TS>( - fields: &'p PT, - multipart_boundary: &str, - file_name: &str, - file_content_type: &str, - file_content: &[u8], - ) -> AnyStdResult - where - PT: std::iter::IntoIterator)> + Clone, - TS: AsRef + 'p + Send, - { - let mut output = BytesMut::with_capacity(file_content.len() + 512); - output.write_str("\r\n")?; - output.write_str("\r\n")?; - output.write_str("--")?; - output.write_str(multipart_boundary)?; - output.write_str("\r\n")?; - output.write_str(&format!( - "Content-Disposition: form-data; name=\"file\"; filename=\"{}\"", - file_name - ))?; - output.write_str("\r\n")?; - output.write_str(&format!("Content-Type: {}", file_content_type))?; - output.write_str("\r\n")?; - output.write_str(&format!("Content-Length: {}", file_content.len()))?; - output.write_str("\r\n")?; - output.write_str("\r\n")?; - output.put_slice(file_content); - - for (k, mv) in fields.clone().into_iter() { - if let Some(v) = mv { - let vs = v.as_ref(); - output.write_str("\r\n")?; - output.write_str("--")?; - output.write_str(multipart_boundary)?; - output.write_str("\r\n")?; - output.write_str(&format!("Content-Disposition: form-data; name=\"{}\"", k))?; - output.write_str("\r\n")?; - output.write_str(&format!("Content-Length: {}", vs.len()))?; - output.write_str("\r\n")?; - output.write_str("\r\n")?; - output.write_str(vs)?; - } - } - - output.write_str("\r\n")?; - output.write_str("--")?; - output.write_str(multipart_boundary)?; - - Ok(output.freeze()) - } } diff --git a/src/lib.rs b/src/lib.rs index cae1a839..9de2d36a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,6 +101,7 @@ mod scroller; pub mod signature_verifier; pub mod socket_mode; +mod multipart_form; mod token; #[cfg(feature = "hyper")] diff --git a/src/multipart_form.rs b/src/multipart_form.rs new file mode 100644 index 00000000..7f6d366c --- /dev/null +++ b/src/multipart_form.rs @@ -0,0 +1,66 @@ +use crate::AnyStdResult; +use bytes::{BufMut, Bytes, BytesMut}; +use std::fmt::Write; + +pub fn generate_multipart_boundary() -> String { + format!( + "----WebKitFormBoundarySlackMorphismRust{}", + chrono::Utc::now().timestamp() + ) +} + +pub struct FileMultipartData<'a> { + pub name: &'a str, + pub content_type: &'a str, + pub data: &'a [u8], +} + +pub fn create_multipart_file_content<'p, PT, TS>( + fields: &'p PT, + multipart_boundary: &str, + file: FileMultipartData<'p>, +) -> AnyStdResult +where + PT: std::iter::IntoIterator)> + Clone, + TS: AsRef + 'p + Send, +{ + let mut output = BytesMut::with_capacity(file.data.len() + 512); + output.write_str("\r\n")?; + output.write_str("\r\n")?; + output.write_str("--")?; + output.write_str(multipart_boundary)?; + output.write_str("\r\n")?; + output.write_str(&format!( + "Content-Disposition: form-data; name=\"file\"; filename=\"{}\"", + file.name + ))?; + output.write_str("\r\n")?; + output.write_str(&format!("Content-Type: {}", file.content_type))?; + output.write_str("\r\n")?; + output.write_str(&format!("Content-Length: {}", file.data.len()))?; + output.write_str("\r\n")?; + output.write_str("\r\n")?; + output.put_slice(file.data); + + for (k, mv) in fields.clone().into_iter() { + if let Some(v) = mv { + let vs = v.as_ref(); + output.write_str("\r\n")?; + output.write_str("--")?; + output.write_str(multipart_boundary)?; + output.write_str("\r\n")?; + output.write_str(&format!("Content-Disposition: form-data; name=\"{}\"", k))?; + output.write_str("\r\n")?; + output.write_str(&format!("Content-Length: {}", vs.len()))?; + output.write_str("\r\n")?; + output.write_str("\r\n")?; + output.write_str(vs)?; + } + } + + output.write_str("\r\n")?; + output.write_str("--")?; + output.write_str(multipart_boundary)?; + + Ok(output.freeze()) +}