extern crate self as rust_web_server;
pub mod app;
#[cfg(feature = "auth")]
pub mod auth;
#[cfg(feature = "macros")]
pub use rws_macros::{delete, get, patch, post, put, route, Config, FromRequest, JsonSchema, Validate};
#[cfg(all(feature = "macros", any(feature = "model-sqlite", feature = "model-postgres", feature = "model-mysql")))]
pub use rws_macros::Model;
#[cfg(feature = "http2")]
pub mod async_state;
#[cfg(feature = "http2")]
pub(crate) mod async_bridge;
pub mod session;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) mod redis_protocol;
pub mod sse;
pub mod compression;
pub mod cookie;
pub mod error;
pub mod extract;
pub mod ip_filter;
pub mod macros;
pub mod blocklist;
pub mod cache;
pub mod config_reload;
#[cfg(not(target_arch = "wasm32"))]
pub mod config_watch;
pub mod config_check;
#[cfg(any(feature = "auth-asymmetric", feature = "sso"))]
pub mod rsa_key;
pub mod server_config;
pub mod feature;
pub mod maintenance;
pub mod metrics;
pub mod mcp;
pub mod request_log;
pub mod request_id;
#[cfg(not(target_arch = "wasm32"))]
pub mod otel;
#[cfg(feature = "acme")]
pub mod acme;
pub mod middleware;
pub mod rate_limit;
pub mod router;
pub mod schema;
pub mod state;
pub mod test_client;
pub mod application;
pub mod body;
pub mod client_hint;
pub mod controller;
pub mod core;
pub mod cors;
pub mod entry_point;
pub mod ext;
pub mod header;
pub mod http;
pub mod json;
pub mod language;
#[cfg(not(target_arch = "wasm32"))]
pub mod log;
pub mod mime_type;
pub mod null;
pub mod range;
pub mod request;
pub mod response;
pub mod server;
pub mod symbol;
#[cfg(not(target_arch = "wasm32"))]
pub mod thread_pool;
pub mod url;
pub mod pagination;
#[cfg(not(target_arch = "wasm32"))]
pub mod proxy;
pub mod rewrite;
pub mod scheduler;
#[cfg(not(target_arch = "wasm32"))]
pub mod tcp_proxy;
#[cfg(not(target_arch = "wasm32"))]
pub mod udp_proxy;
#[cfg(not(target_arch = "wasm32"))]
pub mod ws_proxy;
#[cfg(not(target_arch = "wasm32"))]
pub mod canary;
#[cfg(not(target_arch = "wasm32"))]
pub mod circuit_breaker;
pub mod service_discovery;
pub(crate) mod dns;
pub mod config_binding;
pub mod di;
#[cfg(not(target_arch = "wasm32"))]
pub mod proxy_config;
#[cfg(not(target_arch = "wasm32"))]
pub mod llm;
#[cfg(not(target_arch = "wasm32"))]
pub mod admin;
#[cfg(feature = "wasm-plugins")]
pub mod wasm_plugin;
#[cfg(not(target_arch = "wasm32"))]
pub mod ingress;
#[cfg(feature = "tera")]
pub mod template;
pub mod validate;
pub mod virtual_host;
#[cfg(any(feature = "model-sqlite", feature = "model-postgres", feature = "model-mysql"))]
pub mod model;
#[cfg(not(target_arch = "wasm32"))]
pub mod websocket;
pub mod http_client;
#[cfg(feature = "crypto")]
pub mod crypto;
#[cfg(feature = "csrf")]
pub mod csrf;
#[cfg(feature = "sso")]
pub mod sso;
#[cfg(all(feature = "mailer", not(target_arch = "wasm32")))]
pub mod mailer;
#[cfg(feature = "jobs")]
pub mod jobs;
#[cfg(any(feature = "storage-local", feature = "storage-s3", feature = "storage-azure", feature = "secrets"))]
pub mod storage;
#[cfg(feature = "openapi")]
pub mod openapi;
#[cfg(feature = "tower")]
pub mod tower;
#[cfg(feature = "gateway-api")]
pub mod gateway_api;
#[cfg(feature = "webhook")]
pub mod webhook;
#[cfg(feature = "secrets")]
pub mod secrets;
#[cfg(not(target_arch = "wasm32"))]
pub mod timeout;
pub mod prelude;
#[cfg(feature = "http2")]
#[doc(hidden)]
pub mod tls;
#[cfg(feature = "http2")]
#[doc(hidden)]
pub mod h2_server;
#[cfg(feature = "http3")]
#[doc(hidden)]
pub mod h3_handler;
#[cfg(test)]
mod stack_depth_tests;
#[cfg(all(any(feature = "http-client", feature = "http2"), not(target_arch = "wasm32")))]
pub(crate) fn tls_client_builder() -> rustls::ConfigBuilder<rustls::ClientConfig, rustls::WantsVerifier> {
rustls::ClientConfig::builder_with_provider(std::sync::Arc::new(rustls::crypto::aws_lc_rs::default_provider()))
.with_safe_default_protocol_versions()
.expect("aws-lc-rs supports rustls's default protocol versions")
}
#[cfg(test)]
pub mod test_env {
use std::sync::{Mutex, OnceLock};
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
pub fn lock() -> std::sync::MutexGuard<'static, ()> {
LOCK.get_or_init(|| Mutex::new(()))
.lock()
.unwrap_or_else(|e| e.into_inner())
}
pub struct TempPath(std::path::PathBuf);
impl TempPath {
pub fn new(name: &str) -> Self {
TempPath(std::env::temp_dir().join(format!("rws_test_{}_{}", std::process::id(), name)))
}
pub fn new_in_cwd(name: &str) -> Self {
TempPath(std::env::current_dir().expect("working directory").join(format!("rws_test_{}_{}", std::process::id(), name)))
}
pub fn as_str(&self) -> &str {
self.0.to_str().expect("temp dir path is UTF-8")
}
}
impl std::ops::Deref for TempPath {
type Target = std::path::Path;
fn deref(&self) -> &std::path::Path {
&self.0
}
}
impl Drop for TempPath {
fn drop(&mut self) {
let is_link = std::fs::symlink_metadata(&self.0).is_ok_and(|m| m.file_type().is_symlink());
if is_link {
let _ = std::fs::remove_file(&self.0);
} else if self.0.is_dir() {
let _ = std::fs::remove_dir_all(&self.0);
} else {
let _ = std::fs::remove_file(&self.0);
}
}
}
}
#[cfg(fuzzing)]
#[doc(hidden)]
pub mod fuzz_api {
pub fn proxy_normalize_target(data: &[u8]) {
let Ok(target) = std::str::from_utf8(data) else { return };
let once = crate::proxy_config::normalize_request_target(target).into_owned();
let twice = crate::proxy_config::normalize_request_target(&once).into_owned();
assert_eq!(once, twice, "normalization is not idempotent for {:?}", target);
}
pub fn static_path_decode(data: &[u8]) {
let Ok(path) = std::str::from_utf8(data) else { return };
if let Ok(fs_path) = crate::app::controller::static_resource::static_fs_path(path) {
let relative = &fs_path[crate::app::controller::static_resource::working_directory().len()..];
assert!(!relative.split(['/', '\\']).any(|segment| segment == ".."), "{:?} -> {:?}", path, fs_path);
}
}
pub fn accept_encoding(data: &[u8]) {
let Ok(value) = std::str::from_utf8(data) else { return };
let request = crate::request::Request {
method: "GET".to_string(),
request_uri: "/".to_string(),
http_version: "HTTP/1.1".to_string(),
headers: vec![crate::header::Header::new(crate::header::Header::_ACCEPT_ENCODING, value.to_string())],
body: Default::default(),
};
for preference in [crate::compression::DYNAMIC_PREFERENCE, crate::compression::STATIC_PREFERENCE] {
if let Some(chosen) = crate::compression::negotiate(&request, preference) {
assert!(preference.contains(&chosen));
let refused = value.split(',').any(|c| {
let mut parts = c.split(';');
parts.next().unwrap_or("").trim().eq_ignore_ascii_case(chosen.token())
&& parts
.map(str::trim)
.find(|p| p.as_bytes().get(..2).is_some_and(|b| b.eq_ignore_ascii_case(b"q=")))
.is_some_and(|q| q[2..].trim().parse::<f32>() == Ok(0.0))
});
let listed_once = value.split(',').filter(|c| c.split(';').next().unwrap_or("").trim().eq_ignore_ascii_case(chosen.token())).count() == 1;
assert!(!(refused && listed_once), "{value:?} refused {chosen:?}");
}
}
}
#[cfg(feature = "wasm-plugins")]
pub fn wasm_plugin_output(data: &[u8]) {
let _ = crate::wasm_plugin::parse_request_output(data);
let _ = crate::wasm_plugin::parse_response_output(data);
}
pub fn llm_usage(data: &[u8]) {
for content_type in ["application/json", "text/event-stream"] {
let mut parser = crate::llm::UsageParser::new(content_type);
for piece in data.chunks(7) {
parser.feed(piece);
}
let _ = parser.finish();
}
let mut decoder = crate::llm::usage::ChunkDecoder::default();
let mut out = Vec::new();
let _ = decoder.decode(data, &mut out);
}
pub fn mcp_gateway_reply(data: &[u8]) {
for content_type in ["application/json", "text/event-stream"] {
if let Ok(result) = crate::mcp::gateway::parse_reply(content_type, data, 1) {
let _ = crate::mcp::gateway::fuzzing::tools(&result);
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn websocket_frame(data: &[u8]) {
let mut cursor = std::io::Cursor::new(data);
while let Ok(frame) = crate::websocket::WebSocket::read_frame_limited(&mut cursor, 4096) {
let len = match &frame {
crate::websocket::Frame::Text(t) => t.len(),
crate::websocket::Frame::Binary(b) | crate::websocket::Frame::Ping(b) | crate::websocket::Frame::Pong(b) => b.len(),
crate::websocket::Frame::Continuation { data, .. } => data.len(),
crate::websocket::Frame::Close(_, reason) => reason.len() + 2,
};
assert!(len <= 4096 + 2);
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn dns_srv_response(data: &[u8]) {
let id = data.get(..2).map_or(0, |b| u16::from_be_bytes([b[0], b[1]]));
let _ = crate::service_discovery::dns_srv::parse_response(data, id);
}
pub fn x509_subject_alt_names(data: &[u8]) {
let _ = crate::core::x509::subject_alt_names(data);
}
pub fn dns_message(data: &[u8]) {
if let Ok(message) = crate::dns::parse(data) {
for record in message.answers.iter().chain(&message.authority).chain(&message.additional) {
let _ = crate::dns::txt_value(&data[record.rdata.clone()]);
}
}
#[cfg(feature = "acme")]
let _ = crate::acme::dns01::check_update_reply(data, 0);
}
#[cfg(not(target_arch = "wasm32"))]
pub fn redis_reply(data: &[u8]) {
let _ = crate::redis_protocol::read_reply(&mut std::io::Cursor::new(data));
}
pub fn cookie_header(data: &[u8]) {
let Ok(value) = std::str::from_utf8(data) else { return };
let jar = crate::cookie::CookieJar::parse(value);
assert!(jar.cookies.iter().all(|c| !c.name.is_empty()));
}
#[cfg(not(target_arch = "wasm32"))]
pub fn otel_baggage(data: &[u8]) {
let Ok(value) = std::str::from_utf8(data) else { return };
let baggage = crate::otel::Baggage::parse(value);
assert!(baggage.len() <= crate::otel::baggage::MAX_MEMBERS);
let again = crate::otel::Baggage::parse(&baggage.to_header());
for (key, value) in baggage.iter() {
if let Some(round_tripped) = again.get(key) {
assert_eq!(value, round_tripped);
}
}
}
#[cfg(feature = "acme")]
pub fn acme_renewal(data: &[u8]) {
let _ = crate::acme::fuzzing::cert_info_from_der(data);
if let Ok(text) = std::str::from_utf8(data) {
let _ = crate::acme::fuzzing::parse_suggested_window(text);
let _ = crate::acme::fuzzing::parse_rfc3339(text);
let _ = crate::acme::fuzzing::json_object_keys(text, "profiles");
}
}
#[cfg(feature = "sso-saml")]
pub fn saml_xml_parse(xml: &str) {
let _ = crate::sso::saml::xml_parse_for_fuzzing(xml);
}
pub fn http1_framing(data: &[u8]) {
use crate::server::framing::{self, BodyFraming, ChunkedBody, HeadScan};
let mut pending = data.to_vec();
framing::skip_leading_empty_lines(&mut pending);
let HeadScan::Complete(head_len) = framing::scan_head(&pending) else { return };
assert!(head_len <= pending.len() && head_len <= framing::MAX_HEAD_SIZE);
let Ok(request) = crate::request::Request::parse(&pending[..head_len]) else { return };
for header in &request.headers {
let _ = crate::http::date::parse(&header.value);
}
let _ = framing::validate_head(&pending[..head_len], &request);
let _ = framing::keep_alive(&request);
let body = &pending[head_len..];
if let Ok(BodyFraming::Chunked) = framing::body_framing(&request) {
let split = data.first().map_or(0, |&b| b as usize).min(body.len());
let mut decoder = ChunkedBody::new(1 << 20);
let mut buffer = body[..split].to_vec();
for next in [&body[split..], &[][..]] {
match decoder.feed(&buffer) {
Ok((consumed, _)) => {
assert!(consumed <= buffer.len());
buffer.drain(..consumed);
}
Err(_) => return,
}
buffer.extend_from_slice(next);
}
assert!(decoder.body.len() <= 1 << 20);
}
}
}
#[cfg(all(doctest, feature = "http3"))]
#[doc = include_str!("../README.md")]
pub struct ReadmeDoctests;