Remove UI related settings from CLI (#8783)

* Remove all ui reference in dapps interface

* Pass primary cli build

* Add back parity wallet dapp as builtin

* Clean up ui settings

* Fix all tests in cli

* Missed ui files to commit

* Add parity-utils endpoint back

* Fix non-dapp feature compiling

* Inline styles

* Remove parity-utils endpoint

* Remove ui precompiled crate

* Remove parity-ui alltogether

* Remove ui feature flags

* Move errors to static methods

* Fix tests

* Remove all reference to utils endpoint and remove server side injection

According to https://github.com/paritytech/parity/pull/8539, inject.js is already handled by Parity UI.
This commit is contained in:
Wei Tang 2018-06-06 16:05:52 +08:00 committed by Marek Kotewicz
parent 114d4433a9
commit a5190449da
42 changed files with 306 additions and 1639 deletions

56
Cargo.lock generated
View File

@ -2071,8 +2071,6 @@ dependencies = [
"parity-dapps-glue 1.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-hash-fetch 1.12.0",
"parity-reactor 0.1.0",
"parity-ui 1.12.0",
"parity-ui-deprecation 1.10.0",
"parity-version 1.12.0",
"parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2283,56 +2281,6 @@ dependencies = [
"tokio-uds 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parity-ui"
version = "1.12.0"
dependencies = [
"parity-ui-dev 1.9.0 (git+https://github.com/parity-js/shell.git?rev=eecaadcb9e421bce31e91680d14a20bbd38f92a2)",
"parity-ui-old-dev 1.9.0 (git+https://github.com/parity-js/dapp-wallet.git?rev=65deb02e7c007a0fd8aab0c089c93e3fd1de6f87)",
"parity-ui-old-precompiled 1.9.0 (git+https://github.com/js-dist-paritytech/parity-master-1-10-wallet.git?rev=4b6f112412716cd05123d32eeb7fda448288a6c6)",
"parity-ui-precompiled 1.9.0 (git+https://github.com/js-dist-paritytech/parity-master-1-10-shell.git?rev=bd25b41cd642c6b822d820dded3aa601a29aa079)",
"rustc_version 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parity-ui-deprecation"
version = "1.10.0"
dependencies = [
"parity-dapps-glue 1.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parity-ui-dev"
version = "1.9.0"
source = "git+https://github.com/parity-js/shell.git?rev=eecaadcb9e421bce31e91680d14a20bbd38f92a2#eecaadcb9e421bce31e91680d14a20bbd38f92a2"
dependencies = [
"parity-dapps-glue 1.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parity-ui-old-dev"
version = "1.9.0"
source = "git+https://github.com/parity-js/dapp-wallet.git?rev=65deb02e7c007a0fd8aab0c089c93e3fd1de6f87#65deb02e7c007a0fd8aab0c089c93e3fd1de6f87"
dependencies = [
"parity-dapps-glue 1.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parity-ui-old-precompiled"
version = "1.9.0"
source = "git+https://github.com/js-dist-paritytech/parity-master-1-10-wallet.git?rev=4b6f112412716cd05123d32eeb7fda448288a6c6#4b6f112412716cd05123d32eeb7fda448288a6c6"
dependencies = [
"parity-dapps-glue 1.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parity-ui-precompiled"
version = "1.9.0"
source = "git+https://github.com/js-dist-paritytech/parity-master-1-10-shell.git?rev=bd25b41cd642c6b822d820dded3aa601a29aa079#bd25b41cd642c6b822d820dded3aa601a29aa079"
dependencies = [
"parity-dapps-glue 1.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parity-updater"
version = "1.12.0"
@ -3970,10 +3918,6 @@ dependencies = [
"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37"
"checksum parity-dapps-glue 1.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "261c025c67ba416e9fe63aa9b3236520ce3c74cfbe43590c9cdcec4ccc8180e4"
"checksum parity-tokio-ipc 0.1.5 (git+https://github.com/nikvolf/parity-tokio-ipc)" = "<none>"
"checksum parity-ui-dev 1.9.0 (git+https://github.com/parity-js/shell.git?rev=eecaadcb9e421bce31e91680d14a20bbd38f92a2)" = "<none>"
"checksum parity-ui-old-dev 1.9.0 (git+https://github.com/parity-js/dapp-wallet.git?rev=65deb02e7c007a0fd8aab0c089c93e3fd1de6f87)" = "<none>"
"checksum parity-ui-old-precompiled 1.9.0 (git+https://github.com/js-dist-paritytech/parity-master-1-10-wallet.git?rev=4b6f112412716cd05123d32eeb7fda448288a6c6)" = "<none>"
"checksum parity-ui-precompiled 1.9.0 (git+https://github.com/js-dist-paritytech/parity-master-1-10-shell.git?rev=bd25b41cd642c6b822d820dded3aa601a29aa079)" = "<none>"
"checksum parity-wasm 0.27.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a93ad771f67ce8a6af64c6444a99c07b15f4674203657496fc31244ffb1de2c3"
"checksum parity-wordlist 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d0dec124478845b142f68b446cbee953d14d4b41f1bc0425024417720dce693"
"checksum parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd9d732f2de194336fb02fe11f9eed13d9e76f13f4315b4d88a14ca411750cd"

View File

@ -88,16 +88,7 @@ winapi = { version = "0.3.4", features = ["winsock2", "winuser", "shellapi"] }
daemonize = { git = "https://github.com/paritytech/daemonize" }
[features]
default = ["ui-precompiled"]
ui = [
"ui-enabled",
"parity-dapps/ui",
]
ui-precompiled = [
"ui-enabled",
"parity-dapps/ui-precompiled",
]
ui-enabled = ["dapps"]
default = ["dapps"]
dapps = ["parity-dapps"]
json-tests = ["ethcore/json-tests"]
test-heavy = ["ethcore/test-heavy"]

View File

@ -34,8 +34,6 @@ fetch = { path = "../util/fetch" }
node-health = { path = "./node-health" }
parity-hash-fetch = { path = "../hash-fetch" }
parity-reactor = { path = "../util/reactor" }
parity-ui = { path = "./ui" }
parity-ui-deprecation = { path = "./ui-deprecation" }
keccak-hash = { path = "../util/hash" }
parity-version = { path = "../util/version" }
registrar = { path = "../registrar" }
@ -43,7 +41,3 @@ registrar = { path = "../registrar" }
[dev-dependencies]
env_logger = "0.4"
ethcore-devtools = { path = "../devtools" }
[features]
ui = ["parity-ui/no-precompiled-js"]
ui-precompiled = ["parity-ui/use-precompiled-js"]

View File

@ -27,7 +27,6 @@ use mime_guess::Mime;
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest};
use handlers::{ContentValidator, ValidatorResponse};
use page::{local, PageCache};
use Embeddable;
type OnDone = Box<Fn(Option<local::Dapp>) + Send>;
@ -124,17 +123,15 @@ pub struct Dapp {
id: String,
dapps_path: PathBuf,
on_done: OnDone,
embeddable_on: Embeddable,
pool: CpuPool,
}
impl Dapp {
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Embeddable, pool: CpuPool) -> Self {
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, pool: CpuPool) -> Self {
Dapp {
id,
dapps_path,
on_done,
embeddable_on,
pool,
}
}
@ -170,7 +167,6 @@ impl ContentValidator for Dapp {
fn validate_and_install(self, response: fetch::Response) -> Result<ValidatorResponse, ValidationError> {
let id = self.id.clone();
let pool = self.pool;
let embeddable_on = self.embeddable_on;
let validate = move |dapp_path: PathBuf| {
let (file, zip_path) = write_response_and_check_hash(&id, dapp_path.clone(), &format!("{}.zip", id), response)?;
trace!(target: "dapps", "Opening dapp bundle at {:?}", zip_path);
@ -210,7 +206,7 @@ impl ContentValidator for Dapp {
let mut manifest_file = fs::File::create(manifest_path)?;
manifest_file.write_all(manifest_str.as_bytes())?;
// Create endpoint
let endpoint = local::Dapp::new(pool, dapp_path, manifest.into(), PageCache::Enabled, embeddable_on);
let endpoint = local::Dapp::new(pool, dapp_path, manifest.into(), PageCache::Enabled);
Ok(endpoint)
};

View File

@ -31,7 +31,7 @@ use hash_fetch::urlhint::{URLHintContract, URLHint, URLHintResult};
use hyper::StatusCode;
use ethereum_types::H256;
use {Embeddable, SyncStatus, random_filename};
use {SyncStatus, random_filename};
use parking_lot::Mutex;
use page::local;
use handlers::{ContentHandler, ContentFetcherHandler};
@ -50,7 +50,6 @@ pub struct ContentFetcher<F: Fetch = FetchClient, R: URLHint + 'static = URLHint
resolver: R,
cache: Arc<Mutex<ContentCache>>,
sync: Arc<SyncStatus>,
embeddable_on: Embeddable,
fetch: F,
pool: CpuPool,
only_content: bool,
@ -78,7 +77,6 @@ impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
resolver,
sync,
cache: Arc::new(Mutex::new(ContentCache::default())),
embeddable_on: None,
fetch,
pool,
only_content: true,
@ -90,38 +88,30 @@ impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
self
}
pub fn embeddable_on(mut self, embeddable_on: Embeddable) -> Self {
self.embeddable_on = embeddable_on;
self
}
fn not_found(embeddable: Embeddable) -> endpoint::Response {
fn not_found() -> endpoint::Response {
Box::new(future::ok(ContentHandler::error(
StatusCode::NotFound,
"Resource Not Found",
"Requested resource was not found.",
None,
embeddable,
).into()))
}
fn still_syncing(embeddable: Embeddable) -> endpoint::Response {
fn still_syncing() -> endpoint::Response {
Box::new(future::ok(ContentHandler::error(
StatusCode::ServiceUnavailable,
"Sync In Progress",
"Your node is still syncing. We cannot resolve any content before it's fully synced.",
Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"),
embeddable,
).into()))
}
fn dapps_disabled(address: Embeddable) -> endpoint::Response {
fn dapps_disabled() -> endpoint::Response {
Box::new(future::ok(ContentHandler::error(
StatusCode::ServiceUnavailable,
"Network Dapps Not Available",
"This interface doesn't support network dapps for security reasons.",
None,
address,
).into()))
}
@ -195,10 +185,10 @@ impl<R: URLHint + 'static, F: Fetch> Endpoint for ContentFetcher<F, R> {
match content {
// Don't serve dapps if we are still syncing (but serve content)
Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => {
(None, Self::still_syncing(self.embeddable_on.clone()))
(None, Self::still_syncing())
},
Some(URLHintResult::Dapp(_)) if self.only_content => {
(None, Self::dapps_disabled(self.embeddable_on.clone()))
(None, Self::dapps_disabled())
},
Some(content) => {
let handler = match content {
@ -211,10 +201,8 @@ impl<R: URLHint + 'static, F: Fetch> Endpoint for ContentFetcher<F, R> {
content_id.clone(),
self.cache_path.clone(),
Box::new(on_done),
self.embeddable_on.clone(),
self.pool.clone(),
),
self.embeddable_on.clone(),
self.fetch.clone(),
self.pool.clone(),
)
@ -228,10 +216,8 @@ impl<R: URLHint + 'static, F: Fetch> Endpoint for ContentFetcher<F, R> {
content_id.clone(),
self.cache_path.clone(),
Box::new(on_done),
self.embeddable_on.clone(),
self.pool.clone(),
),
self.embeddable_on.clone(),
self.fetch.clone(),
self.pool.clone(),
)
@ -248,7 +234,6 @@ impl<R: URLHint + 'static, F: Fetch> Endpoint for ContentFetcher<F, R> {
Box::new(on_done),
self.pool.clone(),
),
self.embeddable_on.clone(),
self.fetch.clone(),
self.pool.clone(),
)
@ -258,12 +243,12 @@ impl<R: URLHint + 'static, F: Fetch> Endpoint for ContentFetcher<F, R> {
(Some(ContentStatus::Fetching(handler.fetch_control())), Box::new(handler) as endpoint::Response)
},
None if self.sync.is_major_importing() => {
(None, Self::still_syncing(self.embeddable_on.clone()))
(None, Self::still_syncing())
},
None => {
// This may happen when sync status changes in between
// `contains` and `to_handler`
(None, Self::not_found(self.embeddable_on.clone()))
(None, Self::not_found())
},
}
},
@ -330,7 +315,7 @@ mod tests {
icon_url: "".into(),
local_url: Some("".into()),
allow_js_eval: None,
}, Default::default(), None);
}, Default::default());
// when
fetcher.set_status("test", ContentStatus::Ready(handler));

View File

@ -24,7 +24,6 @@ use futures_cpupool::CpuPool;
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
use endpoint::{Endpoint, EndpointInfo};
use page::{local, PageCache};
use Embeddable;
struct LocalDapp {
id: String,
@ -65,14 +64,14 @@ fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
/// Returns Dapp Id and Local Dapp Endpoint for given filesystem path.
/// Parses the path to extract last component (for name).
/// `None` is returned when path is invalid or non-existent.
pub fn local_endpoint<P: AsRef<Path>>(path: P, embeddable: Embeddable, pool: CpuPool) -> Option<(String, Box<local::Dapp>)> {
pub fn local_endpoint<P: AsRef<Path>>(path: P, pool: CpuPool) -> Option<(String, Box<local::Dapp>)> {
let path = path.as_ref().to_owned();
path.canonicalize().ok().and_then(|path| {
let name = path.file_name().and_then(|name| name.to_str());
name.map(|name| {
let dapp = local_dapp(name.into(), path.clone());
(dapp.id, Box::new(local::Dapp::new(
pool.clone(), dapp.path, dapp.info, PageCache::Disabled, embeddable.clone())
pool.clone(), dapp.path, dapp.info, PageCache::Disabled)
))
})
})
@ -90,12 +89,12 @@ fn local_dapp(name: String, path: PathBuf) -> LocalDapp {
/// Returns endpoints for Local Dapps found for given filesystem path.
/// Scans the directory and collects `local::Dapp`.
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, embeddable: Embeddable, pool: CpuPool) -> BTreeMap<String, Box<Endpoint>> {
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, pool: CpuPool) -> BTreeMap<String, Box<Endpoint>> {
let mut pages = BTreeMap::<String, Box<Endpoint>>::new();
for dapp in local_dapps(dapps_path.as_ref()) {
pages.insert(
dapp.id,
Box::new(local::Dapp::new(pool.clone(), dapp.path, dapp.info, PageCache::Disabled, embeddable.clone()))
Box::new(local::Dapp::new(pool.clone(), dapp.path, dapp.info, PageCache::Disabled))
);
}
pages

View File

@ -17,17 +17,15 @@
use std::path::PathBuf;
use std::sync::Arc;
use endpoint::{Endpoints, Endpoint};
use endpoint::Endpoints;
use futures_cpupool::CpuPool;
use page;
use proxypac::ProxyPac;
use web::Web;
use fetch::Fetch;
use {WebProxyTokens, ParentFrameSettings};
use WebProxyTokens;
mod app;
mod cache;
mod ui;
pub mod fs;
pub mod fetcher;
pub mod manifest;
@ -37,68 +35,35 @@ pub use self::app::App;
pub const HOME_PAGE: &'static str = "home";
pub const RPC_PATH: &'static str = "rpc";
pub const API_PATH: &'static str = "api";
pub const UTILS_PATH: &'static str = "parity-utils";
pub const WEB_PATH: &'static str = "web";
pub const URL_REFERER: &'static str = "__referer=";
pub fn utils(pool: CpuPool) -> Box<Endpoint> {
Box::new(page::builtin::Dapp::new(pool, ::parity_ui::App::default()))
}
pub fn ui(pool: CpuPool) -> Box<Endpoint> {
Box::new(page::builtin::Dapp::with_fallback_to_index(pool, ::parity_ui::App::default()))
}
pub fn ui_deprecation(pool: CpuPool) -> Box<Endpoint> {
Box::new(page::builtin::Dapp::with_fallback_to_index(pool, ::parity_ui_deprecation::App::default()))
}
pub fn ui_redirection(embeddable: Option<ParentFrameSettings>) -> Box<Endpoint> {
Box::new(ui::Redirection::new(embeddable))
}
pub fn all_endpoints<F: Fetch>(
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
dapps_domain: &str,
embeddable: Option<ParentFrameSettings>,
web_proxy_tokens: Arc<WebProxyTokens>,
fetch: F,
pool: CpuPool,
) -> (Vec<String>, Endpoints) {
// fetch fs dapps at first to avoid overwriting builtins
let mut pages = fs::local_endpoints(dapps_path.clone(), embeddable.clone(), pool.clone());
let mut pages = fs::local_endpoints(dapps_path.clone(), pool.clone());
let local_endpoints: Vec<String> = pages.keys().cloned().collect();
for path in extra_dapps {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), embeddable.clone(), pool.clone()) {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), pool.clone()) {
pages.insert(id, endpoint);
} else {
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
}
}
// NOTE [ToDr] Dapps will be currently embeded on 8180
pages.insert(
"ui".into(),
Box::new(page::builtin::Dapp::new_safe_to_embed(pool.clone(), ::parity_ui::App::default(), embeddable.clone()))
);
// old version
pages.insert(
"v1".into(),
Box::new({
let mut page = page::builtin::Dapp::new_safe_to_embed(pool.clone(), ::parity_ui::old::App::default(), embeddable.clone());
// allow JS eval on old Wallet
page.allow_js_eval();
page
})
);
pages.insert(
"proxy".into(),
ProxyPac::boxed(embeddable.clone(), dapps_domain.to_owned())
ProxyPac::boxed(dapps_domain.to_owned())
);
pages.insert(
WEB_PATH.into(),
Web::boxed(embeddable.clone(), web_proxy_tokens.clone(), fetch.clone(), pool.clone())
Web::boxed(web_proxy_tokens.clone(), fetch.clone(), pool.clone())
);
(local_endpoints, pages)

View File

@ -1,57 +0,0 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! UI redirections
use hyper::StatusCode;
use futures::future;
use endpoint::{Endpoint, Request, Response, EndpointPath};
use {handlers, Embeddable};
/// Redirection to UI server.
pub struct Redirection {
embeddable_on: Embeddable,
}
impl Redirection {
pub fn new(
embeddable_on: Embeddable,
) -> Self {
Redirection {
embeddable_on,
}
}
}
impl Endpoint for Redirection {
fn respond(&self, _path: EndpointPath, req: Request) -> Response {
Box::new(future::ok(if let Some(ref frame) = self.embeddable_on {
trace!(target: "dapps", "Redirecting to signer interface.");
let protocol = req.uri().scheme().unwrap_or("http");
handlers::Redirection::new(format!("{}://{}:{}", protocol, &frame.host, frame.port)).into()
} else {
trace!(target: "dapps", "Signer disabled, returning 404.");
handlers::ContentHandler::error(
StatusCode::NotFound,
"404 Not Found",
"Your homepage is not available when Trusted Signer is disabled.",
Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."),
None,
).into()
}))
}
}

View File

@ -4,7 +4,88 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>{title}</title>
<link rel="stylesheet" href="/parity-utils/styles.css">
<style>
:root, :root body {{
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
background: rgb(95, 95, 95);
color: rgba(255, 255, 255, 0.75);
font-size: 16px;
font-family: sans-serif;
font-weight: 300;
}}
:root a, :root a:visited {{
text-decoration: none;
cursor: pointer;
color: rgb(0, 151, 167); /* #f80 */
}}
:root a:hover {{
color: rgb(0, 174, 193);
}}
h1,h2,h3,h4,h5,h6 {{
font-weight: 300;
text-transform: uppercase;
text-decoration: none;
}}
h1 {{
font-size: 24px;
line-height: 36px;
color: rgb(0, 151, 167);
}}
h2 {{
font-size: 20px;
line-height: 34px;
}}
code,kbd,pre,samp {{
font-family: monospace;
}}
.parity-navbar {{
background: rgb(65, 65, 65);
height: 72px;
padding: 0 1rem;
display: flex;
justify-content: space-between;
}}
.parity-status {{
clear: both;
padding: 1rem;
margin: 1rem 0;
text-align: right;
opacity: 0.75;
}}
.parity-box {{
margin: 1rem;
padding: 1rem;
background-color: rgb(48, 48, 48);
box-sizing: border-box;
box-shadow: rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px;
border-radius: 2px;
z-index: 1;
color: #aaa;
}}
.parity-box h1,
.parity-box h2,
.parity-box h3,
.parity-box h4,
.parity-box h5,
.parity-box h6 {{
margin: 0;
}}
</style>
</head>
<body>
<div class="parity-navbar">

View File

@ -22,14 +22,12 @@ use hyper::StatusCode;
use parity_version::version;
use handlers::add_security_headers;
use Embeddable;
#[derive(Debug, Clone)]
pub struct ContentHandler {
code: StatusCode,
content: String,
mimetype: mime::Mime,
safe_to_embed_on: Embeddable,
}
impl ContentHandler {
@ -37,8 +35,8 @@ impl ContentHandler {
Self::new(StatusCode::Ok, content, mimetype)
}
pub fn html(code: StatusCode, content: String, embeddable_on: Embeddable) -> Self {
Self::new_embeddable(code, content, mime::TEXT_HTML, embeddable_on)
pub fn html(code: StatusCode, content: String) -> Self {
Self::new(code, content, mime::TEXT_HTML)
}
pub fn error(
@ -46,7 +44,6 @@ impl ContentHandler {
title: &str,
message: &str,
details: Option<&str>,
embeddable_on: Embeddable,
) -> Self {
Self::html(code, format!(
include_str!("../error_tpl.html"),
@ -54,24 +51,18 @@ impl ContentHandler {
message=message,
details=details.unwrap_or_else(|| ""),
version=version(),
), embeddable_on)
))
}
pub fn new(code: StatusCode, content: String, mimetype: mime::Mime) -> Self {
Self::new_embeddable(code, content, mimetype, None)
}
pub fn new_embeddable(
pub fn new(
code: StatusCode,
content: String,
mimetype: mime::Mime,
safe_to_embed_on: Embeddable,
) -> Self {
ContentHandler {
code,
content,
mimetype,
safe_to_embed_on,
}
}
}
@ -82,7 +73,7 @@ impl Into<hyper::Response> for ContentHandler {
.with_status(self.code)
.with_header(header::ContentType(self.mimetype))
.with_body(self.content);
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on, false);
add_security_headers(&mut res.headers_mut(), false);
res
}
}

View File

@ -40,7 +40,7 @@ impl Into<hyper::Response> for EchoHandler {
.with_header(content_type.unwrap_or(header::ContentType::json()))
.with_body(self.request.body());
add_security_headers(res.headers_mut(), None, false);
add_security_headers(res.headers_mut(), false);
res
}
}

View File

@ -0,0 +1,66 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Handler errors.
use handlers::{ContentHandler, FETCH_TIMEOUT};
use hyper::StatusCode;
use std::fmt;
pub fn streaming() -> ContentHandler {
ContentHandler::error(
StatusCode::BadGateway,
"Streaming Error",
"This content is being streamed in other place.",
None,
)
}
pub fn download_error<E: fmt::Debug>(e: E) -> ContentHandler {
ContentHandler::error(
StatusCode::BadGateway,
"Download Error",
"There was an error when fetching the content.",
Some(&format!("{:?}", e)),
)
}
pub fn invalid_content<E: fmt::Debug>(e: E) -> ContentHandler {
ContentHandler::error(
StatusCode::BadGateway,
"Invalid Dapp",
"Downloaded bundle does not contain a valid content.",
Some(&format!("{:?}", e)),
)
}
pub fn timeout_error() -> ContentHandler {
ContentHandler::error(
StatusCode::GatewayTimeout,
"Download Timeout",
&format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT.as_secs()),
None,
)
}
pub fn method_not_allowed() -> ContentHandler {
ContentHandler::error(
StatusCode::MethodNotAllowed,
"Method Not Allowed",
"Only <code>GET</code> requests are allowed.",
None,
)
}

View File

@ -19,20 +19,17 @@
use std::{fmt, mem};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Instant, Duration};
use std::time::Instant;
use fetch::{self, Fetch};
use futures::sync::oneshot;
use futures::{self, Future};
use futures_cpupool::CpuPool;
use hyper::{self, StatusCode};
use hyper;
use parking_lot::Mutex;
use endpoint::{self, EndpointPath};
use handlers::{ContentHandler, StreamingHandler};
use handlers::{ContentHandler, StreamingHandler, FETCH_TIMEOUT, errors};
use page::local;
use {Embeddable};
const FETCH_TIMEOUT: Duration = Duration::from_secs(300);
pub enum ValidatorResponse {
Local(local::Dapp),
@ -134,8 +131,7 @@ impl Future for WaitingHandler {
return Ok(futures::Async::Ready(handler.into()));
},
WaitResult::NonAwaitable => {
let errors = Errors { embeddable_on: None };
return Ok(futures::Async::Ready(errors.streaming().into()));
return Ok(futures::Async::Ready(errors::streaming().into()));
},
WaitResult::Done(endpoint) => {
WaitState::Done(endpoint.to_response(&self.path).into())
@ -152,63 +148,6 @@ impl Future for WaitingHandler {
}
}
#[derive(Debug, Clone)]
struct Errors {
embeddable_on: Embeddable,
}
impl Errors {
fn streaming(&self) -> ContentHandler {
ContentHandler::error(
StatusCode::BadGateway,
"Streaming Error",
"This content is being streamed in other place.",
None,
self.embeddable_on.clone(),
)
}
fn download_error<E: fmt::Debug>(&self, e: E) -> ContentHandler {
ContentHandler::error(
StatusCode::BadGateway,
"Download Error",
"There was an error when fetching the content.",
Some(&format!("{:?}", e)),
self.embeddable_on.clone(),
)
}
fn invalid_content<E: fmt::Debug>(&self, e: E) -> ContentHandler {
ContentHandler::error(
StatusCode::BadGateway,
"Invalid Dapp",
"Downloaded bundle does not contain a valid content.",
Some(&format!("{:?}", e)),
self.embeddable_on.clone(),
)
}
fn timeout_error(&self) -> ContentHandler {
ContentHandler::error(
StatusCode::GatewayTimeout,
"Download Timeout",
&format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT.as_secs()),
None,
self.embeddable_on.clone(),
)
}
fn method_not_allowed(&self) -> ContentHandler {
ContentHandler::error(
StatusCode::MethodNotAllowed,
"Method Not Allowed",
"Only <code>GET</code> requests are allowed.",
None,
self.embeddable_on.clone(),
)
}
}
enum FetchState {
Error(ContentHandler),
InProgress(Box<Future<Item=FetchState, Error=()> + Send>),
@ -237,7 +176,6 @@ impl fmt::Debug for FetchState {
pub struct ContentFetcherHandler {
fetch_control: FetchControl,
status: FetchState,
errors: Errors,
}
impl ContentFetcherHandler {
@ -250,12 +188,10 @@ impl ContentFetcherHandler {
url: &str,
path: EndpointPath,
installer: H,
embeddable_on: Embeddable,
fetch: F,
pool: CpuPool,
) -> Self {
let fetch_control = FetchControl::default();
let errors = Errors { embeddable_on };
// Validation of method
let status = match *method {
@ -268,18 +204,16 @@ impl ContentFetcherHandler {
url,
fetch_control.abort.clone(),
path,
errors.clone(),
installer,
))
},
// or return error
_ => FetchState::Error(errors.method_not_allowed()),
_ => FetchState::Error(errors::method_not_allowed()),
};
ContentFetcherHandler {
fetch_control,
status,
errors,
}
}
@ -289,7 +223,6 @@ impl ContentFetcherHandler {
url: &str,
abort: Arc<AtomicBool>,
path: EndpointPath,
errors: Errors,
installer: H,
) -> Box<Future<Item=FetchState, Error=()> + Send> {
// Start fetching the content
@ -311,12 +244,12 @@ impl ContentFetcherHandler {
},
Err(e) => {
trace!(target: "dapps", "Error while validating content: {:?}", e);
FetchState::Error(errors.invalid_content(e))
FetchState::Error(errors::invalid_content(e))
},
},
Err(e) => {
warn!(target: "dapps", "Unable to fetch content: {:?}", e);
FetchState::Error(errors.download_error(e))
FetchState::Error(errors::download_error(e))
},
})
});
@ -347,7 +280,7 @@ impl Future for ContentFetcherHandler {
// Request may time out
FetchState::InProgress(_) if self.fetch_control.is_deadline_reached() => {
trace!(target: "dapps", "Fetching dapp failed because of timeout.");
FetchState::Error(self.errors.timeout_error())
FetchState::Error(errors::timeout_error())
},
FetchState::InProgress(ref mut receiver) => {
// Check if there is a response

View File

@ -22,6 +22,7 @@ mod fetch;
mod reader;
mod redirect;
mod streaming;
mod errors;
pub use self::content::ContentHandler;
pub use self::echo::EchoHandler;
@ -30,20 +31,16 @@ pub use self::reader::Reader;
pub use self::redirect::Redirection;
pub use self::streaming::StreamingHandler;
use std::iter;
use itertools::Itertools;
use hyper::header;
use {apps, address, Embeddable};
use std::time::Duration;
const FETCH_TIMEOUT: Duration = Duration::from_secs(300);
/// Adds security-related headers to the Response.
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Embeddable, allow_js_eval: bool) {
pub fn add_security_headers(headers: &mut header::Headers, allow_js_eval: bool) {
headers.set_raw("X-XSS-Protection", "1; mode=block");
headers.set_raw("X-Content-Type-Options", "nosniff");
// Embedding header:
if let None = embeddable_on {
headers.set_raw("X-Frame-Options", "SAMEORIGIN");
}
// Content Security Policy headers
headers.set_raw("Content-Security-Policy", String::new()
@ -70,11 +67,7 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Embedd
+ "object-src 'none';"
// Allow scripts
+ {
let script_src = embeddable_on.as_ref()
.map(|e| e.extra_script_src.iter()
.map(|&(ref host, port)| address(host, port))
.join(" ")
).unwrap_or_default();
let script_src = "";
let eval = if allow_js_eval { " 'unsafe-eval'" } else { "" };
&format!(
@ -93,29 +86,6 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Embedd
// Never allow mixed content
+ "block-all-mixed-content;"
// Specify if the site can be embedded.
+ &match embeddable_on {
Some(ref embed) => {
let std = address(&embed.host, embed.port);
let proxy = format!("{}.{}", apps::HOME_PAGE, embed.dapps_domain);
let domain = format!("*.{}:{}", embed.dapps_domain, embed.port);
let mut ancestors = vec![std, domain, proxy]
.into_iter()
.chain(embed.extra_embed_on
.iter()
.map(|&(ref host, port)| address(host, port))
);
let ancestors = if embed.host == "127.0.0.1" {
let localhost = address("localhost", embed.port);
ancestors.chain(iter::once(localhost)).join(" ")
} else {
ancestors.join(" ")
};
format!("frame-ancestors {};", ancestors)
},
None => format!("frame-ancestors 'self';"),
}
+ "frame-ancestors 'self';"
);
}

View File

@ -20,24 +20,21 @@ use std::io;
use hyper::{self, header, mime, StatusCode};
use handlers::{add_security_headers, Reader};
use Embeddable;
pub struct StreamingHandler<R> {
initial: Vec<u8>,
content: R,
status: StatusCode,
mimetype: mime::Mime,
safe_to_embed_on: Embeddable,
}
impl<R: io::Read> StreamingHandler<R> {
pub fn new(content: R, status: StatusCode, mimetype: mime::Mime, safe_to_embed_on: Embeddable) -> Self {
pub fn new(content: R, status: StatusCode, mimetype: mime::Mime) -> Self {
StreamingHandler {
initial: Vec::new(),
content,
status,
mimetype,
safe_to_embed_on,
}
}
@ -51,7 +48,7 @@ impl<R: io::Read> StreamingHandler<R> {
.with_status(self.status)
.with_header(header::ContentType(self.mimetype))
.with_body(body);
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on, false);
add_security_headers(&mut res.headers_mut(), false);
(reader, res)
}

View File

@ -38,8 +38,6 @@ extern crate fetch;
extern crate node_health;
extern crate parity_dapps_glue as parity_dapps;
extern crate parity_hash_fetch as hash_fetch;
extern crate parity_ui;
extern crate parity_ui_deprecation;
extern crate keccak_hash as hash;
extern crate parity_version;
extern crate registrar;
@ -84,6 +82,7 @@ use node_health::NodeHealth;
pub use registrar::{RegistrarClient, Asynchronous};
pub use node_health::SyncStatus;
pub use page::builtin::Dapp;
/// Validates Web Proxy tokens
pub trait WebProxyTokens: Send + Sync {
@ -101,7 +100,6 @@ pub struct Endpoints {
local_endpoints: Arc<RwLock<Vec<String>>>,
endpoints: Arc<RwLock<endpoint::Endpoints>>,
dapps_path: PathBuf,
embeddable: Option<ParentFrameSettings>,
pool: Option<CpuPool>,
}
@ -119,7 +117,7 @@ impl Endpoints {
None => return,
Some(pool) => pool,
};
let new_local = apps::fs::local_endpoints(&self.dapps_path, self.embeddable.clone(), pool.clone());
let new_local = apps::fs::local_endpoints(&self.dapps_path, pool.clone());
let old_local = mem::replace(&mut *self.local_endpoints.write(), new_local.keys().cloned().collect());
let (_, to_remove): (_, Vec<_>) = old_local
.into_iter()
@ -151,69 +149,10 @@ impl Middleware {
&self.endpoints
}
/// Creates new middleware for UI server.
pub fn ui<F: Fetch>(
pool: CpuPool,
health: NodeHealth,
dapps_domain: &str,
registrar: Arc<RegistrarClient<Call=Asynchronous>>,
sync_status: Arc<SyncStatus>,
fetch: F,
info_page_only: bool,
) -> Self {
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
hash_fetch::urlhint::URLHintContract::new(registrar),
sync_status.clone(),
fetch.clone(),
pool.clone(),
).embeddable_on(None).allow_dapps(false));
if info_page_only {
let mut special = HashMap::default();
special.insert(router::SpecialEndpoint::Home, Some(apps::ui_deprecation(pool.clone())));
return Middleware {
endpoints: Default::default(),
router: router::Router::new(
content_fetcher,
None,
special,
None,
dapps_domain.to_owned(),
),
}
}
let special = {
let mut special = special_endpoints(
pool.clone(),
health,
content_fetcher.clone(),
);
special.insert(router::SpecialEndpoint::Home, Some(apps::ui(pool.clone())));
special
};
let router = router::Router::new(
content_fetcher,
None,
special,
None,
dapps_domain.to_owned(),
);
Middleware {
endpoints: Default::default(),
router: router,
}
}
/// Creates new Dapps server middleware.
pub fn dapps<F: Fetch>(
pool: CpuPool,
health: NodeHealth,
ui_address: Option<(String, u16)>,
extra_embed_on: Vec<(String, u16)>,
extra_script_src: Vec<(String, u16)>,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
dapps_domain: &str,
@ -222,18 +161,16 @@ impl Middleware {
web_proxy_tokens: Arc<WebProxyTokens>,
fetch: F,
) -> Self {
let embeddable = as_embeddable(ui_address, extra_embed_on, extra_script_src, dapps_domain);
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
hash_fetch::urlhint::URLHintContract::new(registrar),
sync_status.clone(),
fetch.clone(),
pool.clone(),
).embeddable_on(embeddable.clone()).allow_dapps(true));
).allow_dapps(true));
let (local_endpoints, endpoints) = apps::all_endpoints(
dapps_path.clone(),
extra_dapps,
dapps_domain,
embeddable.clone(),
web_proxy_tokens,
fetch.clone(),
pool.clone(),
@ -242,28 +179,18 @@ impl Middleware {
endpoints: Arc::new(RwLock::new(endpoints)),
dapps_path,
local_endpoints: Arc::new(RwLock::new(local_endpoints)),
embeddable: embeddable.clone(),
pool: Some(pool.clone()),
};
let special = {
let mut special = special_endpoints(
pool.clone(),
let special = special_endpoints(
health,
content_fetcher.clone(),
);
special.insert(
router::SpecialEndpoint::Home,
Some(apps::ui_redirection(embeddable.clone())),
);
special
};
let router = router::Router::new(
content_fetcher,
Some(endpoints.clone()),
special,
embeddable,
dapps_domain.to_owned(),
);
@ -281,13 +208,11 @@ impl http::RequestMiddleware for Middleware {
}
fn special_endpoints(
pool: CpuPool,
health: NodeHealth,
content_fetcher: Arc<apps::fetcher::Fetcher>,
) -> HashMap<router::SpecialEndpoint, Option<Box<endpoint::Endpoint>>> {
let mut special = HashMap::new();
special.insert(router::SpecialEndpoint::Rpc, None);
special.insert(router::SpecialEndpoint::Utils, Some(apps::utils(pool)));
special.insert(router::SpecialEndpoint::Api, Some(api::RestApi::new(
content_fetcher,
health,
@ -295,45 +220,9 @@ fn special_endpoints(
special
}
fn address(host: &str, port: u16) -> String {
format!("{}:{}", host, port)
}
fn as_embeddable(
ui_address: Option<(String, u16)>,
extra_embed_on: Vec<(String, u16)>,
extra_script_src: Vec<(String, u16)>,
dapps_domain: &str,
) -> Option<ParentFrameSettings> {
ui_address.map(|(host, port)| ParentFrameSettings {
host,
port,
extra_embed_on,
extra_script_src,
dapps_domain: dapps_domain.to_owned(),
})
}
/// Random filename
fn random_filename() -> String {
use ::rand::Rng;
let mut rng = ::rand::OsRng::new().unwrap();
rng.gen_ascii_chars().take(12).collect()
}
type Embeddable = Option<ParentFrameSettings>;
/// Parent frame host and port allowed to embed the content.
#[derive(Debug, Clone)]
pub struct ParentFrameSettings {
/// Hostname
pub host: String,
/// Port
pub port: u16,
/// Additional URLs the dapps can be embedded on.
pub extra_embed_on: Vec<(String, u16)>,
/// Additional URLs the dapp scripts can be loaded from.
pub extra_script_src: Vec<(String, u16)>,
/// Dapps Domain (web3.site)
pub dapps_domain: String,
}

View File

@ -23,15 +23,13 @@ use parity_dapps::{WebApp, Info};
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Request, Response};
use page::{handler, PageCache};
use Embeddable;
/// Represents a builtin Dapp.
pub struct Dapp<T: WebApp + 'static> {
/// futures cpu pool
pool: CpuPool,
/// Content of the files
app: T,
/// Safe to be loaded in frame by other origin. (use wisely!)
safe_to_embed_on: Embeddable,
info: EndpointInfo,
fallback_to_index_html: bool,
}
@ -43,7 +41,6 @@ impl<T: WebApp + 'static> Dapp<T> {
Dapp {
pool,
app,
safe_to_embed_on: None,
info: EndpointInfo::from(info),
fallback_to_index_html: false,
}
@ -56,26 +53,11 @@ impl<T: WebApp + 'static> Dapp<T> {
Dapp {
pool,
app,
safe_to_embed_on: None,
info: EndpointInfo::from(info),
fallback_to_index_html: true,
}
}
/// Creates new `Dapp` which can be safely used in iframe
/// even from different origin. It might be dangerous (clickjacking).
/// Use wisely!
pub fn new_safe_to_embed(pool: CpuPool, app: T, address: Embeddable) -> Self {
let info = app.info();
Dapp {
pool,
app,
safe_to_embed_on: address,
info: EndpointInfo::from(info),
fallback_to_index_html: false,
}
}
/// Allow the dapp to use `unsafe-eval` to run JS.
pub fn allow_js_eval(&mut self) {
self.info.allow_js_eval = Some(true);
@ -121,7 +103,6 @@ impl<T: WebApp> Endpoint for Dapp<T> {
let (reader, response) = handler::PageHandler {
file,
cache: PageCache::Disabled,
safe_to_embed_on: self.safe_to_embed_on.clone(),
allow_js_eval: self.info.allow_js_eval.clone().unwrap_or(false),
}.into_response();

View File

@ -20,7 +20,6 @@ use hyper::{self, header, StatusCode};
use hyper::mime::{Mime};
use handlers::{Reader, ContentHandler, add_security_headers};
use {Embeddable};
/// Represents a file that can be sent to client.
/// Implementation should keep track of bytes already sent internally.
@ -54,8 +53,6 @@ impl Default for PageCache {
pub struct PageHandler<T: DappFile> {
/// File currently being served
pub file: Option<T>,
/// Flag indicating if the file can be safely embeded (put in iframe).
pub safe_to_embed_on: Embeddable,
/// Cache settings for this page.
pub cache: PageCache,
/// Allow JS unsafe-eval.
@ -70,7 +67,6 @@ impl<T: DappFile> PageHandler<T> {
"File not found",
"Requested file has not been found.",
None,
self.safe_to_embed_on,
).into()),
Some(file) => file,
};
@ -94,7 +90,7 @@ impl<T: DappFile> PageHandler<T> {
headers.set(header::ContentType(file.content_type().to_owned()));
add_security_headers(&mut headers, self.safe_to_embed_on, self.allow_js_eval);
add_security_headers(&mut headers, self.allow_js_eval);
}
let (reader, body) = Reader::pair(file.into_reader(), Vec::new());

View File

@ -22,7 +22,6 @@ use futures_cpupool::CpuPool;
use page::handler::{self, PageCache};
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Request, Response};
use hyper::mime::Mime;
use Embeddable;
#[derive(Clone)]
pub struct Dapp {
@ -31,7 +30,6 @@ pub struct Dapp {
mime: Option<Mime>,
info: Option<EndpointInfo>,
cache: PageCache,
embeddable_on: Embeddable,
}
impl fmt::Debug for Dapp {
@ -41,20 +39,18 @@ impl fmt::Debug for Dapp {
.field("mime", &self.mime)
.field("info", &self.info)
.field("cache", &self.cache)
.field("embeddable_on", &self.embeddable_on)
.finish()
}
}
impl Dapp {
pub fn new(pool: CpuPool, path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Embeddable) -> Self {
pub fn new(pool: CpuPool, path: PathBuf, info: EndpointInfo, cache: PageCache) -> Self {
Dapp {
pool,
path,
mime: None,
info: Some(info),
cache,
embeddable_on,
}
}
@ -65,7 +61,6 @@ impl Dapp {
mime: Some(mime),
info: None,
cache,
embeddable_on: None,
}
}
@ -96,7 +91,6 @@ impl Dapp {
let (reader, response) = handler::PageHandler {
file: self.get_file(path),
cache: self.cache,
safe_to_embed_on: self.embeddable_on.clone(),
allow_js_eval: self.info.as_ref().and_then(|x| x.allow_js_eval).unwrap_or(false),
}.into_response();

View File

@ -21,25 +21,20 @@ use endpoint::{Endpoint, Request, Response, EndpointPath};
use futures::future;
use handlers::ContentHandler;
use hyper::mime;
use {address, Embeddable};
pub struct ProxyPac {
embeddable: Embeddable,
dapps_domain: String,
}
impl ProxyPac {
pub fn boxed(embeddable: Embeddable, dapps_domain: String) -> Box<Endpoint> {
Box::new(ProxyPac { embeddable, dapps_domain })
pub fn boxed(dapps_domain: String) -> Box<Endpoint> {
Box::new(ProxyPac { dapps_domain })
}
}
impl Endpoint for ProxyPac {
fn respond(&self, path: EndpointPath, _req: Request) -> Response {
let ui = self.embeddable
.as_ref()
.map(|ref parent| address(&parent.host, parent.port))
.unwrap_or_else(|| format!("{}:{}", path.host, path.port));
let ui = format!("{}:{}", path.host, path.port);
let content = format!(
r#"

View File

@ -29,14 +29,12 @@ use apps::fetcher::Fetcher;
use endpoint::{self, Endpoint, EndpointPath};
use Endpoints;
use handlers;
use Embeddable;
/// Special endpoints are accessible on every domain (every dapp)
#[derive(Debug, PartialEq, Hash, Eq)]
pub enum SpecialEndpoint {
Rpc,
Api,
Utils,
Home,
None,
}
@ -52,16 +50,14 @@ pub struct Router {
endpoints: Option<Endpoints>,
fetch: Arc<Fetcher>,
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
embeddable_on: Embeddable,
dapps_domain: String,
}
impl Router {
fn resolve_request(&self, req: hyper::Request, refresh_dapps: bool) -> (bool, Response) {
fn resolve_request(&self, req: hyper::Request, refresh_dapps: bool) -> Response {
// Choose proper handler depending on path / domain
let endpoint = extract_endpoint(req.uri(), req.headers().get(), &self.dapps_domain);
let referer = extract_referer_endpoint(&req, &self.dapps_domain);
let is_utils = endpoint.1 == SpecialEndpoint::Utils;
let is_get_request = *req.method() == hyper::Method::Get;
let is_head_request = *req.method() == hyper::Method::Head;
let has_dapp = |dapp: &str| self.endpoints
@ -71,7 +67,7 @@ impl Router {
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", req.uri(), req);
debug!(target: "dapps", "Handling endpoint request: {:?}, referer: {:?}", endpoint, referer);
(is_utils, match (endpoint.0, endpoint.1, referer) {
match (endpoint.0, endpoint.1, referer) {
// Handle invalid web requests that we can recover from
(ref path, SpecialEndpoint::None, Some(ref referer))
if referer.app_id == apps::WEB_PATH
@ -132,7 +128,6 @@ impl Router {
"404 Not Found",
"Requested content was not found.",
None,
self.embeddable_on.clone(),
).into())))
}
},
@ -161,20 +156,19 @@ impl Router {
"404 Not Found",
"Requested content was not found.",
None,
self.embeddable_on.clone(),
).into())))
},
})
}
}
}
impl http::RequestMiddleware for Router {
fn on_request(&self, req: hyper::Request) -> http::RequestMiddlewareAction {
let is_origin_set = req.headers().get::<header::Origin>().is_some();
let (is_utils, response) = self.resolve_request(req, self.endpoints.is_some());
let response = self.resolve_request(req, self.endpoints.is_some());
match response {
Response::Some(response) => http::RequestMiddlewareAction::Respond {
should_validate_hosts: !is_utils,
should_validate_hosts: true,
response,
},
Response::None(request) => http::RequestMiddlewareAction::Proceed {
@ -190,14 +184,12 @@ impl Router {
content_fetcher: Arc<Fetcher>,
endpoints: Option<Endpoints>,
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
embeddable_on: Embeddable,
dapps_domain: String,
) -> Self {
Router {
endpoints: endpoints,
fetch: content_fetcher,
special: special,
embeddable_on: embeddable_on,
dapps_domain: format!(".{}", dapps_domain),
}
}
@ -250,7 +242,6 @@ fn extract_endpoint(url: &Uri, extra_host: Option<&header::Host>, dapps_domain:
match path[0].as_ref() {
apps::RPC_PATH => SpecialEndpoint::Rpc,
apps::API_PATH => SpecialEndpoint::Api,
apps::UTILS_PATH => SpecialEndpoint::Utils,
apps::HOME_PAGE => SpecialEndpoint::Home,
_ => SpecialEndpoint::None,
}
@ -351,30 +342,6 @@ mod tests {
}), SpecialEndpoint::Rpc)
);
assert_eq!(
extract_endpoint(&"http://my.status.web3.site/parity-utils/inject.js".parse().unwrap(), None, dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["my".into(), "inject.js".into()],
query: None,
host: "my.status.web3.site".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Utils)
);
assert_eq!(
extract_endpoint(&"http://my.status.web3.site/inject.js".parse().unwrap(), None, dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["my".into(), "inject.js".into()],
query: None,
host: "my.status.web3.site".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::None)
);
// By Subdomain
assert_eq!(
extract_endpoint(&"http://status.web3.site/test.html".parse().unwrap(), None, dapps_domain),

View File

@ -19,7 +19,7 @@ use rustc_hex::FromHex;
use tests::helpers::{
serve_with_registrar, serve_with_registrar_and_sync, serve_with_fetch,
serve_with_registrar_and_fetch,
request, assert_security_headers_for_embed,
request, assert_security_headers
};
#[test]
@ -40,7 +40,7 @@ fn should_resolve_dapp() {
// then
response.assert_status("HTTP/1.1 404 Not Found");
assert_eq!(registrar.calls.lock().len(), 4);
assert_security_headers_for_embed(&response.headers);
assert_security_headers(&response.headers);
}
#[test]
@ -61,7 +61,7 @@ fn should_return_503_when_syncing_but_should_make_the_calls() {
// then
response.assert_status("HTTP/1.1 503 Service Unavailable");
assert_eq!(registrar.calls.lock().len(), 2);
assert_security_headers_for_embed(&response.headers);
assert_security_headers(&response.headers);
}
const GAVCOIN_DAPP: &'static str = "00000000000000000000000000000000000000000000000000000000000000609faf32e1e3845e237cc6efd27187cee13b3b99db000000000000000000000000000000000000000000000000d8bd350823e28ff75e74a34215faefdc8a52fd8e00000000000000000000000000000000000000000000000000000000000000116761766f66796f726b2f676176636f696e000000000000000000000000000000";
@ -95,7 +95,7 @@ fn should_return_502_on_hash_mismatch() {
response.assert_status("HTTP/1.1 502 Bad Gateway");
assert!(response.body.contains("HashMismatch"), "Expected hash mismatch response, got: {:?}", response.body);
assert_security_headers_for_embed(&response.headers);
assert_security_headers(&response.headers);
}
#[test]
@ -126,7 +126,7 @@ fn should_return_error_for_invalid_dapp_zip() {
response.assert_status("HTTP/1.1 502 Bad Gateway");
assert!(response.body.contains("InvalidArchive"), "Expected invalid zip response, got: {:?}", response.body);
assert_security_headers_for_embed(&response.headers);
assert_security_headers(&response.headers);
}
#[test]
@ -165,7 +165,7 @@ fn should_return_fetched_dapp_content() {
fetch.assert_no_more_requests();
response1.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response1.headers);
assert_security_headers(&response1.headers);
assert!(
response1.body.contains(r#"18
<h1>Hello Gavcoin!</h1>
@ -178,7 +178,7 @@ fn should_return_fetched_dapp_content() {
);
response2.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response2.headers);
assert_security_headers(&response2.headers);
assert_eq!(
response2.body,
r#"EA
@ -331,7 +331,7 @@ fn should_stream_web_content() {
// then
response.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response.headers);
assert_security_headers(&response.headers);
fetch.assert_requested("https://parity.io/");
fetch.assert_no_more_requests();
@ -354,7 +354,7 @@ fn should_support_base32_encoded_web_urls() {
// then
response.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response.headers);
assert_security_headers(&response.headers);
fetch.assert_requested("https://parity.io/styles.css?test=123");
fetch.assert_no_more_requests();
@ -377,7 +377,7 @@ fn should_correctly_handle_long_label_when_splitted() {
// then
response.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response.headers);
assert_security_headers(&response.headers);
fetch.assert_requested("https://contribution.melonport.com/styles.css?test=123");
fetch.assert_no_more_requests();
@ -400,7 +400,7 @@ fn should_support_base32_encoded_web_urls_as_path() {
// then
response.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response.headers);
assert_security_headers(&response.headers);
fetch.assert_requested("https://parity.io/styles.css?test=123");
fetch.assert_no_more_requests();
@ -423,7 +423,7 @@ fn should_return_error_on_non_whitelisted_domain() {
// then
response.assert_status("HTTP/1.1 400 Bad Request");
assert_security_headers_for_embed(&response.headers);
assert_security_headers(&response.headers);
fetch.assert_no_more_requests();
}
@ -445,7 +445,7 @@ fn should_return_error_on_invalid_token() {
// then
response.assert_status("HTTP/1.1 400 Bad Request");
assert_security_headers_for_embed(&response.headers);
assert_security_headers(&response.headers);
fetch.assert_no_more_requests();
}
@ -467,7 +467,7 @@ fn should_return_error_on_invalid_protocol() {
// then
response.assert_status("HTTP/1.1 400 Bad Request");
assert_security_headers_for_embed(&response.headers);
assert_security_headers(&response.headers);
fetch.assert_no_more_requests();
}
@ -492,7 +492,7 @@ fn should_disallow_non_get_requests() {
// then
response.assert_status("HTTP/1.1 405 Method Not Allowed");
assert_security_headers_for_embed(&response.headers);
assert_security_headers(&response.headers);
fetch.assert_no_more_requests();
}

View File

@ -36,8 +36,6 @@ mod fetch;
use self::registrar::FakeRegistrar;
use self::fetch::FakeFetch;
const SIGNER_PORT: u16 = 18180;
#[derive(Debug)]
struct FakeSync(bool);
impl SyncStatus for FakeSync {
@ -63,8 +61,7 @@ pub fn init_server<F, B>(process: F, io: IoHandler) -> (Server, Arc<FakeRegistra
let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
let mut builder = ServerBuilder::new(FetchClient::new().unwrap(), &dapps_path, registrar.clone());
builder.signer_address = Some(("127.0.0.1".into(), SIGNER_PORT));
let builder = ServerBuilder::new(FetchClient::new().unwrap(), &dapps_path, registrar.clone());
let server = process(builder).start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), io).unwrap();
(
server,
@ -122,13 +119,6 @@ pub fn serve() -> Server {
init_server(|builder| builder, Default::default()).0
}
pub fn serve_ui() -> Server {
init_server(|mut builder| {
builder.serve_ui = true;
builder
}, Default::default()).0
}
pub fn request(server: Server, request: &str) -> http_client::Response {
http_client::request(server.addr(), request)
}
@ -136,9 +126,6 @@ pub fn request(server: Server, request: &str) -> http_client::Response {
pub fn assert_security_headers(headers: &[String]) {
http_client::assert_security_headers_present(headers, None)
}
pub fn assert_security_headers_for_embed(headers: &[String]) {
http_client::assert_security_headers_present(headers, Some(SIGNER_PORT))
}
/// Webapps HTTP+RPC server build.
pub struct ServerBuilder<T: Fetch = FetchClient> {
@ -146,10 +133,8 @@ pub struct ServerBuilder<T: Fetch = FetchClient> {
registrar: Arc<RegistrarClient<Call=Asynchronous>>,
sync_status: Arc<SyncStatus>,
web_proxy_tokens: Arc<WebProxyTokens>,
signer_address: Option<(String, u16)>,
allowed_hosts: DomainsValidation<Host>,
fetch: T,
serve_ui: bool,
}
impl ServerBuilder {
@ -160,10 +145,8 @@ impl ServerBuilder {
registrar: registrar,
sync_status: Arc::new(FakeSync(false)),
web_proxy_tokens: Arc::new(|_| None),
signer_address: None,
allowed_hosts: DomainsValidation::Disabled,
fetch: fetch,
serve_ui: false,
}
}
}
@ -176,10 +159,8 @@ impl<T: Fetch> ServerBuilder<T> {
registrar: self.registrar,
sync_status: self.sync_status,
web_proxy_tokens: self.web_proxy_tokens,
signer_address: self.signer_address,
allowed_hosts: self.allowed_hosts,
fetch: fetch,
serve_ui: self.serve_ui,
}
}
@ -190,7 +171,6 @@ impl<T: Fetch> ServerBuilder<T> {
addr,
io,
self.allowed_hosts,
self.signer_address,
self.dapps_path,
vec![],
self.registrar,
@ -198,7 +178,6 @@ impl<T: Fetch> ServerBuilder<T> {
self.web_proxy_tokens,
Remote::new_sync(),
self.fetch,
self.serve_ui,
)
}
}
@ -215,7 +194,6 @@ impl Server {
addr: &SocketAddr,
io: IoHandler,
allowed_hosts: DomainsValidation<Host>,
signer_address: Option<(String, u16)>,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
registrar: Arc<RegistrarClient<Call=Asynchronous>>,
@ -223,7 +201,6 @@ impl Server {
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
fetch: F,
serve_ui: bool,
) -> io::Result<Server> {
let health = NodeHealth::new(
sync_status.clone(),
@ -231,23 +208,10 @@ impl Server {
remote.clone(),
);
let pool = ::futures_cpupool::CpuPool::new(1);
let middleware = if serve_ui {
Middleware::ui(
pool,
health,
DAPPS_DOMAIN.into(),
registrar,
sync_status,
fetch,
false,
)
} else {
let middleware =
Middleware::dapps(
pool,
health,
signer_address,
vec![],
vec![],
dapps_path,
extra_dapps,
DAPPS_DOMAIN.into(),
@ -255,8 +219,7 @@ impl Server {
sync_status,
web_proxy_tokens,
fetch,
)
};
);
let mut allowed_hosts: Option<Vec<Host>> = allowed_hosts.into();
allowed_hosts.as_mut().map(|hosts| {

View File

@ -1,62 +0,0 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve_ui, request, assert_security_headers};
#[test]
fn should_serve_home_js() {
// given
let server = serve_ui();
// when
let response = request(server,
"\
GET /inject.js HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Content-Type", "application/javascript");
assert_eq!(response.body.contains("function(){"), true, "Expected function in: {}", response.body);
assert_security_headers(&response.headers);
}
#[test]
fn should_serve_home() {
// given
let server = serve_ui();
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Content-Type", "text/html");
assert_security_headers(&response.headers);
}

View File

@ -20,7 +20,5 @@ mod helpers;
mod api;
mod fetch;
mod home;
mod redirection;
mod rpc;
mod validation;

View File

@ -1,206 +0,0 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve, request, assert_security_headers, assert_security_headers_for_embed};
#[test]
fn should_redirect_to_home() {
// given
let server = serve();
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 302 Found");
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
}
#[test]
fn should_redirect_to_home_with_domain() {
// given
let server = serve();
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: home.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 302 Found");
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
}
#[test]
fn should_redirect_to_home_when_trailing_slash_is_missing() {
// given
let server = serve();
// when
let response = request(server,
"\
GET /app HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 302 Found");
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
}
#[test]
fn should_display_404_on_invalid_dapp() {
// given
let server = serve();
// when
let response = request(server,
"\
GET /invaliddapp/ HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 404 Not Found");
assert_security_headers_for_embed(&response.headers);
}
#[test]
fn should_display_404_on_invalid_dapp_with_domain() {
// given
let server = serve();
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: invaliddapp.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 404 Not Found");
assert_security_headers_for_embed(&response.headers);
}
#[test]
fn should_serve_rpc() {
// given
let server = serve();
// when
let response = request(server,
"\
POST / HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
Content-Type: application/json\r\n
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, format!("4C\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#));
}
#[test]
fn should_serve_rpc_at_slash_rpc() {
// given
let server = serve();
// when
let response = request(server,
"\
POST /rpc HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
Content-Type: application/json\r\n
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, format!("4C\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#));
}
#[test]
fn should_serve_proxy_pac() {
// given
let server = serve();
// when
let response = request(server,
"\
GET /proxy/proxy.pac HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, "DB\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"home.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned());
assert_security_headers(&response.headers);
}
#[test]
fn should_serve_utils() {
// given
let server = serve();
// when
let response = request(server,
"\
GET /parity-utils/inject.js HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Content-Type", "application/javascript");
assert_eq!(response.body.contains("function(){"), true, "Expected function in: {}", response.body);
assert_security_headers(&response.headers);
}

View File

@ -37,26 +37,6 @@ fn should_reject_invalid_host() {
assert!(response.body.contains("Provided Host header is not whitelisted."), response.body);
}
#[test]
fn should_allow_valid_host() {
// given
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
// when
let response = request(server,
"\
GET /ui/ HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
}
#[test]
fn should_serve_dapps_domains() {
// given
@ -66,28 +46,7 @@ fn should_serve_dapps_domains() {
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: ui.web3.site\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
}
#[test]
// NOTE [todr] This is required for error pages to be styled properly.
fn should_allow_parity_utils_even_on_invalid_domain() {
// given
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
// when
let response = request(server,
"\
GET /parity-utils/styles.css HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Host: proxy.web3.site\r\n\
Connection: close\r\n\
\r\n\
{}

View File

@ -30,10 +30,9 @@ use handlers::{
ContentFetcherHandler, ContentHandler, ContentValidator, ValidatorResponse,
StreamingHandler,
};
use {Embeddable, WebProxyTokens};
use WebProxyTokens;
pub struct Web<F> {
embeddable_on: Embeddable,
web_proxy_tokens: Arc<WebProxyTokens>,
fetch: F,
pool: CpuPool,
@ -41,13 +40,11 @@ pub struct Web<F> {
impl<F: Fetch> Web<F> {
pub fn boxed(
embeddable_on: Embeddable,
web_proxy_tokens: Arc<WebProxyTokens>,
fetch: F,
pool: CpuPool,
) -> Box<Endpoint> {
Box::new(Web {
embeddable_on,
web_proxy_tokens,
fetch,
pool,
@ -64,7 +61,6 @@ impl<F: Fetch> Web<F> {
"Invalid parameter",
"Couldn't parse given parameter:",
path.app_params.get(0).map(String::as_str),
self.embeddable_on.clone()
))?;
let mut token_it = token_and_url.split('+');
@ -76,7 +72,7 @@ impl<F: Fetch> Web<F> {
Some(domain) => domain,
_ => {
return Err(ContentHandler::error(
StatusCode::BadRequest, "Invalid Access Token", "Invalid or old web proxy access token supplied.", Some("Try refreshing the page."), self.embeddable_on.clone()
StatusCode::BadRequest, "Invalid Access Token", "Invalid or old web proxy access token supplied.", Some("Try refreshing the page."),
));
}
};
@ -86,14 +82,14 @@ impl<F: Fetch> Web<F> {
Some(url) if url.starts_with("http://") || url.starts_with("https://") => url.to_owned(),
_ => {
return Err(ContentHandler::error(
StatusCode::BadRequest, "Invalid Protocol", "Invalid protocol used.", None, self.embeddable_on.clone()
StatusCode::BadRequest, "Invalid Protocol", "Invalid protocol used.", None,
));
}
};
if !target_url.starts_with(&*domain) {
return Err(ContentHandler::error(
StatusCode::BadRequest, "Invalid Domain", "Dapp attempted to access invalid domain.", Some(&target_url), self.embeddable_on.clone(),
StatusCode::BadRequest, "Invalid Domain", "Dapp attempted to access invalid domain.", Some(&target_url),
));
}
@ -128,10 +124,8 @@ impl<F: Fetch> Endpoint for Web<F> {
&target_url,
path,
WebInstaller {
embeddable_on: self.embeddable_on.clone(),
token,
},
self.embeddable_on.clone(),
self.fetch.clone(),
self.pool.clone(),
))
@ -139,7 +133,6 @@ impl<F: Fetch> Endpoint for Web<F> {
}
struct WebInstaller {
embeddable_on: Embeddable,
token: String,
}
@ -154,12 +147,10 @@ impl ContentValidator for WebInstaller {
fetch::BodyReader::new(response),
status,
mime,
self.embeddable_on,
);
if is_html {
handler.set_initial_content(&format!(
r#"<script src="/{}/inject.js"></script><script>history.replaceState({{}}, "", "/?{}{}/{}")</script>"#,
apps::UTILS_PATH,
r#"<script>history.replaceState({{}}, "", "/?{}{}/{}")</script>"#,
apps::URL_REFERER,
apps::WEB_PATH,
&self.token,

View File

@ -1,18 +0,0 @@
[package]
description = "Parity UI deprecation notice."
name = "parity-ui-deprecation"
version = "1.10.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"
[features]
default = ["with-syntex", "use-precompiled-js"]
use-precompiled-js = ["parity-dapps-glue/use-precompiled-js"]
with-syntex = ["parity-dapps-glue/with-syntex"]
[build-dependencies]
parity-dapps-glue = "1.9"
[dependencies]
parity-dapps-glue = "1.9"

View File

@ -1,21 +0,0 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate parity_dapps_glue;
fn main() {
parity_dapps_glue::generate();
}

View File

@ -1,119 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Parity</title>
<style>
/* Copyright 2015-2018 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
:root, :root body {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
background: rgb(95, 95, 95);
color: rgba(255, 255, 255, 0.75);
font-size: 16px;
font-family: 'Roboto', sans-serif;
font-weight: 300;
}
:root a, :root a:visited {
text-decoration: none;
cursor: pointer;
color: rgb(0, 151, 167); /* #f80 */
}
:root a:hover {
color: rgb(0, 174, 193);
}
h1,h2,h3,h4,h5,h6 {
font-weight: 300;
text-transform: uppercase;
text-decoration: none;
}
h1 {
font-size: 24px;
line-height: 36px;
color: rgb(0, 151, 167);
}
h2 {
font-size: 20px;
line-height: 34px;
}
code,kbd,pre,samp {
font-family: 'Roboto Mono', monospace;
}
.parity-navbar {
background: rgb(65, 65, 65);
height: 72px;
padding: 0 1rem;
display: flex;
justify-content: space-between;
}
.parity-status {
clear: both;
padding: 1rem;
margin: 1rem 0;
text-align: right;
opacity: 0.75;
}
.parity-box {
margin: 1rem;
padding: 1rem;
background-color: rgb(48, 48, 48);
box-sizing: border-box;
box-shadow: rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px;
border-radius: 2px;
z-index: 1;
color: #aaa;
}
.parity-box h1,
.parity-box h2,
.parity-box h3,
.parity-box h4,
.parity-box h5,
.parity-box h6 {
margin: 0;
}
</style>
</head>
<body>
<div class="parity-navbar">
</div>
<div class="parity-box">
<h1>The Parity UI has been split off into a standalone project.</h1>
<h3>Get the standalone Parity UI from <a href="https://github.com/Parity-JS/shell/releases">here</a></h3>
<p>
</p>
</div>
<div class="parity-status">
</div>
</body>
</html>

View File

@ -1,21 +0,0 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
#[cfg(feature = "with-syntex")]
include!(concat!(env!("OUT_DIR"), "/lib.rs"));
#[cfg(not(feature = "with-syntex"))]
include!("lib.rs.in");

View File

@ -1,55 +0,0 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate parity_dapps_glue;
use std::collections::HashMap;
use parity_dapps_glue::{WebApp, File, Info};
#[derive(WebAppFiles)]
#[webapp(path = "../build")]
pub struct App {
pub files: HashMap<&'static str, File>,
}
impl Default for App {
fn default() -> App {
App {
files: Self::files(),
}
}
}
impl WebApp for App {
fn file(&self, path: &str) -> Option<&File> {
self.files.get(path)
}
fn info(&self) -> Info {
Info {
name: "Parity Wallet info page",
version: env!("CARGO_PKG_VERSION"),
author: "Parity <admin@parity.io>",
description: "Deprecation notice for Parity Wallet",
icon_url: "icon.png",
}
}
}
#[test]
fn test_js() {
parity_dapps_glue::js::build(env!("CARGO_MANIFEST_DIR"), "build");
}

View File

@ -1,20 +0,0 @@
[package]
description = "Ethcore Parity UI"
homepage = "http://parity.io"
license = "GPL-3.0"
name = "parity-ui"
version = "1.12.0"
authors = ["Parity Technologies <admin@parity.io>"]
[build-dependencies]
rustc_version = "0.2"
[dependencies]
parity-ui-dev = { git = "https://github.com/parity-js/shell.git", rev = "eecaadcb9e421bce31e91680d14a20bbd38f92a2", optional = true }
parity-ui-old-dev = { git = "https://github.com/parity-js/dapp-wallet.git", rev = "65deb02e7c007a0fd8aab0c089c93e3fd1de6f87", optional = true }
parity-ui-precompiled = { git = "https://github.com/js-dist-paritytech/parity-master-1-10-shell.git", rev="bd25b41cd642c6b822d820dded3aa601a29aa079", optional = true }
parity-ui-old-precompiled = { git = "https://github.com/js-dist-paritytech/parity-master-1-10-wallet.git", rev="4b6f112412716cd05123d32eeb7fda448288a6c6", optional = true }
[features]
no-precompiled-js = ["parity-ui-dev", "parity-ui-old-dev"]
use-precompiled-js = ["parity-ui-precompiled", "parity-ui-old-precompiled"]

View File

@ -1,45 +0,0 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
#[cfg(feature = "parity-ui-dev")]
mod inner {
extern crate parity_ui_dev;
pub use self::parity_ui_dev::*;
}
#[cfg(feature = "parity-ui-precompiled")]
mod inner {
extern crate parity_ui_precompiled;
pub use self::parity_ui_precompiled::*;
}
#[cfg(feature = "parity-ui-old-dev")]
pub mod old {
extern crate parity_ui_old_dev;
pub use self::parity_ui_old_dev::*;
}
#[cfg(feature = "parity-ui-old-precompiled")]
pub mod old {
extern crate parity_ui_old_precompiled;
pub use self::parity_ui_old_precompiled::*;
}
pub use self::inner::*;

View File

@ -26,10 +26,6 @@ usage! {
// Arguments must start with arg_
// Flags must start with flag_
CMD cmd_ui {
"Manage ui",
}
CMD cmd_dapp
{
"Manage dapps",
@ -376,35 +372,10 @@ usage! {
"Provide a file containing passwords for unlocking accounts (signer, private account, validators).",
["UI options"]
FLAG flag_force_ui: (bool) = false, or |c: &Config| c.ui.as_ref()?.force.clone(),
"--force-ui",
"Enable Trusted UI WebSocket endpoint, even when --unlock is in use.",
FLAG flag_no_ui: (bool) = false, or |c: &Config| c.ui.as_ref()?.disable.clone(),
"--no-ui",
"Disable Trusted UI WebSocket endpoint.",
// NOTE [todr] For security reasons don't put this to config files
FLAG flag_ui_no_validation: (bool) = false, or |_| None,
"--ui-no-validation",
"Disable Origin and Host headers validation for Trusted UI. WARNING: INSECURE. Used only for development.",
ARG arg_ui_interface: (String) = "local", or |c: &Config| c.ui.as_ref()?.interface.clone(),
"--ui-interface=[IP]",
"Specify the hostname portion of the Trusted UI server, IP should be an interface's IP address, or local.",
ARG arg_ui_hosts: (String) = "none", or |c: &Config| c.ui.as_ref()?.hosts.as_ref().map(|vec| vec.join(",")),
"--ui-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\",.",
ARG arg_ui_path: (String) = "$BASE/signer", or |c: &Config| c.ui.as_ref()?.path.clone(),
"--ui-path=[PATH]",
"Specify directory where Trusted UIs tokens should be stored.",
ARG arg_ui_port: (u16) = 8180u16, or |c: &Config| c.ui.as_ref()?.port.clone(),
"--ui-port=[PORT]",
"Specify the port of Trusted UI server.",
["Networking options"]
FLAG flag_no_warp: (bool) = false, or |c: &Config| c.network.as_ref()?.warp.clone().map(|w| !w),
"--no-warp",
@ -948,6 +919,30 @@ usage! {
"--public-node",
"Does nothing; Public node is removed from Parity.",
FLAG flag_force_ui: (bool) = false, or |_| None,
"--force-ui",
"Does nothing; UI is now a separate project.",
FLAG flag_no_ui: (bool) = false, or |_| None,
"--no-ui",
"Does nothing; UI is now a separate project.",
FLAG flag_ui_no_validation: (bool) = false, or |_| None,
"--ui-no-validation",
"Does nothing; UI is now a separate project.",
ARG arg_ui_interface: (String) = "local", or |_| None,
"--ui-interface=[IP]",
"Does nothing; UI is now a separate project.",
ARG arg_ui_hosts: (String) = "none", or |_| None,
"--ui-hosts=[HOSTS]",
"Does nothing; UI is now a separate project.",
ARG arg_ui_port: (u16) = 8180u16, or |_| None,
"--ui-port=[PORT]",
"Does nothing; UI is now a separate project.",
ARG arg_dapps_port: (Option<u16>) = None, or |c: &Config| c.dapps.as_ref()?.port.clone(),
"--dapps-port=[PORT]",
"Dapps server is merged with RPC server. Use --jsonrpc-port.",
@ -1111,12 +1106,18 @@ struct PrivateTransactions {
#[derive(Default, Debug, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
struct Ui {
force: Option<bool>,
disable: Option<bool>,
port: Option<u16>,
interface: Option<String>,
hosts: Option<Vec<String>>,
path: Option<String>,
#[serde(rename="force")]
_legacy_force: Option<bool>,
#[serde(rename="disable")]
_legacy_disable: Option<bool>,
#[serde(rename="port")]
_legacy_port: Option<u16>,
#[serde(rename="interface")]
_legacy_interface: Option<String>,
#[serde(rename="hosts")]
_legacy_hosts: Option<Vec<String>>,
}
#[derive(Default, Debug, PartialEq, Deserialize)]
@ -1404,15 +1405,13 @@ mod tests {
let args = Args::parse(&["parity", "--secretstore-nodes", "abc@127.0.0.1:3333,cde@10.10.10.10:4444"]).unwrap();
assert_eq!(args.arg_secretstore_nodes, "abc@127.0.0.1:3333,cde@10.10.10.10:4444");
let args = Args::parse(&["parity", "--password", "~/.safe/1", "--password", "~/.safe/2", "--ui-port", "8123", "ui"]).unwrap();
let args = Args::parse(&["parity", "--password", "~/.safe/1", "--password", "~/.safe/2", "--ui-port", "8123"]).unwrap();
assert_eq!(args.arg_password, vec!["~/.safe/1".to_owned(), "~/.safe/2".to_owned()]);
assert_eq!(args.arg_ui_port, 8123);
assert_eq!(args.cmd_ui, true);
let args = Args::parse(&["parity", "--password", "~/.safe/1,~/.safe/2", "--ui-port", "8123", "ui"]).unwrap();
let args = Args::parse(&["parity", "--password", "~/.safe/1,~/.safe/2", "--ui-port", "8123"]).unwrap();
assert_eq!(args.arg_password, vec!["~/.safe/1".to_owned(), "~/.safe/2".to_owned()]);
assert_eq!(args.arg_ui_port, 8123);
assert_eq!(args.cmd_ui, true);
}
#[test]
@ -1476,7 +1475,6 @@ mod tests {
// then
assert_eq!(args, Args {
// Commands
cmd_ui: false,
cmd_dapp: false,
cmd_daemon: false,
cmd_account: false,
@ -1566,7 +1564,7 @@ mod tests {
flag_force_ui: false,
flag_no_ui: false,
arg_ui_port: 8180u16,
arg_ui_interface: "127.0.0.1".into(),
arg_ui_interface: "local".into(),
arg_ui_hosts: "none".into(),
arg_ui_path: "$HOME/.parity/signer".into(),
flag_ui_no_validation: false,
@ -1820,12 +1818,12 @@ mod tests {
fast_unlock: None,
}),
ui: Some(Ui {
force: None,
disable: Some(true),
port: None,
interface: None,
hosts: None,
path: None,
_legacy_force: None,
_legacy_disable: Some(true),
_legacy_port: None,
_legacy_interface: None,
_legacy_hosts: None,
}),
network: Some(Network {
warp: Some(false),

View File

@ -20,7 +20,6 @@ use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::collections::BTreeMap;
use std::cmp;
use std::str::FromStr;
use cli::{Args, ArgsError};
use hash::keccak;
use ethereum_types::{U256, H256, Address};
@ -34,7 +33,7 @@ use ethcore::miner::{stratum, MinerOptions};
use ethcore::verification::queue::VerifierSettings;
use miner::pool;
use rpc::{IpcConfiguration, HttpConfiguration, WsConfiguration, UiConfiguration};
use rpc::{IpcConfiguration, HttpConfiguration, WsConfiguration};
use parity_rpc::NetworkSettings;
use cache::CacheConfig;
use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, geth_ipc_path, parity_ipc_path, to_bootnodes, to_addresses, to_address, to_queue_strategy, to_queue_penalization, passwords_from_files};
@ -65,7 +64,7 @@ pub enum Cmd {
Account(AccountCmd),
ImportPresaleWallet(ImportWallet),
Blockchain(BlockchainCmd),
SignerToken(WsConfiguration, UiConfiguration, LogConfig),
SignerToken(WsConfiguration, LogConfig),
SignerSign {
id: Option<usize>,
pwfile: Option<PathBuf>,
@ -130,7 +129,6 @@ impl Configuration {
let http_conf = self.http_config()?;
let ipc_conf = self.ipc_config()?;
let net_conf = self.net_config()?;
let ui_conf = self.ui_config();
let network_id = self.network_id();
let cache_config = self.cache_config();
let tracing = self.args.arg_tracing.parse()?;
@ -150,7 +148,7 @@ impl Configuration {
let authfile = ::signer::codes_path(&ws_conf.signer_path);
if self.args.cmd_signer_new_token {
Cmd::SignerToken(ws_conf, ui_conf, logger_config.clone())
Cmd::SignerToken(ws_conf, logger_config.clone())
} else if self.args.cmd_signer_sign {
let pwfile = self.accounts_config()?.password_files.first().map(|pwfile| {
PathBuf::from(pwfile)
@ -381,13 +379,11 @@ impl Configuration {
net_settings: self.network_settings()?,
dapps_conf: dapps_conf,
ipfs_conf: ipfs_conf,
ui_conf: ui_conf,
secretstore_conf: secretstore_conf,
private_provider_conf: private_provider_conf,
private_encryptor_conf: private_enc_conf,
private_tx_enabled,
dapp: self.dapp_to_open()?,
ui: self.args.cmd_ui,
name: self.args.arg_identity,
custom_bootnodes: self.args.arg_bootnodes.is_some(),
no_periodic_snapshot: self.args.flag_no_periodic_snapshot,
@ -588,29 +584,11 @@ impl Configuration {
})
}
fn ui_port(&self) -> u16 {
self.args.arg_ports_shift + self.args.arg_ui_port
}
fn ntp_servers(&self) -> Vec<String> {
self.args.arg_ntp_servers.split(",").map(str::to_owned).collect()
}
fn ui_config(&self) -> UiConfiguration {
let ui = self.ui_enabled();
UiConfiguration {
enabled: ui.enabled,
interface: self.ui_interface(),
port: self.ui_port(),
hosts: self.ui_hosts(),
info_page_only: ui.info_page_only,
}
}
fn dapps_config(&self) -> DappsConfiguration {
let dev_ui = if self.args.flag_ui_no_validation { vec![("127.0.0.1".to_owned(), 3000)] } else { vec![] };
let ui_port = self.ui_port();
DappsConfiguration {
enabled: self.dapps_enabled(),
dapps_path: PathBuf::from(self.directories().dapps),
@ -619,31 +597,6 @@ impl Configuration {
} else {
vec![]
},
extra_embed_on: {
let mut extra_embed = dev_ui.clone();
match self.ui_hosts() {
// In case host validation is disabled allow all frame ancestors
None => {
// NOTE Chrome does not seem to support "*:<port>"
// we use `http(s)://*:<port>` instead.
extra_embed.push(("http://*".to_owned(), ui_port));
extra_embed.push(("https://*".to_owned(), ui_port));
},
Some(hosts) => extra_embed.extend(hosts.into_iter().filter_map(|host| {
let mut it = host.split(":");
let host = it.next();
let port = it.next().and_then(|v| u16::from_str(v).ok());
match (host, port) {
(Some(host), Some(port)) => Some((host.into(), port)),
(Some(host), None) => Some((host.into(), ui_port)),
_ => None,
}
})),
}
extra_embed
},
extra_script_src: dev_ui,
}
}
@ -863,10 +816,6 @@ impl Configuration {
Some(hosts)
}
fn ui_hosts(&self) -> Option<Vec<String>> {
self.hosts(&self.args.arg_ui_hosts, &self.ui_interface())
}
fn rpc_hosts(&self) -> Option<Vec<String>> {
self.hosts(&self.args.arg_jsonrpc_hosts, &self.rpc_interface())
}
@ -876,7 +825,7 @@ impl Configuration {
}
fn ws_origins(&self) -> Option<Vec<String>> {
if self.args.flag_unsafe_expose || self.args.flag_ui_no_validation {
if self.args.flag_unsafe_expose {
return None;
}
@ -925,7 +874,6 @@ impl Configuration {
}
fn ws_config(&self) -> Result<WsConfiguration, String> {
let ui = self.ui_config();
let http = self.http_config()?;
let support_token_api =
@ -941,7 +889,6 @@ impl Configuration {
origins: self.ws_origins(),
signer_path: self.directories().signer.into(),
support_token_api,
ui_address: ui.address(),
dapps_address: http.address(),
max_connections: self.args.arg_ws_max_connections,
};
@ -1065,10 +1012,6 @@ impl Configuration {
}.into()
}
fn ui_interface(&self) -> String {
self.interface(&self.args.arg_ui_interface)
}
fn rpc_interface(&self) -> String {
let rpc_interface = self.args.arg_rpcaddr.clone().unwrap_or(self.args.arg_jsonrpc_interface.clone());
self.interface(&rpc_interface)
@ -1184,24 +1127,6 @@ impl Configuration {
into_secretstore_service_contract_address(self.args.arg_secretstore_doc_sretr_contract.as_ref())
}
fn ui_enabled(&self) -> UiEnabled {
if self.args.flag_force_ui {
return UiEnabled {
enabled: true,
info_page_only: false,
};
}
let ui_disabled = self.args.arg_unlock.is_some() ||
self.args.flag_geth ||
self.args.flag_no_ui;
return UiEnabled {
enabled: (self.args.cmd_ui || !ui_disabled) && cfg!(feature = "ui-enabled"),
info_page_only: !self.args.cmd_ui,
}
}
fn verifier_settings(&self) -> VerifierSettings {
let mut settings = VerifierSettings::default();
settings.scale_verifiers = self.args.flag_scale_verifiers;
@ -1220,12 +1145,6 @@ impl Configuration {
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct UiEnabled {
pub enabled: bool,
pub info_page_only: bool,
}
fn into_secretstore_service_contract_address(s: &str) -> Result<Option<SecretStoreContractAddress>, String> {
match s {
"none" => Ok(None),
@ -1254,7 +1173,7 @@ mod tests {
use helpers::{default_network_config};
use params::SpecType;
use presale::ImportWallet;
use rpc::{WsConfiguration, UiConfiguration};
use rpc::WsConfiguration;
use rpc_apis::ApiSet;
use run::RunCmd;
@ -1438,16 +1357,9 @@ mod tests {
origins: Some(vec!["parity://*".into(),"chrome-extension://*".into(), "moz-extension://*".into()]),
hosts: Some(vec![]),
signer_path: expected.into(),
ui_address: Some("127.0.0.1:8180".into()),
dapps_address: Some("127.0.0.1:8545".into()),
support_token_api: true,
max_connections: 100,
}, UiConfiguration {
enabled: true,
interface: "127.0.0.1".into(),
port: 8180,
hosts: Some(vec![]),
info_page_only: true,
}, LogConfig {
color: true,
mode: None,
@ -1516,12 +1428,10 @@ mod tests {
net_settings: Default::default(),
dapps_conf: Default::default(),
ipfs_conf: Default::default(),
ui_conf: Default::default(),
secretstore_conf: Default::default(),
private_provider_conf: Default::default(),
private_encryptor_conf: Default::default(),
private_tx_enabled: false,
ui: false,
dapp: None,
name: "".into(),
custom_bootnodes: false,
@ -1704,49 +1614,6 @@ mod tests {
assert_eq!(conf2.ipfs_cors(), Some(vec!["http://parity.io".into(),"http://something.io".into()]));
}
#[test]
fn should_disable_signer_in_geth_compat() {
// given
// when
let conf0 = parse(&["parity", "--geth"]);
let conf1 = parse(&["parity", "--geth", "--force-ui"]);
let conf2 = parse(&["parity", "--geth", "ui"]);
let conf3 = parse(&["parity"]);
// then
assert_eq!(conf0.ui_enabled(), UiEnabled {
enabled: false,
info_page_only: true,
});
assert_eq!(conf1.ui_enabled(), UiEnabled {
enabled: true,
info_page_only: false,
});
assert_eq!(conf2.ui_enabled(), UiEnabled {
enabled: true,
info_page_only: false,
});
assert_eq!(conf3.ui_enabled(), UiEnabled {
enabled: true,
info_page_only: true,
});
}
#[test]
fn should_disable_signer_when_account_is_unlocked() {
// given
// when
let conf0 = parse(&["parity", "--unlock", "0x0"]);
// then
assert_eq!(conf0.ui_enabled(), UiEnabled {
enabled: false,
info_page_only: true,
});
}
#[test]
fn should_parse_ui_configuration() {
// given
@ -1757,69 +1624,22 @@ mod tests {
let conf2 = parse(&["parity", "--ui-path=signer", "--ui-port", "3123"]);
let conf3 = parse(&["parity", "--ui-path=signer", "--ui-interface", "test"]);
let conf4 = parse(&["parity", "--ui-path=signer", "--force-ui"]);
let conf5 = parse(&["parity", "--ui-path=signer", "ui"]);
// then
assert_eq!(conf0.directories().signer, "signer".to_owned());
assert_eq!(conf0.ui_config(), UiConfiguration {
enabled: true,
interface: "127.0.0.1".into(),
port: 8180,
hosts: Some(vec![]),
info_page_only: true,
});
assert!(conf1.ws_config().unwrap().hosts.is_some());
assert_eq!(conf1.ws_config().unwrap().origins, None);
assert_eq!(conf1.ws_config().unwrap().origins, Some(vec!["parity://*".into(), "chrome-extension://*".into(), "moz-extension://*".into()]));
assert_eq!(conf1.directories().signer, "signer".to_owned());
assert_eq!(conf1.ui_config(), UiConfiguration {
enabled: true,
interface: "127.0.0.1".into(),
port: 8180,
hosts: Some(vec![]),
info_page_only: true,
});
assert_eq!(conf1.dapps_config().extra_embed_on, vec![("127.0.0.1".to_owned(), 3000)]);
assert!(conf2.ws_config().unwrap().hosts.is_some());
assert_eq!(conf2.directories().signer, "signer".to_owned());
assert_eq!(conf2.ui_config(), UiConfiguration {
enabled: true,
interface: "127.0.0.1".into(),
port: 3123,
hosts: Some(vec![]),
info_page_only: true,
});
assert!(conf3.ws_config().unwrap().hosts.is_some());
assert_eq!(conf3.directories().signer, "signer".to_owned());
assert_eq!(conf3.ui_config(), UiConfiguration {
enabled: true,
interface: "test".into(),
port: 8180,
hosts: Some(vec![]),
info_page_only: true,
});
assert!(conf4.ws_config().unwrap().hosts.is_some());
assert_eq!(conf4.directories().signer, "signer".to_owned());
assert_eq!(conf4.ui_config(), UiConfiguration {
enabled: true,
interface: "127.0.0.1".into(),
port: 8180,
hosts: Some(vec![]),
info_page_only: false,
});
assert!(conf5.ws_config().unwrap().hosts.is_some());
assert_eq!(conf5.directories().signer, "signer".to_owned());
assert_eq!(conf5.ui_config(), UiConfiguration {
enabled: true,
interface: "127.0.0.1".into(),
port: 8180,
hosts: Some(vec![]),
info_page_only: false,
});
}
#[test]
@ -1976,7 +1796,6 @@ mod tests {
assert_eq!(conf0.network_settings().unwrap().rpc_port, 8546);
assert_eq!(conf0.http_config().unwrap().port, 8546);
assert_eq!(conf0.ws_config().unwrap().port, 8547);
assert_eq!(conf0.ui_config().port, 8181);
assert_eq!(conf0.secretstore_config().unwrap().port, 8084);
assert_eq!(conf0.secretstore_config().unwrap().http_port, 8083);
assert_eq!(conf0.ipfs_config().port, 5002);
@ -1987,7 +1806,6 @@ mod tests {
assert_eq!(conf1.network_settings().unwrap().rpc_port, 8545);
assert_eq!(conf1.http_config().unwrap().port, 8545);
assert_eq!(conf1.ws_config().unwrap().port, 8547);
assert_eq!(conf1.ui_config().port, 8181);
assert_eq!(conf1.secretstore_config().unwrap().port, 8084);
assert_eq!(conf1.secretstore_config().unwrap().http_port, 8083);
assert_eq!(conf1.ipfs_config().port, 5002);
@ -2007,8 +1825,6 @@ mod tests {
assert_eq!(&conf0.ws_config().unwrap().interface, "0.0.0.0");
assert_eq!(conf0.ws_config().unwrap().hosts, None);
assert_eq!(conf0.ws_config().unwrap().origins, None);
assert_eq!(&conf0.ui_config().interface, "0.0.0.0");
assert_eq!(conf0.ui_config().hosts, None);
assert_eq!(&conf0.secretstore_config().unwrap().interface, "0.0.0.0");
assert_eq!(&conf0.secretstore_config().unwrap().http_interface, "0.0.0.0");
assert_eq!(&conf0.ipfs_config().interface, "0.0.0.0");

View File

@ -39,8 +39,6 @@ pub struct Configuration {
pub enabled: bool,
pub dapps_path: PathBuf,
pub extra_dapps: Vec<PathBuf>,
pub extra_embed_on: Vec<(String, u16)>,
pub extra_script_src: Vec<(String, u16)>,
}
impl Default for Configuration {
@ -50,8 +48,6 @@ impl Default for Configuration {
enabled: true,
dapps_path: replace_home(&data_dir, "$BASE/dapps").into(),
extra_dapps: vec![],
extra_embed_on: vec![],
extra_script_src: vec![],
}
}
}
@ -163,8 +159,6 @@ pub struct Dependencies {
pub fetch: FetchClient,
pub pool: CpuPool,
pub signer: Arc<SignerService>,
pub ui_address: Option<(String, u16)>,
pub info_page_only: bool,
}
pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<Middleware>, String> {
@ -177,19 +171,6 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<Mi
configuration.dapps_path,
configuration.extra_dapps,
rpc::DAPPS_DOMAIN,
configuration.extra_embed_on,
configuration.extra_script_src,
).map(Some)
}
pub fn new_ui(enabled: bool, deps: Dependencies) -> Result<Option<Middleware>, String> {
if !enabled {
return Ok(None);
}
server::ui_middleware(
deps,
rpc::DAPPS_DOMAIN,
).map(Some)
}
@ -215,19 +196,10 @@ mod server {
_dapps_path: PathBuf,
_extra_dapps: Vec<PathBuf>,
_dapps_domain: &str,
_extra_embed_on: Vec<(String, u16)>,
_extra_script_src: Vec<(String, u16)>,
) -> Result<Middleware, String> {
Err("Your Parity version has been compiled without WebApps support.".into())
}
pub fn ui_middleware(
_deps: Dependencies,
_dapps_domain: &str,
) -> Result<Middleware, String> {
Err("Your Parity version has been compiled without UI support.".into())
}
pub fn service(_: &Option<Middleware>) -> Option<Arc<rpc_apis::DappsService>> {
None
}
@ -249,8 +221,6 @@ mod server {
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
dapps_domain: &str,
extra_embed_on: Vec<(String, u16)>,
extra_script_src: Vec<(String, u16)>,
) -> Result<Middleware, String> {
let signer = deps.signer;
let web_proxy_tokens = Arc::new(move |token| signer.web_proxy_access_token_domain(&token));
@ -258,9 +228,6 @@ mod server {
Ok(parity_dapps::Middleware::dapps(
deps.pool,
deps.node_health,
deps.ui_address,
extra_embed_on,
extra_script_src,
dapps_path,
extra_dapps,
dapps_domain,
@ -271,21 +238,6 @@ mod server {
))
}
pub fn ui_middleware(
deps: Dependencies,
dapps_domain: &str,
) -> Result<Middleware, String> {
Ok(parity_dapps::Middleware::ui(
deps.pool,
deps.node_health,
dapps_domain,
deps.contract_client,
deps.sync_status,
deps.fetch,
deps.info_page_only,
))
}
pub fn service(middleware: &Option<Middleware>) -> Option<Arc<rpc_apis::DappsService>> {
middleware.as_ref().map(|m| Arc::new(DappsServiceWrapper {
endpoints: m.endpoints().clone(),

View File

@ -118,15 +118,13 @@ mod user_defaults;
mod whisper;
mod db;
use std::net::{TcpListener};
use std::io::BufReader;
use std::fs::File;
use ansi_term::Style;
use hash::keccak_buffer;
use cli::Args;
use configuration::{Cmd, Execute};
use deprecated::find_deprecated;
use ethcore_logger::{Config as LogConfig, setup_log};
use ethcore_logger::setup_log;
pub use self::configuration::Configuration;
pub use self::run::RunningClient;
@ -195,24 +193,6 @@ fn execute<Cr, Rr>(command: Execute, on_client_rq: Cr, on_updater_rq: Rr) -> Res
match command.cmd {
Cmd::Run(run_cmd) => {
if run_cmd.ui_conf.enabled && !run_cmd.ui_conf.info_page_only {
warn!("{}", Style::new().bold().paint("Parity browser interface is deprecated. It's going to be removed in the next version, use standalone Parity UI instead."));
warn!("{}", Style::new().bold().paint("Standalone Parity UI: https://github.com/Parity-JS/shell/releases"));
}
if run_cmd.ui && run_cmd.dapps_conf.enabled {
// Check if Parity is already running
let addr = format!("{}:{}", run_cmd.ui_conf.interface, run_cmd.ui_conf.port);
if !TcpListener::bind(&addr as &str).is_ok() {
return open_ui(&run_cmd.ws_conf, &run_cmd.ui_conf, &run_cmd.logger_config).map(|_| ExecutionAction::Instant(None));
}
}
// start ui
if run_cmd.ui {
open_ui(&run_cmd.ws_conf, &run_cmd.ui_conf, &run_cmd.logger_config)?;
}
if let Some(ref dapp) = run_cmd.dapp {
open_dapp(&run_cmd.dapps_conf, &run_cmd.http_conf, dapp)?;
}
@ -225,7 +205,7 @@ fn execute<Cr, Rr>(command: Execute, on_client_rq: Cr, on_updater_rq: Rr) -> Res
Cmd::Account(account_cmd) => account::execute(account_cmd).map(|s| ExecutionAction::Instant(Some(s))),
Cmd::ImportPresaleWallet(presale_cmd) => presale::execute(presale_cmd).map(|s| ExecutionAction::Instant(Some(s))),
Cmd::Blockchain(blockchain_cmd) => blockchain::execute(blockchain_cmd).map(|_| ExecutionAction::Instant(None)),
Cmd::SignerToken(ws_conf, ui_conf, logger_config) => signer::execute(ws_conf, ui_conf, logger_config).map(|s| ExecutionAction::Instant(Some(s))),
Cmd::SignerToken(ws_conf, logger_config) => signer::execute(ws_conf, logger_config).map(|s| ExecutionAction::Instant(Some(s))),
Cmd::SignerSign { id, pwfile, port, authfile } => rpc_cli::signer_sign(id, pwfile, port, authfile).map(|s| ExecutionAction::Instant(Some(s))),
Cmd::SignerList { port, authfile } => rpc_cli::signer_list(port, authfile).map(|s| ExecutionAction::Instant(Some(s))),
Cmd::SignerReject { id, port, authfile } => rpc_cli::signer_reject(id, port, authfile).map(|s| ExecutionAction::Instant(Some(s))),
@ -257,19 +237,6 @@ pub fn start<Cr, Rr>(conf: Configuration, on_client_rq: Cr, on_updater_rq: Rr) -
execute(conf.into_command()?, on_client_rq, on_updater_rq)
}
fn open_ui(ws_conf: &rpc::WsConfiguration, ui_conf: &rpc::UiConfiguration, logger_config: &LogConfig) -> Result<(), String> {
if !ui_conf.enabled {
return Err("Cannot use UI command with UI turned off.".into())
}
let token = signer::generate_token_and_url(ws_conf, ui_conf, logger_config)?;
// Open a browser
url::open(&token.url).map_err(|e| format!("{}", e))?;
// Print a message
println!("{}", token.message);
Ok(())
}
fn open_dapp(dapps_conf: &dapps::Configuration, rpc_conf: &rpc::HttpConfiguration, dapp: &str) -> Result<(), String> {
if !dapps_conf.enabled {
return Err("Cannot use DAPP command with Dapps turned off.".into())

View File

@ -68,58 +68,6 @@ impl Default for HttpConfiguration {
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct UiConfiguration {
pub enabled: bool,
pub interface: String,
pub port: u16,
pub hosts: Option<Vec<String>>,
pub info_page_only: bool,
}
impl UiConfiguration {
pub fn address(&self) -> Option<rpc::Host> {
address(self.enabled, &self.interface, self.port, &self.hosts)
}
pub fn redirection_address(&self) -> Option<(String, u16)> {
self.address().map(|host| {
let mut it = host.split(':');
let hostname: Option<String> = it.next().map(|s| s.to_owned());
let port: Option<u16> = it.next().and_then(|s| s.parse().ok());
(hostname.unwrap_or_else(|| "localhost".into()), port.unwrap_or(8180))
})
}
}
impl From<UiConfiguration> for HttpConfiguration {
fn from(conf: UiConfiguration) -> Self {
HttpConfiguration {
enabled: conf.enabled,
interface: conf.interface,
port: conf.port,
apis: rpc_apis::ApiSet::UnsafeContext,
cors: Some(vec![]),
hosts: conf.hosts,
server_threads: 1,
processing_threads: 0,
}
}
}
impl Default for UiConfiguration {
fn default() -> Self {
UiConfiguration {
enabled: cfg!(feature = "ui-enabled"),
port: 8180,
interface: "127.0.0.1".into(),
hosts: Some(vec![]),
info_page_only: true,
}
}
}
#[derive(Debug, PartialEq)]
pub struct IpcConfiguration {
pub enabled: bool,
@ -153,7 +101,6 @@ pub struct WsConfiguration {
pub hosts: Option<Vec<String>>,
pub signer_path: PathBuf,
pub support_token_api: bool,
pub ui_address: Option<rpc::Host>,
pub dapps_address: Option<rpc::Host>,
}
@ -170,7 +117,6 @@ impl Default for WsConfiguration {
hosts: Some(Vec::new()),
signer_path: replace_home(&data_dir, "$BASE/signer").into(),
support_token_api: true,
ui_address: Some("127.0.0.1:8180".into()),
dapps_address: Some("127.0.0.1:8545".into()),
}
}
@ -225,9 +171,8 @@ pub fn new_ws<D: rpc_apis::Dependencies>(
};
let remote = deps.remote.clone();
let ui_address = conf.ui_address.clone();
let allowed_origins = into_domains(with_domain(conf.origins, domain, &ui_address, &conf.dapps_address));
let allowed_hosts = into_domains(with_domain(conf.hosts, domain, &Some(url.clone().into()), &None));
let allowed_origins = into_domains(with_domain(conf.origins, domain, &conf.dapps_address));
let allowed_hosts = into_domains(with_domain(conf.hosts, domain, &Some(url.clone().into())));
let signer_path;
let path = match conf.support_token_api {
@ -276,7 +221,7 @@ pub fn new_http<D: rpc_apis::Dependencies>(
let remote = deps.remote.clone();
let cors_domains = into_domains(conf.cors);
let allowed_hosts = into_domains(with_domain(conf.hosts, domain, &Some(url.clone().into()), &None));
let allowed_hosts = into_domains(with_domain(conf.hosts, domain, &Some(url.clone().into())));
let start_result = rpc::start_http(
&addr,
@ -328,7 +273,7 @@ fn into_domains<T: From<String>>(items: Option<Vec<String>>) -> DomainsValidatio
items.map(|vals| vals.into_iter().map(T::from).collect()).into()
}
fn with_domain(items: Option<Vec<String>>, domain: &str, ui_address: &Option<rpc::Host>, dapps_address: &Option<rpc::Host>) -> Option<Vec<String>> {
fn with_domain(items: Option<Vec<String>>, domain: &str, dapps_address: &Option<rpc::Host>) -> Option<Vec<String>> {
fn extract_port(s: &str) -> Option<u16> {
s.split(':').nth(1).and_then(|s| s.parse().ok())
}
@ -347,7 +292,6 @@ fn with_domain(items: Option<Vec<String>>, domain: &str, ui_address: &Option<rpc
}
};
add_hosts(ui_address);
add_hosts(dapps_address);
}
items.into_iter().collect()

View File

@ -112,13 +112,11 @@ pub struct RunCmd {
pub net_settings: NetworkSettings,
pub dapps_conf: dapps::Configuration,
pub ipfs_conf: ipfs::Configuration,
pub ui_conf: rpc::UiConfiguration,
pub secretstore_conf: secretstore::Configuration,
pub private_provider_conf: ProviderConfig,
pub private_encryptor_conf: EncryptorConfig,
pub private_tx_enabled: bool,
pub dapp: Option<String>,
pub ui: bool,
pub name: String,
pub custom_bootnodes: bool,
pub stratum: Option<stratum::Options>,
@ -185,7 +183,7 @@ fn execute_light_impl(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<Runnin
execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, &cmd.compaction)?;
// create dirs used by parity
cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.ui_conf.enabled, cmd.secretstore_conf.enabled)?;
cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.acc_conf.unlocked_accounts.len() == 0, cmd.secretstore_conf.enabled)?;
//print out running parity environment
print_running_environment(&spec.name, &cmd.dirs, &db_dirs, &cmd.dapps_conf);
@ -323,13 +321,10 @@ fn execute_light_impl(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<Runnin
fetch: fetch.clone(),
pool: cpu_pool.clone(),
signer: signer_service.clone(),
ui_address: cmd.ui_conf.redirection_address(),
info_page_only: cmd.ui_conf.info_page_only,
})
};
let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps.clone())?;
let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, dapps_deps)?;
// start RPCs
let dapps_service = dapps::service(&dapps_middleware);
@ -373,7 +368,6 @@ fn execute_light_impl(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<Runnin
let ws_server = rpc::new_ws(cmd.ws_conf, &dependencies)?;
let http_server = rpc::new_http("HTTP JSON-RPC", "jsonrpc", cmd.http_conf.clone(), &dependencies, dapps_middleware)?;
let ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?;
let ui_server = rpc::new_http("Parity Wallet (UI)", "ui", cmd.ui_conf.clone().into(), &dependencies, ui_middleware)?;
// the informant
let informant = Arc::new(Informant::new(
@ -394,7 +388,7 @@ fn execute_light_impl(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<Runnin
rpc: rpc_direct,
informant,
client,
keep_alive: Box::new((event_loop, service, ws_server, http_server, ipc_server, ui_server)),
keep_alive: Box::new((event_loop, service, ws_server, http_server, ipc_server)),
}
})
}
@ -444,7 +438,7 @@ fn execute_impl<Cr, Rr>(cmd: RunCmd, logger: Arc<RotatingLogger>, on_client_rq:
execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, &cmd.compaction)?;
// create dirs used by parity
cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.ui_conf.enabled, cmd.secretstore_conf.enabled)?;
cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.acc_conf.unlocked_accounts.len() == 0, cmd.secretstore_conf.enabled)?;
// run in daemon mode
if let Some(pid_file) = cmd.daemon {
@ -756,12 +750,9 @@ fn execute_impl<Cr, Rr>(cmd: RunCmd, logger: Arc<RotatingLogger>, on_client_rq:
fetch: fetch.clone(),
pool: cpu_pool.clone(),
signer: signer_service.clone(),
ui_address: cmd.ui_conf.redirection_address(),
info_page_only: cmd.ui_conf.info_page_only,
})
};
let dapps_middleware = dapps::new(cmd.dapps_conf.clone(), dapps_deps.clone())?;
let ui_middleware = dapps::new_ui(cmd.ui_conf.enabled, dapps_deps)?;
let dapps_service = dapps::service(&dapps_middleware);
let deps_for_rpc_apis = Arc::new(rpc_apis::FullDependencies {
@ -807,8 +798,6 @@ fn execute_impl<Cr, Rr>(cmd: RunCmd, logger: Arc<RotatingLogger>, on_client_rq:
let ws_server = rpc::new_ws(cmd.ws_conf.clone(), &dependencies)?;
let ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?;
let http_server = rpc::new_http("HTTP JSON-RPC", "jsonrpc", cmd.http_conf.clone(), &dependencies, dapps_middleware)?;
// the ui server
let ui_server = rpc::new_http("UI WALLET", "ui", cmd.ui_conf.clone().into(), &dependencies, ui_middleware)?;
// secret store key server
let secretstore_deps = secretstore::Dependencies {
@ -881,7 +870,7 @@ fn execute_impl<Cr, Rr>(cmd: RunCmd, logger: Arc<RotatingLogger>, on_client_rq:
informant,
client,
client_service: Arc::new(service),
keep_alive: Box::new((watcher, updater, ws_server, http_server, ipc_server, ui_server, secretstore_key_server, ipfs_server, event_loop)),
keep_alive: Box::new((watcher, updater, ws_server, http_server, ipc_server, secretstore_key_server, ipfs_server, event_loop)),
}
})
}

View File

@ -28,7 +28,6 @@ pub const CODES_FILENAME: &'static str = "authcodes";
pub struct NewToken {
pub token: String,
pub url: String,
pub message: String,
}
@ -49,22 +48,19 @@ pub fn codes_path(path: &Path) -> PathBuf {
p
}
pub fn execute(ws_conf: rpc::WsConfiguration, ui_conf: rpc::UiConfiguration, logger_config: LogConfig) -> Result<String, String> {
Ok(generate_token_and_url(&ws_conf, &ui_conf, &logger_config)?.message)
pub fn execute(ws_conf: rpc::WsConfiguration, logger_config: LogConfig) -> Result<String, String> {
Ok(generate_token_and_url(&ws_conf, &logger_config)?.message)
}
pub fn generate_token_and_url(ws_conf: &rpc::WsConfiguration, ui_conf: &rpc::UiConfiguration, logger_config: &LogConfig) -> Result<NewToken, String> {
pub fn generate_token_and_url(ws_conf: &rpc::WsConfiguration, logger_config: &LogConfig) -> Result<NewToken, String> {
let code = generate_new_token(&ws_conf.signer_path, logger_config.color).map_err(|err| format!("Error generating token: {:?}", err))?;
let auth_url = format!("http://{}:{}/#/auth?token={}", ui_conf.interface, ui_conf.port, code);
let colored = |s: String| match logger_config.color {
true => format!("{}", White.bold().paint(s)),
false => s,
};
if !ui_conf.enabled {
return Ok(NewToken {
Ok(NewToken {
token: code.clone(),
url: auth_url.clone(),
message: format!(
r#"
Generated token:
@ -75,22 +71,6 @@ Generated token:
})
}
// And print in to the console
Ok(NewToken {
token: code.clone(),
url: auth_url.clone(),
message: format!(
r#"
Open: {}
to authorize your browser.
Or use the generated token:
{}"#,
colored(auth_url),
code
)
})
}
fn generate_new_token(path: &Path, logger_config_color: bool) -> io::Result<String> {
let path = codes_path(path);
let mut codes = parity_rpc::AuthCodes::from_file(&path)?;