Skip to content

Commit aa04117

Browse files
committed
change cors config, support string config
1 parent 6709ff5 commit aa04117

13 files changed

+75
-35
lines changed

config.release.toml

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
## directory to store static web files. if you use docker, please mount a persistence volume for it.
22
file_dir = "/data"
33

4-
## enable cors, default is false, its implementation is simple now.
4+
## enable cors, default is none, if cors is [], then all cors is ok.
55
## Access-Control-Allow-Origin: $ORIGIN
66
## Access-Control-Allow-Methods: OPTION,GET,HEAD
77
## Access-Control-Max-Age: 3600
8-
## If you put the server behind HTTPS proxy, please enable it
9-
cors = false
8+
## If you put the server behind HTTPS proxy, please enable it, or domains.cors = ['http://www.example.com:8080']
9+
## Attension: domains.cors config would overwrite the cors config, rather than merge this.
10+
cors = []
1011
## http bind, if set port <= 0 or remove http, will disable http server(need set https config)
1112
[http]
1213
port = 80
1314
addr = "0.0.0.0"
14-
## port when serving public PI,default is http port. external_port should not be 0.
15+
## port when serving public network,default is http port. external_port should not be 0.
1516
# external_port = 80
1617

1718
## optional, when https enabled, redirect_https default value true
@@ -21,7 +22,7 @@ addr = "0.0.0.0"
2122
# [https]
2223
# port = 443 # https bind address
2324
# addr = "0.0.0.0"
24-
## port when serving public PI,default is https port. external_port should not be 0.
25+
## port when serving public network,default is https port. external_port should not be 0.
2526
# external_port = 443
2627

2728
## if set true, http server(80) will send client
@@ -89,7 +90,7 @@ addr = "0.0.0.0"
8990
# domain = "www.example.com"
9091
## optional, `example.com` would redirect to `www.example.com`
9192
# alias = ["example.com"]
92-
# cors = false
93+
# cors = ['https://www.example.com', 'http://www.baidu.com']
9394
# [domains.https]
9495
## optional, when https enabled, redirect_https default value true
9596
## it would the port would be https.external_port(https.external_port should be defined), otherwise is false
@@ -106,7 +107,6 @@ addr = "0.0.0.0"
106107
# expire = '30d' # 30day
107108
# extension_names = ['icon', 'gif', 'jpg', 'jpeg', 'png', 'js']
108109

109-
[openTelemetry]
110-
endpoint = "http://localhost:4317"
111-
112110

111+
# [openTelemetry]
112+
# endpoint = "http://localhost:4317"

docs/develop/change-log.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Change Log
22

3+
### Version 2.4.1
4+
- spa-server: `cors` value bool to array string.
5+
36
### Version 2.4.0
47

58
- improve: extract client and server common entity

docs/guide/break-changes.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Break Changes
2-
## V2.4.0
2+
## V2.4.1
3+
* spa-server: `cors` value bool to array string.
4+
## V2.4.0(2024-08-04)
35
* remove hocon config format, change to toml.
46
## V2.3.0(2024-07-01)
57
* spa-server: `https.http_redirect_to_https` move to `http.redirect_https`, and value is bool.

docs/guide/spa-server-configuration.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,18 @@ The config default path is `./config.toml`, you can change it by environment `SP
1414
## directory to store static web files. if you use docker, please mount a persistence volume for it.
1515
file_dir = "/data"
1616

17-
## enable cors, default is false, its implementation is simple now.
17+
## enable cors, default is none, if cors is [], then all cors is ok.
1818
## Access-Control-Allow-Origin: $ORIGIN
1919
## Access-Control-Allow-Methods: OPTION,GET,HEAD
2020
## Access-Control-Max-Age: 3600
21-
## If you put the server behind HTTPS proxy, please enable it
22-
cors = false
21+
## If you put the server behind HTTPS proxy, please enable it, or domains.cors = ['http://www.example.com:8080']
22+
## Attension: domains.cors config would overwrite the cors config, rather than merge this.
23+
cors = []
2324
## http bind, if set port <= 0 or remove http, will disable http server(need set https config)
2425
[http]
2526
port = 80
2627
addr = "0.0.0.0"
27-
## port when serving public PI,default is http port. external_port should not be 0.
28+
## port when serving public network,default is http port. external_port should not be 0.
2829
# external_port = 80
2930

3031
## optional, when https enabled, redirect_https default value true
@@ -34,7 +35,7 @@ addr = "0.0.0.0"
3435
# [https]
3536
# port = 443 # https bind address
3637
# addr = "0.0.0.0"
37-
## port when serving public PI,default is https port. external_port should not be 0.
38+
## port when serving public network,default is https port. external_port should not be 0.
3839
# external_port = 443
3940

4041
## if set true, http server(80) will send client
@@ -102,7 +103,7 @@ addr = "0.0.0.0"
102103
# domain = "www.example.com"
103104
## optional, `example.com` would redirect to `www.example.com`
104105
# alias = ["example.com"]
105-
# cors = false
106+
# cors = ['https://www.example.com', 'http://www.baidu.com']
106107
# [domains.https]
107108
## optional, when https enabled, redirect_https default value true
108109
## it would the port would be https.external_port(https.external_port should be defined), otherwise is false

server/src/config.rs

+28-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use anyhow::{bail, Context};
22
use duration_str::deserialize_duration;
3-
use serde::Deserialize;
3+
use serde::{Deserialize, Deserializer};
44
use small_acme::LetsEncrypt;
55
use std::time::Duration;
66
use std::{env, fs};
7+
use std::collections::HashSet;
8+
use headers::{HeaderValue, Origin};
79
use tracing::warn;
810

911
const CONFIG_PATH: &str = "/config/config.toml";
@@ -12,7 +14,7 @@ const CONFIG_PATH: &str = "/config/config.toml";
1214
pub struct Config {
1315
pub file_dir: String,
1416
#[serde(default)]
15-
pub cors: bool,
17+
pub cors: Option<HashSet<OriginWrapper>>,
1618
pub admin_config: Option<AdminConfig>,
1719
pub http: Option<HttpConfig>,
1820
pub https: Option<HttpsConfig>,
@@ -94,7 +96,7 @@ fn default_max_upload_size() -> u64 {
9496
#[derive(Deserialize, Debug, Clone, PartialEq)]
9597
pub struct DomainConfig {
9698
pub domain: String,
97-
pub cors: Option<bool>,
99+
pub cors: Option<HashSet<OriginWrapper>>,
98100
pub cache: Option<DomainCacheConfig>,
99101
pub https: Option<DomainHttpsConfig>,
100102
pub alias: Option<Vec<String>>,
@@ -226,6 +228,29 @@ pub fn get_host_path_from_domain(domain: &str) -> (&str, &str) {
226228
}
227229
}
228230

231+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
232+
pub struct OriginWrapper(HeaderValue);
233+
234+
pub(crate) fn extract_origin(data:&Option<HashSet<OriginWrapper>>) -> Option<HashSet<HeaderValue>> {
235+
data.as_ref().map(|set| set.iter().map(|o| o.0.clone()).collect())
236+
}
237+
238+
impl <'de> Deserialize<'de> for OriginWrapper {
239+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
240+
where
241+
D: Deserializer<'de>
242+
{
243+
let data = String::deserialize(deserializer)?;
244+
let mut parts = data.splitn(2, "://");
245+
let scheme = parts.next().expect("missing scheme");
246+
let rest = parts.next().expect("missing scheme");
247+
let origin = Origin::try_from_parts(scheme, rest, None).expect("invalid Origin");
248+
249+
Ok(OriginWrapper(origin.to_string().parse()
250+
.expect("Origin is always a valid HeaderValue")))
251+
}
252+
}
253+
229254
#[cfg(test)]
230255
mod test {
231256
use std::env;

server/src/cors.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,23 @@ pub fn cors_resp(mut res: Response<Body>, origin: HeaderValue) -> Response<Body>
3030
res
3131
}
3232

33+
fn is_origin_allowed(origins: &Option<HashSet<HeaderValue>>, origin: &HeaderValue) -> bool {
34+
if let Some(ref allowed) = origins {
35+
allowed.is_empty()||allowed.contains(origin)
36+
} else {
37+
false
38+
}
39+
}
40+
// preflight response
3341
pub fn resp_cors_request(
3442
method: &Method,
3543
headers: &HeaderMap,
36-
allow_cors: bool,
44+
origins: &Option<HashSet<HeaderValue>>,
3745
) -> Either<Validated, Response<Body>> {
3846
match (headers.get(header::ORIGIN), method) {
3947
(Some(origin), &Method::OPTIONS) => {
40-
if !allow_cors {
48+
49+
if !is_origin_allowed(origins, origin) {
4150
return Either::Right(resp(StatusCode::FORBIDDEN, "origin not allowed"));
4251
}
4352
if let Some(req_method) = headers.get(header::ACCESS_CONTROL_REQUEST_METHOD) {
@@ -59,7 +68,7 @@ pub fn resp_cors_request(
5968
Either::Right(res)
6069
}
6170
(Some(origin), _) => {
62-
if !allow_cors {
71+
if !is_origin_allowed(origins, origin) {
6372
Either::Right(resp(StatusCode::FORBIDDEN, "origin not allowed"))
6473
} else {
6574
Either::Left(Validated::Simple(origin.clone()))

server/src/service.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ use crate::acme::{get_challenge_path, ChallengePath, ACME_CHALLENGE};
22
use crate::cors::{cors_resp, resp_cors_request, Validated};
33
use crate::DomainStorage;
44
use futures_util::future::Either;
5-
use headers::HeaderMapExt;
5+
use headers::{HeaderMapExt, HeaderValue};
66
use hyper::header::LOCATION;
77
use hyper::http::uri::Authority;
88
use hyper::{Body, Request, Response, StatusCode};
9-
use std::collections::HashMap;
9+
use std::collections::{HashMap, HashSet};
1010
use std::convert::Infallible;
1111
use std::str::FromStr;
1212
use std::sync::Arc;
@@ -24,7 +24,7 @@ pub struct ServiceConfig {
2424
}
2525

2626
pub struct DomainServiceConfig {
27-
pub cors: bool,
27+
pub cors: Option<HashSet<HeaderValue>>,
2828
pub redirect_https: Option<u16>,
2929
pub enable_acme: bool,
3030
}
@@ -130,7 +130,7 @@ pub async fn create_http_service(
130130

131131
let service_config = service_config.get_domain_service_config(host);
132132
// cors
133-
let origin_opt = match resp_cors_request(req.method(), req.headers(), service_config.cors) {
133+
let origin_opt = match resp_cors_request(req.method(), req.headers(), &service_config.cors) {
134134
Either::Left(x) => Some(x),
135135
Either::Right(v) => return Ok(v),
136136
};
@@ -194,7 +194,7 @@ pub async fn create_https_service(
194194

195195
let service_config = service_config.get_domain_service_config(host);
196196
// cors
197-
let origin_opt = match resp_cors_request(req.method(), req.headers(), service_config.cors) {
197+
let origin_opt = match resp_cors_request(req.method(), req.headers(), &service_config.cors) {
198198
Either::Left(x) => Some(x),
199199
Either::Right(v) => return Ok(v),
200200
};

server/src/web_server.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use futures_util::future::Either;
1515
use tokio::net::TcpListener as TKTcpListener;
1616
use tokio::sync::oneshot::Receiver;
1717

18-
use crate::config::{Config, HttpConfig, HttpsConfig};
18+
use crate::config::{extract_origin, Config, HttpConfig, HttpsConfig};
1919
use crate::domain_storage::DomainStorage;
2020
use crate::service::{create_http_service, create_https_service, DomainServiceConfig, ServiceConfig};
2121
use crate::tls::TlsAcceptor;
@@ -93,7 +93,7 @@ impl Server {
9393
};
9494

9595
let default = DomainServiceConfig {
96-
cors: conf.cors,
96+
cors: extract_origin(&conf.cors),
9797
redirect_https: default_http_redirect_to_https,
9898
enable_acme: conf.https.as_ref().and_then(|x| x.acme.as_ref()).is_some(),
9999
};
@@ -116,7 +116,7 @@ impl Server {
116116
Some(false) => None
117117
};
118118
let domain_service_config: DomainServiceConfig = DomainServiceConfig {
119-
cors: domain.cors.unwrap_or(default.cors),
119+
cors: extract_origin(&domain.cors).or_else(||default.cors.clone()),
120120
redirect_https,
121121
enable_acme: domain
122122
.https

tests/data/server_config.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
file_dir = "./data/web"
2-
cors = true
2+
cors = []
33
[http]
44
port = 8080
55
addr = "0.0.0.0"

tests/data/server_config_acme.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
file_dir = "./data/web"
2-
cors = true
2+
cors = []
33

44
# http bind, if set port <= 0, will disable http server(need set https config)
55
[http]

tests/data/server_config_acme_alias.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
file_dir = "./data/web"
2-
cors = true
2+
cors = []
33

44
# http bind, if set port <= 0, will disable http server(need set https config)
55
[http]

tests/data/server_config_alias.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cors = true
1+
cors = []
22
file_dir = "./data/web"
33

44

tests/data/server_config_https.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
file_dir = "./data/web"
2-
cors = true
2+
cors = []
33

44
# http bind, if set port <= 0, will disable http server(need set https config)
55
[http]

0 commit comments

Comments
 (0)