mod auth;
mod handlers;
mod http;
mod storage;
mod ws;
pub use auth::{AuthRejection, BearerClaims};
pub use http::{
content_range_none_value, content_range_value, etag_matches_if_none_match, if_range_matches,
parse_range_header, ParsedRange, RangeParseError,
};
pub use storage::{FsStorage, FsSink};
pub use ws::ws_handler;
use std::sync::Arc;
use axum::extract::Request;
use axum::http::StatusCode;
use axum::middleware::Next;
use axum::response::{IntoResponse, Response};
use axum::Router;
use libfw_core::auth::{AuthError, TokenVerifier, Validator};
use libfw_core::compress::CompressionFormat;
use libfw_core::storage::StorageBackend;
use libfw_core::{protocol_compatible, protocol_header_value, DEFAULT_MAX_UPLOAD_SIZE, HEADER_PROTOCOL};
pub use libfw_core::{
HEADER_COMPRESS, HEADER_FILE_META, HEADER_FINAL, HEADER_OFFSET, HEADER_SESSION,
HEADER_SESSION_STATUS,
};
pub struct ServerState {
pub storage: Arc<dyn StorageBackend>,
pub verifier: Arc<dyn TokenVerifier>,
pub validator: Arc<dyn Validator>,
pub compression: CompressionFormat,
pub max_upload_size: u64,
}
impl ServerState {
pub fn builder() -> ServerStateBuilder {
ServerStateBuilder::default()
}
pub fn authorize(
&self,
claims: &libfw_core::claims::TokenClaims,
path: &str,
action: libfw_core::auth::Action,
) -> Result<(), AuthError> {
self.validator.validate(claims, path, action)
}
}
pub struct ServerStateBuilder {
storage: Option<Arc<dyn StorageBackend>>,
verifier: Option<Arc<dyn TokenVerifier>>,
validator: Option<Arc<dyn Validator>>,
compression: CompressionFormat,
max_upload_size: u64,
}
impl Default for ServerStateBuilder {
fn default() -> Self {
ServerStateBuilder {
storage: None,
verifier: None,
validator: None,
compression: CompressionFormat::Zrip,
max_upload_size: DEFAULT_MAX_UPLOAD_SIZE,
}
}
}
impl ServerStateBuilder {
pub fn storage(mut self, storage: impl StorageBackend) -> Self {
self.storage = Some(Arc::new(storage));
self
}
pub fn verifier(mut self, verifier: impl TokenVerifier) -> Self {
self.verifier = Some(Arc::new(verifier));
self
}
pub fn validator(mut self, validator: impl Validator) -> Self {
self.validator = Some(Arc::new(validator));
self
}
pub fn compression(mut self, format: CompressionFormat) -> Self {
self.compression = format;
self
}
pub fn max_upload_size(mut self, size: u64) -> Self {
self.max_upload_size = size;
self
}
pub fn build(self) -> ServerState {
ServerState {
storage: self.storage.expect("storage is required"),
verifier: self.verifier.expect("verifier is required"),
validator: self.validator.expect("validator is required"),
compression: self.compression,
max_upload_size: self.max_upload_size,
}
}
}
async fn validate_protocol(req: Request, next: Next) -> Response {
if let Some(value) = req
.headers()
.get(HEADER_PROTOCOL)
.and_then(|v| v.to_str().ok())
{
if !protocol_compatible(value) {
return (
StatusCode::UPGRADE_REQUIRED,
format!(
"unsupported protocol `{value}`; expected `{}`",
protocol_header_value()
),
)
.into_response();
}
}
next.run(req).await
}
pub fn router(state: Arc<ServerState>) -> Router {
use axum::routing::{get, post};
Router::new()
.route("/file/{*path}", get(handlers::download).head(handlers::head_file))
.route("/file/{*path}", post(handlers::upload))
.route("/dir", get(handlers::list_dir_root))
.route("/dir/{*path}", get(handlers::list_dir))
.route("/ws", get(ws_handler))
.layer(axum::middleware::from_fn(validate_protocol))
.with_state(state)
}
pub fn validate_rel_path(path: &str) -> Result<String, &'static str> {
if path.contains('\0') {
return Err("path contains NUL byte");
}
if path.starts_with('/') {
return Err("path must be relative");
}
let mut out = String::with_capacity(path.len());
for segment in path.split('/') {
match segment {
"" | "." => {}
".." => return Err("path escapes the mount root"),
seg => {
if !out.is_empty() {
out.push('/');
}
out.push_str(seg);
}
}
}
Ok(out)
}