feat: new matcher system instead of manually 'match'ing in servcie

This commit is contained in:
hexlocation 2025-08-02 20:40:44 +02:00
parent f305cb5a85
commit 638b0376d8
15 changed files with 646 additions and 225 deletions

180
src/matchers/api.rs Normal file
View file

@ -0,0 +1,180 @@
use std::{net::IpAddr, pin::Pin, sync::Arc};
use async_trait::async_trait;
use base64::{Engine, prelude::BASE64_STANDARD};
use http::request::Parts;
use http_body_util::BodyExt;
use hyper::{
Method, Request, StatusCode,
body::{self, Incoming},
service::Service,
};
use json::JsonValue;
use log::{debug, error, warn};
use tokio::{net::TcpStream, sync::Mutex};
use crate::{
config::{Client, Config},
db::{BoxyDatabase, Endpoint},
routes::api::{AddHost, RegisterEndpoint, RemoveHost},
server::{GeneralResponse, TcpIntercept, custom_resp, default_response, json_to_vec},
services::matcher::{Matcher, MatcherService},
};
#[derive(Debug, Clone)]
pub struct ApiMatcher {
pub database: Arc<Mutex<&'static mut BoxyDatabase>>,
pub config: Config,
pub _address: Option<IpAddr>,
pub body: Option<JsonValue>,
}
impl ApiMatcher {
pub async fn new(database: Arc<Mutex<&'static mut BoxyDatabase>>, config: Config) -> Self {
Self {
database,
config,
_address: None,
body: None,
}
}
}
#[async_trait]
impl Matcher for ApiMatcher {
async fn unimatch(
&mut self,
req: &Request<Incoming>,
) -> (bool, Option<Result<GeneralResponse, hyper::Error>>) {
let address = self._address.unwrap();
let encoded_header = match req.headers().get(hyper::header::AUTHORIZATION) {
None => {
error!("Authorization header not given for request from {address}",);
return (
false,
Some(Ok(custom_resp(
StatusCode::BAD_REQUEST,
"Invalid credentials.".to_string(),
)
.await)),
);
}
Some(x) => x,
}
.to_str()
.unwrap();
debug!("authorization header: {}", encoded_header);
let auth_bytes = match BASE64_STANDARD.decode(&encoded_header[6..]) {
Ok(x) => x,
Err(e) => {
error!("Error while decoding authorization header from {address}: {e}",);
return (
false,
Some(Ok(custom_resp(
StatusCode::BAD_REQUEST,
"Invalid base64 string given.".to_string(),
)
.await)),
);
}
};
let auth_string = match String::from_utf8(auth_bytes) {
Ok(x) => x,
Err(e) => {
error!("Error while decoding authorization header from {address}: {e}",);
return (
false,
Some(Ok(custom_resp(
StatusCode::BAD_REQUEST,
"Invalid UTF-8 in body.".to_string(),
)
.await)),
);
}
};
debug!("decoded auth string: {}", auth_string);
if !Client::verify(auth_string.clone(), self.config.clone()).await {
warn!(
"Authentication for string {} from {} failed.",
auth_string, address
);
return (
false,
Some(Ok(custom_resp(
StatusCode::UNAUTHORIZED,
"Invalid credentials.".to_string(),
)
.await)),
);
}
return (true, None);
}
fn retrieve(&self) -> Vec<Arc<dyn crate::services::matcher::Route<Self> + Sync + Send>> {
vec![
Arc::new(RegisterEndpoint {}),
Arc::new(AddHost {}),
Arc::new(RemoveHost {}),
]
}
fn stream(&mut self, stream: &TcpStream) {
self._address = Some(stream.peer_addr().unwrap().ip());
}
async fn body(&mut self, body: Incoming) -> Option<Result<GeneralResponse, hyper::Error>> {
let address = self._address.unwrap();
let body_string = match String::from_utf8(
body.collect()
.await
.unwrap()
.to_bytes()
.iter()
.cloned()
.collect::<Vec<u8>>()
.clone(),
) {
Ok(x) => x,
Err(e) => {
error!("Error while inferring UTF-8 string from {address}'s request body: {e}",);
return Some(Ok(custom_resp(
StatusCode::BAD_REQUEST,
"Invalid UTF-8 in body.".to_string(),
)
.await));
}
};
debug!("body: {}", body_string);
let json = match json::parse(body_string.as_str()) {
Ok(x) => x,
Err(e) => {
error!("Error while parsing JSON body from {address}: {e}",);
return Some(Ok(custom_resp(
StatusCode::BAD_REQUEST,
"Invalid JSON in body.".to_string(),
)
.await));
}
};
self.body = Some(json);
None
}
}