1extern crate self as rust_web_server;
39
40pub mod app;
41#[cfg(feature = "auth")]
42pub mod auth;
43
44#[cfg(feature = "macros")]
45pub use rws_macros::{delete, get, patch, post, put, route, Config, FromRequest, JsonSchema, Validate};
46#[cfg(all(feature = "macros", any(feature = "model-sqlite", feature = "model-postgres", feature = "model-mysql")))]
47pub use rws_macros::Model;
48#[cfg(feature = "http2")]
49pub mod async_state;
50#[cfg(feature = "http2")]
51pub(crate) mod async_bridge;
52pub mod session;
53#[cfg(not(target_arch = "wasm32"))]
56pub(crate) mod redis_protocol;
57pub mod sse;
58pub mod compression;
59pub mod cookie;
60pub mod error;
61pub mod extract;
62pub mod ip_filter;
63pub mod macros;
64pub mod blocklist;
65pub mod cache;
66pub mod config_reload;
67pub mod config_check;
68#[cfg(any(feature = "auth-asymmetric", feature = "sso"))]
70pub mod rsa_key;
71pub mod server_config;
72pub mod feature;
73pub mod maintenance;
74pub mod metrics;
75pub mod mcp;
76pub mod request_log;
77pub mod request_id;
78#[cfg(not(target_arch = "wasm32"))]
79pub mod otel;
80#[cfg(feature = "acme")]
81pub mod acme;
82pub mod middleware;
83pub mod rate_limit;
84pub mod router;
85pub mod schema;
86pub mod state;
87pub mod test_client;
88pub mod application;
89pub mod body;
90pub mod client_hint;
91pub mod controller;
92pub mod core;
93pub mod cors;
94pub mod entry_point;
95pub mod ext;
96pub mod header;
97pub mod http;
98pub mod json;
99pub mod language;
100#[cfg(not(target_arch = "wasm32"))]
101pub mod log;
102pub mod mime_type;
103pub mod null;
104pub mod range;
105pub mod request;
106pub mod response;
107pub mod server;
108pub mod symbol;
109#[cfg(not(target_arch = "wasm32"))]
110pub mod thread_pool;
111pub mod url;
112pub mod pagination;
113#[cfg(not(target_arch = "wasm32"))]
114pub mod proxy;
115pub mod rewrite;
116pub mod scheduler;
117#[cfg(not(target_arch = "wasm32"))]
118pub mod tcp_proxy;
119#[cfg(not(target_arch = "wasm32"))]
120pub mod udp_proxy;
121#[cfg(not(target_arch = "wasm32"))]
122pub mod ws_proxy;
123#[cfg(not(target_arch = "wasm32"))]
124pub mod canary;
125#[cfg(not(target_arch = "wasm32"))]
126pub mod circuit_breaker;
127pub mod service_discovery;
132pub mod config_binding;
133pub mod di;
134#[cfg(not(target_arch = "wasm32"))]
135pub mod proxy_config;
136#[cfg(not(target_arch = "wasm32"))]
137pub mod ingress;
138#[cfg(feature = "tera")]
139pub mod template;
140pub mod validate;
141pub mod virtual_host;
142#[cfg(any(feature = "model-sqlite", feature = "model-postgres", feature = "model-mysql"))]
143pub mod model;
144#[cfg(not(target_arch = "wasm32"))]
145pub mod websocket;
146pub mod http_client;
150#[cfg(feature = "crypto")]
151pub mod crypto;
152#[cfg(feature = "csrf")]
153pub mod csrf;
154#[cfg(feature = "sso")]
155pub mod sso;
156#[cfg(all(feature = "mailer", not(target_arch = "wasm32")))]
160pub mod mailer;
161#[cfg(feature = "jobs")]
162pub mod jobs;
163#[cfg(any(feature = "storage-local", feature = "storage-s3", feature = "storage-azure", feature = "secrets"))]
166pub mod storage;
167#[cfg(feature = "openapi")]
168pub mod openapi;
169#[cfg(feature = "webhook")]
170pub mod webhook;
171#[cfg(feature = "secrets")]
172pub mod secrets;
173#[cfg(not(target_arch = "wasm32"))]
176pub mod timeout;
177pub mod prelude;
178
179#[cfg(feature = "http2")]
180#[doc(hidden)]
181pub mod tls;
182
183#[cfg(feature = "http2")]
184#[doc(hidden)]
185pub mod h2_server;
186
187#[cfg(feature = "http3")]
188#[doc(hidden)]
189pub mod h3_handler;
190
191#[cfg(test)]
198mod stack_depth_tests;
199
200#[cfg(all(any(feature = "http-client", feature = "http2"), not(target_arch = "wasm32")))]
207pub(crate) fn tls_client_builder() -> rustls::ConfigBuilder<rustls::ClientConfig, rustls::WantsVerifier> {
208 rustls::ClientConfig::builder_with_provider(std::sync::Arc::new(rustls::crypto::aws_lc_rs::default_provider()))
209 .with_safe_default_protocol_versions()
210 .expect("aws-lc-rs supports rustls's default protocol versions")
211}
212
213#[cfg(test)]
214pub mod test_env {
215 use std::sync::{Mutex, OnceLock};
216 static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
217 pub fn lock() -> std::sync::MutexGuard<'static, ()> {
218 LOCK.get_or_init(|| Mutex::new(()))
219 .lock()
220 .unwrap_or_else(|e| e.into_inner())
221 }
222
223 pub struct TempPath(std::path::PathBuf);
227
228 impl TempPath {
229 pub fn new(name: &str) -> Self {
230 TempPath(std::env::temp_dir().join(format!("rws_test_{}_{}", std::process::id(), name)))
231 }
232
233 pub fn new_in_cwd(name: &str) -> Self {
236 TempPath(std::env::current_dir().expect("working directory").join(format!("rws_test_{}_{}", std::process::id(), name)))
237 }
238
239 pub fn as_str(&self) -> &str {
240 self.0.to_str().expect("temp dir path is UTF-8")
241 }
242 }
243
244 impl std::ops::Deref for TempPath {
245 type Target = std::path::Path;
246 fn deref(&self) -> &std::path::Path {
247 &self.0
248 }
249 }
250
251 impl Drop for TempPath {
252 fn drop(&mut self) {
253 let is_link = std::fs::symlink_metadata(&self.0).is_ok_and(|m| m.file_type().is_symlink());
256 if is_link {
257 let _ = std::fs::remove_file(&self.0);
258 } else if self.0.is_dir() {
259 let _ = std::fs::remove_dir_all(&self.0);
260 } else {
261 let _ = std::fs::remove_file(&self.0);
262 }
263 }
264 }
265}
266
267#[cfg(fuzzing)]
271#[doc(hidden)]
272pub mod fuzz_api {
273 pub fn proxy_normalize_target(data: &[u8]) {
276 let Ok(target) = std::str::from_utf8(data) else { return };
277 let once = crate::proxy_config::normalize_request_target(target).into_owned();
278 let twice = crate::proxy_config::normalize_request_target(&once).into_owned();
279 assert_eq!(once, twice, "normalization is not idempotent for {:?}", target);
280 }
281
282 pub fn static_path_decode(data: &[u8]) {
285 let Ok(path) = std::str::from_utf8(data) else { return };
286 if let Ok(fs_path) = crate::app::controller::static_resource::static_fs_path(path) {
287 let relative = &fs_path[crate::app::controller::static_resource::working_directory().len()..];
288 assert!(!relative.split(['/', '\\']).any(|segment| segment == ".."), "{:?} -> {:?}", path, fs_path);
289 }
290 }
291
292 pub fn mcp_gateway_reply(data: &[u8]) {
295 for content_type in ["application/json", "text/event-stream"] {
296 if let Ok(result) = crate::mcp::gateway::parse_reply(content_type, data, 1) {
297 let _ = crate::mcp::gateway::fuzzing::tools(&result);
298 }
299 }
300 }
301
302 #[cfg(feature = "acme")]
306 pub fn acme_renewal(data: &[u8]) {
307 let _ = crate::acme::fuzzing::cert_info_from_der(data);
308 if let Ok(text) = std::str::from_utf8(data) {
309 let _ = crate::acme::fuzzing::parse_suggested_window(text);
310 let _ = crate::acme::fuzzing::parse_rfc3339(text);
311 let _ = crate::acme::fuzzing::json_object_keys(text, "profiles");
312 }
313 }
314
315 #[cfg(feature = "sso-saml")]
317 pub fn saml_xml_parse(xml: &str) {
318 let _ = crate::sso::saml::xml_parse_for_fuzzing(xml);
319 }
320
321 pub fn http1_framing(data: &[u8]) {
325 use crate::server::framing::{self, BodyFraming, ChunkedBody, HeadScan};
326 let mut pending = data.to_vec();
327 framing::skip_leading_empty_lines(&mut pending);
328 let HeadScan::Complete(head_len) = framing::scan_head(&pending) else { return };
329 assert!(head_len <= pending.len() && head_len <= framing::MAX_HEAD_SIZE);
330 let Ok(request) = crate::request::Request::parse(&pending[..head_len]) else { return };
331 for header in &request.headers {
332 let _ = crate::http::date::parse(&header.value);
333 }
334 let _ = framing::validate_head(&pending[..head_len], &request);
335 let _ = framing::keep_alive(&request);
336 let body = &pending[head_len..];
337 if let Ok(BodyFraming::Chunked) = framing::body_framing(&request) {
338 let split = data.first().map_or(0, |&b| b as usize).min(body.len());
339 let mut decoder = ChunkedBody::new(1 << 20);
340 let mut buffer = body[..split].to_vec();
341 for next in [&body[split..], &[][..]] {
342 match decoder.feed(&buffer) {
343 Ok((consumed, _)) => {
344 assert!(consumed <= buffer.len());
345 buffer.drain(..consumed);
346 }
347 Err(_) => return,
348 }
349 buffer.extend_from_slice(next);
350 }
351 assert!(decoder.body.len() <= 1 << 20);
352 }
353 }
354}
355
356
357#[cfg(all(doctest, feature = "http3"))]
359#[doc = include_str!("../README.md")]
360pub struct ReadmeDoctests;