1use http::{header::ORIGIN, HeaderValue, Method, Request, Response, StatusCode};
2use jsonrpsee::server::{ws, HttpBody};
3use tower_http::{
4 cors::{AllowOrigin, Any, CorsLayer},
5 validate_request::ValidateRequest,
6};
7
8#[derive(Debug, thiserror::Error)]
10pub enum CorsDomainError {
11 #[error("{domain} is an invalid header value")]
13 InvalidHeader {
14 domain: String,
16 },
17
18 #[error("wildcard origin (`*`) cannot be passed as part of a list: {input}")]
20 WildCardNotAllowed {
21 input: String,
23 },
24}
25
26pub(crate) fn create_cors_layer(http_cors_domains: &str) -> Result<CorsLayer, CorsDomainError> {
28 let cors = match parse_origins(http_cors_domains)? {
29 None => CorsLayer::new()
30 .allow_methods([Method::GET, Method::POST])
31 .allow_origin(Any)
32 .allow_headers(Any),
33 Some(origins) => {
34 let origin = AllowOrigin::list(origins);
35 CorsLayer::new()
36 .allow_methods([Method::GET, Method::POST])
37 .allow_origin(origin)
38 .allow_headers(Any)
39 }
40 };
41 Ok(cors)
42}
43
44#[derive(Clone, Debug)]
46pub(crate) struct WsOriginValidator {
47 origins: Option<Vec<HeaderValue>>,
48}
49
50impl WsOriginValidator {
51 pub(crate) fn new(domains: Option<&str>) -> Result<Self, CorsDomainError> {
52 Ok(Self { origins: domains.map(parse_origins).transpose()?.flatten() })
53 }
54}
55
56impl<B> ValidateRequest<B> for WsOriginValidator {
57 type ResponseBody = HttpBody;
58
59 fn validate(&mut self, request: &mut Request<B>) -> Result<(), Response<HttpBody>> {
60 if let Some(allowed) = &self.origins &&
61 ws::is_upgrade_request(request)
62 {
63 let mut origins = request.headers().get_all(ORIGIN).iter();
64 if let Some(origin) = origins.next() &&
66 (origins.next().is_some() || !allowed.contains(origin))
67 {
68 let mut response = Response::new(HttpBody::empty());
69 *response.status_mut() = StatusCode::FORBIDDEN;
70 return Err(response)
71 }
72 }
73 Ok(())
74 }
75}
76
77fn parse_origins(domains: &str) -> Result<Option<Vec<HeaderValue>>, CorsDomainError> {
78 if domains.trim() == "*" {
79 return Ok(None)
80 }
81 let iter = domains.split(',').map(str::trim);
82 if iter.clone().any(|origin| origin == "*") {
83 return Err(CorsDomainError::WildCardNotAllowed { input: domains.to_string() })
84 }
85 iter.map(|domain| {
86 domain.parse().map_err(|_| CorsDomainError::InvalidHeader { domain: domain.to_string() })
87 })
88 .collect::<Result<Vec<_>, _>>()
89 .map(Some)
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_wildcard_with_spaces_rejected() {
98 let result = create_cors_layer("http://example.com, *");
100 assert!(matches!(result, Err(CorsDomainError::WildCardNotAllowed { .. })));
101 }
102}