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
This commit is contained in:
Maciej Hirsz 2017-02-24 10:32:42 +01:00 committed by Gav Wood
parent 9b5bcb81fd
commit f97e775498
11 changed files with 285 additions and 103 deletions

1
Cargo.lock generated
View File

@ -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",

View File

@ -8,6 +8,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
[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" }

View File

@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use {multihash, cid, hyper};
use handler::Out;
use route::Out;
pub type Result<T> = ::std::result::Result<T, Error>;
@ -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<ServerError> for String {
match err {
ServerError::IoError(err) => err.to_string(),
ServerError::Other(err) => err.to_string(),
ServerError::InvalidInterface => "Invalid --ipfs-api-interface parameter".into(),
}
}
}

View File

@ -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<String>,
/// Allowed CORS domains
cors_domains: Option<Vec<AccessControlAllowOrigin>>,
/// Hostnames allowed in the `Host` request header
allowed_hosts: Option<Vec<String>>,
/// Reference to the Blockchain Client
client: Arc<BlockChainClient>,
}
impl IpfsHandler {
pub fn client(&self) -> &BlockChainClient {
&*self.client
}
pub fn new(cors: Option<Vec<String>>, hosts: Option<Vec<String>>, client: Arc<BlockChainClient>) -> 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<HttpStream>) -> 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<HttpStream> for IpfsHandler {
fn on_request(&mut self, req: Request<HttpStream>) -> Next {
@ -45,9 +115,16 @@ impl Handler<HttpStream> for IpfsHandler {
return Next::write();
}
// Reject requests if the Origin header isn't valid
if req.headers().get::<Origin>().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<HttpStream> 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<HttpStream>) -> Next {
@ -82,26 +161,28 @@ impl Handler<HttpStream> 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)));
}
}
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<HttpStream>) -> Next {
use Out::*;
@ -116,11 +197,12 @@ impl Handler<HttpStream> for IpfsHandler {
}
}
/// Attempt to write entire `data` from current `progress`
fn write_chunk<W: Write>(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<W: Write>(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<W: Write>(transport: &mut W, progress: &mut usize, data: &[u8]) -
}
}
pub fn start_server(port: u16, client: Arc<BlockChainClient>) -> Result<Listening, ServerError> {
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<String>, interface: String, port: u16) -> Vec<String> {
hosts.push(match port {
80 => interface,
_ => format!("{}:{}", interface, port),
});
hosts
}
pub fn start_server(
port: u16,
interface: String,
cors: Option<Vec<String>>,
hosts: Option<Vec<String>>,
client: Arc<BlockChainClient>
) -> Result<Listening, ServerError> {
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 || {

View File

@ -14,15 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
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<BlockChainClient>,
/// 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<BlockChainClient>) -> 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<Out> {
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<Out> {
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<Out> {
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<Out> {
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<Out> {
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"));
}
}

View File

@ -76,6 +76,9 @@ path = "$HOME/.parity/secretstore"
[ipfs]
enable = false
port = 5001
interface = "local"
cors = "null"
hosts = ["none"]
[mining]
author = "0xdeadbeefcafe0000000000000000000000000001"

View File

@ -96,7 +96,7 @@ usage! {
// -- Account Options
flag_unlock: Option<String> = 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<String> = Vec::new(),
or |c: &Config| otry!(c.account).password.clone(),
flag_keys_iterations: u32 = 10240u32,
@ -138,7 +138,7 @@ usage! {
flag_network_id: Option<u64> = None,
or |c: &Config| otry!(c.network).id.clone().map(Some),
flag_bootnodes: Option<String> = 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<String> = None,
@ -160,9 +160,9 @@ usage! {
flag_jsonrpc_cors: Option<String> = 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<String> = 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<String> = 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<String> = None,
@ -249,7 +255,7 @@ usage! {
flag_remove_solved: bool = false,
or |c: &Config| otry!(c.mining).remove_solved.clone(),
flag_notify_work: Option<String> = 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<bool>,
port: Option<u16>,
interface: Option<String>,
cors: Option<String>,
hosts: Option<Vec<String>>,
}
#[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()),

View File

@ -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})

View File

@ -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<Vec<String>> {
cors.map(|ref c| c.split(',').map(Into::into).collect())
}
fn rpc_cors(&self) -> Option<Vec<String>> {
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<Vec<String>> {
Self::cors(self.args.flag_ipfs_api_cors.as_ref())
}
fn hosts(hosts: &str) -> Option<Vec<String>> {
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<Vec<String>> {
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<Vec<String>> {
match self.args.flag_dapps_hosts.as_ref() {
"none" => return Some(Vec::new()),
"all" => return None,
_ => {}
Self::hosts(&self.args.flag_dapps_hosts)
}
let hosts = self.args.flag_dapps_hosts.split(',').map(|h| h.into()).collect();
Some(hosts)
fn ipfs_hosts(&self) -> Option<Vec<String>> {
Self::hosts(&self.args.flag_ipfs_api_hosts)
}
fn ipc_config(&self) -> Result<IpcConfiguration, String> {
@ -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

View File

@ -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<Vec<String>>,
pub hosts: Option<Vec<String>>,
}
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<BlockChainClient>) -> Result<Option<Listening>, ServerError> {
if !conf.enabled {
return Ok(None);
}
parity_ipfs_api::start_server(
conf.port,
conf.interface,
conf.cors,
conf.hosts,
client
).map(Some)
}

View File

@ -471,10 +471,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> 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(