diff --git a/Cargo.toml b/Cargo.toml index 1261685..a7361f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ license = "MIT/Apache-2.0" [features] sass = ["rsass"] +sri = ["ssri"] mime02 = [] mime03 = ["mime"] warp02 = ["mime03"] @@ -24,6 +25,7 @@ md5 = "0.7" nom = "5.0.0" rsass = { version = "0.13.0", optional = true } +ssri = { version = "5.0.0", optional = true } mime = { version = "0.3", optional = true } [badges] diff --git a/examples/actix/Cargo.toml b/examples/actix/Cargo.toml index be896fc..db7bb98 100644 --- a/examples/actix/Cargo.toml +++ b/examples/actix/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" build = "src/build.rs" [build-dependencies] -ructe = { path = "../..", features = ["sass", "mime03"] } +ructe = { path = "../..", features = ["sass", "sri", "mime03"] } [dependencies] actix-web = "2.0.0" diff --git a/examples/actix/templates/error.rs.html b/examples/actix/templates/error.rs.html index 506741b..5b3e771 100644 --- a/examples/actix/templates/error.rs.html +++ b/examples/actix/templates/error.rs.html @@ -9,7 +9,7 @@ Error @code.as_u16(): @code.canonical_reason().unwrap_or("error") - +
diff --git a/examples/actix/templates/page.rs.html b/examples/actix/templates/page.rs.html index c8ad365..8e61adb 100644 --- a/examples/actix/templates/page.rs.html +++ b/examples/actix/templates/page.rs.html @@ -8,7 +8,7 @@ Example - +
diff --git a/examples/ed2018/Cargo.toml b/examples/ed2018/Cargo.toml index b13d41c..f225f00 100644 --- a/examples/ed2018/Cargo.toml +++ b/examples/ed2018/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" build = "src/build.rs" [build-dependencies] -ructe = { path = "../..", features = ["mime03", "sass"] } +ructe = { path = "../..", features = ["mime03", "sri", "sass"] } [dependencies] mime = "~0.3" diff --git a/examples/ed2018/templates/page.rs.html b/examples/ed2018/templates/page.rs.html index d8298ea..38233ff 100644 --- a/examples/ed2018/templates/page.rs.html +++ b/examples/ed2018/templates/page.rs.html @@ -4,7 +4,7 @@ Example with stylesheet - + Hello world! diff --git a/examples/gotham/Cargo.toml b/examples/gotham/Cargo.toml index 935f537..2d013b5 100644 --- a/examples/gotham/Cargo.toml +++ b/examples/gotham/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Rasmus Kaj "] build = "src/build.rs" [build-dependencies] -ructe = { path = "../..", features = ["sass", "mime03"] } +ructe = { path = "../..", features = ["sass", "sri", "mime03"] } [dependencies] gotham = "0.4.0" diff --git a/examples/gotham/templates/page.rs.html b/examples/gotham/templates/page.rs.html index 5bb78e8..87a071c 100644 --- a/examples/gotham/templates/page.rs.html +++ b/examples/gotham/templates/page.rs.html @@ -8,7 +8,7 @@ Example - +
diff --git a/examples/iron/Cargo.toml b/examples/iron/Cargo.toml index 0781210..2e42834 100644 --- a/examples/iron/Cargo.toml +++ b/examples/iron/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Rasmus Kaj "] build = "src/build.rs" [build-dependencies] -ructe = { path = "../..", features = ["sass", "mime02"] } +ructe = { path = "../..", features = ["sass", "sri", "mime02"] } [dependencies] iron = "0.6" diff --git a/examples/iron/templates/page.rs.html b/examples/iron/templates/page.rs.html index 0f548ab..d1a7042 100644 --- a/examples/iron/templates/page.rs.html +++ b/examples/iron/templates/page.rs.html @@ -8,7 +8,7 @@ Example - +
diff --git a/examples/nickel/Cargo.toml b/examples/nickel/Cargo.toml index 779de5c..eeb13bd 100644 --- a/examples/nickel/Cargo.toml +++ b/examples/nickel/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Rasmus Kaj "] build = "src/build.rs" [build-dependencies] -ructe = { path = "../..", features = ["sass", "mime02"] } +ructe = { path = "../..", features = ["sass", "sri", "mime02"] } [dependencies] nickel = "0.11.0" diff --git a/examples/nickel/templates/page.rs.html b/examples/nickel/templates/page.rs.html index a8130b2..27f1fbc 100644 --- a/examples/nickel/templates/page.rs.html +++ b/examples/nickel/templates/page.rs.html @@ -8,7 +8,7 @@ Example - +
diff --git a/examples/static-sass/Cargo.toml b/examples/static-sass/Cargo.toml index 80dfce7..a4186e5 100644 --- a/examples/static-sass/Cargo.toml +++ b/examples/static-sass/Cargo.toml @@ -6,4 +6,4 @@ authors = ["Rasmus Kaj "] build = "src/build.rs" [build-dependencies] -ructe = { path = "../..", features = ["sass"] } +ructe = { path = "../..", features = ["sass", "sri"] } diff --git a/examples/static-sass/templates/page.rs.html b/examples/static-sass/templates/page.rs.html index d8298ea..38233ff 100644 --- a/examples/static-sass/templates/page.rs.html +++ b/examples/static-sass/templates/page.rs.html @@ -4,7 +4,7 @@ Example with stylesheet - + Hello world! diff --git a/examples/statics/Cargo.toml b/examples/statics/Cargo.toml index 2f2d3ba..1ff8300 100644 --- a/examples/statics/Cargo.toml +++ b/examples/statics/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Rasmus Kaj "] build = "src/build.rs" [build-dependencies] -ructe = { path = "../..", features = ["mime03"] } +ructe = { path = "../..", features = ["sri", "mime03"] } [dependencies] mime = "0.3.0" diff --git a/examples/statics/templates/page.rs.html b/examples/statics/templates/page.rs.html index d8298ea..38233ff 100644 --- a/examples/statics/templates/page.rs.html +++ b/examples/statics/templates/page.rs.html @@ -4,7 +4,7 @@ Example with stylesheet - + Hello world! diff --git a/examples/warp02/Cargo.toml b/examples/warp02/Cargo.toml index 8bb401f..0b1cc0f 100644 --- a/examples/warp02/Cargo.toml +++ b/examples/warp02/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" build = "src/build.rs" [build-dependencies] -ructe = { path = "../..", features = ["warp02", "sass"] } +ructe = { path = "../..", features = ["warp02", "sass", "sri"] } [dependencies] warp = "0.2.0" diff --git a/examples/warp02/templates/error.rs.html b/examples/warp02/templates/error.rs.html index 8ce6610..0fc5796 100644 --- a/examples/warp02/templates/error.rs.html +++ b/examples/warp02/templates/error.rs.html @@ -9,7 +9,7 @@ Error @code.as_u16(): @code.canonical_reason().unwrap_or("error") - +
diff --git a/examples/warp02/templates/page.rs.html b/examples/warp02/templates/page.rs.html index c8ad365..8e61adb 100644 --- a/examples/warp02/templates/page.rs.html +++ b/examples/warp02/templates/page.rs.html @@ -8,7 +8,7 @@ Example - +
diff --git a/src/lib.rs b/src/lib.rs index f36ab30..396f324 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,9 @@ //! version 0.2.x of the [mime] crate. //! * `warp02` -- Provide an extension to [`Response::Builder`] to //! simplify template rendering in the [warp] framework, versions 0.2.x. +//! * `sri` -- Generate [subresource +//! integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) +//! strings using the [ssri] crate //! //! [`response::Builder`]: ../http/response/struct.Builder.html //! [mime]: https://crates.rs/crates/mime @@ -103,7 +106,7 @@ //! build = "src/build.rs" //! //! [build-dependencies] -//! ructe = { version = "0.6.0", features = ["sass", "mime03"] } +//! ructe = { version = "0.6.0", features = ["sass", "sri", "mime03"] } //! //! [dependencies] //! mime = "0.3.13" @@ -118,6 +121,8 @@ extern crate mime; extern crate nom; #[cfg(feature = "sass")] extern crate rsass; +#[cfg(feature = "sri")] +extern crate ssri; pub mod Template_syntax; mod expression; diff --git a/src/staticfiles.rs b/src/staticfiles.rs index 7434693..9a63afa 100644 --- a/src/staticfiles.rs +++ b/src/staticfiles.rs @@ -2,6 +2,7 @@ use super::Result; use base64; use itertools::Itertools; use md5; +use std::borrow::Cow; use std::collections::BTreeMap; use std::fmt::{self, Display}; use std::fs::{read_dir, File}; @@ -225,6 +226,11 @@ pub struct StaticFile { if cfg!(feature = "mime03") { src.write_all(b" pub mime: &'static Mime,\n")?; } + if cfg!(feature = "sri") { + src.write_all(b" pub integrity_sha256: &'static str,\n")?; + src.write_all(b" pub integrity_sha384: &'static str,\n")?; + src.write_all(b" pub integrity_sha512: &'static str,\n")?; + } src.write_all( b"} #[allow(dead_code)] @@ -332,7 +338,7 @@ impl StaticFile { &path, &rust_name, &url_name, - &FileContent(&path), + FileContent(&path), ext, )?; } @@ -350,7 +356,7 @@ impl StaticFile { let path = &self.path_for(path); let ext = name_and_ext(path).map(|(_, e)| e).unwrap_or(""); println!("cargo:rerun-if-changed={}", path.display()); - self.add_static(path, url_name, url_name, &FileContent(path), ext)?; + self.add_static(path, url_name, url_name, FileContent(path), ext)?; Ok(()) } @@ -407,7 +413,7 @@ impl StaticFile { path, &rust_name, &url_name, - &ByteString(data), + ByteString(data), ext, )?; } @@ -474,14 +480,17 @@ impl StaticFile { self.add_file_data(&src.with_extension("css"), &css) } - fn add_static( + fn add_static<'a, C>( &mut self, path: &Path, rust_name: &str, url_name: &str, - content: &impl Display, + content: C, suffix: &str, - ) -> io::Result<()> { + ) -> io::Result<()> + where + C: Display + Into>, + { let rust_name = rust_name .replace("/", "_") .replace("-", "_") @@ -494,12 +503,14 @@ impl StaticFile { \n content: {content},\ \n name: \"{url_name}\",\ \n{mime}\ + \n{sri}\ }};", path = path, rust_name = rust_name, url_name = url_name, - content = content, + content = format!("{}", content), mime = mime_arg(suffix), + sri = sri_args(&content.into()), )?; self.names.insert(rust_name.clone(), url_name.into()); self.names_r.insert(url_name.into(), rust_name); @@ -560,6 +571,22 @@ impl<'a> Display for FileContent<'a> { } } +impl<'a> From> for Cow<'a, [u8]> { + fn from(file_content: FileContent<'a>) -> Self { + let mut bytes = vec![]; + let mut file = File::open(file_content.0).expect(&format!( + "path does not exist: {}", + file_content.0.display() + )); + file.read_to_end(&mut bytes).expect(&format!( + "could not read file: {}", + file_content.0.display() + )); + + Cow::Owned(bytes) + } +} + struct ByteString<'a>(&'a [u8]); impl<'a> Display for ByteString<'a> { @@ -581,6 +608,12 @@ impl<'a> Display for ByteString<'a> { } } +impl<'a> From> for Cow<'a, [u8]> { + fn from(bytes: ByteString<'a>) -> Self { + Cow::Borrowed(bytes.0) + } +} + fn name_and_ext(path: &Path) -> Option<(&str, &str)> { if let (Some(name), Some(ext)) = (path.file_name(), path.extension()) { if let (Some(name), Some(ext)) = (name.to_str(), ext.to_str()) { @@ -594,6 +627,41 @@ fn name_and_ext(path: &Path) -> Option<(&str, &str)> { fn checksum_slug(data: &[u8]) -> String { base64::encode_config(&md5::compute(data)[..6], base64::URL_SAFE) } + +#[cfg(not(feature = "sri"))] +fn sri_args(_: &[u8]) -> String { + "".to_string() +} + +#[cfg(feature = "sri")] +fn sri_args(content: &[u8]) -> String { + use ssri::{Algorithm, IntegrityOpts}; + + let sha256 = IntegrityOpts::new() + .algorithm(Algorithm::Sha256) + .chain(content) + .result() + .to_string(); + + let sha384 = IntegrityOpts::new() + .algorithm(Algorithm::Sha384) + .chain(content) + .result() + .to_string(); + let sha512 = IntegrityOpts::new() + .algorithm(Algorithm::Sha512) + .chain(content) + .result() + .to_string(); + + format!( + "integrity_sha256: {sha256:?},\nintegrity_sha384: {sha384:?},\nintegrity_sha512: {sha512:?}", + sha256 = sha256, + sha384 = sha384, + sha512 = sha512, + ) +} + #[cfg(not(feature = "mime02"))] #[cfg(not(feature = "mime03"))] fn mime_arg(_: &str) -> String {