Skip to content

Commit 94d9926

Browse files
authored
Merge pull request #414 from WhiteSevs/fix-gm-api
✨ feat: GM_setValue关联适配 & GM_xmlhttpRequest适配cookiePartition
2 parents 85a7404 + 88db30d commit 94d9926

3 files changed

Lines changed: 127 additions & 32 deletions

File tree

src/app/service/service_worker/gm_api.ts

Lines changed: 113 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@ export type RequestResultParams = {
3737
responseHeader: string;
3838
};
3939

40-
export const unsafeHeaders: { [key: string]: boolean } = {
40+
/**
41+
* 这里的值如果末尾是-结尾,将会判断使用.startsWith()判断,否则使用.includes()
42+
*
43+
* @link https://developer.mozilla.org/zh-CN/docs/Glossary/Forbidden_request_header
44+
*/
45+
export const unsafeHeaders: {
46+
[key: string]: boolean;
47+
} = {
4148
// 部分浏览器中并未允许
4249
"user-agent": true,
4350
// 这两个是前缀
@@ -66,6 +73,25 @@ export const unsafeHeaders: { [key: string]: boolean } = {
6673
via: true,
6774
};
6875

76+
/**
77+
* 检测是否存在不安全的请求头(xhr不允许自定义的的请求头)
78+
* @returns
79+
* + true 存在
80+
* + false 不存在
81+
*/
82+
export const checkHasUnsafeHeaders = (key: string) => {
83+
key = key.toLowerCase();
84+
if (unsafeHeaders[key]) {
85+
return true;
86+
}
87+
// ends with "-"
88+
let specialHeaderKeys = ["proxy-", "sec-"];
89+
if (specialHeaderKeys.some((specialHeaderKey) => key.startsWith(specialHeaderKey))) {
90+
return true;
91+
}
92+
return false;
93+
};
94+
6995
type NotificationData = {
7096
uuid: string;
7197
details: GMTypes.NotificationDetails;
@@ -254,7 +280,7 @@ export default class GMApi {
254280
return Promise.resolve(true);
255281
}
256282

257-
@PermissionVerify.API()
283+
@PermissionVerify.API({ link: ["GM_deleteValue", "GM_setValues", "GM_deleteValues"] })
258284
async GM_setValue(request: Request, sender: GetSender) {
259285
if (!request.params || request.params.length < 1) {
260286
throw new Error("param is failed");
@@ -380,7 +406,11 @@ export default class GMApi {
380406
}
381407

382408
// 根据header生成dnr规则
383-
async buildDNRRule(reqeustId: number, params: GMSend.XHRDetails): Promise<{ [key: string]: string }> {
409+
async buildDNRRule(
410+
reqeustId: number,
411+
params: GMSend.XHRDetails,
412+
sender: GetSender
413+
): Promise<{ [key: string]: string }> {
384414
// 检查是否有unsafe header,有则生成dnr规则
385415
const headers = params.headers;
386416
if (!headers) {
@@ -392,27 +422,6 @@ export default class GMApi {
392422
operation: chrome.declarativeNetRequest.HeaderOperation.REMOVE,
393423
},
394424
] as chrome.declarativeNetRequest.ModifyHeaderInfo[];
395-
Object.keys(headers).forEach((key) => {
396-
const lowKey = key.toLowerCase();
397-
if (headers[key]) {
398-
if (unsafeHeaders[lowKey] || lowKey.startsWith("sec-") || lowKey.startsWith("proxy-")) {
399-
if (headers[key]) {
400-
requestHeaders.push({
401-
header: key,
402-
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
403-
value: headers[key],
404-
});
405-
}
406-
delete headers[key];
407-
}
408-
} else {
409-
requestHeaders.push({
410-
header: key,
411-
operation: chrome.declarativeNetRequest.HeaderOperation.REMOVE,
412-
});
413-
delete headers[key];
414-
}
415-
});
416425
// 判断是否是anonymous
417426
if (params.anonymous) {
418427
// 如果是anonymous,并且有cookie,则设置为自定义的cookie
@@ -429,10 +438,76 @@ export default class GMApi {
429438
operation: chrome.declarativeNetRequest.HeaderOperation.REMOVE,
430439
});
431440
}
432-
} else if (params.cookie) {
433-
// 否则正常携带cookie header
434-
headers["cookie"] = params.cookie;
441+
} else {
442+
if (params.cookie) {
443+
// 否则正常携带cookie header
444+
headers["cookie"] = params.cookie;
445+
}
446+
447+
// 追加该网站本身存储的cookie
448+
let tabId = sender.getExtMessageSender().tabId;
449+
let storeId: string | undefined;
450+
if (tabId !== -1) {
451+
const stores = await chrome.cookies.getAllCookieStores();
452+
const store = stores.find((val) => val.tabIds.includes(tabId));
453+
if (store) {
454+
storeId = store.id;
455+
}
456+
}
457+
458+
let cookies = await chrome.cookies.getAll({
459+
domain: undefined,
460+
name: undefined,
461+
path: undefined,
462+
secure: undefined,
463+
session: undefined,
464+
url: params.url,
465+
storeId: storeId,
466+
partitionKey: params.cookiePartition,
467+
});
468+
// 追加cookie
469+
if (cookies.length) {
470+
const cookieStr = cookies.map((c) => `${c.name}=${c.value}`).join("; ");
471+
if (!("cookie" in headers)) {
472+
headers.cookie = "";
473+
}
474+
headers["cookie"] = headers["cookie"].trim();
475+
if (headers["cookie"] === "") {
476+
// 空的
477+
headers["cookie"] = cookieStr;
478+
} else {
479+
// 非空
480+
if (!headers["cookie"].endsWith(";")) {
481+
headers["cookie"] = headers["cookie"] + "; ";
482+
}
483+
headers["cookie"] = headers["cookie"] + cookieStr;
484+
}
485+
}
435486
}
487+
488+
Object.keys(headers).forEach((key) => {
489+
/** 请求的header的值 */
490+
const headerValue = headers[key];
491+
let deleteHeader = false;
492+
if (headerValue) {
493+
if (checkHasUnsafeHeaders(key)) {
494+
requestHeaders.push({
495+
header: key,
496+
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
497+
value: headerValue,
498+
});
499+
deleteHeader = true;
500+
}
501+
} else {
502+
requestHeaders.push({
503+
header: key,
504+
operation: chrome.declarativeNetRequest.HeaderOperation.REMOVE,
505+
});
506+
deleteHeader = true;
507+
}
508+
deleteHeader && delete headers[key];
509+
});
510+
436511
const ruleId = reqeustId;
437512
const rule = {} as chrome.declarativeNetRequest.Rule;
438513
rule.id = ruleId;
@@ -609,8 +684,18 @@ export default class GMApi {
609684
if (!params.headers) {
610685
params.headers = {};
611686
}
687+
688+
// 处理cookiePartition
689+
if (typeof params.cookiePartition !== "object" || params.cookiePartition == null) {
690+
params.cookiePartition = {};
691+
}
692+
if (typeof params.cookiePartition.topLevelSite !== "string") {
693+
// string | undefined
694+
params.cookiePartition.topLevelSite = undefined;
695+
}
696+
612697
params.headers["X-Scriptcat-GM-XHR-Request-Id"] = requestId.toString();
613-
params.headers = await this.buildDNRRule(requestId, request.params[0]);
698+
params.headers = await this.buildDNRRule(requestId, request.params[0], sender);
614699
let resultParam: RequestResultParams = {
615700
requestId,
616701
statusCode: 0,

src/app/service/service_worker/permission_verify.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export interface ApiParam {
4242
// 别名
4343
alias?: string[];
4444
// 关联
45-
link?: string;
45+
link?: string | string[];
4646
}
4747

4848
export interface ApiValue {
@@ -118,13 +118,16 @@ export default class PermissionVerify {
118118
return Promise.reject(new Error("grant is undefined"));
119119
}
120120
for (let i = 0; i < grant.length; i += 1) {
121+
let grantName = grant[i];
121122
if (
122123
// 名称相等
123-
grant[i] === request.api ||
124+
grantName === request.api ||
124125
// 别名相等
125-
(api.param.alias && api.param.alias.includes(grant[i])) ||
126+
(api.param.alias && api.param.alias.includes(grantName)) ||
126127
// 有关联的
127-
grant[i] === api.param.link
128+
(typeof api.param.link === "string" && grantName === api.param.link) ||
129+
// 关联包含
130+
(Array.isArray(api.param.link) && api.param.link.includes(grantName))
128131
) {
129132
// 需要用户确认
130133
if (api.param.confirm) {

src/types/main.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ declare namespace GMSend {
1919
headers?: { [key: string]: string };
2020
data?: string | Array<XHRFormData>;
2121
cookie?: string;
22+
/**
23+
*
24+
* @link https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/API/cookies#storage_partitioning
25+
*/
26+
cookiePartition?: {
27+
topLevelSite?: string;
28+
};
2229
binary?: boolean;
2330
timeout?: number;
2431
context?: CONTEXT_TYPE;

0 commit comments

Comments
 (0)