diff --git a/Cargo.lock b/Cargo.lock index e716b85b8..531557c2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,6 +328,7 @@ name = "ethcore-dapps" version = "1.4.0" dependencies = [ "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-devtools 1.4.0", "ethcore-rpc 1.4.0", @@ -340,9 +341,7 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-dapps-glue 1.4.0", - "parity-dapps-home 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-ui 1.4.0", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -854,7 +853,7 @@ dependencies = [ [[package]] name = "jsonrpc-http-server" version = "6.1.1" -source = "git+https://github.com/ethcore/jsonrpc-http-server.git#ee72e4778583daf901b5692468fc622f46abecb6" +source = "git+https://github.com/ethcore/jsonrpc-http-server.git#cd6d4cb37d672cc3057aecd0692876f9e85f3ba5" dependencies = [ "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1175,20 +1174,6 @@ name = "owning_ref" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "parity-dapps" -version = "1.4.0" -source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7" -dependencies = [ - "aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "quasi 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "quasi_codegen 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "parity-dapps-glue" version = "1.4.0" @@ -1202,14 +1187,6 @@ dependencies = [ "syntex_syntax 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "parity-dapps-home" -version = "1.4.0" -source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7" -dependencies = [ - "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", -] - [[package]] name = "parity-ui" version = "1.4.0" @@ -1884,7 +1861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ws" version = "0.5.2" -source = "git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable#609b21fdab96c8fffedec8699755ce3bea9454cb" +source = "git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable#e3d21c119350e753fdf4475b8cd88103b2280540" dependencies = [ "bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2016,8 +1993,6 @@ dependencies = [ "checksum number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "084d05f4bf60621a9ac9bde941a410df548f4de9545f06e5ee9d3aef4b97cd77" "checksum odds 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "b28c06e81b0f789122d415d6394b5fe849bde8067469f4c2980d3cdc10c78ec1" "checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7" -"checksum parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "" -"checksum parity-dapps-home 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "" "checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6" "checksum parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dbc5847584161f273e69edc63c1a86254a22f570a0b5dd87aa6f9773f6f7d125" "checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068" diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 77ed00e19..7ca792d40 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -11,6 +11,7 @@ build = "build.rs" [dependencies] rand = "0.3.14" log = "0.3" +env_logger = "0.3" jsonrpc-core = "3.0" jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.git" } hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } @@ -23,17 +24,13 @@ serde_macros = { version = "0.8", optional = true } zip = { version = "0.1", default-features = false } ethabi = "0.2.2" linked-hash-map = "0.3" +mime = "0.2" ethcore-devtools = { path = "../devtools" } ethcore-rpc = { path = "../rpc" } ethcore-util = { path = "../util" } fetch = { path = "../util/fetch" } parity-ui = { path = "./ui" } parity-dapps-glue = { path = "./js-glue" } -mime = "0.2" -### DEPRECATED -parity-dapps = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" } -parity-dapps-home = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" } -### /DEPRECATED mime_guess = { version = "1.6.1" } clippy = { version = "0.0.90", optional = true} @@ -46,7 +43,4 @@ default = ["serde_codegen"] nightly = ["serde_macros"] dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"] -use-precompiled-js = [ - "parity-ui/use-precompiled-js", - "parity-dapps-home/use-precompiled-js", -] +use-precompiled-js = ["parity-ui/use-precompiled-js"] diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index 80c5e09de..90ab837f5 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -15,24 +15,31 @@ // along with Parity. If not, see . use std::sync::Arc; +use unicase::UniCase; use hyper::{server, net, Decoder, Encoder, Next, Control}; +use hyper::header; +use hyper::method::Method; +use hyper::header::AccessControlAllowOrigin; + use api::types::{App, ApiError}; -use api::response::{as_json, as_json_error, ping_response}; +use api::response; +use apps::fetcher::ContentFetcher; + use handlers::extract_url; use endpoint::{Endpoint, Endpoints, Handler, EndpointPath}; -use apps::fetcher::ContentFetcher; +use jsonrpc_http_server::cors; #[derive(Clone)] pub struct RestApi { - local_domain: String, + cors_domains: Option>, endpoints: Arc, fetcher: Arc, } impl RestApi { - pub fn new(local_domain: String, endpoints: Arc, fetcher: Arc) -> Box { + pub fn new(cors_domains: Vec, endpoints: Arc, fetcher: Arc) -> Box { Box::new(RestApi { - local_domain: local_domain, + cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()), endpoints: endpoints, fetcher: fetcher, }) @@ -53,6 +60,7 @@ impl Endpoint for RestApi { struct RestApiRouter { api: RestApi, + origin: Option, path: Option, control: Option, handler: Box, @@ -62,9 +70,10 @@ impl RestApiRouter { fn new(api: RestApi, path: EndpointPath, control: Control) -> Self { RestApiRouter { path: Some(path), + origin: None, control: Some(control), api: api, - handler: as_json_error(&ApiError { + handler: response::as_json_error(&ApiError { code: "404".into(), title: "Not Found".into(), detail: "Resource you requested has not been found.".into(), @@ -80,11 +89,40 @@ impl RestApiRouter { _ => None } } + + /// Returns basic headers for a response (it may be overwritten by the handler) + fn response_headers(&self) -> header::Headers { + let mut headers = header::Headers::new(); + headers.set(header::AccessControlAllowCredentials); + headers.set(header::AccessControlAllowMethods(vec![ + Method::Options, + Method::Post, + Method::Get, + ])); + headers.set(header::AccessControlAllowHeaders(vec![ + UniCase("origin".to_owned()), + UniCase("content-type".to_owned()), + UniCase("accept".to_owned()), + ])); + + if let Some(cors_header) = cors::get_cors_header(&self.api.cors_domains, &self.origin) { + headers.set(cors_header); + } + + headers + } } impl server::Handler for RestApiRouter { fn on_request(&mut self, request: server::Request) -> Next { + self.origin = cors::read_origin(&request); + + if let Method::Options = *request.method() { + self.handler = response::empty(); + return Next::write(); + } + let url = extract_url(&request); if url.is_none() { // Just return 404 if we can't parse URL @@ -99,11 +137,11 @@ impl server::Handler for RestApiRouter { let hash = url.path.get(2).map(|v| v.as_str()); // at this point path.app_id contains 'api', adjust it to the hash properly, otherwise // we will try and retrieve 'api' as the hash when doing the /api/content route - if let Some(hash) = hash.clone() { path.app_id = hash.to_owned() } + if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() } let handler = endpoint.and_then(|v| match v { - "apps" => Some(as_json(&self.api.list_apps())), - "ping" => Some(ping_response(&self.api.local_domain)), + "apps" => Some(response::as_json(&self.api.list_apps())), + "ping" => Some(response::ping()), "content" => self.resolve_content(hash, path, control), _ => None }); @@ -121,6 +159,7 @@ impl server::Handler for RestApiRouter { } fn on_response(&mut self, res: &mut server::Response) -> Next { + *res.headers_mut() = self.response_headers(); self.handler.on_response(res) } diff --git a/dapps/src/api/cors.rs b/dapps/src/api/cors.rs new file mode 100644 index 000000000..e69de29bb diff --git a/dapps/src/api/response.rs b/dapps/src/api/response.rs index 4064490b3..3e8f196ec 100644 --- a/dapps/src/api/response.rs +++ b/dapps/src/api/response.rs @@ -19,6 +19,10 @@ use serde_json; use endpoint::Handler; use handlers::{ContentHandler, EchoHandler}; +pub fn empty() -> Box { + Box::new(ContentHandler::ok("".into(), mime!(Text/Plain))) +} + pub fn as_json(val: &T) -> Box { let json = serde_json::to_string(val) .expect("serialization to string is infallible; qed"); @@ -31,10 +35,6 @@ pub fn as_json_error(val: &T) -> Box { Box::new(ContentHandler::not_found(json, mime!(Application/Json))) } -pub fn ping_response(local_domain: &str) -> Box { - Box::new(EchoHandler::cors(vec![ - format!("http://{}", local_domain), - // Allow CORS calls also for localhost - format!("http://{}", local_domain.replace("127.0.0.1", "localhost")), - ])) +pub fn ping() -> Box { + Box::new(EchoHandler::default()) } diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index 145c3e820..40ddb7064 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -17,8 +17,7 @@ use endpoint::{Endpoints, Endpoint}; use page::PageEndpoint; use proxypac::ProxyPac; -use parity_dapps::{self, WebApp}; -use parity_dapps_glue::WebApp as NewWebApp; +use parity_dapps::WebApp; mod cache; mod fs; @@ -26,17 +25,14 @@ pub mod urlhint; pub mod fetcher; pub mod manifest; -extern crate parity_dapps_home; extern crate parity_ui; +pub const HOME_PAGE: &'static str = "home"; pub const DAPPS_DOMAIN : &'static str = ".parity"; pub const RPC_PATH : &'static str = "rpc"; pub const API_PATH : &'static str = "api"; pub const UTILS_PATH : &'static str = "parity-utils"; -pub fn main_page() -> &'static str { - "home" -} pub fn redirection_address(using_dapps_domains: bool, app_id: &str) -> String { if using_dapps_domains { format!("http://{}{}/", app_id, DAPPS_DOMAIN) @@ -46,7 +42,7 @@ pub fn redirection_address(using_dapps_domains: bool, app_id: &str) -> String { } pub fn utils() -> Box { - Box::new(PageEndpoint::with_prefix(parity_dapps_home::App::default(), UTILS_PATH.to_owned())) + Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned())) } pub fn all_endpoints(dapps_path: String, signer_port: Option) -> Endpoints { @@ -54,64 +50,21 @@ pub fn all_endpoints(dapps_path: String, signer_port: Option) -> Endpoints let mut pages = fs::local_endpoints(dapps_path); // NOTE [ToDr] Dapps will be currently embeded on 8180 - pages.insert("ui".into(), Box::new( - PageEndpoint::new_safe_to_embed(NewUi::default(), signer_port) - )); - - pages.insert("proxy".into(), ProxyPac::boxed()); - insert::(&mut pages, "home"); - + insert::(&mut pages, "ui", Embeddable::Yes(signer_port)); + pages.insert("proxy".into(), ProxyPac::boxed(signer_port)); pages } -fn insert(pages: &mut Endpoints, id: &str) { - pages.insert(id.to_owned(), Box::new(PageEndpoint::new(T::default()))); +fn insert(pages: &mut Endpoints, id: &str, embed_at: Embeddable) { + pages.insert(id.to_owned(), Box::new(match embed_at { + Embeddable::Yes(port) => PageEndpoint::new_safe_to_embed(T::default(), port), + Embeddable::No => PageEndpoint::new(T::default()), + })); } -// TODO [ToDr] Temporary wrapper until we get rid of old built-ins. -use std::collections::HashMap; - -struct NewUi { - app: parity_ui::App, - files: HashMap<&'static str, parity_dapps::File>, -} - -impl Default for NewUi { - fn default() -> Self { - let app = parity_ui::App::default(); - let files = { - let mut files = HashMap::new(); - for (k, v) in &app.files { - files.insert(*k, parity_dapps::File { - path: v.path, - content: v.content, - content_type: v.content_type, - }); - } - files - }; - - NewUi { - app: app, - files: files, - } - } -} - -impl WebApp for NewUi { - fn file(&self, path: &str) -> Option<&parity_dapps::File> { - self.files.get(path) - } - - fn info(&self) -> parity_dapps::Info { - let info = self.app.info(); - parity_dapps::Info { - name: info.name, - version: info.version, - author: info.author, - description: info.description, - icon_url: info.icon_url, - } - } +enum Embeddable { + Yes(Option), + #[allow(dead_code)] + No, } diff --git a/dapps/src/handlers/echo.rs b/dapps/src/handlers/echo.rs index 9266de0c6..165ceb171 100644 --- a/dapps/src/handlers/echo.rs +++ b/dapps/src/handlers/echo.rs @@ -17,76 +17,19 @@ //! Echo Handler use std::io::Read; -use hyper::{header, server, Decoder, Encoder, Next}; -use hyper::method::Method; +use hyper::{server, Decoder, Encoder, Next}; use hyper::net::HttpStream; -use unicase::UniCase; use super::ContentHandler; -#[derive(Debug, PartialEq)] -/// Type of Cross-Origin request -enum Cors { - /// Not a Cross-Origin request - no headers needed - No, - /// Cross-Origin request with valid Origin - Allowed(String), - /// Cross-Origin request with invalid Origin - Forbidden, -} - +#[derive(Default)] pub struct EchoHandler { - safe_origins: Vec, content: String, - cors: Cors, handler: Option, } -impl EchoHandler { - - pub fn cors(safe_origins: Vec) -> Self { - EchoHandler { - safe_origins: safe_origins, - content: String::new(), - cors: Cors::Forbidden, - handler: None, - } - } - - fn cors_header(&self, origin: Option) -> Cors { - fn origin_is_allowed(origin: &str, safe_origins: &[String]) -> bool { - for safe in safe_origins { - if origin.starts_with(safe) { - return true; - } - } - false - } - - match origin { - Some(ref origin) if origin_is_allowed(origin, &self.safe_origins) => { - Cors::Allowed(origin.clone()) - }, - None => Cors::No, - _ => Cors::Forbidden, - } - } -} - impl server::Handler for EchoHandler { - fn on_request(&mut self, request: server::Request) -> Next { - let origin = request.headers().get_raw("origin") - .and_then(|list| list.get(0)) - .and_then(|origin| String::from_utf8(origin.clone()).ok()); - - self.cors = self.cors_header(origin); - - // Don't even read the payload if origin is forbidden! - if let Cors::Forbidden = self.cors { - self.handler = Some(ContentHandler::ok(String::new(), mime!(Text/Plain))); - Next::write() - } else { - Next::read() - } + fn on_request(&mut self, _: server::Request) -> Next { + Next::read() } fn on_request_readable(&mut self, decoder: &mut Decoder) -> Next { @@ -104,16 +47,6 @@ impl server::Handler for EchoHandler { } fn on_response(&mut self, res: &mut server::Response) -> Next { - if let Cors::Allowed(ref domain) = self.cors { - let mut headers = res.headers_mut(); - headers.set(header::Allow(vec![Method::Options, Method::Post, Method::Get])); - headers.set(header::AccessControlAllowHeaders(vec![ - UniCase("origin".to_owned()), - UniCase("content-type".to_owned()), - UniCase("accept".to_owned()), - ])); - headers.set(header::AccessControlAllowOrigin::Value(domain.clone())); - } self.handler.as_mut() .expect("handler always set in on_request, which is before now; qed") .on_response(res) @@ -125,28 +58,3 @@ impl server::Handler for EchoHandler { .on_response_writable(encoder) } } - -#[test] -fn should_return_correct_cors_value() { - // given - let safe_origins = vec!["chrome-extension://".to_owned(), "http://localhost:8080".to_owned()]; - let cut = EchoHandler { - safe_origins: safe_origins, - content: String::new(), - cors: Cors::No, - handler: None, - }; - - // when - let res1 = cut.cors_header(Some("http://ethcore.io".into())); - let res2 = cut.cors_header(Some("http://localhost:8080".into())); - let res3 = cut.cors_header(Some("chrome-extension://deadbeefcafe".into())); - let res4 = cut.cors_header(None); - - - // then - assert_eq!(res1, Cors::Forbidden); - assert_eq!(res2, Cors::Allowed("http://localhost:8080".into())); - assert_eq!(res3, Cors::Allowed("chrome-extension://deadbeefcafe".into())); - assert_eq!(res4, Cors::No); -} diff --git a/dapps/src/handlers/mod.rs b/dapps/src/handlers/mod.rs index c4f98fc26..3d96e8a40 100644 --- a/dapps/src/handlers/mod.rs +++ b/dapps/src/handlers/mod.rs @@ -30,6 +30,7 @@ pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl}; use url::Url; use hyper::{server, header, net, uri}; +use signer_address; /// Adds security-related headers to the Response. pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option) { @@ -40,7 +41,7 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option if let Some(port) = embeddable_at { headers.set_raw( "X-Frame-Options", - vec![format!("ALLOW-FROM http://127.0.0.1:{}", port).into_bytes()] + vec![format!("ALLOW-FROM http://{}", signer_address(port)).into_bytes()] ); } else { // TODO [ToDr] Should we be more strict here (DENY?)? diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 9e9a26ee3..00c8b275e 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -59,19 +59,17 @@ extern crate ethcore_rpc; extern crate ethcore_util as util; extern crate linked_hash_map; extern crate fetch; - +extern crate parity_dapps_glue as parity_dapps; #[macro_use] extern crate log; - #[macro_use] extern crate mime; #[cfg(test)] extern crate ethcore_devtools as devtools; +#[cfg(test)] +extern crate env_logger; -extern crate parity_dapps_glue; -// TODO [ToDr] - Deprecate when we get rid of old dapps. -extern crate parity_dapps; mod endpoint; mod apps; @@ -95,7 +93,7 @@ use jsonrpc_core::{IoHandler, IoDelegate}; use router::auth::{Authorization, NoAuth, HttpBasicAuth}; use ethcore_rpc::Extendable; -static DAPPS_DOMAIN : &'static str = ".parity"; +use self::apps::{HOME_PAGE, DAPPS_DOMAIN}; /// Indicates sync status pub trait SyncStatus: Send + Sync { @@ -197,6 +195,17 @@ impl Server { Some(allowed) } + /// Returns a list of CORS domains for API endpoint. + fn cors_domains(signer_port: Option) -> Vec { + match signer_port { + Some(port) => vec![ + format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), + format!("http://{}", signer_address(port)), + ], + None => vec![], + } + } + fn start_http( addr: &SocketAddr, hosts: Option>, @@ -210,14 +219,16 @@ impl Server { let panic_handler = Arc::new(Mutex::new(None)); let authorization = Arc::new(authorization); let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status)); - let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_port)); + let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_port.clone())); + let cors_domains = Self::cors_domains(signer_port); + let special = Arc::new({ let mut special = HashMap::new(); special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone())); special.insert(router::SpecialEndpoint::Utils, apps::utils()); special.insert( router::SpecialEndpoint::Api, - api::RestApi::new(format!("{}", addr), endpoints.clone(), content_fetcher.clone()) + api::RestApi::new(cors_domains, endpoints.clone(), content_fetcher.clone()) ); special }); @@ -226,7 +237,7 @@ impl Server { try!(hyper::Server::http(addr)) .handle(move |ctrl| router::Router::new( ctrl, - apps::main_page(), + signer_port.clone(), content_fetcher.clone(), endpoints.clone(), special.clone(), @@ -290,6 +301,10 @@ pub fn random_filename() -> String { rng.gen_ascii_chars().take(12).collect() } +fn signer_address(port: u16) -> String { + format!("127.0.0.1:{}", port) +} + #[cfg(test)] mod util_tests { use super::Server; @@ -309,4 +324,17 @@ mod util_tests { assert_eq!(address, Some(vec!["localhost".into(), "127.0.0.1".into()])); assert_eq!(some, Some(vec!["ethcore.io".into(), "localhost".into(), "127.0.0.1".into()])); } + + #[test] + fn should_return_cors_domains() { + // given + + // when + let none = Server::cors_domains(None); + let some = Server::cors_domains(Some(18180)); + + // then + assert_eq!(none, Vec::::new()); + assert_eq!(some, vec!["http://home.parity".to_owned(), "http://127.0.0.1:18180".into()]); + } } diff --git a/dapps/src/proxypac.rs b/dapps/src/proxypac.rs index cd225330a..2d7c4e3ce 100644 --- a/dapps/src/proxypac.rs +++ b/dapps/src/proxypac.rs @@ -18,30 +18,45 @@ use endpoint::{Endpoint, Handler, EndpointPath}; use handlers::ContentHandler; -use apps::DAPPS_DOMAIN; +use apps::{HOME_PAGE, DAPPS_DOMAIN}; +use signer_address; -pub struct ProxyPac; +pub struct ProxyPac { + signer_port: Option, +} impl ProxyPac { - pub fn boxed() -> Box { - Box::new(ProxyPac) + pub fn boxed(signer_port: Option) -> Box { + Box::new(ProxyPac { + signer_port: signer_port + }) } } impl Endpoint for ProxyPac { fn to_handler(&self, path: EndpointPath) -> Box { + let signer = self.signer_port + .map(signer_address) + .unwrap_or_else(|| format!("{}:{}", path.host, path.port)); + let content = format!( r#" function FindProxyForURL(url, host) {{ - if (shExpMatch(host, "*{0}")) + if (shExpMatch(host, "{0}{1}")) {{ - return "PROXY {1}:{2}"; + return "PROXY {4}"; + }} + + if (shExpMatch(host, "*{1}")) + {{ + return "PROXY {2}:{3}"; }} return "DIRECT"; }} "#, - DAPPS_DOMAIN, path.host, path.port); + HOME_PAGE, DAPPS_DOMAIN, path.host, path.port, signer); + Box::new(ContentHandler::ok(content, mime!(Application/Javascript))) } } diff --git a/dapps/src/router/host_validation.rs b/dapps/src/router/host_validation.rs index 5be30ef8b..2b7e6c9e4 100644 --- a/dapps/src/router/host_validation.rs +++ b/dapps/src/router/host_validation.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . -use DAPPS_DOMAIN; +use apps::DAPPS_DOMAIN; use hyper::{server, header, StatusCode}; use hyper::net::HttpStream; diff --git a/dapps/src/router/mod.rs b/dapps/src/router/mod.rs index 2748b5d24..ff60df996 100644 --- a/dapps/src/router/mod.rs +++ b/dapps/src/router/mod.rs @@ -20,13 +20,13 @@ pub mod auth; mod host_validation; -use DAPPS_DOMAIN; +use signer_address; use std::sync::Arc; use std::collections::HashMap; use url::{Url, Host}; use hyper::{self, server, Next, Encoder, Decoder, Control, StatusCode}; use hyper::net::HttpStream; -use apps; +use apps::{self, DAPPS_DOMAIN}; use apps::fetcher::ContentFetcher; use endpoint::{Endpoint, Endpoints, EndpointPath}; use handlers::{Redirection, extract_url, ContentHandler}; @@ -43,7 +43,7 @@ pub enum SpecialEndpoint { pub struct Router { control: Option, - main_page: &'static str, + signer_port: Option, endpoints: Arc, fetch: Arc, special: Arc>>, @@ -61,57 +61,78 @@ impl server::Handler for Router { let endpoint = extract_endpoint(&url); let is_utils = endpoint.1 == SpecialEndpoint::Utils; + trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req); + // Validate Host header if let Some(ref hosts) = self.allowed_hosts { + trace!(target: "dapps", "Validating host headers against: {:?}", hosts); let is_valid = is_utils || host_validation::is_valid(&req, hosts, self.endpoints.keys().cloned().collect()); if !is_valid { + debug!(target: "dapps", "Rejecting invalid host header."); self.handler = host_validation::host_invalid_response(); return self.handler.on_request(req); } } + trace!(target: "dapps", "Checking authorization."); // Check authorization let auth = self.authorization.is_authorized(&req); if let Authorized::No(handler) = auth { + debug!(target: "dapps", "Authorization denied."); self.handler = handler; return self.handler.on_request(req); } let control = self.control.take().expect("on_request is called only once; control is always defined at start; qed"); + debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint); self.handler = match endpoint { // First check special endpoints (ref path, ref endpoint) if self.special.contains_key(endpoint) => { + trace!(target: "dapps", "Resolving to special endpoint."); self.special.get(endpoint) .expect("special known to contain key; qed") .to_async_handler(path.clone().unwrap_or_default(), control) }, // Then delegate to dapp (Some(ref path), _) if self.endpoints.contains_key(&path.app_id) => { + trace!(target: "dapps", "Resolving to local/builtin dapp."); self.endpoints.get(&path.app_id) .expect("special known to contain key; qed") .to_async_handler(path.clone(), control) }, // Try to resolve and fetch the dapp (Some(ref path), _) if self.fetch.contains(&path.app_id) => { + trace!(target: "dapps", "Resolving to fetchable content."); self.fetch.to_async_handler(path.clone(), control) }, // 404 for non-existent content - (Some(ref path), _) if *req.method() == hyper::method::Method::Get => { - let address = apps::redirection_address(path.using_dapps_domains, self.main_page); + (Some(_), _) if *req.method() == hyper::method::Method::Get => { + trace!(target: "dapps", "Resolving to 404."); Box::new(ContentHandler::error( StatusCode::NotFound, "404 Not Found", "Requested content was not found.", - Some(&format!("Go back to the Home Page.", address)) + None, )) }, - // Redirect any GET request to home. + // Redirect any other GET request to signer. _ if *req.method() == hyper::method::Method::Get => { - let address = apps::redirection_address(false, self.main_page); - Redirection::boxed(address.as_str()) + if let Some(port) = self.signer_port { + trace!(target: "dapps", "Redirecting to signer interface."); + Redirection::boxed(&format!("http://{}", signer_address(port))) + } else { + trace!(target: "dapps", "Signer disabled, returning 404."); + Box::new(ContentHandler::error( + StatusCode::NotFound, + "404 Not Found", + "Your homepage is not available when Trusted Signer is disabled.", + Some("You can still access dapps by writing a correct address, though. Re-enabled Signer to get your homepage back."), + )) + } }, // RPC by default _ => { + trace!(target: "dapps", "Resolving to RPC call."); self.special.get(&SpecialEndpoint::Rpc) .expect("RPC endpoint always stored; qed") .to_async_handler(EndpointPath::default(), control) @@ -141,7 +162,7 @@ impl server::Handler for Router { impl Router { pub fn new( control: Control, - main_page: &'static str, + signer_port: Option, content_fetcher: Arc, endpoints: Arc, special: Arc>>, @@ -154,7 +175,7 @@ impl Router { .to_handler(EndpointPath::default()); Router { control: Some(control), - main_page: main_page, + signer_port: signer_port, endpoints: endpoints, fetch: content_fetcher, special: special, diff --git a/dapps/src/rpc.rs b/dapps/src/rpc.rs index 649d283ce..625bfc269 100644 --- a/dapps/src/rpc.rs +++ b/dapps/src/rpc.rs @@ -24,7 +24,7 @@ pub fn rpc(handler: Arc, panic_handler: Arc Box::new(RpcEndpoint { handler: handler, panic_handler: panic_handler, - cors_domain: Some(vec![AccessControlAllowOrigin::Null]), + cors_domain: None, // NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router. allowed_hosts: None, }) diff --git a/dapps/src/tests/api.rs b/dapps/src/tests/api.rs index c0e3bb93b..ea4c08c60 100644 --- a/dapps/src/tests/api.rs +++ b/dapps/src/tests/api.rs @@ -34,7 +34,7 @@ fn should_return_error() { // then assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); - assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json"); + assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json"); assert_eq!(response.body, format!("58\n{}\n0\n\n", r#"{"code":"404","title":"Not Found","detail":"Resource you requested has not been found."}"#)); assert_security_headers(&response.headers); } @@ -57,8 +57,8 @@ fn should_serve_apps() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json"); - assert!(response.body.contains("Parity Home Screen"), response.body); + assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json"); + assert!(response.body.contains("Parity UI"), response.body); assert_security_headers(&response.headers); } @@ -80,7 +80,7 @@ fn should_handle_ping() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json"); + assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json"); assert_eq!(response.body, "0\n\n".to_owned()); assert_security_headers(&response.headers); } @@ -107,3 +107,54 @@ fn should_try_to_resolve_dapp() { assert_security_headers(&response.headers); } +#[test] +fn should_return_signer_port_cors_headers() { + // given + let server = serve(); + + // when + let response = request(server, + "\ + POST /api/ping HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Origin: http://127.0.0.1:18180\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + assert!( + response.headers_raw.contains("Access-Control-Allow-Origin: http://127.0.0.1:18180"), + "CORS header for signer missing: {:?}", + response.headers + ); +} + +#[test] +fn should_return_signer_port_cors_headers_for_home_parity() { + // given + let server = serve(); + + // when + let response = request(server, + "\ + POST /api/ping HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Origin: http://home.parity\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + assert!( + response.headers_raw.contains("Access-Control-Allow-Origin: http://home.parity"), + "CORS header for home.parity missing: {:?}", + response.headers + ); +} diff --git a/dapps/src/tests/authorization.rs b/dapps/src/tests/authorization.rs index 808214a55..86fe4d207 100644 --- a/dapps/src/tests/authorization.rs +++ b/dapps/src/tests/authorization.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve_with_auth, request, assert_security_headers}; +use tests::helpers::{serve_with_auth, request, assert_security_headers_for_embed}; #[test] fn should_require_authorization() { @@ -66,7 +66,7 @@ fn should_allow_on_valid_auth() { // when let response = request(server, "\ - GET /home/ HTTP/1.1\r\n\ + GET /ui/ HTTP/1.1\r\n\ Host: 127.0.0.1:8080\r\n\ Connection: close\r\n\ Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\r\n @@ -76,5 +76,5 @@ fn should_allow_on_valid_auth() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - assert_security_headers(&response.headers); + assert_security_headers_for_embed(&response.headers); } diff --git a/dapps/src/tests/helpers.rs b/dapps/src/tests/helpers.rs index c1837d5a2..0069dc31b 100644 --- a/dapps/src/tests/helpers.rs +++ b/dapps/src/tests/helpers.rs @@ -18,6 +18,7 @@ use std::env; use std::str; use std::sync::Arc; use rustc_serialize::hex::FromHex; +use env_logger::LogBuilder; use ServerBuilder; use Server; @@ -27,6 +28,7 @@ use devtools::http_client; const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2"; const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000"; +const SIGNER_PORT: u16 = 18180; pub struct FakeRegistrar { pub calls: Arc>>, @@ -58,11 +60,22 @@ impl ContractClient for FakeRegistrar { } } +fn init_logger() { + // Initialize logger + if let Ok(log) = env::var("RUST_LOG") { + let mut builder = LogBuilder::new(); + builder.parse(&log); + builder.init().expect("Logger is initialized only once."); + } +} + pub fn init_server(hosts: Option>) -> (Server, Arc) { + init_logger(); let registrar = Arc::new(FakeRegistrar::new()); let mut dapps_path = env::temp_dir(); dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); - let builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone()); + let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone()); + builder.with_signer_port(Some(SIGNER_PORT)); ( builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(), registrar, @@ -70,10 +83,12 @@ pub fn init_server(hosts: Option>) -> (Server, Arc) { } pub fn serve_with_auth(user: &str, pass: &str) -> Server { + init_logger(); let registrar = Arc::new(FakeRegistrar::new()); let mut dapps_path = env::temp_dir(); dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); - let builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar); + let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar); + builder.with_signer_port(Some(SIGNER_PORT)); builder.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap() } @@ -94,5 +109,8 @@ pub fn request(server: Server, request: &str) -> http_client::Response { } pub fn assert_security_headers(headers: &[String]) { - http_client::assert_security_headers_present(headers) + http_client::assert_security_headers_present(headers, None) +} +pub fn assert_security_headers_for_embed(headers: &[String]) { + http_client::assert_security_headers_present(headers, Some(SIGNER_PORT)) } diff --git a/dapps/src/tests/redirection.rs b/dapps/src/tests/redirection.rs index 92fc6ce80..aee1cb964 100644 --- a/dapps/src/tests/redirection.rs +++ b/dapps/src/tests/redirection.rs @@ -33,7 +33,7 @@ fn should_redirect_to_home() { // then assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned()); - assert_eq!(response.headers.get(0).unwrap(), "Location: /home/"); + assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180"); } #[test] @@ -53,7 +53,7 @@ fn should_redirect_to_home_when_trailing_slash_is_missing() { // then assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned()); - assert_eq!(response.headers.get(0).unwrap(), "Location: /home/"); + assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180"); } #[test] @@ -73,7 +73,6 @@ fn should_display_404_on_invalid_dapp() { // then assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); - assert!(response.body.contains("href=\"/home/")); assert_security_headers(&response.headers); } @@ -94,7 +93,6 @@ fn should_display_404_on_invalid_dapp_with_domain() { // then assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); - assert!(response.body.contains("href=\"http://home.parity/")); assert_security_headers(&response.headers); } @@ -161,7 +159,7 @@ fn should_serve_proxy_pac() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - assert_eq!(response.body, "86\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"*.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned()); + assert_eq!(response.body, "D5\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"home.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned()); assert_security_headers(&response.headers); } diff --git a/dapps/src/tests/validation.rs b/dapps/src/tests/validation.rs index c39350cce..ae02a6c2c 100644 --- a/dapps/src/tests/validation.rs +++ b/dapps/src/tests/validation.rs @@ -45,7 +45,7 @@ fn should_allow_valid_host() { // when let response = request(server, "\ - GET /home/ HTTP/1.1\r\n\ + GET /ui/ HTTP/1.1\r\n\ Host: localhost:8080\r\n\ Connection: close\r\n\ \r\n\ @@ -66,7 +66,7 @@ fn should_serve_dapps_domains() { let response = request(server, "\ GET / HTTP/1.1\r\n\ - Host: home.parity\r\n\ + Host: ui.parity\r\n\ Connection: close\r\n\ \r\n\ {} @@ -98,3 +98,30 @@ fn should_allow_parity_utils_even_on_invalid_domain() { assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); } +#[test] +fn should_not_return_cors_headers_for_rpc() { + // given + let server = serve_hosts(Some(vec!["localhost:8080".into()])); + + // when + let response = request(server, + "\ + POST /rpc HTTP/1.1\r\n\ + Host: localhost:8080\r\n\ + Origin: null\r\n\ + Content-Type: application/json\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + assert!( + !response.headers_raw.contains("Access-Control-Allow-Origin"), + "CORS headers were not expected: {:?}", + response.headers + ); +} + diff --git a/devtools/src/http_client.rs b/devtools/src/http_client.rs index 3f0bd463e..acba2989e 100644 --- a/devtools/src/http_client.rs +++ b/devtools/src/http_client.rs @@ -65,11 +65,18 @@ pub fn request(address: &SocketAddr, request: &str) -> Response { } /// Check if all required security headers are present -pub fn assert_security_headers_present(headers: &[String]) { - assert!( - headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(), - "X-Frame-Options missing: {:?}", headers - ); +pub fn assert_security_headers_present(headers: &[String], port: Option) { + if let Some(port) = port { + assert!( + headers.iter().find(|header| header.as_str() == &format!("X-Frame-Options: ALLOW-FROM http://127.0.0.1:{}", port)).is_some(), + "X-Frame-Options: ALLOW-FROM missing: {:?}", headers + ); + } else { + assert!( + headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(), + "X-Frame-Options: SAMEORIGIN missing: {:?}", headers + ); + } assert!( headers.iter().find(|header| header.as_str() == "X-XSS-Protection: 1; mode=block").is_some(), "X-XSS-Protection missing: {:?}", headers diff --git a/logger/src/lib.rs b/logger/src/lib.rs index 79655d2f6..a79f6fc43 100644 --- a/logger/src/lib.rs +++ b/logger/src/lib.rs @@ -65,11 +65,10 @@ pub fn setup_log(config: &Config) -> Result, String> { builder.filter(Some("rustls"), LogLevelFilter::Warn); builder.filter(None, LogLevelFilter::Info); - if env::var("RUST_LOG").is_ok() { - let lvl = &env::var("RUST_LOG").unwrap(); - levels.push_str(lvl); + if let Ok(lvl) = env::var("RUST_LOG") { + levels.push_str(&lvl); levels.push_str(","); - builder.parse(lvl); + builder.parse(&lvl); } if let Some(ref s) = config.mode { @@ -119,7 +118,7 @@ pub fn setup_log(config: &Config) -> Result, String> { }; builder.format(format); - builder.init().unwrap(); + builder.init().expect("Logger initialized only once."); Ok(logs) } diff --git a/signer/src/tests/mod.rs b/signer/src/tests/mod.rs index e6933382f..9b85f1554 100644 --- a/signer/src/tests/mod.rs +++ b/signer/src/tests/mod.rs @@ -81,7 +81,28 @@ fn should_reject_invalid_host() { // then assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); assert!(response.body.contains("URL Blocked")); - http_client::assert_security_headers_present(&response.headers); + http_client::assert_security_headers_present(&response.headers, None); +} + +#[test] +fn should_allow_home_parity_host() { + // given + let server = serve().0; + + // when + let response = request(server, + "\ + GET http://home.parity/ HTTP/1.1\r\n\ + Host: home.parity\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + http_client::assert_security_headers_present(&response.headers, None); } #[test] @@ -102,7 +123,27 @@ fn should_serve_styles_even_on_disallowed_domain() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); - http_client::assert_security_headers_present(&response.headers); + http_client::assert_security_headers_present(&response.headers, None); +} + +#[test] +fn should_return_200_ok_for_connect_requests() { + // given + let server = serve().0; + + // when + let response = request(server, + "\ + CONNECT home.parity:8080 HTTP/1.1\r\n\ + Host: home.parity\r\n\ + Connection: close\r\n\ + \r\n\ + {} + " + ); + + // then + assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); } #[test] @@ -126,7 +167,7 @@ fn should_block_if_authorization_is_incorrect() { // then assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); - http_client::assert_security_headers_present(&response.headers); + http_client::assert_security_headers_present(&response.headers, None); } #[test] @@ -205,5 +246,6 @@ fn should_allow_initial_connection_but_only_once() { // then assert_eq!(response1.status, "HTTP/1.1 101 Switching Protocols".to_owned()); assert_eq!(response2.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); - http_client::assert_security_headers_present(&response2.headers); + http_client::assert_security_headers_present(&response2.headers, None); } + diff --git a/signer/src/ws_server/session.rs b/signer/src/ws_server/session.rs index 255f2cd8f..2ff2bc10f 100644 --- a/signer/src/ws_server/session.rs +++ b/signer/src/ws_server/session.rs @@ -63,6 +63,8 @@ mod ui { } } +const HOME_DOMAIN: &'static str = "home.parity"; + fn origin_is_allowed(self_origin: &str, header: Option<&[u8]>) -> bool { match header { None => false, @@ -72,6 +74,8 @@ fn origin_is_allowed(self_origin: &str, header: Option<&[u8]>) -> bool { Some(ref origin) if origin.starts_with("chrome-extension://") => true, Some(ref origin) if origin.starts_with(self_origin) => true, Some(ref origin) if origin.starts_with(&format!("http://{}", self_origin)) => true, + Some(ref origin) if origin.starts_with(HOME_DOMAIN) => true, + Some(ref origin) if origin.starts_with(&format!("http://{}", HOME_DOMAIN)) => true, _ => false } } @@ -134,13 +138,20 @@ pub struct Session { impl ws::Handler for Session { #[cfg_attr(feature="dev", allow(collapsible_if))] fn on_request(&mut self, req: &ws::Request) -> ws::Result<(ws::Response)> { - let origin = req.header("origin").or_else(|| req.header("Origin")).map(|x| &x[..]); - let host = req.header("host").or_else(|| req.header("Host")).map(|x| &x[..]); + trace!(target: "signer", "Handling request: {:?}", req); + + // TODO [ToDr] ws server is not handling proxied requests correctly: + // Trim domain name from resource part: + let resource = req.resource().trim_left_matches(&format!("http://{}", HOME_DOMAIN)); + // Styles file is allowed for error pages to display nicely. - let is_styles_file = req.resource() == "/styles.css"; + let is_styles_file = resource == "/styles.css"; // Check request origin and host header. if !self.skip_origin_validation { + let origin = req.header("origin").or_else(|| req.header("Origin")).map(|x| &x[..]); + let host = req.header("host").or_else(|| req.header("Host")).map(|x| &x[..]); + let is_valid = origin_is_allowed(&self.self_origin, origin) || (origin.is_none() && origin_is_allowed(&self.self_origin, host)); let is_valid = is_styles_file || is_valid; @@ -155,6 +166,14 @@ impl ws::Handler for Session { } } + // PROXY requests when running behind home.parity + if req.method() == "CONNECT" { + let mut res = ws::Response::ok("".into()); + res.headers_mut().push(("Content-Length".into(), b"0".to_vec())); + res.headers_mut().push(("Connection".into(), b"keep-alive".to_vec())); + return Ok(res); + } + // Detect if it's a websocket request // (styles file skips origin validation, so make sure to prevent WS connections on this resource) if req.header("sec-websocket-key").is_some() && !is_styles_file { @@ -173,8 +192,9 @@ impl ws::Handler for Session { }); } + debug!(target: "signer", "Requesting resource: {:?}", resource); // Otherwise try to serve a page. - Ok(self.file_handler.handle(req.resource()) + Ok(self.file_handler.handle(resource) .map_or_else( // return 404 not found || error(ErrorType::NotFound, "Not found", "Requested file was not found.", None),