use futures::{Future, Stream};
use hyper::{Body, Chunk, Request};
use hyper::header::{Formatter, Header, Raw};
use hyper::error::{Error as HyperError, Result as HyperResult};
use std::error::Error as StdError;
use std::fmt;
use std::str::from_utf8;
use {Digest, ShaSize};
use prelude::*;
#[derive(Debug)]
pub enum Error {
Hyper(HyperError),
NoBody,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Hyper(ref hyper_error) => write!(f, "Hyper: {}", hyper_error),
Error::NoBody => write!(f, "No body was found in the Request."),
}
}
}
impl StdError for Error {
fn description(&self) -> &str {
match *self {
Error::Hyper(ref hyper_error) => hyper_error.description(),
Error::NoBody => "No body was found in the Request",
}
}
}
impl From<HyperError> for Error {
fn from(e: HyperError) -> Self {
Error::Hyper(e)
}
}
#[derive(Clone)]
pub struct DigestHeader(pub Digest);
impl DigestHeader {
pub fn into_digest(self) -> Digest {
self.0
}
pub fn digest_string(&self) -> String {
self.0.as_string()
}
}
impl Header for DigestHeader {
fn header_name() -> &'static str {
"Digest"
}
fn parse_header(raw: &Raw) -> HyperResult<Self> {
if let Some(one) = raw.one() {
if let Ok(s) = from_utf8(one) {
let digest = s.parse::<Digest>().map_err(|_| HyperError::Header)?;
return Ok(DigestHeader(digest));
}
}
Err(HyperError::Header)
}
fn fmt_header(&self, f: &mut Formatter) -> fmt::Result {
f.fmt_line(&self.0)
}
}
impl IntoDigest for Body {
type Item = Chunk;
type Error = Error;
fn into_digest(self, sha_size: ShaSize) -> Result<(Self::Item, Digest), Self::Error> {
let full_body = self.concat2().wait()?;
let digest = Digest::new(&full_body, sha_size);
Ok((full_body, digest))
}
}
impl AsDigest for Request<Body> {
type Error = Error;
fn as_digest(self, sha_size: ShaSize) -> Result<(Self, Digest), Self::Error> {
let (method, uri, http_version, headers, body) = self.deconstruct();
let (body, digest) = body.into_digest(sha_size)?;
let mut req = Request::new(method, uri);
*req.headers_mut() = headers;
req.set_version(http_version);
req.set_body(body);
Ok((req, digest))
}
}
impl WithDigest for Request<Body> {
fn with_digest(self, sha_size: ShaSize) -> Result<Self, Self::Error> {
let (mut req, digest) = self.as_digest(sha_size)?;
req.headers_mut().set(DigestHeader(digest));
Ok(req)
}
}
#[cfg(test)]
mod tests {
use futures::{Future, Stream};
use hyper::{Body, Method, Request};
use hyper::header::{ContentLength, ContentType};
use tokio_core::reactor::Core;
use super::DigestHeader;
use ShaSize;
use prelude::*;
#[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);
}
fn add_digest_to_request(sha_size: ShaSize) {
let mut core = Core::new().unwrap();
let uri = "http://example.com".parse().unwrap();
let json = r#"{"Library":"Hyper"}"#;
let mut req: Request<Body> = Request::new(Method::Post, uri);
req.set_body(json);
req.headers_mut().set(ContentType::json());
req.headers_mut().set(ContentLength(json.len() as u64));
let req = req.with_digest(sha_size);
assert!(req.is_ok());
let mut req = req.unwrap();
let digest_header = req.headers_mut().remove::<DigestHeader>();
assert!(digest_header.is_some());
let digest_header = digest_header.unwrap();
let digest_header = digest_header.into_digest();
let fut = req.body().concat2().and_then(|body| {
assert!(digest_header.verify(&body).is_ok());
Ok(())
});
core.run(fut).unwrap();
}
}