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:
parent
9b5bcb81fd
commit
f97e775498
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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",
|
||||
|
@ -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" }
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
139
ipfs/src/lib.rs
139
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<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,25 +161,27 @@ 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)));
|
||||
|
||||
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 {
|
||||
@ -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 || {
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
@ -76,6 +76,9 @@ path = "$HOME/.parity/secretstore"
|
||||
[ipfs]
|
||||
enable = false
|
||||
port = 5001
|
||||
interface = "local"
|
||||
cors = "null"
|
||||
hosts = ["none"]
|
||||
|
||||
[mining]
|
||||
author = "0xdeadbeefcafe0000000000000000000000000001"
|
||||
|
@ -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()),
|
||||
|
@ -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})
|
||||
|
@ -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,
|
||||
_ => {}
|
||||
}
|
||||
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<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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user