1use std::ops::ControlFlow;
7use webx_core::error::Result;
8use webx_core::http::IHttpContext;
9use webx_core::middleware::IMiddleware;
10
11#[derive(Debug, Clone)]
13pub struct CorsConfig {
14 pub origins: Vec<String>,
16 pub methods: Vec<String>,
18 pub headers: Vec<String>,
20 pub expose_headers: Vec<String>,
22 pub allow_credentials: bool,
24 pub max_age: u32,
26}
27
28impl Default for CorsConfig {
29 fn default() -> Self {
30 Self {
31 origins: vec!["*".to_string()],
32 methods: vec![
33 "GET".to_string(),
34 "POST".to_string(),
35 "PUT".to_string(),
36 "DELETE".to_string(),
37 "PATCH".to_string(),
38 "OPTIONS".to_string(),
39 ],
40 headers: vec!["Content-Type".to_string(), "Authorization".to_string()],
41 expose_headers: Vec::new(),
42 allow_credentials: false,
43 max_age: 86400,
44 }
45 }
46}
47
48pub struct CorsMiddleware {
53 config: CorsConfig,
54}
55
56impl CorsMiddleware {
57 pub fn new(config: CorsConfig) -> Self {
59 Self { config }
60 }
61}
62
63#[async_trait::async_trait]
64impl IMiddleware for CorsMiddleware {
65 async fn invoke(&self, ctx: &mut dyn IHttpContext) -> Result<ControlFlow<()>> {
66 let origin = ctx.request().header("origin").map(str::to_string);
69
70 let allowed = self
72 .config
73 .origins
74 .iter()
75 .find(|o| *o == "*" || origin.as_deref().is_some_and(|orig| *o == orig))
76 .cloned();
77
78 if let Some(ref origin_value) = allowed {
79 let header_value = if origin_value == "*" && self.config.allow_credentials {
82 origin.as_deref().unwrap_or("*").to_string()
83 } else if origin_value == "*" {
84 "*".to_string()
85 } else if let Some(o) = origin.as_deref() {
86 o.to_string()
87 } else {
88 origin_value.clone()
89 };
90
91 ctx.response_mut()
92 .set_header("access-control-allow-origin", &header_value);
93
94 if self.config.allow_credentials {
95 ctx.response_mut()
96 .set_header("access-control-allow-credentials", "true");
97 }
98 }
99
100 let is_preflight = ctx.request().method().eq_ignore_ascii_case("OPTIONS")
107 && origin.is_some()
108 && ctx
109 .request()
110 .header("access-control-request-method")
111 .is_some();
112 if is_preflight {
113 ctx.response_mut().set_status(204);
114 ctx.response_mut().set_header(
115 "access-control-allow-methods",
116 &self.config.methods.join(", "),
117 );
118 ctx.response_mut().set_header(
119 "access-control-allow-headers",
120 &self.config.headers.join(", "),
121 );
122 ctx.response_mut()
123 .set_header("access-control-max-age", &self.config.max_age.to_string());
124 return Ok(ControlFlow::Break(()));
127 }
128
129 if !self.config.expose_headers.is_empty() {
131 ctx.response_mut().set_header(
132 "access-control-expose-headers",
133 &self.config.expose_headers.join(", "),
134 );
135 }
136
137 Ok(ControlFlow::Continue(()))
138 }
139}