From 1af7d280132ebf42e7160d61f483c0fe5174236e Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Tue, 5 May 2020 02:30:52 +0200 Subject: [PATCH] feat: CORS support (resolves #18) --- Cargo.lock | 11 +++++++++++ Cargo.toml | 1 + README.md | 12 ++++++++---- Tasks.Dev.toml | 1 + src/config.rs | 3 +++ src/main.rs | 6 ++++++ src/staticfiles.rs | 23 ++++++++++++++++++++++- 7 files changed, 52 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 81c96c8..4b0fe29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,16 @@ dependencies = [ ] [[package]] +name = "iron-cors" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b02b8856c7f14e443c483e802cf0ce693f3bec19f49d2c9a242b18f88c9b70" +dependencies = [ + "iron", + "log 0.4.8", +] + +[[package]] name = "iron-test" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -913,6 +923,7 @@ dependencies = [ "hyper", "hyper-native-tls", "iron", + "iron-cors", "iron-test", "iron_staticfile_middleware", "log 0.4.8", diff --git a/Cargo.toml b/Cargo.toml index c6aa6c8..0bf721b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ iron_staticfile_middleware = { git = "https://github.com/joseluisq/iron-staticfi hyper-native-tls = "0.3" nix = "0.14" signal = "0.7" +iron-cors = "0.8" [dev-dependencies] openssl = { version = "0.10", features = ["vendored"] } diff --git a/README.md b/README.md index 5868a17..01aa142 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Server can be configured either via environment variables or their equivalent co | `SERVER_TLS` | Enables TLS/SSL support. Make sure also to adjust current server port. | Default `false` | | `SERVER_TLS_PKCS12` | A cryptographic identity [PKCS #12](https://docs.rs/native-tls/0.2.3/native_tls/struct.Identity.html#method.from_pkcs12) bundle file path containing a [X509 certificate](https://en.wikipedia.org/wiki/X.509) along with its corresponding private key and chain of certificates to a trusted root. | Default empty | | `SERVER_TLS_PKCS12_PASSWD` | A specified password to decrypt the private key. | Default empty | +| `SERVER_CORS_ALLOW_ORIGINS` | Specify a CORS list of allowed origin hosts separated by comas with no whitespaces. Host ports or protocols aren't being checked. Use an asterisk (*) to allow any host. See [Iron CORS crate](https://docs.rs/iron-cors/0.8.0/iron_cors/#mode-1-whitelist). | Default empty (which means CORS is disabled) | ### Command-line arguments @@ -64,22 +65,25 @@ OPTIONS: --assets Assets directory path for add cache headers functionality [env: SERVER_ASSETS=] [default: ./public/assets] - --host Host address (E.g 127.0.0.1) [env: SERVER_HOST=] [default: [::]] + --cors-allow-origins + Specify a CORS list of allowed origin hosts separated by comas with no whitespaces. Host ports or protocols + aren't being checked. Use an asterisk (*) to allow any host [env: SERVER_CORS_ALLOW_ORIGINS=] [default: ] + --host Host address (E.g 127.0.0.1) [env: SERVER_HOST=] [default: [::]] --log-level Specify a logging level in lower case [env: SERVER_LOG_LEVEL=] [default: error] - --name Name for server [env: SERVER_NAME=] [default: my-static-server] + --name Name for server [env: SERVER_NAME=] [default: my-static-server] --page404 HTML file path for 404 errors. If path is not specified or simply don't exists then server will use a generic HTML error message [env: SERVER_ERROR_PAGE_404=] [default: ./public/404.html] --page50x HTML file path for 50x errors. If path is not specified or simply don't exists then server will use a generic HTML error message [env: SERVER_ERROR_PAGE_50X=] [default: ./public/50x.html] - --port Host port [env: SERVER_PORT=] [default: 80] + --port Host port [env: SERVER_PORT=] [default: 80] --root Root directory path of static files [env: SERVER_ROOT=] [default: ./public] - --tls Enables TLS/SSL support [env: SERVER_TLS=] + --tls Enables TLS/SSL support [env: SERVER_TLS=] --tls-pkcs12 A cryptographic identity PKCS #12 bundle file path containing a X509 certificate along with its corresponding private key and chain of certificates to a trusted root [env: SERVER_TLS_PKCS12=] [default: ] diff --git a/Tasks.Dev.toml b/Tasks.Dev.toml index 495fe2a..300554b 100644 --- a/Tasks.Dev.toml +++ b/Tasks.Dev.toml @@ -4,6 +4,7 @@ E_URL = "http://locahost" SERVER_LOG_LEVEL = "trace" SERVER_ROOT = "./public" SERVER_ASSETS = "./public/assets" +SERVER_CORS_ALLOW_ORIGINS = "*" [tasks.watch] command = "cargo" diff --git a/src/config.rs b/src/config.rs index 8223481..4b7448d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -44,4 +44,7 @@ pub struct Options { #[structopt(long, default_value = "error", env = "SERVER_LOG_LEVEL")] /// Specify a logging level in lower case. pub log_level: String, + #[structopt(long, default_value = "", env = "SERVER_CORS_ALLOW_ORIGINS")] + /// Specify a CORS list of allowed origin hosts separated by comas with no whitespaces. Host ports or protocols aren't being checked. Use an asterisk (*) to allow any host. + pub cors_allow_origins: String, } diff --git a/src/main.rs b/src/main.rs index 61eea09..1f7c6aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,7 @@ fn main() { assets_dir: opts.assets, page_50x_path: opts.page50x, page_404_path: opts.page404, + cors_allow_origins: opts.cors_allow_origins, }); if opts.tls { @@ -121,6 +122,7 @@ mod test { assets_dir: opts.assets, page_50x_path: opts.page50x, page_404_path: opts.page404, + cors_allow_origins: "".to_string(), }); let response = request::head("http://127.0.0.1/", Headers::new(), &files.handle()) @@ -143,6 +145,7 @@ mod test { assets_dir: opts.assets, page_50x_path: opts.page50x, page_404_path: opts.page404, + cors_allow_origins: "".to_string(), }); let res = request::head("http://127.0.0.1/", Headers::new(), &files.handle()) @@ -170,6 +173,7 @@ mod test { assets_dir: assets.path().to_str().unwrap().to_string(), page_50x_path: opts.page50x, page_404_path: opts.page404, + cors_allow_origins: "".to_string(), }); let res = request::head("http://127.0.0.1/", Headers::new(), &files.handle()) @@ -191,6 +195,7 @@ mod test { assets_dir: opts.assets, page_50x_path: opts.page50x, page_404_path: opts.page404, + cors_allow_origins: "".to_string(), }); let res = request::head("http://127.0.0.1/unknown", Headers::new(), &files.handle()) @@ -212,6 +217,7 @@ mod test { assets_dir: opts.assets, page_50x_path: opts.page50x, page_404_path: opts.page404, + cors_allow_origins: "".to_string(), }); let response = request::post("http://127.0.0.1/", Headers::new(), "", &files.handle()) diff --git a/src/staticfiles.rs b/src/staticfiles.rs index dcb0e0a..2eba414 100644 --- a/src/staticfiles.rs +++ b/src/staticfiles.rs @@ -1,10 +1,12 @@ use crate::error_page::ErrorPage; use crate::gzip::GzipMiddleware; use crate::helpers; -use crate::logger::Logger; +use crate::logger::{log_server, Logger}; use iron::prelude::*; +use iron_cors::CorsMiddleware; use iron_staticfile_middleware::{Cache, GuessContentType, ModifyWith, Prefix, Staticfile}; +use std::collections::HashSet; use std::time::Duration; /// An Iron middleware for static files-serving. @@ -17,6 +19,7 @@ pub struct StaticFilesOptions { pub assets_dir: String, pub page_50x_path: String, pub page_404_path: String, + pub cors_allow_origins: String, } impl StaticFiles { @@ -65,6 +68,24 @@ impl StaticFiles { .parse() .expect("Unable to create a default content type header"); + // CORS support + let allowed_hosts = &self.opts.cors_allow_origins; + + if !allowed_hosts.is_empty() { + log_server("CORS enabled"); + log_server(&format!("Access-Control-Allow-Origin: {}", allowed_hosts)); + + if allowed_hosts == "*" { + chain.link_around(CorsMiddleware::with_allow_any()); + } else { + let allowed_hosts = allowed_hosts + .split(',') + .map(|s| s.to_string()) + .collect::>(); + chain.link_around(CorsMiddleware::with_whitelist(allowed_hosts)); + }; + } + chain.link_after(ModifyWith::new(Cache::new(one_day))); chain.link_after(Prefix::new(&[assets_dirname], Cache::new(one_year))); chain.link_after(GuessContentType::new(default_content_type)); -- libgit2 1.7.2