diff --git a/Cargo.lock b/Cargo.lock index e3dc752f2..eae4823f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -283,6 +283,7 @@ dependencies = [ "serde_codegen 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 94af56ca3..c42ed8eff 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -13,6 +13,7 @@ log = "0.3" jsonrpc-core = "2.0" jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.git" } hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } +unicase = "1.3" url = "1.0" rustc-serialize = "0.3" serde = "0.7.0" diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index ab3632074..84db93f63 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -15,20 +15,23 @@ // along with Parity. If not, see . use std::sync::Arc; -use endpoint::{Endpoint, Endpoints, Handler, EndpointPath}; -use api::types::{App, ApiError}; -use api::response::{as_json, as_json_error}; use hyper::{server, net, Decoder, Encoder, Next}; +use api::types::{App, ApiError}; +use api::response::{as_json, as_json_error, ping_response}; +use handlers::extract_url; +use endpoint::{Endpoint, Endpoints, Handler, EndpointPath}; #[derive(Clone)] pub struct RestApi { + local_domain: String, endpoints: Arc, } impl RestApi { - pub fn new(endpoints: Arc) -> Box { + pub fn new(local_domain: String, endpoints: Arc) -> Box { Box::new(RestApi { - endpoints: endpoints + local_domain: local_domain, + endpoints: endpoints, }) } @@ -59,9 +62,28 @@ struct RestApiRouter { impl server::Handler for RestApiRouter { - fn on_request(&mut self, _request: server::Request) -> Next { - self.handler = as_json(&self.api.list_apps()); - Next::write() + fn on_request(&mut self, request: server::Request) -> Next { + let url = extract_url(&request); + if url.is_none() { + // Just return 404 if we can't parse URL + return Next::write(); + } + + let url = url.expect("Check for None is above; qed"); + let endpoint = url.path.get(1).map(|v| v.as_str()); + + let handler = endpoint.and_then(|v| match v { + "apps" => Some(as_json(&self.api.list_apps())), + "ping" => Some(ping_response(&self.api.local_domain)), + _ => None, + }); + + // Overwrite default + if let Some(h) = handler { + self.handler = h; + } + + self.handler.on_request(request) } fn on_request_readable(&mut self, decoder: &mut Decoder) -> Next { diff --git a/dapps/src/api/response.rs b/dapps/src/api/response.rs index ba0922e7a..ce6eb2921 100644 --- a/dapps/src/api/response.rs +++ b/dapps/src/api/response.rs @@ -17,7 +17,7 @@ use serde::Serialize; use serde_json; use endpoint::Handler; -use handlers::ContentHandler; +use handlers::{ContentHandler, EchoHandler}; pub fn as_json(val: &T) -> Box { Box::new(ContentHandler::ok(serde_json::to_string(val).unwrap(), "application/json".to_owned())) @@ -26,3 +26,11 @@ pub fn as_json(val: &T) -> Box { pub fn as_json_error(val: &T) -> Box { Box::new(ContentHandler::not_found(serde_json::to_string(val).unwrap(), "application/json".to_owned())) } + +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")), + ])) +} diff --git a/dapps/src/api/types.rs b/dapps/src/api/types.rs index d99d767eb..64697123b 100644 --- a/dapps/src/api/types.rs +++ b/dapps/src/api/types.rs @@ -19,5 +19,3 @@ include!("types.rs.in"); #[cfg(not(feature = "serde_macros"))] include!(concat!(env!("OUT_DIR"), "/types.rs")); - - diff --git a/dapps/src/api/types.rs.in b/dapps/src/api/types.rs.in index 72a8cd221..a7961a144 100644 --- a/dapps/src/api/types.rs.in +++ b/dapps/src/api/types.rs.in @@ -48,4 +48,3 @@ pub struct ApiError { pub detail: String, } - diff --git a/dapps/src/handlers/echo.rs b/dapps/src/handlers/echo.rs new file mode 100644 index 000000000..b2ea8f580 --- /dev/null +++ b/dapps/src/handlers/echo.rs @@ -0,0 +1,148 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Echo Handler + +use std::io::Read; +use hyper::{header, server, Decoder, Encoder, Next}; +use hyper::method::Method; +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, +} + +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(), "text/plain".into())); + Next::write() + } else { + Next::read() + } + } + + fn on_request_readable(&mut self, decoder: &mut Decoder) -> Next { + match decoder.read_to_string(&mut self.content) { + Ok(0) => { + self.handler = Some(ContentHandler::ok(self.content.clone(), "application/json".into())); + Next::write() + }, + Ok(_) => Next::read(), + Err(e) => match e.kind() { + ::std::io::ErrorKind::WouldBlock => Next::read(), + _ => Next::end(), + } + } + } + + 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().unwrap().on_response(res) + } + + fn on_response_writable(&mut self, encoder: &mut Encoder) -> Next { + self.handler.as_mut().unwrap().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 933538655..5fa9fda95 100644 --- a/dapps/src/handlers/mod.rs +++ b/dapps/src/handlers/mod.rs @@ -17,9 +17,41 @@ //! Hyper handlers implementations. mod auth; +mod echo; mod content; mod redirect; pub use self::auth::AuthRequiredHandler; +pub use self::echo::EchoHandler; pub use self::content::ContentHandler; pub use self::redirect::Redirection; + +use url::Url; +use hyper::{server, header, net, uri}; + +pub fn extract_url(req: &server::Request) -> Option { + match *req.uri() { + uri::RequestUri::AbsoluteUri(ref url) => { + match Url::from_generic_url(url.clone()) { + Ok(url) => Some(url), + _ => None, + } + }, + uri::RequestUri::AbsolutePath(ref path) => { + // Attempt to prepend the Host header (mandatory in HTTP/1.1) + let url_string = match req.headers().get::() { + Some(ref host) => { + format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path) + }, + None => return None, + }; + + match Url::parse(&url_string) { + Ok(url) => Some(url), + _ => None, + } + }, + _ => None, + } +} + diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 6952d509c..a94cf1bde 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -45,8 +45,9 @@ #[macro_use] extern crate log; -extern crate url; +extern crate url as url_lib; extern crate hyper; +extern crate unicase; extern crate serde; extern crate serde_json; extern crate jsonrpc_core; @@ -64,6 +65,7 @@ mod handlers; mod rpc; mod api; mod proxypac; +mod url; use std::sync::{Arc, Mutex}; use std::net::SocketAddr; @@ -123,7 +125,7 @@ impl Server { let special = Arc::new({ let mut special = HashMap::new(); special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone())); - special.insert(router::SpecialEndpoint::Api, api::RestApi::new(endpoints.clone())); + special.insert(router::SpecialEndpoint::Api, api::RestApi::new(format!("{}", addr), endpoints.clone())); special.insert(router::SpecialEndpoint::Utils, apps::utils()); special }); diff --git a/dapps/src/router/mod.rs b/dapps/src/router/mod.rs index f04c8b614..8fbb37cf3 100644 --- a/dapps/src/router/mod.rs +++ b/dapps/src/router/mod.rs @@ -17,22 +17,18 @@ //! Router implementation //! Processes request handling authorization and dispatching it to proper application. -mod url; pub mod auth; use DAPPS_DOMAIN; use std::sync::Arc; use std::collections::HashMap; -use url::Host; -use hyper; -use hyper::{server, uri, header}; -use hyper::{Next, Encoder, Decoder}; +use url::{Url, Host}; +use hyper::{self, server, Next, Encoder, Decoder}; use hyper::net::HttpStream; use apps; use endpoint::{Endpoint, Endpoints, EndpointPath}; -use self::url::Url; +use handlers::{Redirection, extract_url}; use self::auth::{Authorization, Authorized}; -use handlers::Redirection; /// Special endpoints are accessible on every domain (every dapp) #[derive(Debug, PartialEq, Hash, Eq)] @@ -123,32 +119,6 @@ impl Router { } } -fn extract_url(req: &server::Request) -> Option { - match *req.uri() { - uri::RequestUri::AbsoluteUri(ref url) => { - match Url::from_generic_url(url.clone()) { - Ok(url) => Some(url), - _ => None, - } - }, - uri::RequestUri::AbsolutePath(ref path) => { - // Attempt to prepend the Host header (mandatory in HTTP/1.1) - let url_string = match req.headers().get::() { - Some(ref host) => { - format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path) - }, - None => return None, - }; - - match Url::parse(&url_string) { - Ok(url) => Some(url), - _ => None, - } - }, - _ => None, - } -} - fn extract_endpoint(url: &Option) -> (Option, SpecialEndpoint) { fn special_endpoint(url: &Url) -> SpecialEndpoint { if url.path.len() <= 1 { diff --git a/dapps/src/router/url.rs b/dapps/src/url.rs similarity index 96% rename from dapps/src/router/url.rs rename to dapps/src/url.rs index b96168239..c9eb2f78f 100644 --- a/dapps/src/router/url.rs +++ b/dapps/src/url.rs @@ -16,14 +16,14 @@ //! HTTP/HTTPS URL type. Based on URL type from Iron library. -use url::Host; -use url::{self}; +use url_lib::{self}; +pub use url_lib::Host; /// HTTP/HTTPS URL type for Iron. #[derive(PartialEq, Eq, Clone, Debug)] pub struct Url { /// Raw url of url - pub raw: url::Url, + pub raw: url_lib::Url, /// The host field of the URL, probably a domain. pub host: Host, @@ -62,14 +62,14 @@ impl Url { /// See: http://url.spec.whatwg.org/#special-scheme pub fn parse(input: &str) -> Result { // Parse the string using rust-url, then convert. - match url::Url::parse(input) { + match url_lib::Url::parse(input) { Ok(raw_url) => Url::from_generic_url(raw_url), Err(e) => Err(format!("{}", e)) } } /// Create a `Url` from a `rust-url` `Url`. - pub fn from_generic_url(raw_url: url::Url) -> Result { + pub fn from_generic_url(raw_url: url_lib::Url) -> Result { // Map empty usernames to None. let username = match raw_url.username() { "" => None, diff --git a/ethcore/src/tests/rpc.rs b/ethcore/src/tests/rpc.rs index a25928cf8..786389905 100644 --- a/ethcore/src/tests/rpc.rs +++ b/ethcore/src/tests/rpc.rs @@ -19,7 +19,7 @@ use nanoipc; use std::sync::Arc; use std::sync::atomic::{Ordering, AtomicBool}; -use client::{Client, ClientConfig, RemoteClient}; +use client::{Client, BlockChainClient, ClientConfig, RemoteClient, BlockID}; use tests::helpers::*; use devtools::*; use miner::Miner; @@ -55,3 +55,17 @@ fn can_handshake() { assert!(remote_client.handshake().is_ok()); }) } + +#[test] +fn can_query_block() { + crossbeam::scope(|scope| { + let stop_guard = StopGuard::new(); + let socket_path = "ipc:///tmp/parity-client-rpc-20.ipc"; + run_test_worker(scope, stop_guard.share(), socket_path); + let remote_client = nanoipc::init_client::>(socket_path).unwrap(); + + let non_existant_block = remote_client.block_header(BlockID::Number(999)); + + assert!(non_existant_block.is_none()); + }) +} diff --git a/ipc/codegen/src/serialization.rs b/ipc/codegen/src/serialization.rs index 9c58e198e..0ab70f93a 100644 --- a/ipc/codegen/src/serialization.rs +++ b/ipc/codegen/src/serialization.rs @@ -707,7 +707,12 @@ fn binary_expr_variant( let buffer = &mut buffer[1..]; $write_expr }), - read: quote_arm!(cx, $variant_index_ident => { $read_expr } ), + read: quote_arm!(cx, + $variant_index_ident => { + let buffer = &buffer[1..]; + $read_expr + } + ), }) }, ast::VariantData::Struct(ref fields, _) => { @@ -742,7 +747,12 @@ fn binary_expr_variant( let buffer = &mut buffer[1..]; $write_expr }), - read: quote_arm!(cx, $variant_index_ident => { $read_expr } ), + read: quote_arm!(cx, + $variant_index_ident => { + let buffer = &buffer[1..]; + $read_expr + } + ), }) }, } diff --git a/parity/configuration.rs b/parity/configuration.rs index 4e68c1a61..fc4502673 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -425,11 +425,11 @@ impl Configuration { fn geth_ipc_path(&self) -> String { if cfg!(windows) { r"\\.\pipe\geth.ipc".to_owned() - } - else { - if self.args.flag_testnet { path::ethereum::with_testnet("geth.ipc") } - else { path::ethereum::with_default("geth.ipc") } - .to_str().unwrap().to_owned() + } else { + match self.args.flag_testnet { + true => path::ethereum::with_testnet("geth.ipc"), + false => path::ethereum::with_default("geth.ipc"), + }.to_str().unwrap().to_owned() } } diff --git a/parity/informant.rs b/parity/informant.rs index f0709458f..25ec5ca8b 100644 --- a/parity/informant.rs +++ b/parity/informant.rs @@ -22,7 +22,7 @@ use std::time::{Instant, Duration}; use std::sync::RwLock; use std::ops::{Deref, DerefMut}; use ethsync::{EthSync, SyncProvider}; -use util::{Uint, RwLockable}; +use util::{Uint, RwLockable, NetworkService}; use ethcore::client::*; use number_prefix::{binary_prefix, Standalone, Prefixed}; @@ -76,7 +76,7 @@ impl Informant { } #[cfg_attr(feature="dev", allow(match_bool))] - pub fn tick(&self, client: &Client, maybe_sync: Option<&EthSync>) { + pub fn tick(&self, client: &Client, maybe_sync: Option<(&EthSync, &NetworkService)>) where Message: Send + Sync + Clone + 'static { let elapsed = self.last_tick.unwrapped_read().elapsed(); if elapsed < Duration::from_secs(5) { return; @@ -110,11 +110,13 @@ impl Informant { paint(Yellow.bold(), format!("{:3}", ((report.gas_processed - last_report.gas_processed) / From::from(elapsed.as_milliseconds() * 1000)).low_u64())), match maybe_sync { - Some(sync) => { + Some((sync, net)) => { let sync_info = sync.status(); - format!("{}/{} peers {} ", + let net_config = net.config(); + format!("{}/{}/{} peers {} ", paint(Green.bold(), format!("{:2}", sync_info.num_active_peers)), paint(Green.bold(), format!("{:2}", sync_info.num_peers)), + paint(Green.bold(), format!("{:2}", net_config.ideal_peers)), paint(Cyan.bold(), format!("{:>8}", format!("#{}", sync_info.last_imported_block_number.unwrap_or(chain_info.best_block_number)))), ) } @@ -128,7 +130,7 @@ impl Informant { paint(Purple.bold(), format!("{:>8}", Informant::format_bytes(cache_info.total()))), paint(Purple.bold(), format!("{:>8}", Informant::format_bytes(queue_info.mem_used))), match maybe_sync { - Some(sync) => { + Some((sync, _)) => { let sync_info = sync.status(); format!(" {} sync", paint(Purple.bold(), format!("{:>8}", Informant::format_bytes(sync_info.mem_used)))) } diff --git a/parity/io_handler.rs b/parity/io_handler.rs index 3f0c04fbd..4af630d44 100644 --- a/parity/io_handler.rs +++ b/parity/io_handler.rs @@ -40,7 +40,9 @@ impl IoHandler for ClientIoHandler { fn timeout(&self, _io: &IoContext, timer: TimerToken) { if let INFO_TIMER = timer { - self.info.tick(&self.client, Some(&self.sync)); + if let Some(net) = self.network.upgrade() { + self.info.tick(&self.client, Some((&self.sync, &net))); + } } } diff --git a/parity/main.rs b/parity/main.rs index 495d2a3ad..a4530c830 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -485,7 +485,7 @@ fn execute_import(conf: Configuration) { Err(BlockImportError::Import(ImportError::AlreadyInChain)) => { trace!("Skipping block already in chain."); } Err(e) => die!("Cannot import block: {:?}", e) } - informant.tick(client.deref(), None); + informant.tick::<&'static ()>(client.deref(), None); }; match format { diff --git a/util/src/misc.rs b/util/src/misc.rs index c56bcb85a..fff03259a 100644 --- a/util/src/misc.rs +++ b/util/src/misc.rs @@ -72,7 +72,7 @@ pub trait Lockable { fn locked(&self) -> MutexGuard; } -impl Lockable for Mutex { +impl Lockable for Mutex { fn locked(&self) -> MutexGuard { self.lock().unwrap() } } @@ -85,7 +85,7 @@ pub trait RwLockable { fn unwrapped_write(&self) -> RwLockWriteGuard; } -impl RwLockable for RwLock { +impl RwLockable for RwLock { fn unwrapped_read(&self) -> RwLockReadGuard { self.read().unwrap() } fn unwrapped_write(&self) -> RwLockWriteGuard { self.write().unwrap() } } diff --git a/util/src/network/service.rs b/util/src/network/service.rs index 711a8d860..50084abb4 100644 --- a/util/src/network/service.rs +++ b/util/src/network/service.rs @@ -79,6 +79,11 @@ impl NetworkService where Message: Send + Sync + Clone + 'stat &self.stats } + /// Returns network configuration. + pub fn config(&self) -> &NetworkConfiguration { + &self.config + } + /// Returns external url if available. pub fn external_url(&self) -> Option { let host = self.host.unwrapped_read();