use crate::signature_verifier::*;
use crate::{AnyStdResult, SlackApiToken};
use base64::prelude::*;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use futures_util::TryFutureExt;
use http::request::Parts;
use http::{Request, Response, Uri};
use hyper::body::HttpBody;
use hyper::Body;
use mime::Mime;
use rvstruct::ValueStruct;
use std::collections::HashMap;
use std::fmt::Write;
use std::io::Read;
use url::Url;
pub struct HyperExtensions;
impl HyperExtensions {
pub fn parse_query_params(request: &Request<Body>) -> HashMap<String, String> {
request
.uri()
.query()
.map(|v| {
url::form_urlencoded::parse(v.as_bytes())
.into_owned()
.collect()
})
.unwrap_or_default()
}
pub fn hyper_redirect_to(
url: &str,
) -> Result<Response<Body>, Box<dyn std::error::Error + Send + Sync>> {
Response::builder()
.status(hyper::http::StatusCode::FOUND)
.header(hyper::header::LOCATION, url)
.body(Body::empty())
.map_err(|e| e.into())
}
pub fn setup_token_auth_header(
request_builder: hyper::http::request::Builder,
token: Option<&SlackApiToken>,
) -> hyper::http::request::Builder {
if token.is_none() {
request_builder
} else {
let token_header_value = format!("Bearer {}", token.unwrap().token_value.value());
request_builder.header(hyper::header::AUTHORIZATION, token_header_value)
}
}
pub fn setup_basic_auth_header(
request_builder: hyper::http::request::Builder,
username: &str,
password: &str,
) -> hyper::http::request::Builder {
let header_value = format!(
"Basic {}",
BASE64_STANDARD.encode(format!("{username}:{password}"))
);
request_builder.header(hyper::header::AUTHORIZATION, header_value)
}
pub fn create_http_request(
url: Url,
method: hyper::http::Method,
) -> hyper::http::request::Builder {
let uri: Uri = url.as_str().parse().unwrap();
hyper::http::request::Builder::new()
.method(method)
.uri(uri)
.header("accept-charset", "utf-8")
}
pub async fn http_body_to_string<T>(body: T) -> AnyStdResult<String>
where
T: HttpBody,
T::Error: std::error::Error + Sync + Send + 'static,
{
let http_body = hyper::body::aggregate(body).await?;
let mut http_reader = http_body.reader();
let mut http_body_str = String::new();
http_reader.read_to_string(&mut http_body_str)?;
Ok(http_body_str)
}
pub fn http_response_content_type<RS>(response: &Response<RS>) -> Option<Mime> {
let http_headers = response.headers();
http_headers.get(hyper::header::CONTENT_TYPE).map(|hv| {
let hvs = hv.to_str().unwrap();
hvs.parse::<Mime>().unwrap()
})
}
pub async fn decode_signed_response(
req: Request<Body>,
signature_verifier: &SlackEventSignatureVerifier,
) -> AnyStdResult<(String, Parts)> {
match (
req.headers()
.get(SlackEventSignatureVerifier::SLACK_SIGNED_HASH_HEADER)
.cloned(),
req.headers()
.get(SlackEventSignatureVerifier::SLACK_SIGNED_TIMESTAMP)
.cloned(),
) {
(Some(received_hash), Some(received_ts)) => {
let (parts, req_body) = req.into_parts();
Self::http_body_to_string(req_body)
.and_then(|body| async {
signature_verifier
.verify(
received_hash.to_str().unwrap(),
&body,
received_ts.to_str().unwrap(),
)
.map(|_| (body, parts))
.map_err(|e| e.into())
})
.await
}
_ => 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<Bytes>
where
PT: std::iter::IntoIterator<Item = (&'p str, Option<&'p TS>)> + Clone,
TS: std::string::ToString + '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(&format!("Content-Type: {}", file_content_type))?;
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 {
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("\r\n")?;
output.write_str(&v.to_string())?;
}
}
output.write_str("\r\n")?;
output.write_str("--")?;
output.write_str(multipart_boundary)?;
Ok(output.freeze())
}
}