Renaming dapps repos. Updating dapps (#1142)

* Renaming dapps repos. Updating dapps

* Skipping legacy options for compatibility. Fixing tabs

* Fixing CLI options
This commit is contained in:
Tomasz Drwięga
2016-05-26 18:21:15 +02:00
committed by Gav Wood
parent 3a5e7fc2ed
commit cc1a334ba7
28 changed files with 236 additions and 182 deletions

40
dapps/Cargo.toml Normal file
View File

@@ -0,0 +1,40 @@
[package]
description = "Parity Dapps crate"
name = "ethcore-dapps"
version = "1.2.0"
license = "GPL-3.0"
authors = ["Ethcore <admin@ethcore.io"]
build = "build.rs"
[lib]
[dependencies]
log = "0.3"
jsonrpc-core = "2.0"
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.git" }
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }
url = "1.0"
rustc-serialize = "0.3"
serde = "0.7.0"
serde_json = "0.7.0"
serde_macros = { version = "0.7.0", optional = true }
ethcore-rpc = { path = "../rpc" }
ethcore-util = { path = "../util" }
parity-dapps = { git = "https://github.com/ethcore/parity-dapps-rs.git", version = "0.3" }
# List of apps
parity-dapps-status = { git = "https://github.com/ethcore/parity-dapps-status-rs.git", version = "0.5.0" }
parity-dapps-builtins = { git = "https://github.com/ethcore/parity-dapps-builtins-rs.git", version = "0.5.0" }
parity-dapps-wallet = { git = "https://github.com/ethcore/parity-dapps-wallet-rs.git", version = "0.5.0", optional = true }
parity-dapps-dao = { git = "https://github.com/ethcore/parity-dapps-dao-rs.git", version = "0.3.0", optional = true }
parity-dapps-makerotc = { git = "https://github.com/ethcore/parity-dapps-makerotc-rs.git", version = "0.2.0", optional = true }
clippy = { version = "0.0.69", optional = true}
[build-dependencies]
serde_codegen = { version = "0.7.0", optional = true }
syntex = "0.32.0"
[features]
default = ["serde_codegen", "extra-dapps"]
extra-dapps = ["parity-dapps-wallet", "parity-dapps-dao", "parity-dapps-makerotc"]
nightly = ["serde_macros"]
dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"]

45
dapps/build.rs Normal file
View File

@@ -0,0 +1,45 @@
// Copyright 2015, 2016 Ethcore (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(not(feature = "serde_macros"))]
mod inner {
extern crate syntex;
extern crate serde_codegen;
use std::env;
use std::path::Path;
pub fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let src = Path::new("./src/api/mod.rs.in");
let dst = Path::new(&out_dir).join("mod.rs");
let mut registry = syntex::Registry::new();
serde_codegen::register(&mut registry);
registry.expand("", &src, &dst).unwrap();
}
}
#[cfg(feature = "serde_macros")]
mod inner {
pub fn main() {}
}
fn main() {
inner::main();
}

63
dapps/src/api/api.rs Normal file
View File

@@ -0,0 +1,63 @@
// Copyright 2015, 2016 Ethcore (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 std::sync::Arc;
use endpoint::{Endpoint, Endpoints, Handler, EndpointPath};
use api::response::as_json;
pub struct RestApi {
endpoints: Arc<Endpoints>,
}
#[derive(Debug, PartialEq, Serialize)]
struct App {
pub id: String,
pub name: String,
pub description: String,
pub version: String,
pub author: String,
#[serde(rename="iconUrl")]
pub icon_url: String,
}
impl RestApi {
pub fn new(endpoints: Arc<Endpoints>) -> Box<Endpoint> {
Box::new(RestApi {
endpoints: endpoints
})
}
fn list_apps(&self) -> Vec<App> {
self.endpoints.iter().filter_map(|(ref k, ref e)| {
e.info().map(|ref info| App {
id: k.to_owned().clone(),
name: info.name.to_owned(),
description: info.description.to_owned(),
version: info.version.to_owned(),
author: info.author.to_owned(),
icon_url: info.icon_url.to_owned(),
})
}).collect()
}
}
impl Endpoint for RestApi {
fn to_handler(&self, _path: EndpointPath) -> Box<Handler> {
as_json(&self.list_apps())
}
}

28
dapps/src/api/mod.rs Normal file
View File

@@ -0,0 +1,28 @@
// Copyright 2015, 2016 Ethcore (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/>.
//! REST API
#![warn(missing_docs)]
#![cfg_attr(feature="nightly", feature(custom_derive, custom_attribute, plugin))]
#![cfg_attr(feature="nightly", plugin(serde_macros, clippy))]
#[cfg(feature = "serde_macros")]
include!("mod.rs.in");
#[cfg(not(feature = "serde_macros"))]
include!(concat!(env!("OUT_DIR"), "/mod.rs"));

20
dapps/src/api/mod.rs.in Normal file
View File

@@ -0,0 +1,20 @@
// Copyright 2015, 2016 Ethcore (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/>.
mod api;
mod response;
pub use self::api::RestApi;

23
dapps/src/api/response.rs Normal file
View File

@@ -0,0 +1,23 @@
// Copyright 2015, 2016 Ethcore (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 serde::Serialize;
use serde_json;
use endpoint::{ContentHandler, Handler};
pub fn as_json<T : Serialize>(val: &T) -> Box<Handler> {
Box::new(ContentHandler::new(serde_json::to_string(val).unwrap(), "application/json".to_owned()))
}

79
dapps/src/apps.rs Normal file
View File

@@ -0,0 +1,79 @@
// Copyright 2015, 2016 Ethcore (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 endpoint::{Endpoints, Endpoint};
use page::PageEndpoint;
use proxypac::ProxyPac;
use parity_dapps::WebApp;
extern crate parity_dapps_status;
extern crate parity_dapps_builtins;
pub const DAPPS_DOMAIN : &'static str = ".parity";
pub const RPC_PATH : &'static str = "rpc";
pub const API_PATH : &'static str = "api";
pub const UTILS_PATH : &'static str = "parity-utils";
pub fn main_page() -> &'static str {
"/home/"
}
pub fn utils() -> Box<Endpoint> {
Box::new(PageEndpoint::with_prefix(parity_dapps_builtins::App::default(), UTILS_PATH.to_owned()))
}
pub fn all_endpoints() -> Endpoints {
let mut pages = Endpoints::new();
pages.insert("proxy".to_owned(), ProxyPac::boxed());
insert::<parity_dapps_status::App>(&mut pages, "status");
insert::<parity_dapps_status::App>(&mut pages, "parity");
insert::<parity_dapps_builtins::App>(&mut pages, "home");
wallet_page(&mut pages);
daodapp_page(&mut pages);
makerotc_page(&mut pages);
pages
}
#[cfg(feature = "parity-dapps-wallet")]
fn wallet_page(pages: &mut Endpoints) {
extern crate parity_dapps_wallet;
insert::<parity_dapps_wallet::App>(pages, "wallet");
}
#[cfg(not(feature = "parity-dapps-wallet"))]
fn wallet_page(_pages: &mut Endpoints) {}
#[cfg(feature = "parity-dapps-daodapp")]
fn daodapp_page(pages: &mut Endpoints) {
extern crate parity_dapps_daodapp;
insert::<parity_dapps_daodapp::App>(pages, "dao");
}
#[cfg(not(feature = "parity-dapps-daodapp"))]
fn daodapp_page(_pages: &mut Endpoints) {}
#[cfg(feature = "parity-dapps-makerotc")]
fn makerotc_page(pages: &mut Endpoints) {
extern crate parity_dapps_makerotc;
insert::<parity_dapps_makerotc::App>(pages, "makerotc");
}
#[cfg(not(feature = "parity-dapps-makerotc"))]
fn makerotc_page(_pages: &mut Endpoints) {}
fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str) {
pages.insert(id.to_owned(), Box::new(PageEndpoint::new(T::default())));
}

99
dapps/src/endpoint.rs Normal file
View File

@@ -0,0 +1,99 @@
// Copyright 2015, 2016 Ethcore (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/>.
//! URL Endpoint traits
use hyper::status::StatusCode;
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use std::io::Write;
use std::collections::HashMap;
#[derive(Debug, PartialEq, Default, Clone)]
pub struct EndpointPath {
pub app_id: String,
pub host: String,
pub port: u16,
}
#[derive(Debug, PartialEq)]
pub struct EndpointInfo {
pub name: &'static str,
pub description: &'static str,
pub version: &'static str,
pub author: &'static str,
pub icon_url: &'static str,
}
pub trait Endpoint : Send + Sync {
fn info(&self) -> Option<EndpointInfo> { None }
fn to_handler(&self, path: EndpointPath) -> Box<server::Handler<HttpStream>>;
}
pub type Endpoints = HashMap<String, Box<Endpoint>>;
pub type Handler = server::Handler<HttpStream>;
pub struct ContentHandler {
content: String,
mimetype: String,
write_pos: usize,
}
impl ContentHandler {
pub fn new(content: String, mimetype: String) -> Self {
ContentHandler {
content: content,
mimetype: mimetype,
write_pos: 0
}
}
}
impl server::Handler<HttpStream> for ContentHandler {
fn on_request(&mut self, _request: server::Request) -> Next {
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(StatusCode::Ok);
res.headers_mut().set(header::ContentType(self.mimetype.parse().unwrap()));
Next::write()
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
let bytes = self.content.as_bytes();
if self.write_pos == bytes.len() {
return Next::end();
}
match encoder.write(&bytes[self.write_pos..]) {
Ok(bytes) => {
self.write_pos += bytes;
Next::write()
},
Err(e) => match e.kind() {
::std::io::ErrorKind::WouldBlock => Next::write(),
_ => Next::end()
},
}
}
}

164
dapps/src/lib.rs Normal file
View File

@@ -0,0 +1,164 @@
// Copyright 2015, 2016 Ethcore (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/>.
//! Ethcore Webapplications for Parity
//! ```
//! extern crate jsonrpc_core;
//! extern crate ethcore_dapps;
//!
//! use std::sync::Arc;
//! use jsonrpc_core::IoHandler;
//! use ethcore_dapps::*;
//!
//! struct SayHello;
//! impl MethodCommand for SayHello {
//! fn execute(&self, _params: Params) -> Result<Value, Error> {
//! Ok(Value::String("hello".to_string()))
//! }
//! }
//!
//! fn main() {
//! let io = IoHandler::new();
//! io.add_method("say_hello", SayHello);
//! let _server = Server::start_unsecure_http(
//! &"127.0.0.1:3030".parse().unwrap(),
//! Arc::new(io)
//! );
//! }
//! ```
//!
#![warn(missing_docs)]
#![cfg_attr(feature="nightly", plugin(clippy))]
#[macro_use]
extern crate log;
extern crate url;
extern crate hyper;
extern crate serde;
extern crate serde_json;
extern crate jsonrpc_core;
extern crate jsonrpc_http_server;
extern crate parity_dapps;
mod endpoint;
mod apps;
mod page;
mod router;
mod rpc;
mod api;
mod proxypac;
use std::sync::{Arc, Mutex};
use std::net::SocketAddr;
use std::collections::HashMap;
use jsonrpc_core::{IoHandler, IoDelegate};
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
static DAPPS_DOMAIN : &'static str = ".parity";
/// Webapps HTTP+RPC server build.
pub struct ServerBuilder {
handler: Arc<IoHandler>,
}
impl ServerBuilder {
/// Construct new dapps server
pub fn new() -> Self {
ServerBuilder {
handler: Arc::new(IoHandler::new())
}
}
/// Add io delegate.
pub fn add_delegate<D>(&self, delegate: IoDelegate<D>) where D: Send + Sync + 'static {
self.handler.add_delegate(delegate);
}
/// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error.
pub fn start_unsecure_http(&self, addr: &SocketAddr) -> Result<Server, ServerError> {
Server::start_http(addr, NoAuth, self.handler.clone())
}
/// Asynchronously start server with `HTTP Basic Authentication`,
/// return result with `Server` handle on success or an error.
pub fn start_basic_auth_http(&self, addr: &SocketAddr, username: &str, password: &str) -> Result<Server, ServerError> {
Server::start_http(addr, HttpBasicAuth::single_user(username, password), self.handler.clone())
}
}
/// Webapps HTTP server.
pub struct Server {
server: Option<hyper::server::Listening>,
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
}
impl Server {
fn start_http<A: Authorization + 'static>(addr: &SocketAddr, authorization: A, handler: Arc<IoHandler>) -> Result<Server, ServerError> {
let panic_handler = Arc::new(Mutex::new(None));
let authorization = Arc::new(authorization);
let endpoints = Arc::new(apps::all_endpoints());
let special = Arc::new({
let mut special = HashMap::new();
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone()));
special.insert(router::SpecialEndpoint::Api, api::RestApi::new(endpoints.clone()));
special.insert(router::SpecialEndpoint::Utils, apps::utils());
special
});
try!(hyper::Server::http(addr))
.handle(move |_| router::Router::new(
apps::main_page(),
endpoints.clone(),
special.clone(),
authorization.clone(),
))
.map(|l| Server {
server: Some(l),
panic_handler: panic_handler,
})
.map_err(ServerError::from)
}
/// Set callback for panics.
pub fn set_panic_handler<F>(&self, handler: F) where F : Fn() -> () + Send + 'static {
*self.panic_handler.lock().unwrap() = Some(Box::new(handler));
}
}
impl Drop for Server {
fn drop(&mut self) {
self.server.take().unwrap().close()
}
}
/// Webapp Server startup error
#[derive(Debug)]
pub enum ServerError {
/// Wrapped `std::io::Error`
IoError(std::io::Error),
/// Other `hyper` error
Other(hyper::error::Error),
}
impl From<hyper::error::Error> for ServerError {
fn from(err: hyper::error::Error) -> Self {
match err {
hyper::error::Error::Io(e) => ServerError::IoError(e),
e => ServerError::Other(e),
}
}
}

208
dapps/src/page/mod.rs Normal file
View File

@@ -0,0 +1,208 @@
// Copyright 2015, 2016 Ethcore (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 std::sync::Arc;
use std::io::Write;
use hyper::uri::RequestUri;
use hyper::server;
use hyper::header;
use hyper::status::StatusCode;
use hyper::net::HttpStream;
use hyper::{Decoder, Encoder, Next};
use endpoint::{Endpoint, EndpointInfo, EndpointPath};
use parity_dapps::{WebApp, Info};
pub struct PageEndpoint<T : WebApp + 'static> {
/// Content of the files
pub app: Arc<T>,
/// Prefix to strip from the path (when `None` deducted from `app_id`)
pub prefix: Option<String>,
}
impl<T: WebApp + 'static> PageEndpoint<T> {
pub fn new(app: T) -> Self {
PageEndpoint {
app: Arc::new(app),
prefix: None,
}
}
pub fn with_prefix(app: T, prefix: String) -> Self {
PageEndpoint {
app: Arc::new(app),
prefix: Some(prefix),
}
}
}
impl<T: WebApp> Endpoint for PageEndpoint<T> {
fn info(&self) -> Option<EndpointInfo> {
Some(EndpointInfo::from(self.app.info()))
}
fn to_handler(&self, path: EndpointPath) -> Box<server::Handler<HttpStream>> {
Box::new(PageHandler {
app: self.app.clone(),
prefix: self.prefix.clone(),
path: path,
file: None,
write_pos: 0,
})
}
}
impl From<Info> for EndpointInfo {
fn from(info: Info) -> Self {
EndpointInfo {
name: info.name,
description: info.description,
author: info.author,
icon_url: info.icon_url,
version: info.version,
}
}
}
struct PageHandler<T: WebApp + 'static> {
app: Arc<T>,
prefix: Option<String>,
path: EndpointPath,
file: Option<String>,
write_pos: usize,
}
impl<T: WebApp + 'static> PageHandler<T> {
fn extract_path(&self, path: &str) -> String {
let app_id = &self.path.app_id;
let prefix = "/".to_owned() + self.prefix.as_ref().unwrap_or(app_id);
let prefix_with_slash = prefix.clone() + "/";
let query_pos = path.find('?').unwrap_or_else(|| path.len());
// Index file support
match path == "/" || path == &prefix || path == &prefix_with_slash {
true => "index.html".to_owned(),
false => if path.starts_with(&prefix_with_slash) {
path[prefix_with_slash.len()..query_pos].to_owned()
} else if path.starts_with("/") {
path[1..query_pos].to_owned()
} else {
path[0..query_pos].to_owned()
}
}
}
}
impl<T: WebApp + 'static> server::Handler<HttpStream> for PageHandler<T> {
fn on_request(&mut self, req: server::Request) -> Next {
self.file = match *req.uri() {
RequestUri::AbsolutePath(ref path) => {
Some(self.extract_path(path))
},
RequestUri::AbsoluteUri(ref url) => {
Some(self.extract_path(url.path()))
},
_ => None,
};
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
if let Some(f) = self.file.as_ref().and_then(|f| self.app.file(f)) {
res.set_status(StatusCode::Ok);
res.headers_mut().set(header::ContentType(f.content_type.parse().unwrap()));
Next::write()
} else {
res.set_status(StatusCode::NotFound);
Next::write()
}
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
let (wrote, res) = {
let file = self.file.as_ref().and_then(|f| self.app.file(f));
match file {
None => (None, Next::end()),
Some(f) if self.write_pos == f.content.len() => (None, Next::end()),
Some(f) => match encoder.write(&f.content[self.write_pos..]) {
Ok(bytes) => (Some(bytes), Next::write()),
Err(e) => match e.kind() {
::std::io::ErrorKind::WouldBlock => (None, Next::write()),
_ => (None, Next::end())
},
}
}
};
if let Some(bytes) = wrote {
self.write_pos += bytes;
}
res
}
}
#[cfg(test)]
use parity_dapps::File;
#[cfg(test)]
#[derive(Default)]
struct TestWebapp;
#[cfg(test)]
impl WebApp for TestWebapp {
fn file(&self, _path: &str) -> Option<&File> {
None
}
fn info(&self) -> Info {
unimplemented!()
}
}
#[test]
fn should_extract_path_with_appid() {
// given
let path1 = "/";
let path2= "/test.css";
let path3 = "/app/myfile.txt";
let path4 = "/app/myfile.txt?query=123";
let page_handler = PageHandler {
app: Arc::new(TestWebapp),
prefix: None,
path: EndpointPath {
app_id: "app".to_owned(),
host: "".to_owned(),
port: 8080
},
file: None,
write_pos: 0,
};
// when
let res1 = page_handler.extract_path(path1);
let res2 = page_handler.extract_path(path2);
let res3 = page_handler.extract_path(path3);
let res4 = page_handler.extract_path(path4);
// then
assert_eq!(&res1, "index.html");
assert_eq!(&res2, "test.css");
assert_eq!(&res3, "myfile.txt");
assert_eq!(&res4, "myfile.txt");
}

48
dapps/src/proxypac.rs Normal file
View File

@@ -0,0 +1,48 @@
// Copyright 2015, 2016 Ethcore (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/>.
//! Serving ProxyPac file
use endpoint::{Endpoint, Handler, ContentHandler, EndpointPath};
use apps::DAPPS_DOMAIN;
pub struct ProxyPac;
impl ProxyPac {
pub fn boxed() -> Box<Endpoint> {
Box::new(ProxyPac)
}
}
impl Endpoint for ProxyPac {
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
let content = format!(
r#"
function FindProxyForURL(url, host) {{
if (shExpMatch(host, "*{0}"))
{{
return "PROXY {1}:{2}";
}}
return "DIRECT";
}}
"#,
DAPPS_DOMAIN, path.host, path.port);
Box::new(ContentHandler::new(content, "application/javascript".to_owned()))
}
}

161
dapps/src/router/auth.rs Normal file
View File

@@ -0,0 +1,161 @@
// Copyright 2015, 2016 Ethcore (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/>.
//! HTTP Authorization implementations
use std::io::Write;
use std::collections::HashMap;
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::status::StatusCode;
/// Authorization result
pub enum Authorized {
/// Authorization was successful.
Yes,
/// Unsuccessful authorization. Handler for further work is returned.
No(Box<server::Handler<HttpStream>>),
}
/// Authorization interface
pub trait Authorization : Send + Sync {
/// Checks if authorization is valid.
fn is_authorized(&self, req: &server::Request)-> Authorized;
}
/// HTTP Basic Authorization handler
pub struct HttpBasicAuth {
users: HashMap<String, String>,
}
/// No-authorization implementation (authorization disabled)
pub struct NoAuth;
impl Authorization for NoAuth {
fn is_authorized(&self, _req: &server::Request)-> Authorized {
Authorized::Yes
}
}
impl Authorization for HttpBasicAuth {
fn is_authorized(&self, req: &server::Request) -> Authorized {
let auth = self.check_auth(&req);
match auth {
Access::Denied => {
Authorized::No(Box::new(UnauthorizedHandler { write_pos: 0 }))
},
Access::AuthRequired => {
Authorized::No(Box::new(AuthRequiredHandler))
},
Access::Granted => {
Authorized::Yes
},
}
}
}
#[derive(Debug)]
enum Access {
Granted,
Denied,
AuthRequired,
}
impl HttpBasicAuth {
/// Creates `HttpBasicAuth` instance with only one user.
pub fn single_user(username: &str, password: &str) -> Self {
let mut users = HashMap::new();
users.insert(username.to_owned(), password.to_owned());
HttpBasicAuth {
users: users
}
}
fn is_authorized(&self, username: &str, password: &str) -> bool {
self.users.get(&username.to_owned()).map_or(false, |pass| pass == password)
}
fn check_auth(&self, req: &server::Request) -> Access {
match req.headers().get::<header::Authorization<header::Basic>>() {
Some(&header::Authorization(
header::Basic { ref username, password: Some(ref password) }
)) if self.is_authorized(username, password) => Access::Granted,
Some(_) => Access::Denied,
None => Access::AuthRequired,
}
}
}
pub struct UnauthorizedHandler {
write_pos: usize,
}
impl server::Handler<HttpStream> for UnauthorizedHandler {
fn on_request(&mut self, _request: server::Request) -> Next {
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(StatusCode::Unauthorized);
Next::write()
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
let response = "Unauthorized".as_bytes();
if self.write_pos == response.len() {
return Next::end();
}
match encoder.write(&response[self.write_pos..]) {
Ok(bytes) => {
self.write_pos += bytes;
Next::write()
},
Err(e) => match e.kind() {
::std::io::ErrorKind::WouldBlock => Next::write(),
_ => Next::end()
},
}
}
}
pub struct AuthRequiredHandler;
impl server::Handler<HttpStream> for AuthRequiredHandler {
fn on_request(&mut self, _request: server::Request) -> Next {
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(StatusCode::Unauthorized);
res.headers_mut().set_raw("WWW-Authenticate", vec![b"Basic realm=\"Parity\"".to_vec()]);
Next::write()
}
fn on_response_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
Next::end()
}
}

255
dapps/src/router/mod.rs Normal file
View File

@@ -0,0 +1,255 @@
// Copyright 2015, 2016 Ethcore (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/>.
//! Router implementation
//! Processes request handling authorization and dispatching it to proper application.
mod url;
mod redirect;
pub mod auth;
use DAPPS_DOMAIN;
use std::sync::Arc;
use std::collections::HashMap;
use url::Host;
use hyper;
use hyper::{server, uri, header};
use hyper::{Next, Encoder, Decoder};
use hyper::net::HttpStream;
use apps;
use endpoint::{Endpoint, Endpoints, EndpointPath};
use self::url::Url;
use self::auth::{Authorization, Authorized};
use self::redirect::Redirection;
/// Special endpoints are accessible on every domain (every dapp)
#[derive(Debug, PartialEq, Hash, Eq)]
pub enum SpecialEndpoint {
Rpc,
Api,
Utils,
None,
}
pub struct Router<A: Authorization + 'static> {
main_page: &'static str,
endpoints: Arc<Endpoints>,
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
authorization: Arc<A>,
handler: Box<server::Handler<HttpStream>>,
}
impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
fn on_request(&mut self, req: server::Request) -> Next {
// Check authorization
let auth = self.authorization.is_authorized(&req);
// Choose proper handler depending on path / domain
self.handler = match auth {
Authorized::No(handler) => handler,
Authorized::Yes => {
let url = extract_url(&req);
let endpoint = extract_endpoint(&url);
match endpoint {
// First check special endpoints
(ref path, ref endpoint) if self.special.contains_key(endpoint) => {
self.special.get(endpoint).unwrap().to_handler(path.clone().unwrap_or_default())
},
// Then delegate to dapp
(Some(ref path), _) if self.endpoints.contains_key(&path.app_id) => {
self.endpoints.get(&path.app_id).unwrap().to_handler(path.clone())
},
// Redirection to main page
_ if *req.method() == hyper::method::Method::Get => {
Redirection::new(self.main_page)
},
// RPC by default
_ => {
self.special.get(&SpecialEndpoint::Rpc).unwrap().to_handler(EndpointPath::default())
}
}
}
};
// Delegate on_request to proper handler
self.handler.on_request(req)
}
/// This event occurs each time the `Request` is ready to be read from.
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
self.handler.on_request_readable(decoder)
}
/// This event occurs after the first time this handled signals `Next::write()`.
fn on_response(&mut self, response: &mut server::Response) -> Next {
self.handler.on_response(response)
}
/// This event occurs each time the `Response` is ready to be written to.
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
self.handler.on_response_writable(encoder)
}
}
impl<A: Authorization> Router<A> {
pub fn new(
main_page: &'static str,
endpoints: Arc<Endpoints>,
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
authorization: Arc<A>) -> Self {
let handler = special.get(&SpecialEndpoint::Rpc).unwrap().to_handler(EndpointPath::default());
Router {
main_page: main_page,
endpoints: endpoints,
special: special,
authorization: authorization,
handler: handler,
}
}
}
fn extract_url(req: &server::Request) -> Option<Url> {
match *req.uri() {
uri::RequestUri::AbsoluteUri(ref url) => {
match Url::from_generic_url(url.clone()) {
Ok(url) => Some(url),
_ => None,
}
},
uri::RequestUri::AbsolutePath(ref path) => {
// Attempt to prepend the Host header (mandatory in HTTP/1.1)
let url_string = match req.headers().get::<header::Host>() {
Some(ref host) => {
format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path)
},
None => return None,
};
match Url::parse(&url_string) {
Ok(url) => Some(url),
_ => None,
}
},
_ => None,
}
}
fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint) {
fn special_endpoint(url: &Url) -> SpecialEndpoint {
if url.path.len() <= 1 {
return SpecialEndpoint::None;
}
match url.path[0].as_ref() {
apps::RPC_PATH => SpecialEndpoint::Rpc,
apps::API_PATH => SpecialEndpoint::Api,
apps::UTILS_PATH => SpecialEndpoint::Utils,
_ => SpecialEndpoint::None,
}
}
match *url {
Some(ref url) => match url.host {
Host::Domain(ref domain) if domain.ends_with(DAPPS_DOMAIN) => {
let len = domain.len() - DAPPS_DOMAIN.len();
let id = domain[0..len].to_owned();
(Some(EndpointPath {
app_id: id,
host: domain.clone(),
port: url.port,
}), special_endpoint(url))
},
_ if url.path.len() > 1 => {
let id = url.path[0].clone();
(Some(EndpointPath {
app_id: id.clone(),
host: format!("{}", url.host),
port: url.port,
}), special_endpoint(url))
},
_ => (None, special_endpoint(url)),
},
_ => (None, SpecialEndpoint::None)
}
}
#[test]
fn should_extract_endpoint() {
assert_eq!(extract_endpoint(&None), (None, SpecialEndpoint::None));
// With path prefix
assert_eq!(
extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok()),
(Some(EndpointPath {
app_id: "status".to_owned(),
host: "localhost".to_owned(),
port: 8080,
}), SpecialEndpoint::None)
);
// With path prefix
assert_eq!(
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok()),
(Some(EndpointPath {
app_id: "rpc".to_owned(),
host: "localhost".to_owned(),
port: 8080,
}), SpecialEndpoint::Rpc)
);
assert_eq!(
extract_endpoint(&Url::parse("http://my.status.parity/parity-utils/inject.js").ok()),
(Some(EndpointPath {
app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(),
port: 80,
}), SpecialEndpoint::Utils)
);
// By Subdomain
assert_eq!(
extract_endpoint(&Url::parse("http://my.status.parity/test.html").ok()),
(Some(EndpointPath {
app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(),
port: 80,
}), SpecialEndpoint::None)
);
// RPC by subdomain
assert_eq!(
extract_endpoint(&Url::parse("http://my.status.parity/rpc/").ok()),
(Some(EndpointPath {
app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(),
port: 80,
}), SpecialEndpoint::Rpc)
);
// API by subdomain
assert_eq!(
extract_endpoint(&Url::parse("http://my.status.parity/api/").ok()),
(Some(EndpointPath {
app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(),
port: 80,
}), SpecialEndpoint::Api)
);
}

View File

@@ -0,0 +1,55 @@
// Copyright 2015, 2016 Ethcore (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/>.
//! HTTP Redirection hyper handler
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::status::StatusCode;
pub struct Redirection {
to_url: &'static str
}
impl Redirection {
pub fn new(url: &'static str) -> Box<Self> {
Box::new(Redirection {
to_url: url
})
}
}
impl server::Handler<HttpStream> for Redirection {
fn on_request(&mut self, _request: server::Request) -> Next {
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(StatusCode::MovedPermanently);
res.headers_mut().set(header::Location(self.to_url.to_owned()));
Next::write()
}
fn on_response_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
Next::end()
}
}

145
dapps/src/router/url.rs Normal file
View File

@@ -0,0 +1,145 @@
// Copyright 2015, 2016 Ethcore (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/>.
//! HTTP/HTTPS URL type. Based on URL type from Iron library.
use url::Host;
use url::{self};
/// HTTP/HTTPS URL type for Iron.
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Url {
/// Raw url of url
pub raw: url::Url,
/// The host field of the URL, probably a domain.
pub host: Host,
/// The connection port.
pub port: u16,
/// The URL path, the resource to be accessed.
///
/// A *non-empty* vector encoding the parts of the URL path.
/// Empty entries of `""` correspond to trailing slashes.
pub path: Vec<String>,
/// The URL username field, from the userinfo section of the URL.
///
/// `None` if the `@` character was not part of the input OR
/// if a blank username was provided.
/// Otherwise, a non-empty string.
pub username: Option<String>,
/// The URL password field, from the userinfo section of the URL.
///
/// `None` if the `@` character was not part of the input OR
/// if a blank password was provided.
/// Otherwise, a non-empty string.
pub password: Option<String>,
}
impl Url {
/// Create a URL from a string.
///
/// The input must be a valid URL with a special scheme for this to succeed.
///
/// HTTP and HTTPS are special schemes.
///
/// See: http://url.spec.whatwg.org/#special-scheme
pub fn parse(input: &str) -> Result<Url, String> {
// Parse the string using rust-url, then convert.
match url::Url::parse(input) {
Ok(raw_url) => Url::from_generic_url(raw_url),
Err(e) => Err(format!("{}", e))
}
}
/// Create a `Url` from a `rust-url` `Url`.
pub fn from_generic_url(raw_url: url::Url) -> Result<Url, String> {
// Map empty usernames to None.
let username = match raw_url.username() {
"" => None,
username => Some(username.to_owned())
};
// Map empty passwords to None.
let password = match raw_url.password() {
Some(password) if !password.is_empty() => Some(password.to_owned()),
_ => None,
};
let port = try!(raw_url.port_or_known_default().ok_or_else(|| format!("Unknown port for scheme: `{}`", raw_url.scheme())));
let host = try!(raw_url.host().ok_or_else(|| "Valid host, because only data:, mailto: protocols does not have host.".to_owned())).to_owned();
let path = try!(raw_url.path_segments().ok_or_else(|| "Valid path segments. In HTTP we won't get cannot-be-a-base URLs".to_owned()))
.map(|part| part.to_owned()).collect();
Ok(Url {
port: port,
host: host,
path: path,
raw: raw_url,
username: username,
password: password,
})
}
}
#[cfg(test)]
mod test {
use super::Url;
#[test]
fn test_default_port() {
assert_eq!(Url::parse("http://example.com/wow").unwrap().port, 80u16);
assert_eq!(Url::parse("https://example.com/wow").unwrap().port, 443u16);
}
#[test]
fn test_explicit_port() {
assert_eq!(Url::parse("http://localhost:3097").unwrap().port, 3097u16);
}
#[test]
fn test_empty_username() {
assert!(Url::parse("http://@example.com").unwrap().username.is_none());
assert!(Url::parse("http://:password@example.com").unwrap().username.is_none());
}
#[test]
fn test_not_empty_username() {
let user = Url::parse("http://john:pass@example.com").unwrap().username;
assert_eq!(user.unwrap(), "john");
let user = Url::parse("http://john:@example.com").unwrap().username;
assert_eq!(user.unwrap(), "john");
}
#[test]
fn test_empty_password() {
assert!(Url::parse("http://michael@example.com").unwrap().password.is_none());
assert!(Url::parse("http://:@example.com").unwrap().password.is_none());
}
#[test]
fn test_not_empty_password() {
let pass = Url::parse("http://michael:pass@example.com").unwrap().password;
assert_eq!(pass.unwrap(), "pass");
let pass = Url::parse("http://:pass@example.com").unwrap().password;
assert_eq!(pass.unwrap(), "pass");
}
}

41
dapps/src/rpc.rs Normal file
View File

@@ -0,0 +1,41 @@
// Copyright 2015, 2016 Ethcore (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 std::sync::{Arc, Mutex};
use jsonrpc_core::IoHandler;
use jsonrpc_http_server::{ServerHandler, PanicHandler, AccessControlAllowOrigin};
use endpoint::{Endpoint, EndpointPath, Handler};
pub fn rpc(handler: Arc<IoHandler>, panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>) -> Box<Endpoint> {
Box::new(RpcEndpoint {
handler: handler,
panic_handler: panic_handler,
cors_domain: Some(AccessControlAllowOrigin::Null)
})
}
struct RpcEndpoint {
handler: Arc<IoHandler>,
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
cors_domain: Option<AccessControlAllowOrigin>,
}
impl Endpoint for RpcEndpoint {
fn to_handler(&self, _path: EndpointPath) -> Box<Handler> {
let panic_handler = PanicHandler { handler: self.panic_handler.clone() };
Box::new(ServerHandler::new(self.handler.clone(), self.cors_domain.clone(), panic_handler))
}
}