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, 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;
68pub mod server_config;
69pub mod feature;
70pub mod maintenance;
71pub mod metrics;
72pub mod mcp;
73pub mod request_log;
74pub mod request_id;
75#[cfg(not(target_arch = "wasm32"))]
76pub mod otel;
77#[cfg(feature = "acme")]
78pub mod acme;
79pub mod middleware;
80pub mod rate_limit;
81pub mod router;
82pub mod state;
83pub mod test_client;
84pub mod application;
85pub mod body;
86pub mod client_hint;
87pub mod controller;
88pub mod core;
89pub mod cors;
90pub mod entry_point;
91pub mod ext;
92pub mod header;
93pub mod http;
94pub mod json;
95pub mod language;
96#[cfg(not(target_arch = "wasm32"))]
97pub mod log;
98pub mod mime_type;
99pub mod null;
100pub mod range;
101pub mod request;
102pub mod response;
103pub mod server;
104pub mod symbol;
105#[cfg(not(target_arch = "wasm32"))]
106pub mod thread_pool;
107pub mod url;
108pub mod pagination;
109#[cfg(not(target_arch = "wasm32"))]
110pub mod proxy;
111pub mod rewrite;
112pub mod scheduler;
113#[cfg(not(target_arch = "wasm32"))]
114pub mod tcp_proxy;
115#[cfg(not(target_arch = "wasm32"))]
116pub mod udp_proxy;
117#[cfg(not(target_arch = "wasm32"))]
118pub mod ws_proxy;
119#[cfg(not(target_arch = "wasm32"))]
120pub mod canary;
121#[cfg(not(target_arch = "wasm32"))]
122pub mod circuit_breaker;
123pub mod service_discovery;
128pub mod config_binding;
129pub mod di;
130#[cfg(not(target_arch = "wasm32"))]
131pub mod proxy_config;
132#[cfg(not(target_arch = "wasm32"))]
133pub mod ingress;
134#[cfg(feature = "tera")]
135pub mod template;
136pub mod validate;
137pub mod virtual_host;
138#[cfg(any(feature = "model-sqlite", feature = "model-postgres", feature = "model-mysql"))]
139pub mod model;
140#[cfg(not(target_arch = "wasm32"))]
141pub mod websocket;
142pub mod http_client;
146#[cfg(feature = "crypto")]
147pub mod crypto;
148#[cfg(feature = "csrf")]
149pub mod csrf;
150#[cfg(feature = "sso")]
151pub mod sso;
152#[cfg(all(feature = "mailer", not(target_arch = "wasm32")))]
156pub mod mailer;
157#[cfg(feature = "jobs")]
158pub mod jobs;
159#[cfg(any(feature = "storage-local", feature = "storage-s3", feature = "storage-azure", feature = "secrets"))]
162pub mod storage;
163#[cfg(feature = "openapi")]
164pub mod openapi;
165#[cfg(feature = "webhook")]
166pub mod webhook;
167#[cfg(feature = "secrets")]
168pub mod secrets;
169#[cfg(not(target_arch = "wasm32"))]
172pub mod timeout;
173pub mod prelude;
174
175#[cfg(feature = "http2")]
176#[doc(hidden)]
177pub mod tls;
178
179#[cfg(feature = "http2")]
180#[doc(hidden)]
181pub mod h2_handler;
182
183#[cfg(feature = "http3")]
184#[doc(hidden)]
185pub mod h3_handler;
186
187#[cfg(test)]
194mod stack_depth_tests;
195
196#[cfg(all(any(feature = "http-client", feature = "http2"), not(target_arch = "wasm32")))]
203pub(crate) fn tls_client_builder() -> rustls::ConfigBuilder<rustls::ClientConfig, rustls::WantsVerifier> {
204 rustls::ClientConfig::builder_with_provider(std::sync::Arc::new(rustls::crypto::aws_lc_rs::default_provider()))
205 .with_safe_default_protocol_versions()
206 .expect("aws-lc-rs supports rustls's default protocol versions")
207}
208
209#[cfg(test)]
210pub mod test_env {
211 use std::sync::{Mutex, OnceLock};
212 static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
213 pub fn lock() -> std::sync::MutexGuard<'static, ()> {
214 LOCK.get_or_init(|| Mutex::new(()))
215 .lock()
216 .unwrap_or_else(|e| e.into_inner())
217 }
218
219 pub struct TempPath(std::path::PathBuf);
223
224 impl TempPath {
225 pub fn new(name: &str) -> Self {
226 TempPath(std::env::temp_dir().join(format!("rws_test_{}_{}", std::process::id(), name)))
227 }
228
229 pub fn new_in_cwd(name: &str) -> Self {
232 TempPath(std::env::current_dir().expect("working directory").join(format!("rws_test_{}_{}", std::process::id(), name)))
233 }
234
235 pub fn as_str(&self) -> &str {
236 self.0.to_str().expect("temp dir path is UTF-8")
237 }
238 }
239
240 impl std::ops::Deref for TempPath {
241 type Target = std::path::Path;
242 fn deref(&self) -> &std::path::Path {
243 &self.0
244 }
245 }
246
247 impl Drop for TempPath {
248 fn drop(&mut self) {
249 let is_link = std::fs::symlink_metadata(&self.0).is_ok_and(|m| m.file_type().is_symlink());
252 if is_link {
253 let _ = std::fs::remove_file(&self.0);
254 } else if self.0.is_dir() {
255 let _ = std::fs::remove_dir_all(&self.0);
256 } else {
257 let _ = std::fs::remove_file(&self.0);
258 }
259 }
260 }
261}
262
263#[cfg(fuzzing)]
267#[doc(hidden)]
268pub mod fuzz_api {
269 pub fn proxy_normalize_target(data: &[u8]) {
272 let Ok(target) = std::str::from_utf8(data) else { return };
273 let once = crate::proxy_config::normalize_request_target(target).into_owned();
274 let twice = crate::proxy_config::normalize_request_target(&once).into_owned();
275 assert_eq!(once, twice, "normalization is not idempotent for {:?}", target);
276 }
277
278 pub fn static_path_decode(data: &[u8]) {
281 let Ok(path) = std::str::from_utf8(data) else { return };
282 if let Ok(fs_path) = crate::app::controller::static_resource::static_fs_path(path) {
283 let relative = &fs_path[crate::app::controller::static_resource::working_directory().len()..];
284 assert!(!relative.split(['/', '\\']).any(|segment| segment == ".."), "{:?} -> {:?}", path, fs_path);
285 }
286 }
287
288 #[cfg(feature = "sso-saml")]
290 pub fn saml_xml_parse(xml: &str) {
291 let _ = crate::sso::saml::xml_parse_for_fuzzing(xml);
292 }
293
294 pub fn http1_framing(data: &[u8]) {
298 use crate::server::framing::{self, BodyFraming, ChunkedBody, HeadScan};
299 let mut pending = data.to_vec();
300 framing::skip_leading_empty_lines(&mut pending);
301 let HeadScan::Complete(head_len) = framing::scan_head(&pending) else { return };
302 assert!(head_len <= pending.len() && head_len <= framing::MAX_HEAD_SIZE);
303 let Ok(request) = crate::request::Request::parse(&pending[..head_len]) else { return };
304 let _ = framing::validate_head(&pending[..head_len], &request);
305 let _ = framing::keep_alive(&request);
306 let body = &pending[head_len..];
307 if let Ok(BodyFraming::Chunked) = framing::body_framing(&request) {
308 let split = data.first().map_or(0, |&b| b as usize).min(body.len());
309 let mut decoder = ChunkedBody::new(1 << 20);
310 let mut buffer = body[..split].to_vec();
311 for next in [&body[split..], &[][..]] {
312 match decoder.feed(&buffer) {
313 Ok((consumed, _)) => {
314 assert!(consumed <= buffer.len());
315 buffer.drain(..consumed);
316 }
317 Err(_) => return,
318 }
319 buffer.extend_from_slice(next);
320 }
321 assert!(decoder.body.len() <= 1 << 20);
322 }
323 }
324}
325