rust_web_server/cors/
mod.rs1#[cfg(test)]
2mod tests;
3
4use crate::header::Header;
5use crate::request::{METHOD, Request};
6use crate::response::Error;
7use crate::server_config::ServerConfig;
8
9#[derive(PartialEq, Eq, Clone, Debug)]
10pub struct Cors {
11 pub allow_all: bool,
12 pub allow_origins: Vec<String>,
13 pub allow_methods: Vec<String>,
14 pub allow_headers: Vec<String>,
15 pub allow_credentials: bool,
16 pub expose_headers: Vec<String>,
17 pub max_age: String,
18}
19
20impl Cors {
21 pub const MAX_AGE: &'static str = "86400";
22
23 pub fn origin_is_allowed(allowed: &str, origin: &str) -> bool {
29 let origin = origin.trim();
30 !origin.is_empty() && allowed.split(',').any(|candidate| candidate.trim().eq_ignore_ascii_case(origin))
31 }
32
33 pub fn get_vary_header_value() -> String {
34 Header::_ORIGIN.to_string()
35 }
36
37 pub fn allow_all(request: &Request) -> Result<Vec<Header>, Error> {
38 let mut headers : Vec<Header> = vec![];
39 let origin = request.header(Header::_ORIGIN);
40 if origin.is_some() {
41 let allow_origin = Header {
42 name: Header::_ACCESS_CONTROL_ALLOW_ORIGIN.into(),
43 value: origin.unwrap().value.to_string().into()
44 };
45 headers.push(allow_origin);
46
47 let allow_credentials = Header {
48 name: Header::_ACCESS_CONTROL_ALLOW_CREDENTIALS.into(),
49 value: "true".into()
50 };
51 headers.push(allow_credentials);
52
53 let is_options = request.method == METHOD.options;
54 if is_options {
55 let method = request.header(Header::_ACCESS_CONTROL_REQUEST_METHOD);
56 if method.is_some() {
57 let allow_method = Header {
58 name: Header::_ACCESS_CONTROL_ALLOW_METHODS.into(),
59 value: method.unwrap().value.to_string().into()
60 };
61 headers.push(allow_method);
62 }
63
64 let access_control_request_headers = request.header(Header::_ACCESS_CONTROL_REQUEST_HEADERS);
65 if access_control_request_headers.is_some() {
66 let request_headers = access_control_request_headers.unwrap();
67 let allow_headers = Header {
68 name: Header::_ACCESS_CONTROL_ALLOW_HEADERS.into(),
69 value: request_headers.value.to_lowercase().into(),
70 };
71 headers.push(allow_headers);
72
73 let expose_headers = Header {
74 name: Header::_ACCESS_CONTROL_EXPOSE_HEADERS.into(),
75 value: request_headers.value.to_lowercase().into(),
76 };
77 headers.push(expose_headers);
78 }
79
80 let max_age = Header {
81 name: Header::_ACCESS_CONTROL_MAX_AGE.into(),
82 value: Cors::MAX_AGE.into()
83 };
84 headers.push(max_age);
85 }
86
87 }
88
89 Ok(headers)
90 }
91
92 pub fn _process(request: &Request, cors: &Cors) -> Result<Vec<Header>, Error> {
93 let mut headers : Vec<Header> = vec![];
94
95 let allow_origins = cors.allow_origins.join(",");
96 let boxed_origin = request.header(Header::_ORIGIN);
97
98 if boxed_origin.is_none() {
99 return Ok(headers)
100 }
101
102 let origin = boxed_origin.unwrap();
103 let origin_value = format!("{}", origin.value);
104
105 let is_valid_origin = Cors::origin_is_allowed(&allow_origins, &origin_value);
106 if !is_valid_origin {
107 return Ok(headers)
108 }
109
110 let allow_origin = Header {
111 name: Header::_ACCESS_CONTROL_ALLOW_ORIGIN.into(),
112 value: origin_value.into()
113 };
114 headers.push(allow_origin);
115
116 if cors.allow_credentials {
117 let allow_credentials = Header {
118 name: Header::_ACCESS_CONTROL_ALLOW_CREDENTIALS.into(),
119 value: cors.allow_credentials.to_string().into()
120 };
121 headers.push(allow_credentials);
122 }
123
124 if !cors.expose_headers.is_empty() {
126 headers.push(Header {
127 name: Header::_ACCESS_CONTROL_EXPOSE_HEADERS.into(),
128 value: cors.expose_headers.join(",").to_lowercase().into(),
129 });
130 }
131
132 let is_options = request.method == METHOD.options;
133 if is_options {
134 let methods = cors.allow_methods.join(",");
135 let allow_methods = Header {
136 name: Header::_ACCESS_CONTROL_ALLOW_METHODS.into(),
137 value: methods.into()
138 };
139 headers.push(allow_methods);
140
141 let allow_headers_value = cors.allow_headers.join(",");
142 let allow_headers = Header {
143 name: Header::_ACCESS_CONTROL_ALLOW_HEADERS.into(),
144 value: allow_headers_value.to_lowercase().into()
145 };
146 headers.push(allow_headers);
147
148 let max_age = Header {
149 name: Header::_ACCESS_CONTROL_MAX_AGE.into(),
150 value: cors.max_age.to_string().into()
151 };
152 headers.push(max_age);
153 }
154
155 Ok(headers)
156 }
157
158 pub fn get_headers_from_config(request: &Request, config: &ServerConfig) -> Vec<Header> {
164 if config.cors_allow_all {
165 return Cors::allow_all(request).unwrap_or_default();
166 }
167 Cors::process_using_config(request, config).unwrap_or_default()
168 }
169
170 fn process_using_config(request: &Request, config: &ServerConfig) -> Result<Vec<Header>, Error> {
171 let mut headers: Vec<Header> = vec![];
172
173 let boxed_origin = request.header(Header::_ORIGIN);
174 if boxed_origin.is_none() {
175 return Ok(headers);
176 }
177 let origin_value = boxed_origin.unwrap().value.clone();
178
179 if !Cors::origin_is_allowed(&config.cors_allow_origins, &origin_value) {
180 return Ok(headers);
181 }
182
183 headers.push(Header {
184 name: Header::_ACCESS_CONTROL_ALLOW_ORIGIN.into(),
185 value: origin_value,
186 });
187
188 let credentials_str = &config.cors_allow_credentials;
189 if credentials_str.eq_ignore_ascii_case("true") {
190 headers.push(Header {
191 name: Header::_ACCESS_CONTROL_ALLOW_CREDENTIALS.into(),
192 value: "true".into(),
193 });
194 }
195
196 if !config.cors_expose_headers.is_empty() {
199 headers.push(Header {
200 name: Header::_ACCESS_CONTROL_EXPOSE_HEADERS.into(),
201 value: config.cors_expose_headers.to_lowercase().into(),
202 });
203 }
204
205 if request.method == METHOD.options {
206 if !config.cors_allow_methods.is_empty() {
207 headers.push(Header {
208 name: Header::_ACCESS_CONTROL_ALLOW_METHODS.into(),
209 value: config.cors_allow_methods.clone().into(),
210 });
211 }
212 if !config.cors_allow_headers.is_empty() {
213 headers.push(Header {
214 name: Header::_ACCESS_CONTROL_ALLOW_HEADERS.into(),
215 value: config.cors_allow_headers.to_lowercase().into(),
216 });
217 }
218 if !config.cors_max_age.is_empty() {
219 headers.push(Header {
220 name: Header::_ACCESS_CONTROL_MAX_AGE.into(),
221 value: config.cors_max_age.clone().into(),
222 });
223 }
224 }
225
226 Ok(headers)
227 }
228
229 pub fn process_using_default_config(request: &Request) -> Result<Vec<Header>, Error> {
235 let config = ServerConfig::current();
236 Self::process_using_config(request, &config)
237 }
238
239 pub fn get_headers(request: &Request) -> Vec<Header> {
243 let config = ServerConfig::current();
244 Cors::get_headers_from_config(request, &config)
245 }
246}