Skip to content

Commit 1ca73dd

Browse files
committed
feat: firefox blob csp
1 parent d587351 commit 1ca73dd

File tree

3 files changed

+61
-37
lines changed

3 files changed

+61
-37
lines changed

packages/force-copy/src/content/runtime/script.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const implantScript = () => {
1+
export const importScript = () => {
22
const fn = window[process.env.INJECT_FILE as unknown as number] as unknown as () => void;
33
// #IFDEF GECKO
44
if (fn) {
@@ -10,8 +10,8 @@ export const implantScript = () => {
1010
script.innerText = `;(${fn.toString()})();`;
1111
document.documentElement.appendChild(script);
1212
// 在这里仅移除 script 标签, 但不会删除 window 上的属性
13-
// 保证在 CSP 限制下可以重试, 且处于隔离环境不会受到影响
14-
script.onload = () => script.remove();
13+
// 保证注入重试, inject 幂等且 content 处于隔离环境不会受到影响
14+
script.remove();
1515
}
1616
// #ENDIF
1717
};

packages/force-copy/src/worker/runtime/script.ts

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { URL_MATCH } from "@/utils/constant";
22
import { cross } from "@/utils/global";
33
import { logger } from "@/utils/logger";
4+
import { CODE_PREFIX, CODE_SUFFIX } from "../utils/constant";
45

5-
export const implantScript = () => {
6-
/** RUN INJECT SCRIPT IN DOCUMENT START **/
6+
export const importScript = () => {
77
// #IFDEF CHROMIUM
88
// https://bugs.chromium.org/p/chromium/issues/detail?id=634381
99
// https://stackoverflow.com/questions/75495191/chrome-extension-manifest-v3-how-to-use-window-addeventlistener
@@ -35,6 +35,7 @@ export const implantScript = () => {
3535
return void 0;
3636
}
3737
if (tabId && cross.scripting) {
38+
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript
3839
cross.scripting.executeScript({
3940
target: { tabId: tabId, allFrames: true },
4041
files: [process.env.INJECT_FILE + ".js"],
@@ -49,8 +50,8 @@ export const implantScript = () => {
4950
// #IFDEF GECKO
5051
logger.info("Register Inject Scripts By Content Script Inline Code");
5152
// 使用 cross.tabs.executeScript 得到的 window 是 content window
52-
// 此时就必须要使用 Inject Script 的方式才能正常注入脚本
53-
// 然而这种方式就会受到 CSP 的限制, 因此在这里处理 CSP 问题
53+
// 此时就必须要使用 inject script 的方式才能正常注入脚本
54+
// 然而这种方式就会受到 content security policy 策略的限制
5455
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/onHeadersReceived
5556
chrome.webRequest.onHeadersReceived.addListener(
5657
res => {
@@ -66,7 +67,7 @@ export const implantScript = () => {
6667
const types = value.split(";").map(it => it.trim());
6768
const target: string[] = [];
6869
// CSP 不支持多个 nonce, 但可以配置多个 hash
69-
// 这里的 HASH 会在 WrapperCodePlugin 中计算并替换资源
70+
// 这里的 hash 会在编译时计算并替换资源
7071
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
7172
const hashed = "'sha256-${CSP-HASH}'";
7273
for (const item of types) {
@@ -77,40 +78,51 @@ export const implantScript = () => {
7778
}
7879
target.push(item);
7980
}
80-
// 覆盖原有的响应头, 但扩展的 CSP 总是更倾向于更加严格的模式
81+
// 覆盖原有的响应头, 扩展的 CSP 总是更倾向于更加严格的模式
8182
// 实际测试中仅有完全抹除标头时, 才可以解决冲突的问题
8283
res.responseHeaders[i].value = target.join(";");
83-
// 如果存在 nonce 的配置, 则尝试注入 Inject Script
84+
// 存在 CSP 时尝试直接在 content script 中执行
85+
let code = [
86+
`if (window["${process.env.INJECT_FILE}"] && document instanceof XMLDocument === false) {`,
87+
` window["${process.env.INJECT_FILE}"]();`,
88+
`};`,
89+
].join("\n");
90+
// 如果存在 blob: 的配置 尝试以 blob src 的形式注入
91+
if (/script-src[- \w']+blob:/.test(value)) {
92+
code = [
93+
CODE_PREFIX,
94+
`script.src = URL.createObjectURL(new Blob([code]));`,
95+
CODE_SUFFIX,
96+
].join("\n");
97+
}
98+
// 如果存在 nonce 的配置 则会尝试以 nonce 的形式注入
8499
const nonce = /'nonce-([-+/=\w]+)'/.exec(value);
85100
if (nonce && nonce[1]) {
86-
const nonceId = nonce[1];
87-
const code = `
88-
if (window["${process.env.INJECT_FILE}"] && document instanceof XMLDocument === false) {
89-
const script = document.createElementNS("http://www.w3.org/1999/xhtml", "script");
90-
script.setAttribute("type", "text/javascript");
91-
script.innerText = \`;(\${window["${process.env.INJECT_FILE}"].toString()})();\`;
92-
script.nonce = "${nonceId}";
93-
document.documentElement.appendChild(script);
94-
script.onload = () => script.remove();
95-
};`;
96-
const onUpdate = (_: number, changeInfo: chrome.tabs.TabChangeInfo) => {
97-
if (changeInfo.status !== "loading") return void 0;
98-
// https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/API/tabs/executeScript
99-
cross.tabs
100-
.executeScript(res.tabId, {
101-
allFrames: true,
102-
code: code,
103-
runAt: "document_start",
104-
})
105-
.catch(err => {
106-
if (__DEV__) logger.warning("Inject Script", err);
107-
});
108-
cross.tabs.onUpdated.removeListener(onUpdate);
109-
};
110-
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/onUpdated
111-
// @ts-expect-error filter params
112-
cross.tabs.onUpdated.addListener(onUpdate, { tabId: res.tabId });
101+
code = [
102+
CODE_PREFIX,
103+
`script.nonce = "${nonce[1]}";`,
104+
`script.innerText = code`,
105+
CODE_SUFFIX,
106+
].join("\n");
113107
}
108+
const onUpdate = (_: number, changeInfo: chrome.tabs.TabChangeInfo) => {
109+
if (changeInfo.status !== "loading") return void 0;
110+
// https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/API/tabs/executeScript
111+
cross.tabs
112+
.executeScript(res.tabId, {
113+
allFrames: false,
114+
code: code,
115+
runAt: "document_start",
116+
frameId: res.frameId,
117+
})
118+
.catch(err => {
119+
if (__DEV__) logger.warning("Inject Script", err);
120+
});
121+
cross.tabs.onUpdated.removeListener(onUpdate);
122+
};
123+
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/onUpdated
124+
// @ts-expect-error filter params
125+
cross.tabs.onUpdated.addListener(onUpdate, { tabId: res.tabId });
114126
}
115127
// 返回修改后的响应头配置
116128
return {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const CODE_PREFIX = [
2+
`if (window["${process.env.INJECT_FILE}"] && document instanceof XMLDocument === false) {`,
3+
` const script = document.createElementNS("http://www.w3.org/1999/xhtml", "script");`,
4+
` script.setAttribute("type", "text/javascript");`,
5+
` const code = \`;(\${window["${process.env.INJECT_FILE}"].toString()})();\`;`,
6+
].join("\n");
7+
8+
export const CODE_SUFFIX = [
9+
` document.documentElement.appendChild(script);`,
10+
` script.remove();`,
11+
`}`,
12+
].join("\n");

0 commit comments

Comments
 (0)