use bytes::Buf;
use futures::future::TryFutureExt;
use futures::future::{BoxFuture, FutureExt};
use hyper::body::HttpBody;
use hyper::client::*;
use hyper::http::StatusCode;
use hyper::{Body, Request, Response, Uri};
use hyper_rustls::HttpsConnector;
use mime::Mime;
use rvstruct::ValueStruct;
use slack_morphism::errors::*;
use slack_morphism::signature_verifier::SlackEventAbsentSignatureError;
use slack_morphism::signature_verifier::SlackEventSignatureVerifier;
use slack_morphism::{ClientResult, SlackApiToken, SlackClientHttpConnector, SlackEnvelopeMessage};
use slack_morphism_models::{SlackClientId, SlackClientSecret};
use std::collections::HashMap;
use std::io::Read;
use url::Url;
#[derive(Clone, Debug)]
pub struct SlackClientHyperConnector {
hyper_connector: Client<HttpsConnector<HttpConnector>>,
}
impl SlackClientHyperConnector {
pub fn new() -> Self {
let https_connector = HttpsConnector::with_native_roots();
let http_client = Client::builder().build::<_, hyper::Body>(https_connector);
Self {
hyper_connector: http_client,
}
}
pub(crate) 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_else(HashMap::new)
}
pub(crate) 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())
}
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(crate) 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::encode(format!("{}:{}", username, password))
);
request_builder.header(hyper::header::AUTHORIZATION, header_value)
}
pub(crate) 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")
}
async fn http_body_to_string<T>(body: T) -> ClientResult<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)
}
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(crate) async fn send_webapi_request<RS>(&self, request: Request<Body>) -> ClientResult<RS>
where
RS: for<'de> serde::de::Deserialize<'de>,
{
let http_res = self.hyper_connector.request(request).await?;
let http_status = http_res.status();
let http_content_type = Self::http_response_content_type(&http_res);
let http_body_str = Self::http_body_to_string(http_res).await?;
match http_status {
StatusCode::OK
if http_content_type.iter().all(|response_mime| {
response_mime.type_() == mime::APPLICATION
&& response_mime.subtype() == mime::JSON
}) =>
{
let slack_message: SlackEnvelopeMessage =
serde_json::from_str(http_body_str.as_str())?;
if slack_message.error.is_none() {
let decoded_body = serde_json::from_str(http_body_str.as_str())?;
Ok(decoded_body)
} else {
Err(SlackClientError::ApiError(
SlackClientApiError::new(slack_message.error.unwrap())
.opt_warnings(slack_message.warnings)
.with_http_response_body(http_body_str),
)
.into())
}
}
StatusCode::OK => serde_json::from_str("{}").map_err(|e| e.into()),
_ => Err(SlackClientError::HttpError(
SlackClientHttpError::new(http_status.as_u16())
.with_http_response_body(http_body_str),
)
.into()),
}
}
pub(crate) async fn decode_signed_response(
req: Request<Body>,
signature_verifier: &SlackEventSignatureVerifier,
) -> ClientResult<String> {
let headers = &req.headers().clone();
let req_body = req.into_body();
match (
headers.get(SlackEventSignatureVerifier::SLACK_SIGNED_HASH_HEADER),
headers.get(SlackEventSignatureVerifier::SLACK_SIGNED_TIMESTAMP),
) {
(Some(received_hash), Some(received_ts)) => {
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)
.map_err(|e| e.into())
})
.await
}
_ => Err(Box::new(SlackEventAbsentSignatureError::new())),
}
}
}
impl SlackClientHttpConnector for SlackClientHyperConnector {
fn http_get_uri<'a, RS>(
&'a self,
full_uri: Url,
token: Option<&'a SlackApiToken>,
) -> BoxFuture<'a, ClientResult<RS>>
where
RS: for<'de> serde::de::Deserialize<'de> + Send + 'a + Send,
{
async move {
let base_http_request = Self::create_http_request(full_uri, hyper::http::Method::GET);
let http_request = Self::setup_token_auth_header(base_http_request, token);
let body = self
.send_webapi_request(http_request.body(Body::empty())?)
.await?;
Ok(body)
}
.boxed()
}
fn http_get_with_client_secret<'a, RS>(
&'a self,
full_uri: Url,
client_id: &'a SlackClientId,
client_secret: &'a SlackClientSecret,
) -> BoxFuture<'a, ClientResult<RS>>
where
RS: for<'de> serde::de::Deserialize<'de> + Send + 'a + 'a + Send,
{
async move {
let http_request = Self::setup_basic_auth_header(
Self::create_http_request(full_uri, hyper::http::Method::GET),
&client_id.value(),
&client_secret.value(),
)
.body(Body::empty())?;
self.send_webapi_request(http_request).await
}
.boxed()
}
fn http_post_uri<'a, RQ, RS>(
&'a self,
full_uri: Url,
request_body: &'a RQ,
token: Option<&'a SlackApiToken>,
) -> BoxFuture<'a, ClientResult<RS>>
where
RQ: serde::ser::Serialize + Send + Sync,
RS: for<'de> serde::de::Deserialize<'de> + Send + 'a + Send + 'a,
{
async move {
let post_json = serde_json::to_string(&request_body)?;
let base_http_request = Self::create_http_request(full_uri, hyper::http::Method::POST)
.header("content-type", "application/json; charset=utf-8");
let http_request = Self::setup_token_auth_header(base_http_request, token);
let response_body = self
.send_webapi_request(http_request.body(post_json.into())?)
.await?;
Ok(response_body)
}
.boxed()
}
}