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 1.6.0",
"ethcore-util 1.6.0", "ethcore-util 1.6.0",
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "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)", "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)", "multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0", "rlp 0.1.0",

View File

@ -8,6 +8,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
[dependencies] [dependencies]
ethcore = { path = "../ethcore" } ethcore = { path = "../ethcore" }
ethcore-util = { path = "../util" } ethcore-util = { path = "../util" }
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" }
rlp = { path = "../util/rlp" } rlp = { path = "../util/rlp" }
mime = "0.2" mime = "0.2"
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } 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/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use {multihash, cid, hyper}; use {multihash, cid, hyper};
use handler::Out; use route::Out;
pub type Result<T> = ::std::result::Result<T, Error>; pub type Result<T> = ::std::result::Result<T, Error>;
@ -26,6 +26,8 @@ pub enum ServerError {
IoError(::std::io::Error), IoError(::std::io::Error),
/// Other `hyper` error /// Other `hyper` error
Other(hyper::error::Error), Other(hyper::error::Error),
/// Invalid --ipfs-api-interface
InvalidInterface
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -89,6 +91,7 @@ impl From<ServerError> for String {
match err { match err {
ServerError::IoError(err) => err.to_string(), ServerError::IoError(err) => err.to_string(),
ServerError::Other(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 rlp;
extern crate ethcore; extern crate ethcore;
extern crate ethcore_util as util; extern crate ethcore_util as util;
extern crate jsonrpc_http_server;
mod error; pub mod error;
mod handler; mod route;
use std::io::Write; use std::io::Write;
use std::sync::Arc; use std::sync::Arc;
use std::net::{SocketAddr, IpAddr, Ipv4Addr}; use std::net::{SocketAddr, IpAddr};
use error::ServerError; use error::ServerError;
use handler::{IpfsHandler, Out}; use route::Out;
use jsonrpc_http_server::cors;
use hyper::server::{Listening, Handler, Request, Response}; use hyper::server::{Listening, Handler, Request, Response};
use hyper::net::HttpStream; 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 hyper::{Next, Encoder, Decoder, Method, RequestUri, StatusCode};
use ethcore::client::BlockChainClient; 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 /// Implement Hyper's HTTP handler
impl Handler<HttpStream> for IpfsHandler { impl Handler<HttpStream> for IpfsHandler {
fn on_request(&mut self, req: Request<HttpStream>) -> Next { fn on_request(&mut self, req: Request<HttpStream>) -> Next {
@ -45,9 +115,16 @@ impl Handler<HttpStream> for IpfsHandler {
return Next::write(); return Next::write();
} }
// Reject requests if the Origin header isn't valid self.origin = cors::read_origin(&req);
if req.headers().get::<Origin>().map(|o| "127.0.0.1" != &o.host.hostname).unwrap_or(false) {
self.out = Out::Bad("Illegal Origin"); 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(); return Next::write();
} }
@ -57,7 +134,9 @@ impl Handler<HttpStream> for IpfsHandler {
_ => return Next::write(), _ => 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 { fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
@ -82,25 +161,27 @@ impl Handler<HttpStream> for IpfsHandler {
res.headers_mut().set(ContentLength(bytes.len() as u64)); res.headers_mut().set(ContentLength(bytes.len() as u64));
res.headers_mut().set(ContentType(content_type)); res.headers_mut().set(ContentType(content_type));
Next::write()
}, },
NotFound(reason) => { NotFound(reason) => {
res.set_status(StatusCode::NotFound); res.set_status(StatusCode::NotFound);
res.headers_mut().set(ContentLength(reason.len() as u64)); res.headers_mut().set(ContentLength(reason.len() as u64));
res.headers_mut().set(ContentType(mime!(Text/Plain))); res.headers_mut().set(ContentType(mime!(Text/Plain)));
Next::write()
}, },
Bad(reason) => { Bad(reason) => {
res.set_status(StatusCode::BadRequest); res.set_status(StatusCode::BadRequest);
res.headers_mut().set(ContentLength(reason.len() as u64)); res.headers_mut().set(ContentLength(reason.len() as u64));
res.headers_mut().set(ContentType(mime!(Text/Plain))); 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<HttpStream>) -> Next { fn on_response_writable(&mut self, transport: &mut Encoder<HttpStream>) -> Next {
@ -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 { fn write_chunk<W: Write>(transport: &mut W, progress: &mut usize, data: &[u8]) -> Next {
// Skip any bytes that have already been written // Skip any bytes that have already been written
let chunk = &data[*progress..]; 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) { let written = match transport.write(chunk) {
Ok(written) => written, Ok(written) => written,
Err(_) => return Next::end(), Err(_) => return Next::end(),
@ -128,7 +210,7 @@ fn write_chunk<W: Write>(transport: &mut W, progress: &mut usize, data: &[u8]) -
*progress += written; *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() { if written < chunk.len() {
Next::write() Next::write()
} else { } 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> { /// Add current interface (default: "127.0.0.1:5001") to list of allowed hosts
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port); 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( Ok(
hyper::Server::http(&addr)? hyper::Server::http(&addr)?
.handle(move |_| IpfsHandler::new(client.clone())) .handle(move |_| IpfsHandler::new(cors.clone(), hosts.clone(), client.clone()))
.map(|(listening, srv)| { .map(|(listening, srv)| {
::std::thread::spawn(move || { ::std::thread::spawn(move || {

View File

@ -14,15 +14,13 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use {rlp, multihash}; use {rlp, multihash, IpfsHandler};
use error::{Error, Result}; use error::{Error, Result};
use cid::{ToCid, Codec}; use cid::{ToCid, Codec};
use std::sync::Arc;
use multihash::Hash; use multihash::Hash;
use hyper::Next;
use util::{Bytes, H256}; use util::{Bytes, H256};
use ethcore::client::{BlockId, TransactionId, BlockChainClient}; use ethcore::client::{BlockId, TransactionId};
type Reason = &'static str; type Reason = &'static str;
@ -34,30 +32,10 @@ pub enum Out {
Bad(Reason), 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 { 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 /// Route path + query string to a specialized method
pub fn route(&mut self, path: &str, query: Option<&str>) -> Next { pub fn route(&self, path: &str, query: Option<&str>) -> Out {
self.out = match path { match path {
"/api/v0/block/get" => { "/api/v0/block/get" => {
let arg = query.and_then(|q| get_param(q, "arg")).unwrap_or(""); let arg = query.and_then(|q| get_param(q, "arg")).unwrap_or("");
@ -65,9 +43,7 @@ impl IpfsHandler {
}, },
_ => Out::NotFound("Route not found") _ => Out::NotFound("Route not found")
}; }
Next::write()
} }
/// Attempt to read Content ID from `arg` query parameter, get a hash and /// 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. /// Get block header by hash as raw binary.
fn block(&self, hash: H256) -> Result<Out> { fn block(&self, hash: H256) -> Result<Out> {
let block_id = BlockId::Hash(hash); 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())) Ok(Out::OctetStream(block.into_inner()))
} }
/// Get list of block ommers by hash as raw binary. /// Get list of block ommers by hash as raw binary.
fn block_list(&self, hash: H256) -> Result<Out> { 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())) Ok(Out::OctetStream(rlp::encode(&uncles).to_vec()))
} }
@ -109,21 +85,21 @@ impl IpfsHandler {
/// Get transaction by hash and return as raw binary. /// Get transaction by hash and return as raw binary.
fn transaction(&self, hash: H256) -> Result<Out> { fn transaction(&self, hash: H256) -> Result<Out> {
let tx_id = TransactionId::Hash(hash); 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())) Ok(Out::OctetStream(rlp::encode(&*tx).to_vec()))
} }
/// Get state trie node by hash and return as raw binary. /// Get state trie node by hash and return as raw binary.
fn state_trie(&self, hash: H256) -> Result<Out> { 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)) Ok(Out::OctetStream(data))
} }
/// Get state trie node by hash and return as raw binary. /// Get state trie node by hash and return as raw binary.
fn contract_code(&self, hash: H256) -> Result<Out> { 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)) Ok(Out::OctetStream(data))
} }
@ -138,11 +114,12 @@ fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Arc;
use super::*; use super::*;
use ethcore::client::TestBlockChainClient; use ethcore::client::TestBlockChainClient;
fn get_mocked_handler() -> IpfsHandler { fn get_mocked_handler() -> IpfsHandler {
IpfsHandler::new(Arc::new(TestBlockChainClient::new())) IpfsHandler::new(None, None, Arc::new(TestBlockChainClient::new()))
} }
#[test] #[test]
@ -232,37 +209,37 @@ mod tests {
#[test] #[test]
fn route_block() { 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] #[test]
fn route_block_missing_query() { 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] #[test]
fn route_block_invalid_query() { 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] #[test]
fn route_invalid_route() { 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] [ipfs]
enable = false enable = false
port = 5001 port = 5001
interface = "local"
cors = "null"
hosts = ["none"]
[mining] [mining]
author = "0xdeadbeefcafe0000000000000000000000000001" author = "0xdeadbeefcafe0000000000000000000000000001"

View File

@ -96,7 +96,7 @@ usage! {
// -- Account Options // -- Account Options
flag_unlock: Option<String> = None, 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(), flag_password: Vec<String> = Vec::new(),
or |c: &Config| otry!(c.account).password.clone(), or |c: &Config| otry!(c.account).password.clone(),
flag_keys_iterations: u32 = 10240u32, flag_keys_iterations: u32 = 10240u32,
@ -138,7 +138,7 @@ usage! {
flag_network_id: Option<u64> = None, flag_network_id: Option<u64> = None,
or |c: &Config| otry!(c.network).id.clone().map(Some), or |c: &Config| otry!(c.network).id.clone().map(Some),
flag_bootnodes: Option<String> = None, 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, flag_no_discovery: bool = false,
or |c: &Config| otry!(c.network).discovery.map(|d| !d).clone(), or |c: &Config| otry!(c.network).discovery.map(|d| !d).clone(),
flag_node_key: Option<String> = None, flag_node_key: Option<String> = None,
@ -160,9 +160,9 @@ usage! {
flag_jsonrpc_cors: Option<String> = None, flag_jsonrpc_cors: Option<String> = None,
or |c: &Config| otry!(c.rpc).cors.clone().map(Some), or |c: &Config| otry!(c.rpc).cors.clone().map(Some),
flag_jsonrpc_apis: String = "web3,eth,net,parity,traces,rpc", 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", 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 // IPC
flag_no_ipc: bool = false, flag_no_ipc: bool = false,
@ -170,7 +170,7 @@ usage! {
flag_ipc_path: String = "$BASE/jsonrpc.ipc", flag_ipc_path: String = "$BASE/jsonrpc.ipc",
or |c: &Config| otry!(c.ipc).path.clone(), or |c: &Config| otry!(c.ipc).path.clone(),
flag_ipc_apis: String = "web3,eth,net,parity,parity_accounts,traces,rpc", 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 // DAPPS
flag_no_dapps: bool = false, flag_no_dapps: bool = false,
@ -180,7 +180,7 @@ usage! {
flag_dapps_interface: String = "local", flag_dapps_interface: String = "local",
or |c: &Config| otry!(c.dapps).interface.clone(), or |c: &Config| otry!(c.dapps).interface.clone(),
flag_dapps_hosts: String = "none", 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", flag_dapps_path: String = "$BASE/dapps",
or |c: &Config| otry!(c.dapps).path.clone(), or |c: &Config| otry!(c.dapps).path.clone(),
flag_dapps_user: Option<String> = None, flag_dapps_user: Option<String> = None,
@ -204,6 +204,12 @@ usage! {
or |c: &Config| otry!(c.ipfs).enable.clone(), or |c: &Config| otry!(c.ipfs).enable.clone(),
flag_ipfs_api_port: u16 = 5001u16, flag_ipfs_api_port: u16 = 5001u16,
or |c: &Config| otry!(c.ipfs).port.clone(), 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 // -- Sealing/Mining Options
flag_author: Option<String> = None, flag_author: Option<String> = None,
@ -249,7 +255,7 @@ usage! {
flag_remove_solved: bool = false, flag_remove_solved: bool = false,
or |c: &Config| otry!(c.mining).remove_solved.clone(), or |c: &Config| otry!(c.mining).remove_solved.clone(),
flag_notify_work: Option<String> = None, 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, flag_refuse_service_transactions: bool = false,
or |c: &Config| otry!(c.mining).refuse_service_transactions.clone(), or |c: &Config| otry!(c.mining).refuse_service_transactions.clone(),
@ -439,6 +445,9 @@ struct SecretStore {
struct Ipfs { struct Ipfs {
enable: Option<bool>, enable: Option<bool>,
port: Option<u16>, port: Option<u16>,
interface: Option<String>,
cors: Option<String>,
hosts: Option<Vec<String>>,
} }
#[derive(Default, Debug, PartialEq, RustcDecodable)] #[derive(Default, Debug, PartialEq, RustcDecodable)]
@ -678,6 +687,9 @@ mod tests {
// IPFS // IPFS
flag_ipfs_api: false, flag_ipfs_api: false,
flag_ipfs_api_port: 5001u16, 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 // -- Sealing/Mining Options
flag_author: Some("0xdeadbeefcafe0000000000000000000000000001".into()), flag_author: Some("0xdeadbeefcafe0000000000000000000000000001".into()),
@ -872,7 +884,10 @@ mod tests {
}), }),
ipfs: Some(Ipfs { ipfs: Some(Ipfs {
enable: Some(false), enable: Some(false),
port: Some(5001) port: Some(5001),
interface: None,
cors: None,
hosts: None,
}), }),
mining: Some(Mining { mining: Some(Mining {
author: Some("0xdeadbeefcafe0000000000000000000000000001".into()), 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 Enable IPFS-compatible HTTP API. (default: {flag_ipfs_api})
--ipfs-api-port PORT Configure on which port the IPFS HTTP API should listen. --ipfs-api-port PORT Configure on which port the IPFS HTTP API should listen.
(default: {flag_ipfs_api_port}) (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: Secret Store Options:
--no-secretstore Disable Secret Store functionality. (default: {flag_no_secretstore}) --no-secretstore Disable Secret Store functionality. (default: {flag_no_secretstore})

View File

@ -558,6 +558,9 @@ impl Configuration {
IpfsConfiguration { IpfsConfiguration {
enabled: self.args.flag_ipfs_api, enabled: self.args.flag_ipfs_api,
port: self.args.flag_ipfs_api_port, 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 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>> { fn rpc_cors(&self) -> Option<Vec<String>> {
let cors = self.args.flag_jsonrpc_cors.clone().or(self.args.flag_rpccorsdomain.clone()); let cors = self.args.flag_jsonrpc_cors.as_ref().or(self.args.flag_rpccorsdomain.as_ref());
cors.map(|c| c.split(',').map(|s| s.to_owned()).collect()) 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>> { fn rpc_hosts(&self) -> Option<Vec<String>> {
match self.args.flag_jsonrpc_hosts.as_ref() { Self::hosts(&self.args.flag_jsonrpc_hosts)
"none" => return Some(Vec::new()),
"all" => return None,
_ => {}
}
let hosts = self.args.flag_jsonrpc_hosts.split(',').map(|h| h.into()).collect();
Some(hosts)
} }
fn dapps_hosts(&self) -> Option<Vec<String>> { fn dapps_hosts(&self) -> Option<Vec<String>> {
match self.args.flag_dapps_hosts.as_ref() { Self::hosts(&self.args.flag_dapps_hosts)
"none" => return Some(Vec::new()), }
"all" => return None,
_ => {} fn ipfs_hosts(&self) -> Option<Vec<String>> {
} Self::hosts(&self.args.flag_ipfs_api_hosts)
let hosts = self.args.flag_dapps_hosts.split(',').map(|h| h.into()).collect();
Some(hosts)
} }
fn ipc_config(&self) -> Result<IpcConfiguration, String> { fn ipc_config(&self) -> Result<IpcConfiguration, String> {
@ -850,14 +863,18 @@ impl Configuration {
}.into() }.into()
} }
fn rpc_interface(&self) -> String { fn interface(interface: &str) -> String {
match self.network_settings().rpc_interface.as_str() { match interface {
"all" => "0.0.0.0", "all" => "0.0.0.0",
"local" => "127.0.0.1", "local" => "127.0.0.1",
x => x, x => x,
}.into() }.into()
} }
fn rpc_interface(&self) -> String {
Self::interface(&self.network_settings().rpc_interface)
}
fn dapps_interface(&self) -> String { fn dapps_interface(&self) -> String {
match self.args.flag_dapps_interface.as_str() { match self.args.flag_dapps_interface.as_str() {
"local" => "127.0.0.1", "local" => "127.0.0.1",
@ -865,6 +882,10 @@ impl Configuration {
}.into() }.into()
} }
fn ipfs_interface(&self) -> String {
Self::interface(&self.args.flag_ipfs_api_interface)
}
fn secretstore_interface(&self) -> String { fn secretstore_interface(&self) -> String {
match self.args.flag_secretstore_interface.as_str() { match self.args.flag_secretstore_interface.as_str() {
"local" => "127.0.0.1", "local" => "127.0.0.1",
@ -873,11 +894,7 @@ impl Configuration {
} }
fn stratum_interface(&self) -> String { fn stratum_interface(&self) -> String {
match self.args.flag_stratum_interface.as_str() { Self::interface(&self.args.flag_stratum_interface)
"local" => "127.0.0.1",
"all" => "0.0.0.0",
x => x,
}.into()
} }
fn dapps_enabled(&self) -> bool { fn dapps_enabled(&self) -> bool {
@ -1273,6 +1290,38 @@ mod tests {
assert_eq!(conf3.dapps_hosts(), Some(vec!["ethcore.io".into(), "something.io".into()])); 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] #[test]
fn should_disable_signer_in_geth_compat() { fn should_disable_signer_in_geth_compat() {
// given // 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)] #[derive(Debug, PartialEq, Clone)]
pub struct Configuration { pub struct Configuration {
pub enabled: bool, pub enabled: bool,
pub port: u16, pub port: u16,
pub interface: String,
pub cors: Option<Vec<String>>,
pub hosts: Option<Vec<String>>,
} }
impl Default for Configuration { impl Default for Configuration {
@ -11,6 +18,23 @@ impl Default for Configuration {
Configuration { Configuration {
enabled: false, enabled: false,
port: 5001, 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); let secretstore_key_server = secretstore::start(cmd.secretstore_conf.clone(), secretstore_deps);
// the ipfs server // the ipfs server
let ipfs_server = match cmd.ipfs_conf.enabled { let ipfs_server = ipfs::start_server(cmd.ipfs_conf.clone(), client.clone())?;
true => Some(ipfs::start_server(cmd.ipfs_conf.port, client.clone())?),
false => None,
};
// the informant // the informant
let informant = Arc::new(Informant::new( let informant = Arc::new(Informant::new(