use foctet_core::BodyEnvelopeLimits;
use thiserror::Error;
use crate::{
BODY_ONLY_SCOPE, CONTENT_TYPE, HttpError, HttpOpenOptions, HttpOpener, HttpSealOptions,
HttpSealer, SCOPE_HEADER,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkerRequestMetadata {
pub method: String,
pub path: String,
pub url: String,
pub headers: Vec<(String, String)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OpenedWorkerRequest {
pub metadata: WorkerRequestMetadata,
pub plaintext: Vec<u8>,
}
#[derive(Debug, Error)]
pub enum WorkersError {
#[error("workers operation failed")]
Worker(#[from] worker::Error),
#[error("foctet http operation failed")]
Http(#[from] HttpError),
}
#[derive(Clone, Debug)]
pub struct WorkersOpener {
opener: HttpOpener,
}
#[derive(Clone, Debug)]
pub struct WorkersSealer {
sealer: HttpSealer,
}
impl WorkersOpener {
pub fn new(options: HttpOpenOptions) -> Self {
Self {
opener: HttpOpener::new(options),
}
}
pub fn from_http_opener(opener: HttpOpener) -> Self {
Self { opener }
}
pub fn opener(&self) -> &HttpOpener {
&self.opener
}
pub async fn open_request_body(
&self,
mut request: worker::Request,
) -> Result<Vec<u8>, WorkersError> {
crate::raw::ensure_foctet_content_type(&worker_headers_to_http(&request)?)?;
let body = request.bytes().await?;
self.opener.open_body(&body).map_err(WorkersError::Http)
}
pub async fn open_request(
&self,
request: worker::Request,
) -> Result<OpenedWorkerRequest, WorkersError> {
let metadata = extract_worker_request_metadata(&request)?;
let plaintext = self.open_request_body(request).await?;
Ok(OpenedWorkerRequest {
metadata,
plaintext,
})
}
}
impl WorkersSealer {
pub fn new(options: HttpSealOptions) -> Self {
Self {
sealer: HttpSealer::new(options),
}
}
pub fn from_http_sealer(sealer: HttpSealer) -> Self {
Self { sealer }
}
pub fn sealer(&self) -> &HttpSealer {
&self.sealer
}
pub fn seal_response_body(&self, plaintext: &[u8]) -> Result<worker::Response, WorkersError> {
let sealed = self.sealer.seal_body(plaintext)?;
let mut response = worker::Response::from_bytes(sealed)?;
response.headers_mut().set("content-type", CONTENT_TYPE)?;
response.headers_mut().set(SCOPE_HEADER, BODY_ONLY_SCOPE)?;
Ok(response)
}
}
pub async fn open_worker_request_body(
request: worker::Request,
recipient_secret_key: [u8; 32],
) -> Result<Vec<u8>, WorkersError> {
WorkersOpener::new(HttpOpenOptions::new(recipient_secret_key))
.open_request_body(request)
.await
}
pub async fn open_worker_request_body_with_limits(
request: worker::Request,
recipient_secret_key: [u8; 32],
limits: &BodyEnvelopeLimits,
) -> Result<Vec<u8>, WorkersError> {
WorkersOpener::new(HttpOpenOptions::new(recipient_secret_key).with_limits(limits.clone()))
.open_request_body(request)
.await
}
pub async fn open_worker_request(
request: worker::Request,
recipient_secret_key: [u8; 32],
) -> Result<OpenedWorkerRequest, WorkersError> {
WorkersOpener::new(HttpOpenOptions::new(recipient_secret_key))
.open_request(request)
.await
}
pub async fn open_worker_request_with_limits(
request: worker::Request,
recipient_secret_key: [u8; 32],
limits: &BodyEnvelopeLimits,
) -> Result<OpenedWorkerRequest, WorkersError> {
WorkersOpener::new(HttpOpenOptions::new(recipient_secret_key).with_limits(limits.clone()))
.open_request(request)
.await
}
pub fn seal_worker_response_body(
plaintext: &[u8],
recipient_public_key: [u8; 32],
recipient_key_id: &[u8],
) -> Result<worker::Response, WorkersError> {
WorkersSealer::new(HttpSealOptions::new(recipient_public_key, recipient_key_id))
.seal_response_body(plaintext)
}
fn extract_worker_request_metadata(
request: &worker::Request,
) -> Result<WorkerRequestMetadata, WorkersError> {
let method = request.method().to_string();
let path = request.path();
let url = request.url()?.to_string();
let headers = request.headers().entries().collect();
Ok(WorkerRequestMetadata {
method,
path,
url,
headers,
})
}
fn worker_headers_to_http(request: &worker::Request) -> Result<http::HeaderMap, worker::Error> {
let mut out = http::HeaderMap::new();
for (name, value) in request.headers().entries() {
let name = http::header::HeaderName::from_bytes(name.as_bytes())
.map_err(|err| worker::Error::RustError(err.to_string()))?;
let value = http::header::HeaderValue::from_str(&value)
.map_err(|err| worker::Error::RustError(err.to_string()))?;
out.append(name, value);
}
Ok(out)
}