1use rust_webx_core::error::Result;
7use rust_webx_core::http::IHttpContext;
8use rust_webx_core::middleware::IMiddleware;
9use std::ops::ControlFlow;
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");
67
68 let allowed = self
70 .config
71 .origins
72 .iter()
73 .find(|o| *o == "*" || origin.is_some_and(|orig| *o == orig))
74 .cloned();
75
76 if let Some(ref origin_value) = allowed {
77 let header_value = if origin_value == "*" && self.config.allow_credentials {
80 origin.unwrap_or("*").to_string()
81 } else if origin_value == "*" {
82 "*".to_string()
83 } else if let Some(o) = origin {
84 o.to_string()
85 } else {
86 origin_value.clone()
87 };
88
89 ctx.response_mut()
90 .set_header("access-control-allow-origin", &header_value);
91
92 if self.config.allow_credentials {
93 ctx.response_mut()
94 .set_header("access-control-allow-credentials", "true");
95 }
96 }
97
98 if ctx.request().method().to_uppercase() == "OPTIONS" {
100 ctx.response_mut().set_status(204);
101 ctx.response_mut().set_header(
102 "access-control-allow-methods",
103 &self.config.methods.join(", "),
104 );
105 ctx.response_mut().set_header(
106 "access-control-allow-headers",
107 &self.config.headers.join(", "),
108 );
109 ctx.response_mut()
110 .set_header("access-control-max-age", &self.config.max_age.to_string());
111 return Ok(ControlFlow::Break(()));
114 }
115
116 if !self.config.expose_headers.is_empty() {
118 ctx.response_mut().set_header(
119 "access-control-expose-headers",
120 &self.config.expose_headers.join(", "),
121 );
122 }
123
124 Ok(ControlFlow::Continue(()))
125 }
126}