digest-headers 0.2.1

A simple library to hash a request's body in the headers
/* This file is part of Digest Headers
 *
 * Digest Headers is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Digest Headers is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Digest Headers  If not, see <http://www.gnu.org/licenses/>.
 */

//! The `use_hyper` module provides useful Types and Traits for interacting with `Hyper` with
//! `Digest`s.

use bytes::Bytes;
use failure::Fail;
use reqwest::{header::CONTENT_LENGTH, RequestBuilder, Response};

use std::{borrow::Cow, io::Cursor};

use crate::{Digest, ShaSize};

#[derive(Debug, Fail)]
#[fail(display = "Failed to verify digest")]
pub struct VerifyError;

pub struct DigestBody {
    inner: Bytes,
}

impl From<Bytes> for DigestBody {
    fn from(b: Bytes) -> Self {
        DigestBody { inner: b }
    }
}

impl From<Vec<u8>> for DigestBody {
    fn from(b: Vec<u8>) -> Self {
        DigestBody { inner: b.into() }
    }
}

impl From<&'static [u8]> for DigestBody {
    fn from(b: &'static [u8]) -> Self {
        DigestBody { inner: b.into() }
    }
}

impl From<Cow<'static, [u8]>> for DigestBody {
    fn from(b: Cow<'static, [u8]>) -> Self {
        DigestBody {
            inner: b.into_owned().into(),
        }
    }
}

impl From<String> for DigestBody {
    fn from(b: String) -> Self {
        DigestBody { inner: b.into() }
    }
}

impl From<&'static str> for DigestBody {
    fn from(b: &'static str) -> Self {
        DigestBody { inner: b.into() }
    }
}

impl From<Cow<'static, str>> for DigestBody {
    fn from(b: Cow<'static, str>) -> Self {
        DigestBody {
            inner: b.into_owned().into(),
        }
    }
}

pub trait VerifyDigest {
    fn verify_digest(&mut self) -> Result<Vec<u8>, VerifyError>;
}

pub trait WithDigest {
    fn with_digest<B: Into<DigestBody>>(self, body: B, sha_size: ShaSize) -> RequestBuilder;
}

impl WithDigest for RequestBuilder {
    fn with_digest<B: Into<DigestBody>>(self, body: B, sha_size: ShaSize) -> RequestBuilder {
        let body: DigestBody = body.into();

        let bytes = body.inner;
        let digest = Digest::new(&bytes, sha_size);

        self.header("Digest", digest.as_string())
            .header(CONTENT_LENGTH, bytes.len())
            .body(bytes.to_vec())
    }
}

impl VerifyDigest for Response {
    fn verify_digest(&mut self) -> Result<Vec<u8>, VerifyError> {
        let mut c = Cursor::new(Vec::new());

        let digest: Digest = self
            .headers()
            .get("Digest")
            .ok_or(VerifyError)?
            .to_str()
            .map_err(|_| VerifyError)?
            .parse()
            .map_err(|_| VerifyError)?;

        self.copy_to(&mut c).map_err(|_| VerifyError)?;

        let v = c.into_inner();

        digest.verify(&v).map(move |_| v).map_err(|_| VerifyError)
    }
}

#[cfg(test)]
mod tests {
    use reqwest::{header::CONTENT_TYPE, Client};

    use crate::{prelude::*, 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);
    }

    fn add_digest_to_request(sha_size: ShaSize) {
        let uri = "http://example.com";
        let json = r#"{"Library":"Hyper"}"#;

        Client::new()
            .post(uri)
            .header(CONTENT_TYPE, "application/json")
            .with_digest(json, sha_size)
            .build()
            .unwrap();
    }
}