From c67e274fa807f9b8f976f35321eef66f99e3c308 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Fri, 21 Nov 2025 18:23:54 +0800 Subject: [PATCH 1/2] fix: should match relative protocol of public path and tag src --- Cargo.lock | 1 + crates/rspack_plugin_sri/Cargo.toml | 1 + crates/rspack_plugin_sri/src/html.rs | 29 +++- .../SubresourceIntegrityPlugin.ts | 31 +++- .../sri/remote-src-protocol/chunk.js | 0 .../sri/remote-src-protocol/index.js | 2 + .../sri/remote-src-protocol/rspack.config.js | 155 ++++++++++++++++++ .../sri/remote-src-protocol/test.config.js | 3 + 8 files changed, 209 insertions(+), 13 deletions(-) create mode 100644 tests/rspack-test/configCases/sri/remote-src-protocol/chunk.js create mode 100644 tests/rspack-test/configCases/sri/remote-src-protocol/index.js create mode 100644 tests/rspack-test/configCases/sri/remote-src-protocol/rspack.config.js create mode 100644 tests/rspack-test/configCases/sri/remote-src-protocol/test.config.js diff --git a/Cargo.lock b/Cargo.lock index 75fc38ec12e1..9914a8a171d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4868,6 +4868,7 @@ dependencies = [ "derive_more", "futures", "indexmap", + "once_cell", "pathdiff", "rayon", "regex", diff --git a/crates/rspack_plugin_sri/Cargo.toml b/crates/rspack_plugin_sri/Cargo.toml index bfa984423030..845f87ba2427 100644 --- a/crates/rspack_plugin_sri/Cargo.toml +++ b/crates/rspack_plugin_sri/Cargo.toml @@ -13,6 +13,7 @@ cow-utils = { workspace = true } derive_more = { workspace = true, features = ["debug"] } futures = { workspace = true } indexmap = { workspace = true } +once_cell = { workspace = true } pathdiff = { workspace = true } rayon = { workspace = true } regex = { workspace = true } diff --git a/crates/rspack_plugin_sri/src/html.rs b/crates/rspack_plugin_sri/src/html.rs index 9b2ca9634e07..47a36880c9d4 100644 --- a/crates/rspack_plugin_sri/src/html.rs +++ b/crates/rspack_plugin_sri/src/html.rs @@ -1,6 +1,9 @@ use std::sync::Arc; +use cow_utils::CowUtils; use futures::future::join_all; +use once_cell::sync::Lazy; +use regex::Regex; use rspack_error::{Result, ToStringResultToRspackResultExt}; use rspack_hook::plugin_hook; use rspack_paths::Utf8Path; @@ -13,6 +16,9 @@ use rustc_hash::FxHashMap as HashMap; use tokio::sync::RwLock; use url::Url; +static HTTP_PROTOCOL_REGEX: Lazy = + Lazy::new(|| Regex::new(r"^https?:").expect("Invalid regex")); + use crate::{ SRICompilationContext, SubresourceIntegrityHashFunction, SubresourceIntegrityPlugin, SubresourceIntegrityPluginInner, config::ArcFs, integrity::compute_integrity, @@ -167,16 +173,27 @@ async fn process_tag( return Ok(None); } - let Some(tag_src) = get_tag_src(tag) else { + let Some(mut tag_src) = get_tag_src(tag) else { return Ok(None); }; // A tag which is not generated by chunks should be skipped - if let Ok(url) = Url::parse(&tag_src) - && (url.scheme() == "http" || url.scheme() == "https") - && (public_path.is_empty() || !tag_src.starts_with(public_path)) - { - return Ok(None); + if match Url::parse(&tag_src) { + Ok(url) => url.scheme() == "http" || url.scheme() == "https", + Err(_) => tag_src.starts_with("//"), + } { + if public_path.is_empty() { + return Ok(None); + } + let protocol_relative_public_path = HTTP_PROTOCOL_REGEX.replace(public_path, "").to_string(); + let protocol_relative_tag_src = HTTP_PROTOCOL_REGEX.replace(&tag_src, "").to_string(); + if protocol_relative_tag_src.starts_with(&protocol_relative_public_path) { + tag_src = protocol_relative_tag_src + .cow_replace(&protocol_relative_public_path, public_path) + .into_owned(); + } else { + return Ok(None); + } } let src = get_asset_path(&tag_src, public_path); diff --git a/packages/rspack/src/builtin-plugin/SubresourceIntegrityPlugin.ts b/packages/rspack/src/builtin-plugin/SubresourceIntegrityPlugin.ts index f82d8ce51d96..6087bb173634 100644 --- a/packages/rspack/src/builtin-plugin/SubresourceIntegrityPlugin.ts +++ b/packages/rspack/src/builtin-plugin/SubresourceIntegrityPlugin.ts @@ -14,6 +14,8 @@ import { create } from "./base"; const PLUGIN_NAME = "SubresourceIntegrityPlugin"; const NATIVE_HTML_PLUGIN = "HtmlRspackPlugin"; +const HTTP_PROTOCOL_REGEX = /^https?:/; + type HtmlTagObject = { attributes: { [attributeName: string]: string | boolean | null | undefined; @@ -185,21 +187,36 @@ export class SubresourceIntegrityPlugin extends NativeSubresourceIntegrityPlugin return; } - const tagSrc = getTagSrc(tag); + let tagSrc = getTagSrc(tag); if (!tagSrc) { return; } + let isUrlSrc = false; try { const url = new URL(tagSrc); - if ( - (url.protocol === "http:" || url.protocol === "https:") && - (!publicPath || !tagSrc.startsWith(publicPath)) - ) { + isUrlSrc = url.protocol === "http:" || url.protocol === "https:"; + } catch (_) { + isUrlSrc = tagSrc.startsWith("//"); + } + + if (isUrlSrc) { + if (!publicPath) { + return; + } + const protocolRelativePublicPath = publicPath.replace( + HTTP_PROTOCOL_REGEX, + "" + ); + const protocolRelativeTagSrc = tagSrc.replace(HTTP_PROTOCOL_REGEX, ""); + if (protocolRelativeTagSrc.startsWith(protocolRelativePublicPath)) { + tagSrc = protocolRelativeTagSrc.replace( + protocolRelativePublicPath, + publicPath + ); + } else { return; } - } catch (_) { - // do nothing } const src = relative(publicPath, decodeURIComponent(tagSrc)); diff --git a/tests/rspack-test/configCases/sri/remote-src-protocol/chunk.js b/tests/rspack-test/configCases/sri/remote-src-protocol/chunk.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/rspack-test/configCases/sri/remote-src-protocol/index.js b/tests/rspack-test/configCases/sri/remote-src-protocol/index.js new file mode 100644 index 000000000000..f6c96bb66f2b --- /dev/null +++ b/tests/rspack-test/configCases/sri/remote-src-protocol/index.js @@ -0,0 +1,2 @@ +import(/* webpackChunkName: "chunk" */ "./chunk.js"); +it("should compile", () => { }); diff --git a/tests/rspack-test/configCases/sri/remote-src-protocol/rspack.config.js b/tests/rspack-test/configCases/sri/remote-src-protocol/rspack.config.js new file mode 100644 index 000000000000..47649d393d11 --- /dev/null +++ b/tests/rspack-test/configCases/sri/remote-src-protocol/rspack.config.js @@ -0,0 +1,155 @@ +const { experiments, HtmlRspackPlugin } = require("@rspack/core"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const fs = require("fs"); +const path = require("path"); + +/** @type {import("@rspack/core").Configuration} */ +module.exports = (_, { testPath }) => ([{ + target: "web", + output: { + publicPath: "http://localhost:3000/", + chunkFilename: "[name].0.js", + crossOriginLoading: "anonymous", + }, + plugins: [ + new experiments.SubresourceIntegrityPlugin(), + new HtmlRspackPlugin({ + filename: "index.html", + }), + { + apply(compiler) { + compiler.hooks.compilation.tap('TestPlugin', (compilation) => { + HtmlRspackPlugin.getCompilationHooks(compilation).beforeAssetTagGeneration.tap('SubresourceIntegrityPlugin', (data) => { + data.assets.js.push("//localhost:3000/chunk.0.js"); + data.assets.js.push("http://localhost:3000/chunk.0.js"); + data.assets.js.push("//rspack.dev/chunk.0.js"); + data.assets.js.push("http://rspack.dev/chunk.0.js"); + }); + }); + } + }, + { + apply(compiler) { + compiler.hooks.done.tap('TestPlugin', () => { + const htmlContent = fs.readFileSync(path.resolve(testPath, "index.html"), "utf-8"); + expect(htmlContent).toMatch(/