#![allow(dead_code)]
use std::{fmt, str};
use std::borrow::Cow;
use std::str::FromStr;
use bytes::Bytes;
use http_body_util::{BodyExt, Limited};
use hyper::Method;
use hyper::body::Body;
use hyper::header::USER_AGENT;
use hyper::http::uri::PathAndQuery;
use percent_encoding::percent_decode;
use rpki::ca::idexchange::MyHandle;
use serde::de::DeserializeOwned;
use crate::api::status::ErrorResponse;
use crate::commons::error::Error;
use crate::config::Config;
use crate::constants::HTTP_USER_AGENT_TRUNCATE;
use super::auth::{AuthInfo, Permission};
use super::response::HttpResponse;
use super::server::HttpServer;
pub type HyperRequest = hyper::Request<hyper::body::Incoming>;
pub struct Request<'a> {
request: HyperRequest,
server: &'a HttpServer,
auth: AuthInfo,
limits: BodyLimits,
}
impl<'a> Request<'a> {
pub fn new(
request: HyperRequest,
server: &'a HttpServer,
auth: AuthInfo,
limits: BodyLimits,
) ->Self {
Self { request, server, auth, limits }
}
pub fn testbed_enabled(&self) -> bool {
self.server.krill().testbed_enabled()
}
pub fn method(&self) -> &Method {
self.request.method()
}
pub fn check_get(&self) -> Result<(), HttpResponse> {
match *self.request.method() {
Method::GET => Ok(()),
_ => Err(HttpResponse::method_not_allowed()),
}
}
pub fn check_post(&self) -> Result<(), HttpResponse> {
match *self.request.method() {
Method::POST => Ok(()),
_ => Err(HttpResponse::method_not_allowed()),
}
}
pub fn check_delete(&self) -> Result<(), HttpResponse> {
match *self.request.method() {
Method::DELETE => Ok(()),
_ => Err(HttpResponse::method_not_allowed()),
}
}
pub fn path(&self) -> Result<RequestPath, InvalidPath> {
RequestPath::from_request(self)
}
pub fn hyper(&self) -> &HyperRequest {
&self.request
}
pub fn user_agent(&self) -> Option<String> {
match self.request.headers().get(&USER_AGENT) {
None => None,
Some(value) => value.to_str().ok().map(|s| {
if s.len() > HTTP_USER_AGENT_TRUNCATE {
s[..HTTP_USER_AGENT_TRUNCATE].to_string()
} else {
s.to_string()
}
}),
}
}
pub fn check_permission(
&self, permission: Permission, resource: Option<&MyHandle>
) -> Result<(), HttpResponse> {
self.auth.check_permission(permission, resource).map_err(|err| {
HttpResponse::response_from_error(Error::from(err))
})
}
pub fn proceed_permitted(
self,
permission: Permission,
resource: Option<&MyHandle>,
) -> Result<(AuthedRequest<'a>, AuthInfo), HttpResponse> {
self.check_permission(permission, resource)?;
Ok((
AuthedRequest {
request: self.request,
server: self.server,
limits: self.limits,
},
self.auth
))
}
pub fn proceed_unchecked(
self
) -> (AuthedRequest<'a>, AuthInfo) {
(
AuthedRequest {
request: self.request,
server: self.server,
limits: self.limits,
},
self.auth
)
}
pub fn proceed_raw(self) -> (&'a HttpServer, HyperRequest) {
(self.server, self.request)
}
}
pub struct AuthedRequest<'a> {
request: HyperRequest,
server: &'a HttpServer,
limits: BodyLimits,
}
impl<'a> AuthedRequest<'a> {
pub fn empty(self) -> Result<&'a HttpServer, Error> {
if self.request.body().size_hint().upper() != Some(0) {
return Err(Error::UnexpectedBody)
}
Ok(self.server)
}
pub async fn read_bytes(self) -> Result<(&'a HttpServer, Bytes), Error> {
let limit = self.limits.post_limit_api;
self.read_body(limit).await
}
pub async fn read_json<T: DeserializeOwned>(
self
) -> Result<(&'a HttpServer, T), Error> {
let (server, bytes) = self.read_bytes().await?;
let json = serde_json::from_slice(&bytes).map_err(Error::JsonError)?;
Ok((server, json))
}
pub async fn read_rfc6492_bytes(
self
) -> Result<(&'a HttpServer, Bytes), Error> {
let limit = self.limits.post_limit_rfc6492;
self.read_body(limit).await
}
pub async fn read_rfc8181_bytes(
self
) -> Result<(&'a HttpServer, Bytes), Error> {
let limit = self.limits.post_limit_rfc8181;
self.read_body(limit).await
}
async fn read_body(
self, limit: u64
) -> Result<(&'a HttpServer, Bytes), Error> {
if self.request.body().size_hint().lower() > limit {
return Err(Error::PostTooBig);
}
Ok((
self.server,
Limited::new(
self.request.into_body(),
limit.try_into().unwrap_or(usize::MAX),
).collect().await.map_err(|_| {
Error::PostCannotRead
})?.to_bytes()
))
}
}
#[derive(Debug, Clone)]
pub struct RequestPath {
path: Result<PathAndQuery, String>,
}
impl RequestPath {
fn from_request(request: &Request) -> Result<Self, InvalidPath> {
let path = if let Cow::Owned(some) = percent_decode(
request.request.uri().path().as_bytes()
).decode_utf8().map_err(|_| InvalidPath)? {
Err(some)
}
else {
Ok(
request.request.uri().path_and_query()
.ok_or(InvalidPath)?.clone()
)
};
Ok(Self { path })
}
pub fn as_str(&self) -> &str {
match self.path.as_ref() {
Ok(path) => path.path(),
Err(path) => path.as_str()
}
}
pub fn iter(&self) -> PathIter<'_> {
PathIter::new(self.as_str())
}
}
impl AsRef<str> for RequestPath {
fn as_ref(&self) -> &str {
self.as_str()
}
}
#[derive(Debug)]
pub struct PathIter<'a> {
full: &'a str,
remaining: Option<&'a str>,
}
impl<'a> PathIter<'a> {
fn new(path: &'a str) -> Self {
Self {
full: path,
remaining: Some(path.strip_prefix('/').unwrap_or(path))
}
}
pub fn strip_trailing_slash(&self) -> Self {
let remaining = match self.remaining {
Some("") | None => None,
Some(remaining) => {
Some(remaining.strip_suffix('/').unwrap_or(remaining))
}
};
Self {
full: self.full.strip_suffix('/').unwrap_or(self.full),
remaining
}
}
pub fn full(&self) -> &str {
self.full
}
pub fn remaining(&self) -> Option<&str> {
self.remaining
}
pub fn check_exhausted(&self) -> Result<(), HttpResponse> {
if self.remaining.is_some() {
Err(HttpResponse::not_found())
}
else {
Ok(())
}
}
pub fn parse_next<T: FromStr>(&mut self) -> Result<T, HttpResponse> {
T::from_str(
self.next().ok_or_else(HttpResponse::not_found)?
).map_err(|_| {
HttpResponse::not_found()
})
}
pub fn parse_opt_next<T: FromStr>(
&mut self
) -> Result<Option<T>, HttpResponse> {
self.next().map(|s| {
T::from_str(s).map_err(|_| HttpResponse::not_found())
}).transpose()
}
pub fn parse_opt_next_trailing_slash<T: FromStr>(
&mut self
) -> Result<Option<T>, HttpResponse> {
self.next().map(|s| {
T::from_str(s).map_err(|_| HttpResponse::not_found())
}).transpose()
}
}
impl<'a> Iterator for PathIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
let remaining = self.remaining?;
let slash = match remaining.find('/') {
Some(pos) => pos,
None => {
let res = remaining;
self.remaining = None;
return Some(res)
}
};
let res = &remaining[..slash];
self.remaining = Some(&remaining[slash + 1..]);
Some(res)
}
}
#[derive(Clone, Copy, Debug)]
pub struct BodyLimits {
post_limit_api: u64,
post_limit_rfc6492: u64,
post_limit_rfc8181: u64,
}
impl BodyLimits {
pub fn from_config(config: &Config) -> Self {
Self {
post_limit_api: config.post_limit_api,
post_limit_rfc6492: config.post_limit_rfc6492,
post_limit_rfc8181: config.post_limit_rfc8181,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct InvalidPath;
impl From<InvalidPath> for ErrorResponse {
fn from(_: InvalidPath) -> Self {
Self::new("invalid-path", "The request path was invalid.")
}
}
impl fmt::Display for InvalidPath {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("invalid request path")
}
}
#[cfg(test)]
mod test {
use super::*;
impl RequestPath {
fn test_str(s: &str) -> Self {
Self {
path: Err(
percent_decode(
s.as_bytes()
).decode_utf8().unwrap().into_owned()
)
}
}
}
#[test]
fn request_path_next() {
let path = RequestPath::test_str("/foo/bar/baz/");
let mut path = path.iter();
assert_eq!(path.next(), Some("foo"));
assert_eq!(path.next(), Some("bar"));
assert_eq!(path.next(), Some("baz"));
assert_eq!(path.next(), Some(""));
assert_eq!(path.next(), None);
let path = RequestPath::test_str("/foo/bar/baz");
let mut path = path.iter();
assert_eq!(path.next(), Some("foo"));
assert_eq!(path.next(), Some("bar"));
assert_eq!(path.next(), Some("baz"));
assert_eq!(path.next(), None);
let path = RequestPath::test_str("/foo/b%61%72%2fbaz/");
let mut path = path.iter();
assert_eq!(path.next(), Some("foo"));
assert_eq!(path.next(), Some("bar"));
assert_eq!(path.next(), Some("baz"));
assert_eq!(path.next(), Some(""));
assert_eq!(path.next(), None);
let path = RequestPath::test_str("/foö/bär/baß");
let mut path = path.iter();
assert_eq!(path.next(), Some("foö"));
assert_eq!(path.next(), Some("bär"));
assert_eq!(path.next(), Some("baß"));
assert_eq!(path.next(), None);
}
}