diff --git a/Cargo.lock b/Cargo.lock index ff10c86a5..68ca01982 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,6 +352,7 @@ dependencies = [ "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "zip 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index ddc23c87c..274c7b87b 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -24,6 +24,7 @@ ethabi = "0.2.2" linked-hash-map = "0.3" parity-dapps-glue = "1.4" mime = "0.2" +time = "0.1.35" serde_macros = { version = "0.8", optional = true } zip = { version = "0.1", default-features = false } ethcore-devtools = { path = "../devtools" } diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs index 6c2e5416d..85eef8f73 100644 --- a/dapps/src/apps/fetcher.rs +++ b/dapps/src/apps/fetcher.rs @@ -32,14 +32,15 @@ use random_filename; use SyncStatus; use util::{Mutex, H256}; use util::sha3::sha3; -use page::LocalPageEndpoint; +use page::{LocalPageEndpoint, PageCache}; use handlers::{ContentHandler, ContentFetcherHandler, ContentValidator}; use endpoint::{Endpoint, EndpointPath, Handler}; use apps::cache::{ContentCache, ContentStatus}; use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest}; use apps::urlhint::{URLHintContract, URLHint, URLHintResult}; -const MAX_CACHED_DAPPS: usize = 10; +/// Limit of cached dapps/content +const MAX_CACHED_DAPPS: usize = 20; pub struct ContentFetcher { dapps_path: PathBuf, @@ -259,6 +260,17 @@ impl ContentValidator for ContentInstaller { // Create dir try!(fs::create_dir_all(&self.content_path)); + // Validate hash + let mut file_reader = io::BufReader::new(try!(fs::File::open(&path))); + let hash = try!(sha3(&mut file_reader)); + let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId)); + if id != hash { + return Err(ValidationError::HashMismatch { + expected: id, + got: hash, + }); + } + // And prepare path for a file let filename = path.file_name().expect("We always fetch a file."); let mut content_path = self.content_path.clone(); @@ -270,7 +282,7 @@ impl ContentValidator for ContentInstaller { try!(fs::copy(&path, &content_path)); - Ok((self.id.clone(), LocalPageEndpoint::single_file(content_path, self.mime.clone()))) + Ok((self.id.clone(), LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled))) } fn done(&self, endpoint: Option) { @@ -376,7 +388,7 @@ impl ContentValidator for DappInstaller { try!(manifest_file.write_all(manifest_str.as_bytes())); // Create endpoint - let app = LocalPageEndpoint::new(target, manifest.clone().into(), self.embeddable_at); + let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_at); // Return modified app manifest Ok((manifest.id.clone(), app)) @@ -416,7 +428,7 @@ mod tests { version: "".into(), author: "".into(), icon_url: "".into(), - }, None); + }, Default::default(), None); // when fetcher.set_status("test", ContentStatus::Ready(handler)); diff --git a/dapps/src/apps/fs.rs b/dapps/src/apps/fs.rs index e7a11fc8e..104b33035 100644 --- a/dapps/src/apps/fs.rs +++ b/dapps/src/apps/fs.rs @@ -18,7 +18,7 @@ use std::io; use std::io::Read; use std::fs; use std::path::PathBuf; -use page::LocalPageEndpoint; +use page::{LocalPageEndpoint, PageCache}; use endpoint::{Endpoints, EndpointInfo}; use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest}; @@ -102,7 +102,7 @@ pub fn local_endpoints(dapps_path: String, signer_port: Option) -> Endpoint for dapp in local_dapps(dapps_path) { pages.insert( dapp.id, - Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, signer_port)) + Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_port)) ); } pages diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 0041dbedf..4394d1183 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -44,6 +44,7 @@ #![cfg_attr(feature="nightly", plugin(clippy))] extern crate hyper; +extern crate time; extern crate url as url_lib; extern crate unicase; extern crate serde; diff --git a/dapps/src/page/builtin.rs b/dapps/src/page/builtin.rs index c4f56556e..bf5bf088f 100644 --- a/dapps/src/page/builtin.rs +++ b/dapps/src/page/builtin.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use page::handler; +use page::{handler, PageCache}; use std::sync::Arc; use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler}; use parity_dapps::{WebApp, File, Info}; @@ -80,6 +80,7 @@ impl Endpoint for PageEndpoint { prefix: self.prefix.clone(), path: path, file: handler::ServedFile::new(self.safe_to_embed_at_port.clone()), + cache: PageCache::Disabled, safe_to_embed_at_port: self.safe_to_embed_at_port.clone(), }) } diff --git a/dapps/src/page/handler.rs b/dapps/src/page/handler.rs index a2ca5e258..f908a69c5 100644 --- a/dapps/src/page/handler.rs +++ b/dapps/src/page/handler.rs @@ -15,6 +15,8 @@ // along with Parity. If not, see . use std::io::Write; +use time::{self, Duration}; + use hyper::header; use hyper::server; use hyper::uri::RequestUri; @@ -69,6 +71,19 @@ impl ServedFile { } } +/// Defines what cache headers should be appended to returned resources. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum PageCache { + Enabled, + Disabled, +} + +impl Default for PageCache { + fn default() -> Self { + PageCache::Disabled + } +} + /// A handler for a single webapp. /// Resolves correct paths and serves as a plumbing code between /// hyper server and dapp. @@ -83,6 +98,8 @@ pub struct PageHandler { pub path: EndpointPath, /// Flag indicating if the file can be safely embeded (put in iframe). pub safe_to_embed_at_port: Option, + /// Cache settings for this page. + pub cache: PageCache, } impl PageHandler { @@ -129,9 +146,19 @@ impl server::Handler for PageHandler { ServedFile::File(ref f) => { res.set_status(StatusCode::Ok); + if let PageCache::Enabled = self.cache { + let mut headers = res.headers_mut(); + let validity = Duration::days(365); + headers.set(header::CacheControl(vec![ + header::CacheDirective::Public, + header::CacheDirective::MaxAge(validity.num_seconds() as u32), + ])); + headers.set(header::Expires(header::HttpDate(time::now() + validity))); + } + match f.content_type().parse() { Ok(mime) => res.headers_mut().set(header::ContentType(mime)), - Err(()) => debug!(target: "page_handler", "invalid MIME type: {}", f.content_type()), + Err(()) => debug!(target: "dapps", "invalid MIME type: {}", f.content_type()), } // Security headers: @@ -218,6 +245,7 @@ fn should_extract_path_with_appid() { using_dapps_domains: true, }, file: ServedFile::new(None), + cache: Default::default(), safe_to_embed_at_port: None, }; diff --git a/dapps/src/page/local.rs b/dapps/src/page/local.rs index 5390f5aac..aa98a68cd 100644 --- a/dapps/src/page/local.rs +++ b/dapps/src/page/local.rs @@ -18,7 +18,7 @@ use mime_guess; use std::io::{Seek, Read, SeekFrom}; use std::fs; use std::path::{Path, PathBuf}; -use page::handler; +use page::handler::{self, PageCache}; use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler}; #[derive(Debug, Clone)] @@ -26,24 +26,27 @@ pub struct LocalPageEndpoint { path: PathBuf, mime: Option, info: Option, + cache: PageCache, embeddable_at: Option, } impl LocalPageEndpoint { - pub fn new(path: PathBuf, info: EndpointInfo, embeddable_at: Option) -> Self { + pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_at: Option) -> Self { LocalPageEndpoint { path: path, mime: None, info: Some(info), + cache: cache, embeddable_at: embeddable_at, } } - pub fn single_file(path: PathBuf, mime: String) -> Self { + pub fn single_file(path: PathBuf, mime: String, cache: PageCache) -> Self { LocalPageEndpoint { path: path, mime: Some(mime), info: None, + cache: cache, embeddable_at: None, } } @@ -66,6 +69,7 @@ impl Endpoint for LocalPageEndpoint { path: path, file: handler::ServedFile::new(None), safe_to_embed_at_port: self.embeddable_at, + cache: self.cache, }) } else { Box::new(handler::PageHandler { @@ -74,6 +78,7 @@ impl Endpoint for LocalPageEndpoint { path: path, file: handler::ServedFile::new(None), safe_to_embed_at_port: self.embeddable_at, + cache: self.cache, }) } } diff --git a/dapps/src/page/mod.rs b/dapps/src/page/mod.rs index 349c979c7..92c066df3 100644 --- a/dapps/src/page/mod.rs +++ b/dapps/src/page/mod.rs @@ -21,4 +21,5 @@ mod handler; pub use self::local::LocalPageEndpoint; pub use self::builtin::PageEndpoint; +pub use self::handler::PageCache;