5 releases

0.2.1 Jan 12, 2019
0.2.0 Jan 12, 2019
0.1.2 Jan 15, 2018
0.1.1 Jan 14, 2018
0.1.0 Jan 13, 2018

#1412 in HTTP server

GPL-3.0 license

49KB
818 lines

Digest Headers

A library to aid in the creation and verification of Digest Headers, Sha Digests of HTTP Request Bodies

crates.io documentation

Getting started

Add the following to your Cargo.toml

[dependencies.digest-headers]
version = "0.2"

Available Features

  • use_actix_web
  • use_hyper
  • use_reqwest
  • use_rocket

Usage

Here's some basic usage with all of the supported frameworks.

Building requests with Actix Web

#[derive(Debug, Fail)]
#[fail(display = "Could not build request")]
pub struct RequestBuildError;

fn main() -> Result<(), Error> {
    let json = r#"{"library":"actix"}"#;

    let req = post("http://localhost:5000")
        .content_type("application/json")
        .with_digest(json, ShaSize::TwoFiftySix)
        .map_err(|_| RequestBuildError)?;

    actix_web::actix::run(move || {
        req.send()
            .map_err(|_| ())
            .and_then(|res| {
                println!("POST: {}", res.status());

                res.verify_digest()
                    .map(|ActixWebVerifiedDigest| println!("Verified response!"))
                    .map_err(|_| ())
            })
            .map(|_| {
                actix_web::actix::System::current().stop();
            })
    });

    Ok(())
}

Handling requests with Actix Web

use actix_web::{server, App, HttpResponse};
use digest_headers::{prelude::*, ShaSize};

const PHRASE: &str = "Hewwo, Mr. Obama???";

fn index(_: ActixWebVerifiedDigest) -> HttpResponse {
    println!("Verified request!");

    HttpResponse::Ok()
        .content_type("text/plain")
        .force_close()
        .with_digest(PHRASE, ShaSize::TwoFiftySix)
}

fn main() {
    server::new(move || App::new().resource("/", |r| r.with(index)))
        .bind("127.0.0.1:5000")
        .expect("Can not bind to port 5000")
        .run();
}

Building requests with Hyper

use digest_headers::{prelude::*, ShaSize};

let client = Client::new();

let uri = "http://localhost:8000";
let json = r#"{"Library":"Hyper"}"#;

let req = Request::post(uri)
    .header(CONTENT_TYPE, "application/json")
    .header(CONNECTION, "close")
    .with_digest(json, ShaSize::TwoFiftySix)
    .unwrap();

let post = client.request(req).map_err(|_| ()).and_then(|res| {
    println!("POST: {}", res.status());

    res.verify_digest()
        .map(|_req| {
            println!("Verified resposne");
        })
        .map_err(|_| ())
});

hyper::rt::run(post)

Handling requests with Hyper

use digest_headers::{prelude::*, ShaSize};
use futures::Future;
use hyper::{service::service_fn, Body, Request, Response, Server};

type BoxResponse = Box<Future<Item = Response<Body>, Error = SomeError> + Send>;

fn verify_digest(req: Request<Body>) -> BoxResponse {
    let fut = req.verify_digest().map_err(|_| SomeError).and_then(|_req| {
        println!("Verified!");
        Response::builder()
            .with_digest("Verified", ShaSize::TwoFiftySix)
            .map_err(|_| SomeError)
    });

    Box::new(fut)
}

Building requests with Reqwest

use digest_headers::{prelude::*, ShaSize};
use reqwest::Client;

let payload = r#"{"Library":"Reqwest"}"#;
let client = Client::new();
let req = client
    .post("http://localhost:8000")
    .with_digest(payload, ShaSize::TwoFiftySix)
    .build()
    .unwrap();

let mut res = client.execute(req).unwrap();
println!("GET: {}", res.status());
let body = res.verify_digest().unwrap();
if let Ok(body) = std::str::from_utf8(&body) {
    println!("Verified, {}", body);
} else {
    println!("Verified");
}

Handling requests with Rocket

use digest_headers::{
    use_rocket::{ContentLengthHeader, DigestHeader, Error as DigestError, WithDigest},
    ShaSize,
};
use rocket::{
    config::{Config, Environment},
    data::{self, Data, FromData},
    http::Status,
    request::Request,
    response::Response,
    Outcome,
};

struct DigestVerifiedBody<T>(pub T);

impl<'a> FromData<'a> for DigestVerifiedBody<Vec<u8>> {
    type Owned = Vec<u8>;
    type Borrowed = Vec<u8>;
    type Error = Error;

    fn transform(
        req: &Request,
        data: Data,
    ) -> data::Transform<data::Outcome<Self::Owned, Self::Error>> {
        let outcome = req
            .guard::<DigestHeader>()
            .map(|digest_header| digest_header.0)
            .and_then(move |digest| {
                req.guard::<ContentLengthHeader>()
                    .map(|content_length_header| (digest, content_length_header.0))
            })
            .map_failure(|(s, e)| (s, e.into()))
            .and_then(move |(digest, content_length)| {
                println!("Provided Digest: {:?}", digest);

                let mut body = vec![0u8; content_length];
                // Ensure request is less than 2 MB. This is still likely way too large
                if content_length > 1024 * 1024 * 2 {
                    return Outcome::Failure((Status::BadRequest, Error::RequestTooBig));
                }

                println!("Content Length: {}", content_length);

                // Only read as much data as we expect to avoid DOS
                if data.open().read_exact(&mut body).is_err() {
                    return Outcome::Failure((Status::InternalServerError, Error::ReadFailed));
                }

                if digest.verify(&body).is_err() {
                    return Outcome::Failure((Status::BadRequest, Error::DigestMismatch));
                }

                Outcome::Success(body)
            });

        let outcome = match outcome {
            Outcome::Success(s) => Outcome::Success(s),
            Outcome::Forward(_) => Outcome::Failure((Status::BadRequest, Error::ReadFailed)),
            Outcome::Failure(f) => Outcome::Failure(f),
        };

        data::Transform::Borrowed(outcome)
    }

    fn from_data(
        _: &Request,
        outcome: data::Transformed<'a, Self>,
    ) -> data::Outcome<Self, Self::Error> {
        let body = outcome.borrowed()?;

        Outcome::Success(DigestVerifiedBody(body.to_vec()))
    }
}

#[post("/", data = "<data>")]
fn index(data: DigestVerifiedBody<Vec<u8>>) -> Response<'static> {
    let inner = data.0;
    if let Ok(data) = std::str::from_utf8(&inner) {
        println!("Verified {}", data);
    } else {
        println!("Verified");
    }

    Response::build()
        .with_digest(Cursor::new("woah"), ShaSize::TwoFiftySix)
        .unwrap()
        .finalize()
}

What this library supports

  • Creation of Digest Header strings
  • Verification of Digest Header strings
  • Adding Digest Headers to Requests and Responses for various libraries.

Examples

Notes

  • The Actix Web Client and Server examples are configured to work with each other.
  • The Hyper Client and Server examples are configured to work with each other.
  • The Rocket and Reqwest examples are configured to work with each other.

Contributing

Please be aware that all code contributed to this project will be licensed under the GPL version 3.

License

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. This file is part of Digest Headers

You should have received a copy of the GNU General Public License along with Digest Headers If not, see http://www.gnu.org/licenses/.

Dependencies

~6–17MB
~329K SLoC