From f97e775498c03564af45eb7206657a2a91115b23 Mon Sep 17 00:00:00 2001 From: Maciej Hirsz Date: Fri, 24 Feb 2017 10:32:42 +0100 Subject: [PATCH] More CLI settings for IPFS API (#4608) * TEMP: Bind to 0.0.0.0, don't check Origin * More CLI options for IPFS * CORS and Hosts filtering * Allow current interface as origin * Correctly handle CORS settings * fix grumbles --- Cargo.lock | 1 + ipfs/Cargo.toml | 1 + ipfs/src/error.rs | 5 +- ipfs/src/lib.rs | 139 ++++++++++++++++++++++++++---- ipfs/src/{handler.rs => route.rs} | 71 ++++++--------- parity/cli/config.full.toml | 3 + parity/cli/mod.rs | 31 +++++-- parity/cli/usage.txt | 11 +++ parity/configuration.rs | 95 +++++++++++++++----- parity/ipfs.rs | 26 +++++- parity/run.rs | 5 +- 11 files changed, 285 insertions(+), 103 deletions(-) rename ipfs/src/{handler.rs => route.rs} (74%) diff --git a/Cargo.lock b/Cargo.lock index 842daf0aa..62412092d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1640,6 +1640,7 @@ dependencies = [ "ethcore 1.6.0", "ethcore-util 1.6.0", "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", + "jsonrpc-http-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", diff --git a/ipfs/Cargo.toml b/ipfs/Cargo.toml index 7cc381ee8..992ee0710 100644 --- a/ipfs/Cargo.toml +++ b/ipfs/Cargo.toml @@ -8,6 +8,7 @@ authors = ["Parity Technologies "] [dependencies] ethcore = { path = "../ethcore" } ethcore-util = { path = "../util" } +jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" } rlp = { path = "../util/rlp" } mime = "0.2" hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } diff --git a/ipfs/src/error.rs b/ipfs/src/error.rs index f379f254b..1cbd54f1c 100644 --- a/ipfs/src/error.rs +++ b/ipfs/src/error.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use {multihash, cid, hyper}; -use handler::Out; +use route::Out; pub type Result = ::std::result::Result; @@ -26,6 +26,8 @@ pub enum ServerError { IoError(::std::io::Error), /// Other `hyper` error Other(hyper::error::Error), + /// Invalid --ipfs-api-interface + InvalidInterface } #[derive(Debug, PartialEq)] @@ -89,6 +91,7 @@ impl From for String { match err { ServerError::IoError(err) => err.to_string(), ServerError::Other(err) => err.to_string(), + ServerError::InvalidInterface => "Invalid --ipfs-api-interface parameter".into(), } } } diff --git a/ipfs/src/lib.rs b/ipfs/src/lib.rs index 37373344a..80e910f9e 100644 --- a/ipfs/src/lib.rs +++ b/ipfs/src/lib.rs @@ -23,21 +23,91 @@ extern crate cid; extern crate rlp; extern crate ethcore; extern crate ethcore_util as util; +extern crate jsonrpc_http_server; -mod error; -mod handler; +pub mod error; +mod route; use std::io::Write; use std::sync::Arc; -use std::net::{SocketAddr, IpAddr, Ipv4Addr}; +use std::net::{SocketAddr, IpAddr}; use error::ServerError; -use handler::{IpfsHandler, Out}; +use route::Out; +use jsonrpc_http_server::cors; use hyper::server::{Listening, Handler, Request, Response}; use hyper::net::HttpStream; -use hyper::header::{ContentLength, ContentType, Origin}; +use hyper::header::{Vary, ContentLength, ContentType, AccessControlAllowOrigin}; use hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode}; use ethcore::client::BlockChainClient; + +/// Request/response handler +pub struct IpfsHandler { + /// Response to send out + out: Out, + /// How many bytes from the response have been written + out_progress: usize, + /// Origin request header + origin: Option, + /// Allowed CORS domains + cors_domains: Option>, + /// Hostnames allowed in the `Host` request header + allowed_hosts: Option>, + /// Reference to the Blockchain Client + client: Arc, +} + +impl IpfsHandler { + pub fn client(&self) -> &BlockChainClient { + &*self.client + } + + pub fn new(cors: Option>, hosts: Option>, client: Arc) -> Self { + fn origin_to_header(origin: String) -> AccessControlAllowOrigin { + match origin.as_str() { + "*" => AccessControlAllowOrigin::Any, + "null" | "" => AccessControlAllowOrigin::Null, + _ => AccessControlAllowOrigin::Value(origin), + } + } + + IpfsHandler { + out: Out::Bad("Invalid Request"), + out_progress: 0, + origin: None, + cors_domains: cors.map(|vec| vec.into_iter().map(origin_to_header).collect()), + allowed_hosts: hosts, + client: client, + } + } + + fn is_host_allowed(&self, req: &Request) -> bool { + match self.allowed_hosts { + Some(ref hosts) => jsonrpc_http_server::is_host_header_valid(&req, hosts), + None => true, + } + } + + fn is_origin_allowed(&self) -> bool { + // Check origin header first, no header passed is good news + let origin = match self.origin { + Some(ref origin) => origin, + None => return true, + }; + + let cors_domains = match self.cors_domains { + Some(ref domains) => domains, + None => return false, + }; + + cors_domains.iter().any(|domain| match *domain { + AccessControlAllowOrigin::Value(ref allowed) => origin == allowed, + AccessControlAllowOrigin::Any => true, + AccessControlAllowOrigin::Null => origin == "", + }) + } +} + /// Implement Hyper's HTTP handler impl Handler for IpfsHandler { fn on_request(&mut self, req: Request) -> Next { @@ -45,9 +115,16 @@ impl Handler for IpfsHandler { return Next::write(); } - // Reject requests if the Origin header isn't valid - if req.headers().get::().map(|o| "127.0.0.1" != &o.host.hostname).unwrap_or(false) { - self.out = Out::Bad("Illegal Origin"); + self.origin = cors::read_origin(&req); + + if !self.is_host_allowed(&req) { + self.out = Out::Bad("Disallowed Host header"); + + return Next::write(); + } + + if !self.is_origin_allowed() { + self.out = Out::Bad("Disallowed Origin header"); return Next::write(); } @@ -57,7 +134,9 @@ impl Handler for IpfsHandler { _ => return Next::write(), }; - self.route(path, query) + self.out = self.route(path, query); + + Next::write() } fn on_request_readable(&mut self, _decoder: &mut Decoder) -> Next { @@ -82,25 +161,27 @@ impl Handler for IpfsHandler { res.headers_mut().set(ContentLength(bytes.len() as u64)); res.headers_mut().set(ContentType(content_type)); - Next::write() }, NotFound(reason) => { res.set_status(StatusCode::NotFound); res.headers_mut().set(ContentLength(reason.len() as u64)); res.headers_mut().set(ContentType(mime!(Text/Plain))); - - Next::write() }, Bad(reason) => { res.set_status(StatusCode::BadRequest); res.headers_mut().set(ContentLength(reason.len() as u64)); res.headers_mut().set(ContentType(mime!(Text/Plain))); - - Next::write() } } + + if let Some(cors_header) = cors::get_cors_header(&self.cors_domains, &self.origin) { + res.headers_mut().set(cors_header); + res.headers_mut().set(Vary::Items(vec!["Origin".into()])); + } + + Next::write() } fn on_response_writable(&mut self, transport: &mut Encoder) -> Next { @@ -116,11 +197,12 @@ impl Handler for IpfsHandler { } } +/// Attempt to write entire `data` from current `progress` fn write_chunk(transport: &mut W, progress: &mut usize, data: &[u8]) -> Next { // Skip any bytes that have already been written let chunk = &data[*progress..]; - // Write an get written count + // Write an get the amount of bytes written. End the connection in case of an error. let written = match transport.write(chunk) { Ok(written) => written, Err(_) => return Next::end(), @@ -128,7 +210,7 @@ fn write_chunk(transport: &mut W, progress: &mut usize, data: &[u8]) - *progress += written; - // Close the connection if the entire chunk has been written, otherwise increment progress + // Close the connection if the entire remaining chunk has been written if written < chunk.len() { Next::write() } else { @@ -136,12 +218,31 @@ fn write_chunk(transport: &mut W, progress: &mut usize, data: &[u8]) - } } -pub fn start_server(port: u16, client: Arc) -> Result { - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port); +/// Add current interface (default: "127.0.0.1:5001") to list of allowed hosts +fn include_current_interface(mut hosts: Vec, interface: String, port: u16) -> Vec { + hosts.push(match port { + 80 => interface, + _ => format!("{}:{}", interface, port), + }); + + hosts +} + +pub fn start_server( + port: u16, + interface: String, + cors: Option>, + hosts: Option>, + client: Arc +) -> Result { + + let ip: IpAddr = interface.parse().map_err(|_| ServerError::InvalidInterface)?; + let addr = SocketAddr::new(ip, port); + let hosts = hosts.map(move |hosts| include_current_interface(hosts, interface, port)); Ok( hyper::Server::http(&addr)? - .handle(move |_| IpfsHandler::new(client.clone())) + .handle(move |_| IpfsHandler::new(cors.clone(), hosts.clone(), client.clone())) .map(|(listening, srv)| { ::std::thread::spawn(move || { diff --git a/ipfs/src/handler.rs b/ipfs/src/route.rs similarity index 74% rename from ipfs/src/handler.rs rename to ipfs/src/route.rs index 543792fa5..5b571885d 100644 --- a/ipfs/src/handler.rs +++ b/ipfs/src/route.rs @@ -14,15 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use {rlp, multihash}; +use {rlp, multihash, IpfsHandler}; use error::{Error, Result}; use cid::{ToCid, Codec}; -use std::sync::Arc; use multihash::Hash; -use hyper::Next; use util::{Bytes, H256}; -use ethcore::client::{BlockId, TransactionId, BlockChainClient}; +use ethcore::client::{BlockId, TransactionId}; type Reason = &'static str; @@ -34,30 +32,10 @@ pub enum Out { Bad(Reason), } -/// Request/response handler -pub struct IpfsHandler { - /// Reference to the Blockchain Client - client: Arc, - - /// Response to send out - pub out: Out, - - /// How many bytes from the response have been written - pub out_progress: usize, -} - impl IpfsHandler { - pub fn new(client: Arc) -> Self { - IpfsHandler { - client: client, - out: Out::Bad("Invalid Request"), - out_progress: 0, - } - } - /// Route path + query string to a specialized method - pub fn route(&mut self, path: &str, query: Option<&str>) -> Next { - self.out = match path { + pub fn route(&self, path: &str, query: Option<&str>) -> Out { + match path { "/api/v0/block/get" => { let arg = query.and_then(|q| get_param(q, "arg")).unwrap_or(""); @@ -65,9 +43,7 @@ impl IpfsHandler { }, _ => Out::NotFound("Route not found") - }; - - Next::write() + } } /// Attempt to read Content ID from `arg` query parameter, get a hash and @@ -94,14 +70,14 @@ impl IpfsHandler { /// Get block header by hash as raw binary. fn block(&self, hash: H256) -> Result { let block_id = BlockId::Hash(hash); - let block = self.client.block_header(block_id).ok_or(Error::BlockNotFound)?; + let block = self.client().block_header(block_id).ok_or(Error::BlockNotFound)?; Ok(Out::OctetStream(block.into_inner())) } /// Get list of block ommers by hash as raw binary. fn block_list(&self, hash: H256) -> Result { - let uncles = self.client.find_uncles(&hash).ok_or(Error::BlockNotFound)?; + let uncles = self.client().find_uncles(&hash).ok_or(Error::BlockNotFound)?; Ok(Out::OctetStream(rlp::encode(&uncles).to_vec())) } @@ -109,21 +85,21 @@ impl IpfsHandler { /// Get transaction by hash and return as raw binary. fn transaction(&self, hash: H256) -> Result { let tx_id = TransactionId::Hash(hash); - let tx = self.client.transaction(tx_id).ok_or(Error::TransactionNotFound)?; + let tx = self.client().transaction(tx_id).ok_or(Error::TransactionNotFound)?; Ok(Out::OctetStream(rlp::encode(&*tx).to_vec())) } /// Get state trie node by hash and return as raw binary. fn state_trie(&self, hash: H256) -> Result { - let data = self.client.state_data(&hash).ok_or(Error::StateRootNotFound)?; + let data = self.client().state_data(&hash).ok_or(Error::StateRootNotFound)?; Ok(Out::OctetStream(data)) } /// Get state trie node by hash and return as raw binary. fn contract_code(&self, hash: H256) -> Result { - let data = self.client.state_data(&hash).ok_or(Error::ContractNotFound)?; + let data = self.client().state_data(&hash).ok_or(Error::ContractNotFound)?; Ok(Out::OctetStream(data)) } @@ -138,11 +114,12 @@ fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> { #[cfg(test)] mod tests { + use std::sync::Arc; use super::*; use ethcore::client::TestBlockChainClient; fn get_mocked_handler() -> IpfsHandler { - IpfsHandler::new(Arc::new(TestBlockChainClient::new())) + IpfsHandler::new(None, None, Arc::new(TestBlockChainClient::new())) } #[test] @@ -232,37 +209,37 @@ mod tests { #[test] fn route_block() { - let mut handler = get_mocked_handler(); + let handler = get_mocked_handler(); - let _ = handler.route("/api/v0/block/get", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); + let out = handler.route("/api/v0/block/get", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); - assert_eq!(handler.out, Out::NotFound("Block not found")); + assert_eq!(out, Out::NotFound("Block not found")); } #[test] fn route_block_missing_query() { - let mut handler = get_mocked_handler(); + let handler = get_mocked_handler(); - let _ = handler.route("/api/v0/block/get", None); + let out = handler.route("/api/v0/block/get", None); - assert_eq!(handler.out, Out::Bad("CID parsing failed")); + assert_eq!(out, Out::Bad("CID parsing failed")); } #[test] fn route_block_invalid_query() { - let mut handler = get_mocked_handler(); + let handler = get_mocked_handler(); - let _ = handler.route("/api/v0/block/get", Some("arg=foobarz43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); + let out = handler.route("/api/v0/block/get", Some("arg=foobarz43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); - assert_eq!(handler.out, Out::Bad("CID parsing failed")); + assert_eq!(out, Out::Bad("CID parsing failed")); } #[test] fn route_invalid_route() { - let mut handler = get_mocked_handler(); + let handler = get_mocked_handler(); - let _ = handler.route("/foo/bar/baz", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); + let out = handler.route("/foo/bar/baz", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM")); - assert_eq!(handler.out, Out::NotFound("Route not found")); + assert_eq!(out, Out::NotFound("Route not found")); } } diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index a8b3c4fc1..60670750b 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -76,6 +76,9 @@ path = "$HOME/.parity/secretstore" [ipfs] enable = false port = 5001 +interface = "local" +cors = "null" +hosts = ["none"] [mining] author = "0xdeadbeefcafe0000000000000000000000000001" diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index f105c9c12..0c057ff30 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -96,7 +96,7 @@ usage! { // -- Account Options flag_unlock: Option = None, - or |c: &Config| otry!(c.account).unlock.clone().map(|vec| Some(vec.join(","))), + or |c: &Config| otry!(c.account).unlock.as_ref().map(|vec| Some(vec.join(","))), flag_password: Vec = Vec::new(), or |c: &Config| otry!(c.account).password.clone(), flag_keys_iterations: u32 = 10240u32, @@ -138,7 +138,7 @@ usage! { flag_network_id: Option = None, or |c: &Config| otry!(c.network).id.clone().map(Some), flag_bootnodes: Option = None, - or |c: &Config| otry!(c.network).bootnodes.clone().map(|vec| Some(vec.join(","))), + or |c: &Config| otry!(c.network).bootnodes.as_ref().map(|vec| Some(vec.join(","))), flag_no_discovery: bool = false, or |c: &Config| otry!(c.network).discovery.map(|d| !d).clone(), flag_node_key: Option = None, @@ -160,9 +160,9 @@ usage! { flag_jsonrpc_cors: Option = None, or |c: &Config| otry!(c.rpc).cors.clone().map(Some), flag_jsonrpc_apis: String = "web3,eth,net,parity,traces,rpc", - or |c: &Config| otry!(c.rpc).apis.clone().map(|vec| vec.join(",")), + or |c: &Config| otry!(c.rpc).apis.as_ref().map(|vec| vec.join(",")), flag_jsonrpc_hosts: String = "none", - or |c: &Config| otry!(c.rpc).hosts.clone().map(|vec| vec.join(",")), + or |c: &Config| otry!(c.rpc).hosts.as_ref().map(|vec| vec.join(",")), // IPC flag_no_ipc: bool = false, @@ -170,7 +170,7 @@ usage! { flag_ipc_path: String = "$BASE/jsonrpc.ipc", or |c: &Config| otry!(c.ipc).path.clone(), flag_ipc_apis: String = "web3,eth,net,parity,parity_accounts,traces,rpc", - or |c: &Config| otry!(c.ipc).apis.clone().map(|vec| vec.join(",")), + or |c: &Config| otry!(c.ipc).apis.as_ref().map(|vec| vec.join(",")), // DAPPS flag_no_dapps: bool = false, @@ -180,7 +180,7 @@ usage! { flag_dapps_interface: String = "local", or |c: &Config| otry!(c.dapps).interface.clone(), flag_dapps_hosts: String = "none", - or |c: &Config| otry!(c.dapps).hosts.clone().map(|vec| vec.join(",")), + or |c: &Config| otry!(c.dapps).hosts.as_ref().map(|vec| vec.join(",")), flag_dapps_path: String = "$BASE/dapps", or |c: &Config| otry!(c.dapps).path.clone(), flag_dapps_user: Option = None, @@ -204,6 +204,12 @@ usage! { or |c: &Config| otry!(c.ipfs).enable.clone(), flag_ipfs_api_port: u16 = 5001u16, or |c: &Config| otry!(c.ipfs).port.clone(), + flag_ipfs_api_interface: String = "local", + or |c: &Config| otry!(c.ipfs).interface.clone(), + flag_ipfs_api_cors: Option = None, + or |c: &Config| otry!(c.ipfs).cors.clone().map(Some), + flag_ipfs_api_hosts: String = "none", + or |c: &Config| otry!(c.ipfs).hosts.as_ref().map(|vec| vec.join(",")), // -- Sealing/Mining Options flag_author: Option = None, @@ -249,7 +255,7 @@ usage! { flag_remove_solved: bool = false, or |c: &Config| otry!(c.mining).remove_solved.clone(), flag_notify_work: Option = None, - or |c: &Config| otry!(c.mining).notify_work.clone().map(|vec| Some(vec.join(","))), + or |c: &Config| otry!(c.mining).notify_work.as_ref().map(|vec| Some(vec.join(","))), flag_refuse_service_transactions: bool = false, or |c: &Config| otry!(c.mining).refuse_service_transactions.clone(), @@ -439,6 +445,9 @@ struct SecretStore { struct Ipfs { enable: Option, port: Option, + interface: Option, + cors: Option, + hosts: Option>, } #[derive(Default, Debug, PartialEq, RustcDecodable)] @@ -678,6 +687,9 @@ mod tests { // IPFS flag_ipfs_api: false, flag_ipfs_api_port: 5001u16, + flag_ipfs_api_interface: "local".into(), + flag_ipfs_api_cors: Some("null".into()), + flag_ipfs_api_hosts: "none".into(), // -- Sealing/Mining Options flag_author: Some("0xdeadbeefcafe0000000000000000000000000001".into()), @@ -872,7 +884,10 @@ mod tests { }), ipfs: Some(Ipfs { enable: Some(false), - port: Some(5001) + port: Some(5001), + interface: None, + cors: None, + hosts: None, }), mining: Some(Mining { author: Some("0xdeadbeefcafe0000000000000000000000000001".into()), diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 430154752..dee6dd788 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -178,6 +178,17 @@ API and Console Options: --ipfs-api Enable IPFS-compatible HTTP API. (default: {flag_ipfs_api}) --ipfs-api-port PORT Configure on which port the IPFS HTTP API should listen. (default: {flag_ipfs_api_port}) + --ipfs-api-interface IP Specify the hostname portion of the IPFS API server, + IP should be an interface's IP address or local. + (default: {flag_ipfs_api_interface}) + --ipfs-api-cors URL Specify CORS header for IPFS API responses. + (default: {flag_ipfs_api_cors:?}) + --ipfs-api-hosts HOSTS List of allowed Host header values. This option will + validate the Host header sent by the browser, it + is additional security against some attack + vectors. Special options: "all", "none" + (default: {flag_ipfs_api_hosts}). + Secret Store Options: --no-secretstore Disable Secret Store functionality. (default: {flag_no_secretstore}) diff --git a/parity/configuration.rs b/parity/configuration.rs index dd1b8546c..b7ccddc73 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -558,6 +558,9 @@ impl Configuration { IpfsConfiguration { enabled: self.args.flag_ipfs_api, port: self.args.flag_ipfs_api_port, + interface: self.ipfs_interface(), + cors: self.ipfs_cors(), + hosts: self.ipfs_hosts(), } } @@ -693,29 +696,39 @@ impl Configuration { apis } + fn cors(cors: Option<&String>) -> Option> { + cors.map(|ref c| c.split(',').map(Into::into).collect()) + } + fn rpc_cors(&self) -> Option> { - let cors = self.args.flag_jsonrpc_cors.clone().or(self.args.flag_rpccorsdomain.clone()); - cors.map(|c| c.split(',').map(|s| s.to_owned()).collect()) + let cors = self.args.flag_jsonrpc_cors.as_ref().or(self.args.flag_rpccorsdomain.as_ref()); + Self::cors(cors) + } + + fn ipfs_cors(&self) -> Option> { + Self::cors(self.args.flag_ipfs_api_cors.as_ref()) + } + + fn hosts(hosts: &str) -> Option> { + match hosts { + "none" => return Some(Vec::new()), + "all" => return None, + _ => {} + } + let hosts = hosts.split(',').map(Into::into).collect(); + Some(hosts) } fn rpc_hosts(&self) -> Option> { - match self.args.flag_jsonrpc_hosts.as_ref() { - "none" => return Some(Vec::new()), - "all" => return None, - _ => {} - } - let hosts = self.args.flag_jsonrpc_hosts.split(',').map(|h| h.into()).collect(); - Some(hosts) + Self::hosts(&self.args.flag_jsonrpc_hosts) } fn dapps_hosts(&self) -> Option> { - match self.args.flag_dapps_hosts.as_ref() { - "none" => return Some(Vec::new()), - "all" => return None, - _ => {} - } - let hosts = self.args.flag_dapps_hosts.split(',').map(|h| h.into()).collect(); - Some(hosts) + Self::hosts(&self.args.flag_dapps_hosts) + } + + fn ipfs_hosts(&self) -> Option> { + Self::hosts(&self.args.flag_ipfs_api_hosts) } fn ipc_config(&self) -> Result { @@ -850,14 +863,18 @@ impl Configuration { }.into() } - fn rpc_interface(&self) -> String { - match self.network_settings().rpc_interface.as_str() { + fn interface(interface: &str) -> String { + match interface { "all" => "0.0.0.0", "local" => "127.0.0.1", x => x, }.into() } + fn rpc_interface(&self) -> String { + Self::interface(&self.network_settings().rpc_interface) + } + fn dapps_interface(&self) -> String { match self.args.flag_dapps_interface.as_str() { "local" => "127.0.0.1", @@ -865,6 +882,10 @@ impl Configuration { }.into() } + fn ipfs_interface(&self) -> String { + Self::interface(&self.args.flag_ipfs_api_interface) + } + fn secretstore_interface(&self) -> String { match self.args.flag_secretstore_interface.as_str() { "local" => "127.0.0.1", @@ -873,11 +894,7 @@ impl Configuration { } fn stratum_interface(&self) -> String { - match self.args.flag_stratum_interface.as_str() { - "local" => "127.0.0.1", - "all" => "0.0.0.0", - x => x, - }.into() + Self::interface(&self.args.flag_stratum_interface) } fn dapps_enabled(&self) -> bool { @@ -1273,6 +1290,38 @@ mod tests { assert_eq!(conf3.dapps_hosts(), Some(vec!["ethcore.io".into(), "something.io".into()])); } + #[test] + fn should_parse_ipfs_hosts() { + // given + + // when + let conf0 = parse(&["parity"]); + let conf1 = parse(&["parity", "--ipfs-api-hosts", "none"]); + let conf2 = parse(&["parity", "--ipfs-api-hosts", "all"]); + let conf3 = parse(&["parity", "--ipfs-api-hosts", "ethcore.io,something.io"]); + + // then + assert_eq!(conf0.ipfs_hosts(), Some(Vec::new())); + assert_eq!(conf1.ipfs_hosts(), Some(Vec::new())); + assert_eq!(conf2.ipfs_hosts(), None); + assert_eq!(conf3.ipfs_hosts(), Some(vec!["ethcore.io".into(), "something.io".into()])); + } + + #[test] + fn should_parse_ipfs_cors() { + // given + + // when + let conf0 = parse(&["parity"]); + let conf1 = parse(&["parity", "--ipfs-api-cors", "*"]); + let conf2 = parse(&["parity", "--ipfs-api-cors", "http://ethcore.io,http://something.io"]); + + // then + assert_eq!(conf0.ipfs_cors(), None); + assert_eq!(conf1.ipfs_cors(), Some(vec!["*".into()])); + assert_eq!(conf2.ipfs_cors(), Some(vec!["http://ethcore.io".into(),"http://something.io".into()])); + } + #[test] fn should_disable_signer_in_geth_compat() { // given diff --git a/parity/ipfs.rs b/parity/ipfs.rs index c68ace3c1..e33dcf68b 100644 --- a/parity/ipfs.rs +++ b/parity/ipfs.rs @@ -1,9 +1,16 @@ -pub use parity_ipfs_api::start_server; +use std::sync::Arc; +use parity_ipfs_api; +use parity_ipfs_api::error::ServerError; +use ethcore::client::BlockChainClient; +use hyper::server::Listening; #[derive(Debug, PartialEq, Clone)] pub struct Configuration { pub enabled: bool, pub port: u16, + pub interface: String, + pub cors: Option>, + pub hosts: Option>, } impl Default for Configuration { @@ -11,6 +18,23 @@ impl Default for Configuration { Configuration { enabled: false, port: 5001, + interface: "127.0.0.1".into(), + cors: None, + hosts: Some(Vec::new()), } } } + +pub fn start_server(conf: Configuration, client: Arc) -> Result, ServerError> { + if !conf.enabled { + return Ok(None); + } + + parity_ipfs_api::start_server( + conf.port, + conf.interface, + conf.cors, + conf.hosts, + client + ).map(Some) +} diff --git a/parity/run.rs b/parity/run.rs index d10aa10e1..b25ed3188 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -471,10 +471,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R let secretstore_key_server = secretstore::start(cmd.secretstore_conf.clone(), secretstore_deps); // the ipfs server - let ipfs_server = match cmd.ipfs_conf.enabled { - true => Some(ipfs::start_server(cmd.ipfs_conf.port, client.clone())?), - false => None, - }; + let ipfs_server = ipfs::start_server(cmd.ipfs_conf.clone(), client.clone())?; // the informant let informant = Arc::new(Informant::new(