Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/rspack_plugin_sri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
29 changes: 23 additions & 6 deletions crates/rspack_plugin_sri/src/html.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,6 +16,9 @@ use rustc_hash::FxHashMap as HashMap;
use tokio::sync::RwLock;
use url::Url;

static HTTP_PROTOCOL_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^https?:").expect("Invalid regex"));

use crate::{
SRICompilationContext, SubresourceIntegrityHashFunction, SubresourceIntegrityPlugin,
SubresourceIntegrityPluginInner, config::ArcFs, integrity::compute_integrity,
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import(/* webpackChunkName: "chunk" */ "./chunk.js");
it("should compile", () => { });
155 changes: 155 additions & 0 deletions tests/rspack-test/configCases/sri/remote-src-protocol/rspack.config.js
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check this test case. Does this meet your requirements? @nanianlisao

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

Original file line number Diff line number Diff line change
@@ -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(/<script crossorigin defer integrity=".+" src="\/\/localhost:3000\/chunk\.0\.js">/);
expect(htmlContent).toMatch(/<script crossorigin defer integrity=".+" src="http:\/\/localhost:3000\/chunk\.0\.js">/);
expect(htmlContent).toMatch(/<script defer src="\/\/rspack.dev\/chunk\.0\.js">/);
expect(htmlContent).toMatch(/<script defer src="http:\/\/rspack.dev\/chunk\.0\.js">/);
});
}
}
],
}, {
target: "web",
output: {
publicPath: "http://localhost:3000/",
chunkFilename: "[name].1.js",
crossOriginLoading: "anonymous",
},
plugins: [
new experiments.SubresourceIntegrityPlugin({
htmlPlugin: require.resolve("html-webpack-plugin"),
}),
new HtmlWebpackPlugin({
filename: "index1.html",
}),
{
apply(compiler) {
compiler.hooks.compilation.tap('TestPlugin', (compilation) => {
HtmlWebpackPlugin.getCompilationHooks(compilation).beforeAssetTagGeneration.tap('SubresourceIntegrityPlugin', (data) => {
data.assets.js.push("//localhost:3000/chunk.1.js");
data.assets.js.push("http://localhost:3000/chunk.1.js");
data.assets.js.push("//rspack.dev/chunk.1.js");
data.assets.js.push("http://rspack.dev/chunk.1.js");
});
});
}
},
{
apply(compiler) {
compiler.hooks.done.tap('TestPlugin', () => {
const htmlContent = fs.readFileSync(path.resolve(testPath, "index1.html"), "utf-8");
expect(htmlContent).toMatch(/<script defer="defer" src="\/\/localhost:3000\/chunk\.1\.js" integrity=".+" crossorigin="anonymous">/);
expect(htmlContent).toMatch(/<script defer="defer" src="http:\/\/localhost:3000\/chunk\.1\.js" integrity=".+" crossorigin="anonymous">/);
expect(htmlContent).toMatch(/<script defer="defer" src="\/\/rspack.dev\/chunk\.1\.js">/);
expect(htmlContent).toMatch(/<script defer="defer" src="http:\/\/rspack.dev\/chunk\.1\.js">/);
});
}
}
],
}, {
target: "web",
output: {
publicPath: "//localhost:3000/",
chunkFilename: "[name].2.js",
crossOriginLoading: "anonymous",
},
plugins: [
new experiments.SubresourceIntegrityPlugin(),
new HtmlRspackPlugin({
filename: "index2.html",
}),
{
apply(compiler) {
compiler.hooks.compilation.tap('TestPlugin', (compilation) => {
HtmlRspackPlugin.getCompilationHooks(compilation).beforeAssetTagGeneration.tap('SubresourceIntegrityPlugin', (data) => {
data.assets.js.push("//localhost:3000/chunk.2.js");
data.assets.js.push("http://localhost:3000/chunk.2.js");
data.assets.js.push("//rspack.dev/chunk.2.js");
data.assets.js.push("http://rspack.dev/chunk.2.js");
});
});
}
},
{
apply(compiler) {
compiler.hooks.done.tap('TestPlugin', () => {
const htmlContent = fs.readFileSync(path.resolve(testPath, "index2.html"), "utf-8");
expect(htmlContent).toMatch(/<script crossorigin defer integrity=".+" src="\/\/localhost:3000\/chunk\.2\.js">/);
expect(htmlContent).toMatch(/<script crossorigin defer integrity=".+" src="http:\/\/localhost:3000\/chunk\.2\.js">/);
expect(htmlContent).toMatch(/<script defer src="\/\/rspack.dev\/chunk\.2\.js">/);
expect(htmlContent).toMatch(/<script defer src="http:\/\/rspack.dev\/chunk\.2\.js">/);
});
}
}
],
}, {
target: "web",
output: {
publicPath: "//localhost:3000/",
chunkFilename: "[name].3.js",
crossOriginLoading: "anonymous",
},
plugins: [
new experiments.SubresourceIntegrityPlugin({
htmlPlugin: require.resolve("html-webpack-plugin"),
}),
new HtmlWebpackPlugin({
filename: "index3.html",
}),
{
apply(compiler) {
compiler.hooks.compilation.tap('TestPlugin', (compilation) => {
HtmlWebpackPlugin.getCompilationHooks(compilation).beforeAssetTagGeneration.tap('SubresourceIntegrityPlugin', (data) => {
data.assets.js.push("//localhost:3000/chunk.3.js");
data.assets.js.push("http://localhost:3000/chunk.3.js");
data.assets.js.push("//rspack.dev/chunk.3.js");
data.assets.js.push("http://rspack.dev/chunk.3.js");
});
});
}
},
{
apply(compiler) {
compiler.hooks.done.tap('TestPlugin', () => {
const htmlContent = fs.readFileSync(path.resolve(testPath, "index3.html"), "utf-8");
expect(htmlContent).toMatch(/<script defer="defer" src="\/\/localhost:3000\/chunk\.3\.js" integrity=".+" crossorigin="anonymous">/);
expect(htmlContent).toMatch(/<script defer="defer" src="http:\/\/localhost:3000\/chunk\.3\.js" integrity=".+" crossorigin="anonymous">/);
expect(htmlContent).toMatch(/<script defer="defer" src="\/\/rspack.dev\/chunk\.3\.js">/);
expect(htmlContent).toMatch(/<script defer="defer" src="http:\/\/rspack.dev\/chunk\.3\.js">/);
});
}
}
],
}]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
findBundle() {
return [];
}
};
Loading