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
118 changes: 63 additions & 55 deletions src/app/service/service_worker/gm_api/gm_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,6 @@ const generateUniqueMarkerID = () => {
return `MARKER::${u1}${u2}`;
};

const enum xhrExtraCode {
INVALID_URL = 0x20,
DOMAIN_NOT_INCLUDED = 0x30,
DOMAIN_IN_BLACKLIST = 0x40,
}

type OnBeforeSendHeadersOptions = `${chrome.webRequest.OnBeforeSendHeadersOptions}`;
type OnHeadersReceivedOptions = `${chrome.webRequest.OnHeadersReceivedOptions}`;

Expand Down Expand Up @@ -283,7 +277,7 @@ export default class GMApi {
}

@PermissionVerify.API({
confirm: async (request: GMApiRequest<[string, GMTypes.CookieDetails]>, sender: IGetSender) => {
confirm: async (request: GMApiRequest<[string, GMTypes.CookieDetails]>, sender: IGetSender, gmApi: GMApi) => {
if (request.params[0] === "store") {
return true;
}
Expand All @@ -299,6 +293,14 @@ export default class GMApi {
url.hostname = detail.domain || "";
}
if (getConnectMatched(request.script.metadata.connect, url, sender) === ConnectMatch.NONE) {
// 检查是否配置了权限
const ret = await gmApi.permissionVerify.queryPermission(request, {
permission: "cookie",
permissionValue: url.host,
});
if (ret && ret.allow) {
return true;
}
throw new Error("hostname must be in the definition of connect");
}
const metadata: { [key: string]: string } = {};
Expand Down Expand Up @@ -689,18 +691,37 @@ export default class GMApi {
}

@PermissionVerify.API({
confirm: async (request: GMApiRequest<[GMSend.XHRDetails]>, sender: IGetSender, GMApiInstance: GMApi) => {
const config = <GMSend.XHRDetails>request.params[0];
confirm: async (request: GMApiRequest<[GMSend.XHRDetails?]>, sender: IGetSender, GMApiInstance: GMApi) => {
const msgConn = sender.getConnect();
if (!msgConn) {
throw new Error("GM_xmlhttpRequest ERROR: msgConn is undefined");
}
const throwErrorFn = (error: string) => {
msgConn.sendMessage({
action: "onerror",
data: {
status: 0,
responseHeaders: "",
error: error,
readyState: 4, // ERROR. DONE.
},
});
return new Error(error);
};
const details = request.params[0];
if (!details) {
throw throwErrorFn("param is failed");
}
let url;
try {
url = new URL(config.url);
url = new URL(details.url);
} catch {
request.extraCode = xhrExtraCode.INVALID_URL;
return false;
const msg = `Refused to connect to "${details.url}": The url is invalid`;
throw throwErrorFn(msg);
}
if (GMApiInstance.gmExternalDependencies.isBlacklistNetwork(url)) {
request.extraCode = xhrExtraCode.DOMAIN_IN_BLACKLIST;
return false;
const msg = `Refused to connect to "${details.url}": URL is blacklisted`;
throw throwErrorFn(msg);
}
const connectMatched = getConnectMatched(request.script.metadata.connect, url, sender);
if (connectMatched === ConnectMatch.ALL) {
Expand All @@ -713,15 +734,24 @@ export default class GMApi {
}
// @connect 没有匹配,但有列明 @connect 的话,则自动拒绝
if (request.script.metadata.connect?.find((e) => !!e)) {
request.extraCode = xhrExtraCode.DOMAIN_NOT_INCLUDED;
return false;
// 查询数据库权限记录,如果之前用户允许过该域名,则放行,否则拒绝
const ret = await GMApiInstance.permissionVerify.queryPermission(request, {
permission: "cors",
permissionValue: url.hostname,
wildcard: true,
});
if (ret && ret.allow) {
return true;
}
const msg = `Refused to connect to "${details.url}": This domain is not a part of the @connect list`;
throw throwErrorFn(msg);
}
// 其他情况:要询问用户
}
const metadata: { [key: string]: string } = {};
metadata[i18next.t("script_name")] = i18nName(request.script);
metadata[i18next.t("request_domain")] = url.hostname;
metadata[i18next.t("request_url")] = config.url;
metadata[i18next.t("request_url")] = details.url;

return {
permission: "cors",
Expand All @@ -735,14 +765,12 @@ export default class GMApi {
},
alias: ["GM.xmlHttpRequest"],
})
async GM_xmlhttpRequest(request: GMApiRequest<[GMSend.XHRDetails?]>, sender: IGetSender) {
async GM_xmlhttpRequest(request: GMApiRequest<[GMSend.XHRDetails]>, sender: IGetSender) {
if (!sender.isType(GetSenderType.CONNECT)) {
throw new Error("GM_xmlhttpRequest ERROR: sender is not MessageConnect");
}
const msgConn = sender.getConnect();
if (!msgConn) {
throw new Error("GM_xmlhttpRequest ERROR: msgConn is undefined");
}
const msgConn = sender.getConnect()!;

let isConnDisconnected = false;
msgConn.onDisconnect(() => {
isConnDisconnected = true;
Expand All @@ -754,40 +782,8 @@ export default class GMApi {

const resultParam = new SWRequestResultParams(markerID);

const throwErrorFn = (error: string) => {
if (!isConnDisconnected) {
msgConn.sendMessage({
action: "onerror",
data: {
status: resultParam.statusCode,
responseHeaders: resultParam.responseHeaders,
error: `${error}`,
readyState: 4, // ERROR. DONE.
},
});
}
return new Error(`${error}`);
};

const details = request.params[0];
if (!details) {
throw throwErrorFn("param is failed");
}

if (request.extraCode === xhrExtraCode.INVALID_URL) {
const msg = `Refused to connect to "${details.url}": The url is invalid`;
throw throwErrorFn(msg);
}
if (request.extraCode === xhrExtraCode.DOMAIN_NOT_INCLUDED) {
// 'Refused to connect to "https://nonexistent-domain-abcxyz.test/": This domain is not a part of the @connect list'
const msg = `Refused to connect to "${details.url}": This domain is not a part of the @connect list`;
throw throwErrorFn(msg);
}
if (request.extraCode === xhrExtraCode.DOMAIN_IN_BLACKLIST) {
// 'Refused to connect to "https://example.org/": URL is blacklisted'
const msg = `Refused to connect to "${details.url}": URL is blacklisted`;
throw throwErrorFn(msg);
}
try {
/*
There are TM-specific parameters:
Expand Down Expand Up @@ -879,7 +875,19 @@ export default class GMApi {
msgConn.onDisconnect(offscreenCon.disconnect.bind(offscreenCon));
}
} catch (e: any) {
throw throwErrorFn(`GM_xmlhttpRequest ERROR: ${e?.message || e || "Unknown Error"}`);
const errorMsg = `GM_xmlhttpRequest ERROR: ${e?.message || e || "Unknown Error"}`;
if (!isConnDisconnected) {
msgConn.sendMessage({
action: "onerror",
data: {
status: resultParam.statusCode,
responseHeaders: resultParam.responseHeaders,
error: errorMsg,
readyState: 4, // ERROR. DONE.
},
});
}
throw new Error(errorMsg);
}
}

Expand Down
32 changes: 27 additions & 5 deletions src/app/service/service_worker/permission_verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,27 @@ export default class PermissionVerify {
});
}

async confirm<T>(request: GMApiRequest<T>, confirm: boolean | ConfirmParam, sender: IGetSender): Promise<boolean> {
if (typeof confirm === "boolean") {
return confirm;
buildCacheKey(
request: GMApiRequest,
confirm: {
permission: string;
permissionValue?: string;
}
) {
return `${CACHE_KEY_PERMISSION}${request.script.uuid}:${confirm.permission}:${confirm.permissionValue || ""}`;
}

async queryPermission<T>(
request: GMApiRequest<T>,
confirm: {
permission: string;
permissionValue?: string;
wildcard?: boolean;
}
const cacheKey = `${CACHE_KEY_PERMISSION}${request.script.uuid}:${confirm.permission}:${confirm.permissionValue || ""}`;
): Promise<Permission | undefined> {
const cacheKey = this.buildCacheKey(request, confirm);
// 从数据库中查询是否有此权限
const ret = await cacheInstance.getOrSet(cacheKey, async () => {
return await cacheInstance.getOrSet(cacheKey, async () => {
let model = await this.permissionDAO.findByKey(request.uuid, confirm.permission, confirm.permissionValue || "");
if (!model) {
// 允许通配
Expand All @@ -202,6 +216,13 @@ export default class PermissionVerify {
}
return model;
});
}

async confirm<T>(request: GMApiRequest<T>, confirm: boolean | ConfirmParam, sender: IGetSender): Promise<boolean> {
if (typeof confirm === "boolean") {
return confirm;
}
const ret = await this.queryPermission(request, confirm);
// 有查询到结果,进入判断,不再需要用户确认
if (ret) {
if (ret.allow) {
Expand Down Expand Up @@ -238,6 +259,7 @@ export default class PermissionVerify {
}
// 临时 放入缓存
if (userConfirm.type >= 2) {
const cacheKey = this.buildCacheKey(request, confirm);
cacheInstance.set(cacheKey, model);
}
// 总是 放入数据库
Expand Down
1 change: 0 additions & 1 deletion src/app/service/service_worker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export type MessageRequest<T = any[]> = {

export type GMApiRequest<T = any> = MessageRequest<T> & {
script: Script;
extraCode?: number; // 用于 confirm 传额外资讯
};

export type NotificationMessageOption = {
Expand Down
Loading