use actix_web::{
client::{ClientRequest, ClientRequestBuilder, ClientResponse},
dev::HttpResponseBuilder,
http::header::HeaderValue,
Binary, Error, FromRequest, HttpMessage, HttpRequest, HttpResponse,
};
use bytes::Bytes;
use failure::Fail;
use futures::Future;
use crate::{Digest, ShaSize};
#[derive(Debug, Fail)]
#[fail(display = "Failed to verify digest")]
pub struct VerifyError;
pub struct VerifiedDigest;
impl<T> FromRequest<T> for VerifiedDigest
where
T: 'static,
{
type Config = ();
type Result = Box<dyn Future<Item = VerifiedDigest, Error = Error> + 'static>;
fn from_request(req: &HttpRequest<T>, _: &Self::Config) -> Self::Result {
let maybe_digest = req.headers().get("Digest").cloned();
let fut = req
.body()
.from_err()
.and_then(|bytes| verify(bytes, maybe_digest));
Box::new(fut)
}
}
pub trait VerifyDigest {
fn verify_digest(&self) -> Box<dyn Future<Item = VerifiedDigest, Error = Error>>;
}
impl VerifyDigest for ClientResponse {
fn verify_digest(&self) -> Box<dyn Future<Item = VerifiedDigest, Error = Error>> {
let maybe_digest = self.headers().get("Digest").cloned();
let fut = self
.body()
.from_err()
.and_then(|bytes| verify(bytes, maybe_digest));
Box::new(fut)
}
}
pub trait WithDigest<T> {
fn with_digest<B: Into<Binary>>(&mut self, body: B, sha_size: ShaSize) -> T;
}
impl WithDigest<HttpResponse> for HttpResponseBuilder {
fn with_digest<B: Into<Binary>>(&mut self, body: B, sha_size: ShaSize) -> HttpResponse {
let mut body: Binary = body.into();
let bytes = body.take();
let digest = Digest::new(&bytes, sha_size);
self.header("Digest", digest.as_string())
.content_length(bytes.len() as u64)
.body(bytes)
}
}
impl WithDigest<Result<ClientRequest, Error>> for ClientRequestBuilder {
fn with_digest<B: Into<Binary>>(
&mut self,
body: B,
sha_size: ShaSize,
) -> Result<ClientRequest, Error> {
let mut body: Binary = body.into();
let bytes = body.take();
let digest = Digest::new(&bytes, sha_size);
self.header("Digest", digest.as_string())
.content_length(bytes.len() as u64)
.body(bytes)
}
}
fn verify(bytes: Bytes, maybe_digest: Option<HeaderValue>) -> Result<VerifiedDigest, Error> {
if let Some(digest) = maybe_digest {
digest
.to_str()
.map_err(failure::Error::from)
.map_err(Error::from)
.and_then(|s| {
s.parse()
.map_err(failure::Error::from)
.map_err(Error::from)
.and_then(|parsed: Digest| {
if parsed.verify(&bytes).is_ok() {
Ok(VerifiedDigest)
} else {
Err(failure::Error::from(VerifyError).into())
}
})
})
} else {
Err(failure::Error::from(VerifyError).into())
}
}
#[cfg(test)]
mod tests {
use actix_web::{client::post, Binary, Body, HttpResponse};
use crate::{prelude::*, Digest, ShaSize};
#[test]
fn add_digest_to_request_256() {
add_digest_to_request(ShaSize::TwoFiftySix);
}
#[test]
fn add_digest_to_request_384() {
add_digest_to_request(ShaSize::ThreeEightyFour);
}
#[test]
fn add_digest_to_request_512() {
add_digest_to_request(ShaSize::FiveTwelve);
}
#[test]
fn add_digest_to_response_256() {
add_digest_to_response(ShaSize::TwoFiftySix);
}
#[test]
fn add_digest_to_response_384() {
add_digest_to_response(ShaSize::ThreeEightyFour);
}
#[test]
fn add_digest_to_response_512() {
add_digest_to_response(ShaSize::FiveTwelve);
}
fn add_digest_to_response(sha_size: ShaSize) {
let json = r#"{"Library":"Hyper"}"#;
let mut res = HttpResponse::Ok()
.content_type("application/json")
.with_digest(json, sha_size);
let digest_header = res.headers_mut().remove("Digest");
assert!(digest_header.is_some());
let digest_header: Digest = digest_header.unwrap().to_str().unwrap().parse().unwrap();
match res.replace_body("") {
Body::Binary(mut body) => {
assert!(digest_header.verify(&body.take()).is_ok());
}
_ => {
panic!("Body isn't binary");
}
}
}
fn add_digest_to_request(sha_size: ShaSize) {
let uri = "http://example.com";
let json = r#"{"Library":"Hyper"}"#;
let mut req = post(uri)
.content_type("application/json")
.with_digest(json, sha_size)
.unwrap();
let digest_header = req.headers_mut().remove("Digest");
assert!(digest_header.is_some());
let digest_header: Digest = digest_header.unwrap().to_str().unwrap().parse().unwrap();
let is_ok = match *req.body() {
Body::Binary(ref body) => match body {
Binary::Bytes(ref bytes) => digest_header.verify(bytes).is_ok(),
Binary::Slice(slice) => digest_header.verify(slice).is_ok(),
Binary::SharedVec(vec) => digest_header.verify(&vec).is_ok(),
Binary::SharedString(string) => digest_header.verify(string.as_bytes()).is_ok(),
},
_ => false,
};
assert!(is_ok);
}
}