Repository: cobalt-org/cobalt.rs (rust)

Issue:
RUSTSEC-2021-0136: `sass-rs` has been deprecated

> `sass-rs` has been deprecated

| Details             |                                                |
| ------------------- | ---------------------------------------------- |
| Status              | unmaintained                |
| Package             | `sass-rs`                      |
| Version             | `0.2.2`                   |
| URL                 | [https://github.com/compass-rs/sass-rs/issues/83](https://github.com/compass-rs/sass-rs/issues/83) |
| Date                | 2021-04-07                         |

The `sass-rs` crate is not maintained anymore as libsass is deprecated.
Consider using https://github.com/connorskees/grass or https://github.com/kaj/rsass instead.
(Author&#39;s recomendation.)

See [advisory page](https://rustsec.org/advisories/RUSTSEC-2021-0136.html) for additional details.


Pull request:
fix!: Switch from libsass to grass
Not perfect SASS compatibility

Fixes #1050

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index aa25fa3..19804f5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,6 +17,18 @@ version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
 
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "version_check",
+ "zerocopy 0.7.35",
+]
+
 [[package]]
 name = "aho-corasick"
 version = "1.1.3"
@@ -26,6 +38,12 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
 [[package]]
 name = "android-tzdata"
 version = "0.1.1"
@@ -329,6 +347,7 @@ dependencies = [
  "engarde",
  "env_logger",
  "file-serve",
+ "grass",
  "html-minifier",
  "human-panic",
  "ignore",
@@ -346,7 +365,6 @@ dependencies = [
  "regex",
  "relative-path",
  "rss",
- "sass-rs",
  "scrawl",
  "serde",
  "serde_json",
@@ -392,6 +410,12 @@ dependencies = [
  "walkdir",
 ]
 
+[[package]]
+name = "codemap"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24"
+
 [[package]]
 name = "colorchoice"
 version = "1.0.3"
@@ -636,6 +660,7 @@ dependencies = [
 name = "engarde"
 version = "0.1.13"
 dependencies = [
+ "flate2",
  "syntect",
  "thiserror 1.0.69",
 ]
@@ -790,6 +815,19 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "getrandom"
 version = "0.3.1"
@@ -827,12 +865,46 @@ dependencies = [
  "regex-syntax",
 ]
 
+[[package]]
+name = "grass"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7a68216437ef68f0738e48d6c7bb9e6e6a92237e001b03d838314b068f33c94"
+dependencies = [
+ "getrandom 0.2.15",
+ "grass_compiler",
+]
+
+[[package]]
+name = "grass_compiler"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6"
+dependencies = [
+ "codemap",
+ "indexmap 2.7.1",
+ "lasso",
+ "once_cell",
+ "phf",
+ "rand",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
 
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+dependencies = [
+ "ahash",
+ "allocator-api2",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.15.2"
@@ -845,12 +917,6 @@ version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
-[[package]]
-name = "hermit-abi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
-
 [[package]]
 name = "html-escape"
 version = "0.2.13"
@@ -1259,6 +1325,15 @@ dependencies = [
  "static_assertions",
 ]
 
+[[package]]
+name = "lasso"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb"
+dependencies = [
+ "hashbrown 0.14.5",
+]
+
 [[package]]
 name = "libc"
 version = "0.2.170"
@@ -1468,16 +1543,6 @@ dependencies = [
  "autocfg",
 ]
 
-[[package]]
-name = "num_cpus"
-version = "1.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
-dependencies = [
- "hermit-abi",
- "libc",
-]
-
 [[package]]
 name = "object"
 version = "0.36.7"
@@ -1583,10 +1648,46 @@ dependencies = [
 ]
 
 [[package]]
-name = "pkg-config"
-version = "0.3.31"
+name = "phf"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
+checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
+dependencies = [
+ "siphasher",
+]
 
 [[package]]
 name = "portable-atomic"
@@ -1609,6 +1710,15 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy 0.8.24",
+]
+
 [[package]]
 name = "proc-exit"
 version = "2.0.2"
@@ -1661,6 +1771,36 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.15",
+]
+
 [[package]]
 name = "rayon"
 version = "1.10.0"
@@ -1793,28 +1933,6 @@ dependencies = [
  "winapi-util",
 ]
 
-[[package]]
-name = "sass-rs"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cabcf7c6e55053f359911187ac401409aad2dc14338cae972dec266fee486abd"
-dependencies = [
- "libc",
- "sass-sys",
-]
-
-[[package]]
-name = "sass-sys"
-version = "0.4.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "933bca23b402377f0ab71e79732a826deffc748013746ac3314f6abc7f9fc51c"
-dependencies = [
- "cc",
- "libc",
- "num_cpus",
- "pkg-config",
-]
-
 [[package]]
 name = "scrawl"
 version = "2.0.0"
@@ -1898,6 +2016,12 @@ version = "2.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
 
+[[package]]
+name = "siphasher"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
+
 [[package]]
 name = "sitemap"
 version = "0.4.1"
@@ -2024,7 +2148,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
 dependencies = [
  "fastrand",
- "getrandom",
+ "getrandom 0.3.1",
  "once_cell",
  "rustix 1.0.5",
  "windows-sys 0.59.0",
@@ -2262,7 +2386,7 @@ version = "1.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6"
 dependencies = [
- "getrandom",
+ "getrandom 0.3.1",
 ]
 
 [[package]]
@@ -2538,6 +2662,46 @@ dependencies = [
  "synstructure",
 ]
 
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "zerocopy-derive 0.7.35",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
+dependencies = [
+ "zerocopy-derive 0.8.24",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "zerofrom"
 version = "0.1.6"
diff --git a/Cargo.toml b/Cargo.toml
index f9cb9b5..afa41ab 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -173,7 +173,7 @@ dunce = "1.0.5"
 file-serve = { version = "0.3.8", path = "crates/file-serve", optional = true }
 notify = { version = "8.0.0", optional = true, default-features = false, features = ["macos_fsevent"] }
 
-sass-rs = { version = "0.2.2", optional = true }
+grass = { version = "0.13.4", features = ["random"], default-features = false, optional = true }
 
 html-minifier = {version="5.0.0", optional = true }
 anyhow = "1.0.97"
@@ -192,7 +192,7 @@ preview_unstable = ["cobalt-config/preview_unstable"]
 
 serve = ["file-serve", "notify"]
 syntax-highlight = ["engarde/syntax"]
-sass = ['sass-rs']
+sass = ["dep:grass"]
 
 [profile.dev]
 panic = "abort"
diff --git a/crates/engarde/Cargo.toml b/crates/engarde/Cargo.toml
index 237e4e9..c617bd2 100644
--- a/crates/engarde/Cargo.toml
+++ b/crates/engarde/Cargo.toml
@@ -38,6 +38,7 @@ features =  ["parsing", "regex-fancy", "default-syntaxes", "default-themes", "ht
 
 [target.'cfg(any())'.dependencies]
 thiserror = "1.0.69"  # HACK: bad minimal dep in syntect
+flate2 = "1.0.35"  # HACK: bad minimal dep in syntect
 
 [lints]
 workspace = true
diff --git a/src/cobalt_model/sass.rs b/src/cobalt_model/sass.rs
index 97fb8f4..eb510d1 100644
--- a/src/cobalt_model/sass.rs
+++ b/src/cobalt_model/sass.rs
@@ -1,8 +1,6 @@
 use std::ffi;
 use std::path;
 
-#[cfg(feature = "sass")]
-use sass_rs;
 use serde::{Deserialize, Serialize};
 
 use super::files;
@@ -13,7 +11,7 @@ pub(crate) use cobalt_config::SassOutputStyle;
 #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
 #[serde(deny_unknown_fields, default)]
 pub struct SassBuilder {
-    pub import_dir: Option<String>,
+    pub import_dir: path::PathBuf,
     pub style: SassOutputStyle,
 }
 
@@ -21,11 +19,7 @@ impl SassBuilder {
     pub fn from_config(config: cobalt_config::Sass, source: &path::Path) -> Self {
         Self {
             style: config.style,
-            import_dir: source
-                .join(config.import_dir)
-                .into_os_string()
-                .into_string()
-                .ok(),
+            import_dir: source.join(config.import_dir),
         }
     }
 
@@ -37,7 +31,7 @@ impl SassBuilder {
 
 #[derive(Debug, PartialEq, Eq)]
 pub struct SassCompiler {
-    import_dir: Option<String>,
+    import_dir: path::PathBuf,
     style: SassOutputStyle,
 }
 
@@ -60,18 +54,20 @@ impl SassCompiler {
         file_path: &path::Path,
         minify: &Minify,
     ) -> Result<()> {
-        let sass_opts = sass_rs::Options {
-            include_paths: self.import_dir.iter().cloned().collect(),
-            output_style: match self.style {
-                SassOutputStyle::Nested => sass_rs::OutputStyle::Nested,
-                SassOutputStyle::Expanded => sass_rs::OutputStyle::Expanded,
-                SassOutputStyle::Compact => sass_rs::OutputStyle::Compact,
-                SassOutputStyle::Compressed => sass_rs::OutputStyle::Compressed,
-            },
-            ..Default::default()
+        let sass_opts = grass::Options::default()
+            .style(match self.style {
+                SassOutputStyle::Nested | SassOutputStyle::Expanded => grass::OutputStyle::Expanded,
+                SassOutputStyle::Compact | SassOutputStyle::Compressed => {
+                    grass::OutputStyle::Compressed
+                }
+            })
+            .load_path(&self.import_dir);
+        let content = if let Some(file_path) = file_path.to_str() {
+            grass::from_path(file_path, &sass_opts)?
+        } else {
+            let raw = std::fs::read_to_string(file_path)?;
+            grass::from_string(raw, &sass_opts)?
         };
-        let content =
-            sass_rs::compile_file(file_path, sass_opts).map_err(|e| anyhow::format_err!("{e}"))?;
 
         let rel_src = file_path
             .strip_prefix(source)


Test patch:
diff --git a/tests/cmd/sass.out/_dest/style/indented.css b/tests/cmd/sass.out/_dest/style/indented.css
index 7e67df2..b91315c 100644
--- a/tests/cmd/sass.out/_dest/style/indented.css
+++ b/tests/cmd/sass.out/_dest/style/indented.css
@@ -1,2 +1,3 @@
 body {
-  font-family: Arial; }
+  font-family: Arial;
+}
diff --git a/tests/cmd/sass.out/_dest/style/main.css b/tests/cmd/sass.out/_dest/style/main.css
index 7219bff..8308350 100644
--- a/tests/cmd/sass.out/_dest/style/main.css
+++ b/tests/cmd/sass.out/_dest/style/main.css
@@ -1,2 +1,3 @@
 .main {
-  width: 100vw; }
+  width: 100vw;
+}
diff --git a/tests/cmd/sass.out/_dest/style/nest.css b/tests/cmd/sass.out/_dest/style/nest.css
index 075a210..b98e7f8 100644
--- a/tests/cmd/sass.out/_dest/style/nest.css
+++ b/tests/cmd/sass.out/_dest/style/nest.css
@@ -1,5 +1,7 @@
 .main {
-  width: 100vw; }
+  width: 100vw;
+}
 
 nav {
-  color: blue; }
+  color: blue;
+}
diff --git a/tests/cmd/sass_custom_config.out/_dest/style/indented.css b/tests/cmd/sass_custom_config.out/_dest/style/indented.css
index 57392e3..b71a527 100644
--- a/tests/cmd/sass_custom_config.out/_dest/style/indented.css
+++ b/tests/cmd/sass_custom_config.out/_dest/style/indented.css
@@ -1,1 +1,1 @@
-body{font-family:Arial}
+body{font-family:Arial}
\ No newline at end of file
diff --git a/tests/cmd/sass_custom_config.out/_dest/style/main.css b/tests/cmd/sass_custom_config.out/_dest/style/main.css
index 80e2944..904c293 100644
--- a/tests/cmd/sass_custom_config.out/_dest/style/main.css
+++ b/tests/cmd/sass_custom_config.out/_dest/style/main.css
@@ -1,1 +1,1 @@
-.main{width:100vw}
+.main{width:100vw}
\ No newline at end of file
diff --git a/tests/cmd/sass_custom_config.out/_dest/style/nest.css b/tests/cmd/sass_custom_config.out/_dest/style/nest.css
index 3f19f5d..9525dc3 100644
--- a/tests/cmd/sass_custom_config.out/_dest/style/nest.css
+++ b/tests/cmd/sass_custom_config.out/_dest/style/nest.css
@@ -1,1 +1,1 @@
-.main{width:100vw}nav{color:blue}
+.main{width:100vw}nav{color:blue}
\ No newline at end of file