Skip to content
Merged
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
35 changes: 28 additions & 7 deletions crates/rspack_plugin_sri/src/html.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::sync::Arc;

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 +15,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 @@ -172,14 +177,30 @@ async fn process_tag(
};

// 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);
}
let src = 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) {
let tag_src_with_scheme = format!("http:{}", protocol_relative_tag_src);
let public_path_with_scheme = if protocol_relative_public_path.starts_with("//") {
format!("http:{}", protocol_relative_public_path)
} else {
protocol_relative_public_path.to_string()
};
get_asset_path(&tag_src_with_scheme, &public_path_with_scheme)
} else {
return Ok(None);
}
} else {
get_asset_path(&tag_src, public_path)
};

let src = get_asset_path(&tag_src, public_path);
if let Some(integrity) =
get_integrity_checksum_for_asset(&src, integrities, normalized_integrities).await
{
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 @@ -190,19 +192,40 @@ export class SubresourceIntegrityPlugin extends NativeSubresourceIntegrityPlugin
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("//");
}

let src = "";
if (isUrlSrc) {
if (!publicPath) {
return;
}
} catch (_) {
// do nothing
const protocolRelativePublicPath = publicPath.replace(
HTTP_PROTOCOL_REGEX,
""
);
const protocolRelativeTagSrc = tagSrc.replace(HTTP_PROTOCOL_REGEX, "");
if (protocolRelativeTagSrc.startsWith(protocolRelativePublicPath)) {
const tagSrcWithScheme = `http:${protocolRelativeTagSrc}`;
const publicPathWithScheme = protocolRelativePublicPath.startsWith("//")
? `http:${protocolRelativePublicPath}`
: protocolRelativePublicPath;
src = relative(
publicPathWithScheme,
decodeURIComponent(tagSrcWithScheme)
);
} else {
return;
}
} else {
src = relative(publicPath, decodeURIComponent(tagSrc));
}

const src = relative(publicPath, decodeURIComponent(tagSrc));
tag.attributes.integrity =
this.getIntegrityChecksumForAsset(src) ||
computeIntegrity(
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
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