#[macro_use]
extern crate rocket;

use rocket::data::{self, Data, FromData, Limits, Outcome, ToByteUnit};
use rocket::http::Status;
use rocket::outcome::Outcome::{Failure, Success};
use rocket::request::{self, Request};

use hmac::{Hmac, Mac};
use sha2::Sha256;

use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::BufReader;

const CONFIG_FILENAME: &str = "config.json";

struct SignatureDataGuard;

fn is_valid_signature(received_signature: &str, payload: &Vec<u8>) -> bool {
    let config_file = File::open(CONFIG_FILENAME).unwrap();
    let config_reader = BufReader::new(config_file);
    let config: Config = serde_json::from_reader(config_reader).unwrap();

    type HmacSha256 = Hmac<Sha256>;
    let mut mac = HmacSha256::new_from_slice(config.secret.as_bytes()).unwrap();
    mac.update(payload);
    let expected_signature = mac.finalize().into_bytes();

    received_signature.as_bytes()[..] == expected_signature[..]
}

#[derive(Debug)]
enum Error {
    PayloadTooLarge,
    MissingSignature,
    MoreThatOneSignature,
    InvalidSignature,
    Io(std::io::Error),
}

#[rocket::async_trait]
impl<'r> FromData<'r> for SignatureDataGuard {
    type Error = Error;

    async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> data::Outcome<'r, Self> {
        let payload = match data.open(256.bytes()).into_bytes().await {
            Ok(payload) if payload.is_complete() => payload.into_inner(),
            Ok(_) => return Failure((Status::PayloadTooLarge, Error::PayloadTooLarge)),
            Err(e) => return Failure((Status::InternalServerError, Error::Io(e))),
        };

        let mut received_signatures = req.headers().get("X-GITEA-SIGNATURE");
        let received_signature = match received_signatures.next() {
            Some(signature) => signature,
            None => return Outcome::Failure((Status::BadRequest, Error::MissingSignature)),
        };

        if received_signatures.next().is_some() {
            return Outcome::Failure((Status::BadRequest, Error::MoreThatOneSignature));
        }

        if !is_valid_signature(received_signature, &payload) {
            return Outcome::Failure((Status::BadRequest, Error::InvalidSignature));
        }

        Outcome::Success(SignatureDataGuard)
    }
}

#[derive(Deserialize)]
struct Hook {
    repo_url: String,
    commands: Vec<String>,
}

#[derive(Deserialize)]
struct Config {
    secret: String,
    hooks: Vec<Hook>,
}

#[get("/")]
fn index() -> &'static str {
    "Hello, world!"
}

#[post("/trigger", data = "<payload>")]
fn trigger(payload: SignatureDataGuard) -> &'static str {
    "Sensitive data."
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .mount("/", routes![index])
        .mount("/api", routes![trigger])
}