1mod auth;
41mod handlers;
42mod http;
43mod storage;
44mod ws;
45
46pub use auth::{AuthRejection, BearerClaims};
47pub use http::{
48 content_range_none_value, content_range_value, etag_matches_if_none_match, if_range_matches,
49 parse_range_header, ParsedRange, RangeParseError,
50};
51pub use storage::{FsStorage, FsSink};
52pub use ws::ws_handler;
53
54use std::sync::Arc;
55
56use axum::extract::Request;
57use axum::http::StatusCode;
58use axum::middleware::Next;
59use axum::response::{IntoResponse, Response};
60use axum::Router;
61use libfw_core::auth::{AuthError, TokenVerifier, Validator};
62use libfw_core::compress::CompressionFormat;
63use libfw_core::storage::StorageBackend;
64use libfw_core::{protocol_compatible, protocol_header_value, DEFAULT_MAX_UPLOAD_SIZE, HEADER_PROTOCOL};
65pub use libfw_core::{
66 HEADER_COMPRESS, HEADER_FILE_META, HEADER_FINAL, HEADER_OFFSET, HEADER_SESSION,
67 HEADER_SESSION_STATUS,
68};
69
70pub struct ServerState {
72 pub storage: Arc<dyn StorageBackend>,
74 pub verifier: Arc<dyn TokenVerifier>,
76 pub validator: Arc<dyn Validator>,
78 pub compression: CompressionFormat,
80 pub max_upload_size: u64,
82}
83
84impl ServerState {
85 pub fn builder() -> ServerStateBuilder {
87 ServerStateBuilder::default()
88 }
89
90 pub fn authorize(
92 &self,
93 claims: &libfw_core::claims::TokenClaims,
94 path: &str,
95 action: libfw_core::auth::Action,
96 ) -> Result<(), AuthError> {
97 self.validator.validate(claims, path, action)
98 }
99}
100
101pub struct ServerStateBuilder {
103 storage: Option<Arc<dyn StorageBackend>>,
104 verifier: Option<Arc<dyn TokenVerifier>>,
105 validator: Option<Arc<dyn Validator>>,
106 compression: CompressionFormat,
107 max_upload_size: u64,
108}
109
110impl Default for ServerStateBuilder {
111 fn default() -> Self {
112 ServerStateBuilder {
113 storage: None,
114 verifier: None,
115 validator: None,
116 compression: CompressionFormat::Zrip,
117 max_upload_size: DEFAULT_MAX_UPLOAD_SIZE,
118 }
119 }
120}
121
122impl ServerStateBuilder {
123 pub fn storage(mut self, storage: impl StorageBackend) -> Self {
125 self.storage = Some(Arc::new(storage));
126 self
127 }
128
129 pub fn verifier(mut self, verifier: impl TokenVerifier) -> Self {
131 self.verifier = Some(Arc::new(verifier));
132 self
133 }
134
135 pub fn validator(mut self, validator: impl Validator) -> Self {
137 self.validator = Some(Arc::new(validator));
138 self
139 }
140
141 pub fn compression(mut self, format: CompressionFormat) -> Self {
143 self.compression = format;
144 self
145 }
146
147 pub fn max_upload_size(mut self, size: u64) -> Self {
149 self.max_upload_size = size;
150 self
151 }
152
153 pub fn build(self) -> ServerState {
155 ServerState {
156 storage: self.storage.expect("storage is required"),
157 verifier: self.verifier.expect("verifier is required"),
158 validator: self.validator.expect("validator is required"),
159 compression: self.compression,
160 max_upload_size: self.max_upload_size,
161 }
162 }
163}
164
165async fn validate_protocol(req: Request, next: Next) -> Response {
172 if let Some(value) = req
173 .headers()
174 .get(HEADER_PROTOCOL)
175 .and_then(|v| v.to_str().ok())
176 {
177 if !protocol_compatible(value) {
178 return (
179 StatusCode::UPGRADE_REQUIRED,
180 format!(
181 "unsupported protocol `{value}`; expected `{}`",
182 protocol_header_value()
183 ),
184 )
185 .into_response();
186 }
187 }
188 next.run(req).await
189}
190
191pub fn router(state: Arc<ServerState>) -> Router {
203 use axum::routing::{get, post};
204
205 Router::new()
206 .route("/file/{*path}", get(handlers::download).head(handlers::head_file))
207 .route("/file/{*path}", post(handlers::upload))
208 .route("/dir", get(handlers::list_dir_root))
209 .route("/dir/{*path}", get(handlers::list_dir))
210 .route("/ws", get(ws_handler))
211 .layer(axum::middleware::from_fn(validate_protocol))
212 .with_state(state)
213}
214
215pub fn validate_rel_path(path: &str) -> Result<String, &'static str> {
219 if path.contains('\0') {
220 return Err("path contains NUL byte");
221 }
222 if path.starts_with('/') {
223 return Err("path must be relative");
224 }
225 let mut out = String::with_capacity(path.len());
226 for segment in path.split('/') {
227 match segment {
228 "" | "." => {}
229 ".." => return Err("path escapes the mount root"),
230 seg => {
231 if !out.is_empty() {
232 out.push('/');
233 }
234 out.push_str(seg);
235 }
236 }
237 }
238 Ok(out)
239}