qrock 0.2.2

Helpers for Rocket HTTP server applications.
Documentation
pub mod dbg;

use std::path::PathBuf;

use rocket::{
  http::Status,
  outcome::Outcome,
  request::{self, FromRequest, Request}
};

use base64::{engine::general_purpose, Engine as _};

/// `x-forwarded-host = foo.example.com`
pub struct ProxyHost<'r>(pub &'r str);

#[rocket::async_trait]
impl<'r> FromRequest<'r> for ProxyHost<'r> {
  type Error = ();

  async fn from_request(
    req: &'r Request<'_>
  ) -> request::Outcome<Self, Self::Error> {
    req
      .headers()
      .get_one("x-forwarded-host")
      .map_or(Outcome::Error((Status::BadRequest, ())), |val| {
        Outcome::Success(ProxyHost(val))
      })
  }
}

/// `x-forwarded-server = foo.example.com`
pub struct ProxyServer<'r>(pub &'r str);

#[rocket::async_trait]
impl<'r> FromRequest<'r> for ProxyServer<'r> {
  type Error = ();

  async fn from_request(
    req: &'r Request<'_>
  ) -> request::Outcome<Self, Self::Error> {
    req
      .headers()
      .get_one("x-forwarded-server")
      .map_or(Outcome::Error((Status::BadRequest, ())), |val| {
        Outcome::Success(ProxyServer(val))
      })
  }
}


/// `x-forwarded-for = 192.168.1.100`
pub struct ProxyFor<'r>(pub &'r str);

#[rocket::async_trait]
impl<'r> FromRequest<'r> for ProxyFor<'r> {
  type Error = ();

  async fn from_request(
    req: &'r Request<'_>
  ) -> request::Outcome<Self, Self::Error> {
    req
      .headers()
      .get_one("x-forwarded-for")
      .map_or(Outcome::Error((Status::BadRequest, ())), |val| {
        Outcome::Success(ProxyFor(val))
      })
  }
}


#[derive(Debug)]
pub struct AuthHdr {
  pub authtype: String,
  pub user: String,
  pub pass: String
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for AuthHdr {
  type Error = ();

  async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> {
    let Some(s) = req.headers().get_one("authorization") else {
      return Outcome::Error((Status::ProxyAuthenticationRequired, ()));
    };
    let Some(idx) = s.find(' ') else {
      return Outcome::Error((Status::ProxyAuthenticationRequired, ()));
    };
    let t = &s[..idx];

    let s = &s[(idx + 1)..];
    let Ok(vu8) = general_purpose::STANDARD.decode(s) else {
      return Outcome::Error((Status::ProxyAuthenticationRequired, ()));
    };
    let s = std::str::from_utf8(&vu8).unwrap();
    //println!("{}", s);

    let Some(idx) = s.find(':') else {
      return Outcome::Error((Status::ProxyAuthenticationRequired, ()));
    };

    let user = &s[..idx];
    let pass = &s[(idx + 1)..];

    Outcome::Success(Self {
      authtype: String::from(t),
      user: String::from(user),
      pass: String::from(pass)
    })
  }
}


/// A request guard that returns the relative path to the root.
#[repr(transparent)]
#[derive(Clone, Debug)]
pub struct RelRoot(pub String);

impl RelRoot {
  #[must_use]
  pub fn into_path(self) -> PathBuf {
    PathBuf::from(self.0)
  }

  #[must_use]
  pub fn into_inner(self) -> String {
    self.0
  }

  /// Generate a relative path to a resource from the current location.
  pub fn path_to<P: AsRef<str>>(&self, rel: P) -> String {
    if self.0.is_empty() {
      rel.as_ref().to_string()
    } else {
      format!("{}/{}", self.0, rel.as_ref())
    }
  }
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for RelRoot {
  type Error = ();

  async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> {
    req.route().map_or(
      Outcome::Error((Status::InternalServerError, ())),
      |route| {
        Outcome::Success(Self(
          route
            .uri
            .origin
            .path()
            .segments()
            .map(|_| "..")
            .collect::<Vec<&str>>()
            .join("/")
        ))
      }
    )
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :