diff --git a/.changes/fix-csp-windows.md b/.changes/fix-csp-windows.md new file mode 100644 index 000000000..8321e7b1a --- /dev/null +++ b/.changes/fix-csp-windows.md @@ -0,0 +1,5 @@ +--- +"tauri-codegen": patch:bug +--- + +Fix JavaScript SHA256 hash generation on Windows not ignoring carriage return characters. diff --git a/.changes/normalize_script_for_csp.md b/.changes/normalize_script_for_csp.md new file mode 100644 index 000000000..3fa4bb533 --- /dev/null +++ b/.changes/normalize_script_for_csp.md @@ -0,0 +1,5 @@ +--- +"tauri-utils": patch:feat +--- + +Added `html::normalize_script_for_csp`. diff --git a/crates/tauri-codegen/src/context.rs b/crates/tauri-codegen/src/context.rs index 9176aea64..87a05d702 100644 --- a/crates/tauri-codegen/src/context.rs +++ b/crates/tauri-codegen/src/context.rs @@ -50,7 +50,9 @@ fn inject_script_hashes(document: &NodeRef, key: &AssetKey, csp_hashes: &mut Csp for inline_script_el in inline_script_elements { let script = inline_script_el.as_node().text_contents(); let mut hasher = Sha256::new(); - hasher.update(&script); + hasher.update(tauri_utils::html::normalize_script_for_csp( + script.as_bytes(), + )); let hash = hasher.finalize(); scripts.push(format!( "'sha256-{}'", diff --git a/crates/tauri-codegen/src/embedded_assets.rs b/crates/tauri-codegen/src/embedded_assets.rs index 20b00a105..ede7ee6d9 100644 --- a/crates/tauri-codegen/src/embedded_assets.rs +++ b/crates/tauri-codegen/src/embedded_assets.rs @@ -180,10 +180,12 @@ impl CspHashes { if dangerous_disable_asset_csp_modification.can_modify("script-src") { let mut hasher = Sha256::new(); hasher.update( - &std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead { - path: path.to_path_buf(), - error, - })?, + &std::fs::read(path) + .map(|b| tauri_utils::html::normalize_script_for_csp(&b)) + .map_err(|error| EmbeddedAssetsError::AssetRead { + path: path.to_path_buf(), + error, + })?, ); let hash = hasher.finalize(); self.scripts.push(format!( diff --git a/crates/tauri-utils/src/html.rs b/crates/tauri-utils/src/html.rs index bfa203f00..d29a61d08 100644 --- a/crates/tauri-utils/src/html.rs +++ b/crates/tauri-utils/src/html.rs @@ -286,6 +286,38 @@ pub fn inline_isolation(document: &NodeRef, dir: &Path) { } } +/// Normalize line endings in script content to match what the browser uses for CSP hashing. +/// +/// According to the HTML spec, browsers normalize: +/// - `\r\n` → `\n` +/// - `\r` → `\n` +pub fn normalize_script_for_csp(input: &[u8]) -> Vec { + let mut output = Vec::with_capacity(input.len()); + + let mut i = 0; + while i < input.len() { + match input[i] { + b'\r' => { + if i + 1 < input.len() && input[i + 1] == b'\n' { + // CRLF → LF + output.push(b'\n'); + i += 2; + } else { + // Lone CR → LF + output.push(b'\n'); + i += 1; + } + } + _ => { + output.push(input[i]); + i += 1; + } + } + } + + output +} + #[cfg(test)] mod tests { @@ -307,4 +339,14 @@ mod tests { ); } } + + #[test] + fn normalize_script_for_csp() { + let js = "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\r// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\r\n\r\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\r\n return payload\r\n}\r\n"; + let expected = "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\n return payload\n}\n"; + assert_eq!( + super::normalize_script_for_csp(js.as_bytes()), + expected.as_bytes() + ) + } }