diff --git a/Trunk.toml b/Trunk.toml index c99382d9..06013df7 100644 --- a/Trunk.toml +++ b/Trunk.toml @@ -19,6 +19,10 @@ offline = false frozen = false # Require Cargo.lock is up to date locked = false +# Allow disabling minification +no_minification = false +# Allow disabling sub-resource integrity (SRI) +no_sri = false [watch] # Paths to watch. The `build.target`'s parent folder is watched by default. diff --git a/src/config/models.rs b/src/config/models.rs index 8d287944..64dd4a05 100644 --- a/src/config/models.rs +++ b/src/config/models.rs @@ -120,12 +120,23 @@ pub struct ConfigOptsBuild { #[serde(default)] #[arg(long)] pub root_certificate: Option, + /// Allows request to ignore certificate validation errors. /// /// Can be useful when behind a corporate proxy. #[serde(default)] #[arg(long)] pub accept_invalid_certs: Option, + + /// Allows disabling minification + #[serde(default)] + #[arg(long)] + pub no_minification: bool, + + /// Allows disabling sub-resource integrity (SRI) + #[serde(default)] + #[arg(long)] + pub no_sri: bool, } #[derive(Clone, Debug)] @@ -181,7 +192,7 @@ pub enum WsProtocol { } impl Display for WsProtocol { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "{}", @@ -318,7 +329,7 @@ pub struct ConfigOptsHook { fn deserialize_uri<'de, D, T>(data: D) -> std::result::Result where D: Deserializer<'de>, - T: std::convert::From, + T: From, { let val = String::deserialize(data)?; Uri::from_str(val.as_str()) @@ -430,6 +441,8 @@ impl ConfigOpts { locked: cli.locked, root_certificate: cli.root_certificate, accept_invalid_certs: cli.accept_invalid_certs, + no_minification: cli.no_minification, + no_sri: cli.no_sri, }; let cfg_build = ConfigOpts { build: Some(opts), @@ -650,6 +663,15 @@ impl ConfigOpts { g.pattern_preload = g.pattern_preload.or(l.pattern_preload); g.pattern_script = g.pattern_script.or(l.pattern_script); g.pattern_params = g.pattern_params.or(l.pattern_params); + // NOTE: this can not be disabled in the cascade. + if l.no_minification { + g.no_minification = true; + } + // NOTE: this can not be disabled in the cascade. + if l.no_sri { + g.no_sri = true; + } + Some(g) } }; diff --git a/src/config/rt.rs b/src/config/rt.rs index 27f1567c..47bfb97f 100644 --- a/src/config/rt.rs +++ b/src/config/rt.rs @@ -75,6 +75,10 @@ pub struct RtcBuild { /// /// **WARNING**: Setting this to true can make you vulnerable to man-in-the-middle attacks. Sometimes this is necessary when working behind corporate proxies. pub accept_invalid_certs: Option, + /// Allow disabling minification + pub no_minification: bool, + /// Allow disabling SRI + pub no_sri: bool, } impl RtcBuild { @@ -153,6 +157,8 @@ impl RtcBuild { locked: opts.locked, root_certificate: opts.root_certificate.map(PathBuf::from), accept_invalid_certs: opts.accept_invalid_certs, + no_minification: opts.no_minification, + no_sri: opts.no_sri, }) } @@ -192,6 +198,8 @@ impl RtcBuild { locked: false, root_certificate: None, accept_invalid_certs: None, + no_minification: false, + no_sri: false, }) } } diff --git a/src/pipelines/css.rs b/src/pipelines/css.rs index da8665d7..b6011f43 100644 --- a/src/pipelines/css.rs +++ b/src/pipelines/css.rs @@ -1,6 +1,6 @@ //! CSS asset pipeline. -use super::{AssetFile, AttrWriter, Attrs, TrunkAssetPipelineOutput, ATTR_HREF, ATTR_INTEGRITY}; +use super::{AssetFile, AttrWriter, Attrs, TrunkAssetPipelineOutput, ATTR_HREF}; use crate::{ config::RtcBuild, pipelines::AssetFileType, @@ -9,7 +9,6 @@ use crate::{ use anyhow::{Context, Result}; use nipper::Document; use std::path::PathBuf; -use std::str::FromStr; use std::sync::Arc; use tokio::task::JoinHandle; @@ -44,11 +43,7 @@ impl Css { path.extend(href_attr.split('/')); let asset = AssetFile::new(&html_dir, path).await?; - let integrity = attrs - .get(ATTR_INTEGRITY) - .map(|value| IntegrityType::from_str(value)) - .transpose()? - .unwrap_or_default(); + let integrity = IntegrityType::from_attrs(&attrs, &cfg)?; Ok(Self { id, @@ -75,7 +70,7 @@ impl Css { .copy( &self.cfg.staging_dist, self.cfg.filehash, - self.cfg.release, + self.cfg.release && !self.cfg.no_minification, AssetFileType::Css, ) .await?; diff --git a/src/pipelines/html.rs b/src/pipelines/html.rs index 04c601c4..91a7a251 100644 --- a/src/pipelines/html.rs +++ b/src/pipelines/html.rs @@ -37,7 +37,7 @@ pub struct HtmlPipeline { /// An optional channel to be used to communicate ignore paths to the watcher. ignore_chan: Option>, /// Protocol used for autoreload WebSockets connection. - pub ws_protocol: Option, + ws_protocol: Option, } impl HtmlPipeline { @@ -179,7 +179,7 @@ impl HtmlPipeline { self.finalize_html(&mut target_html); // Assemble a new output index.html file. - let output_html = match self.cfg.release { + let output_html = match self.cfg.release && !self.cfg.no_minification { true => { let mut minify_cfg = minify_html::Cfg::spec_compliant(); minify_cfg.minify_css = true; diff --git a/src/pipelines/icon.rs b/src/pipelines/icon.rs index d17ccb1a..938c8e3d 100644 --- a/src/pipelines/icon.rs +++ b/src/pipelines/icon.rs @@ -1,19 +1,16 @@ //! Icon asset pipeline. +use super::{AssetFile, AttrWriter, Attrs, TrunkAssetPipelineOutput, ATTR_HREF}; +use crate::config::RtcBuild; +use crate::pipelines::{AssetFileType, ImageType}; +use crate::processing::integrity::{IntegrityType, OutputDigest}; +use anyhow::{Context, Result}; +use nipper::Document; use std::collections::HashMap; use std::path::PathBuf; -use std::str::FromStr; use std::sync::Arc; - -use anyhow::{Context, Result}; -use nipper::Document; use tokio::task::JoinHandle; -use super::{AssetFile, AttrWriter, Attrs, TrunkAssetPipelineOutput, ATTR_HREF, ATTR_INTEGRITY}; -use crate::config::RtcBuild; -use crate::pipelines::{AssetFileType, ImageType}; -use crate::processing::integrity::{IntegrityType, OutputDigest}; - /// An Icon asset pipeline. pub struct Icon { /// The ID of this pipeline's source HTML element. @@ -43,11 +40,7 @@ impl Icon { path.extend(href_attr.split('/')); let asset = AssetFile::new(&html_dir, path).await?; - let integrity = attrs - .get(ATTR_INTEGRITY) - .map(|value| IntegrityType::from_str(value)) - .transpose()? - .unwrap_or_default(); + let integrity = IntegrityType::from_attrs(&attrs, &cfg)?; Ok(Self { id, @@ -78,7 +71,7 @@ impl Icon { .copy( &self.cfg.staging_dist, self.cfg.filehash, - self.cfg.release, + self.cfg.release && !self.cfg.no_minification, AssetFileType::Icon(image_type), ) .await?; diff --git a/src/pipelines/js.rs b/src/pipelines/js.rs index 9a714f0f..a0a300b5 100644 --- a/src/pipelines/js.rs +++ b/src/pipelines/js.rs @@ -1,6 +1,6 @@ //! JS asset pipeline. -use super::{AssetFile, AttrWriter, Attrs, TrunkAssetPipelineOutput, ATTR_INTEGRITY, ATTR_SRC}; +use super::{AssetFile, AttrWriter, Attrs, TrunkAssetPipelineOutput, ATTR_SRC}; use crate::{ config::RtcBuild, pipelines::AssetFileType, @@ -9,7 +9,6 @@ use crate::{ use anyhow::{Context, Result}; use nipper::Document; use std::path::PathBuf; -use std::str::FromStr; use std::sync::Arc; use tokio::task::JoinHandle; @@ -42,11 +41,7 @@ impl Js { path.extend(src_attr.split('/')); let asset = AssetFile::new(&html_dir, path).await?; - let integrity = attrs - .get(ATTR_INTEGRITY) - .map(|value| IntegrityType::from_str(value)) - .transpose()? - .unwrap_or_default(); + let integrity = IntegrityType::from_attrs(&attrs, &cfg)?; // Remove src and data-trunk from attributes. let attrs = attrs @@ -79,7 +74,7 @@ impl Js { .copy( &self.cfg.staging_dist, self.cfg.filehash, - self.cfg.release, + self.cfg.release && !self.cfg.no_minification, AssetFileType::Js, ) .await?; diff --git a/src/pipelines/mod.rs b/src/pipelines/mod.rs index d36a3e3a..9cf19561 100644 --- a/src/pipelines/mod.rs +++ b/src/pipelines/mod.rs @@ -45,7 +45,6 @@ const ATTR_HREF: &str = "href"; const ATTR_SRC: &str = "src"; const ATTR_TYPE: &str = "type"; const ATTR_REL: &str = "rel"; -const ATTR_INTEGRITY: &str = "data-integrity"; const SNIPPETS_DIR: &str = "snippets"; const TRUNK_ID: &str = "data-trunk-id"; const PNG_OPTIMIZATION_LEVEL: u8 = 6; diff --git a/src/pipelines/rust/mod.rs b/src/pipelines/rust/mod.rs index cc700467..2c371501 100644 --- a/src/pipelines/rust/mod.rs +++ b/src/pipelines/rust/mod.rs @@ -149,11 +149,7 @@ impl RustApp { .map(|val| CrossOrigin::from_str(val)) .transpose()? .unwrap_or_default(); - let integrity = attrs - .get("data-integrity") - .map(|val| IntegrityType::from_str(val)) - .transpose()? - .unwrap_or_default(); + let integrity = IntegrityType::from_attrs(&attrs, &cfg)?; let manifest = CargoMetadata::new(&manifest_href).await?; let id = Some(id); @@ -238,6 +234,7 @@ impl RustApp { let manifest = CargoMetadata::new(&path).await?; let name = manifest.package.name.clone(); + let integrity = IntegrityType::default_unless(cfg.no_sri); Ok(Some(Self { id: None, @@ -256,7 +253,7 @@ impl RustApp { name, loader_shim: false, cross_origin: Default::default(), - integrity: Default::default(), + integrity, import_bindings: true, import_bindings_name: None, })) diff --git a/src/pipelines/sass.rs b/src/pipelines/sass.rs index 32cb18eb..a7c86e2b 100644 --- a/src/pipelines/sass.rs +++ b/src/pipelines/sass.rs @@ -1,8 +1,6 @@ //! Sass/Scss asset pipeline. -use super::{ - AssetFile, AttrWriter, Attrs, TrunkAssetPipelineOutput, ATTR_HREF, ATTR_INLINE, ATTR_INTEGRITY, -}; +use super::{AssetFile, AttrWriter, Attrs, TrunkAssetPipelineOutput, ATTR_HREF, ATTR_INLINE}; use crate::{ common, config::RtcBuild, @@ -12,7 +10,6 @@ use crate::{ use anyhow::{ensure, Context, Result}; use nipper::Document; use std::path::PathBuf; -use std::str::FromStr; use std::sync::Arc; use tokio::fs; use tokio::task::JoinHandle; @@ -52,11 +49,7 @@ impl Sass { let asset = AssetFile::new(&html_dir, path).await?; let use_inline = attrs.get(ATTR_INLINE).is_some(); - let integrity = attrs - .get(ATTR_INTEGRITY) - .map(|value| IntegrityType::from_str(value)) - .transpose()? - .unwrap_or_default(); + let integrity = IntegrityType::from_attrs(&attrs, &cfg)?; Ok(Self { id, @@ -105,7 +98,7 @@ impl Sass { let args = &[ "--no-source-map", "--style", - match &self.cfg.release { + match self.cfg.release && !self.cfg.no_minification { true => "compressed", false => "expanded", }, diff --git a/src/pipelines/tailwind_css.rs b/src/pipelines/tailwind_css.rs index 08a20d65..b624a8c4 100644 --- a/src/pipelines/tailwind_css.rs +++ b/src/pipelines/tailwind_css.rs @@ -1,21 +1,16 @@ //! Tailwind CSS asset pipeline. -use std::path::PathBuf; -use std::str::FromStr; -use std::sync::Arc; - -use anyhow::{Context, Result}; -use nipper::Document; -use tokio::fs; -use tokio::task::JoinHandle; - -use super::{ - AssetFile, AttrWriter, Attrs, TrunkAssetPipelineOutput, ATTR_HREF, ATTR_INLINE, ATTR_INTEGRITY, -}; +use super::{AssetFile, AttrWriter, Attrs, TrunkAssetPipelineOutput, ATTR_HREF, ATTR_INLINE}; use crate::common; use crate::config::RtcBuild; use crate::processing::integrity::{IntegrityType, OutputDigest}; use crate::tools::{self, Application}; +use anyhow::{Context, Result}; +use nipper::Document; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::fs; +use tokio::task::JoinHandle; /// A tailwind css asset pipeline. pub struct TailwindCss { @@ -51,11 +46,7 @@ impl TailwindCss { let asset = AssetFile::new(&html_dir, path).await?; let use_inline = attrs.get(ATTR_INLINE).is_some(); - let integrity = attrs - .get(ATTR_INTEGRITY) - .map(|value| IntegrityType::from_str(value)) - .transpose()? - .unwrap_or_default(); + let integrity = IntegrityType::from_attrs(&attrs, &cfg)?; Ok(Self { id, diff --git a/src/processing/integrity.rs b/src/processing/integrity.rs index f8ec652e..220df74e 100644 --- a/src/processing/integrity.rs +++ b/src/processing/integrity.rs @@ -1,5 +1,7 @@ //! Integrity processing +use crate::config::RtcBuild; +use crate::pipelines::Attrs; use base64::{display::Base64Display, engine::general_purpose::URL_SAFE}; use sha2::{Digest, Sha256, Sha384, Sha512}; use std::collections::HashMap; @@ -7,25 +9,45 @@ use std::convert::Infallible; use std::fmt::{Display, Formatter}; use std::str::FromStr; +const ATTR_INTEGRITY: &str = "data-integrity"; + /// Integrity type for subresource protection -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum IntegrityType { None, Sha256, - #[default] Sha384, Sha512, } +impl IntegrityType { + /// Get the default, unless it's disabled + pub fn default_unless(disabled: bool) -> IntegrityType { + if disabled { + Self::None + } else { + Self::Sha384 + } + } + + /// Get the integrity setting from the attributes + pub fn from_attrs(attrs: &Attrs, cfg: &RtcBuild) -> anyhow::Result { + Ok(attrs + .get(ATTR_INTEGRITY) + .map(|value| IntegrityType::from_str(value)) + .transpose()? + .unwrap_or_else(|| IntegrityType::default_unless(cfg.no_sri))) + } +} + impl FromStr for IntegrityType { type Err = IntegrityTypeParseError; - fn from_str(s: &str) -> std::result::Result { + fn from_str(s: &str) -> Result { Ok(match s { - "" => Default::default(), "none" => Self::None, "sha256" => Self::Sha256, - "sha384" => Self::Sha384, + "sha384" | "" => Self::Sha384, "sha512" => Self::Sha512, _ => return Err(IntegrityTypeParseError::InvalidValue), })