Files

262 lines
9.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { scaffoldModule } = require("./scaffold-module");
const { buildPluginGraph } = require("./build-plugin-graph");
function clone(value) {
return JSON.parse(JSON.stringify(value || {}));
}
function normalizeNavigatorSeed(seed = {}) {
const navigatorSeed = seed.navigator && typeof seed.navigator === "object" ? seed.navigator : seed;
const plugins = Array.isArray(seed.plugins) ? clone(seed.plugins) : [];
const mimeTypes = Array.isArray(seed.mimeTypes) ? clone(seed.mimeTypes) : [];
return {
navigator: {
userAgent: navigatorSeed.userAgent || "",
platform: navigatorSeed.platform || "",
vendor: navigatorSeed.vendor || "",
language: navigatorSeed.language || "",
languages: Array.isArray(navigatorSeed.languages) ? navigatorSeed.languages.slice() : [],
webdriver: typeof navigatorSeed.webdriver === "boolean" ? navigatorSeed.webdriver : false,
hardwareConcurrency: Number.isFinite(navigatorSeed.hardwareConcurrency)
? navigatorSeed.hardwareConcurrency
: 8,
deviceMemory: Number.isFinite(navigatorSeed.deviceMemory) ? navigatorSeed.deviceMemory : 8,
maxTouchPoints: Number.isFinite(navigatorSeed.maxTouchPoints) ? navigatorSeed.maxTouchPoints : 0,
cookieEnabled: navigatorSeed.cookieEnabled !== false,
pdfViewerEnabled: navigatorSeed.pdfViewerEnabled !== false,
appCodeName: navigatorSeed.appCodeName || "Mozilla",
appName: navigatorSeed.appName || "Netscape",
appVersion: navigatorSeed.appVersion || navigatorSeed.userAgent || "",
userAgentData: clone(seed.userAgentData || navigatorSeed.userAgentData || null),
connection: clone(seed.connection || navigatorSeed.connection || null),
},
plugins,
mimeTypes,
battery: clone(seed.battery || null),
};
}
function renderNavigatorPatch(seed) {
const serializedSeed = JSON.stringify(seed, null, 2);
return `(
function installNavigatorModule(globalObject) {
const seed = ${serializedSeed};
const protector = globalObject.__envNativeProtector || null;
const pluginSeed = Array.isArray(seed.plugins) ? seed.plugins.slice() : [];
const mimeSeed = Array.isArray(seed.mimeTypes) ? seed.mimeTypes.slice() : [];
function defineValue(target, key, value) {
Object.defineProperty(target, key, {
configurable: true,
enumerable: false,
writable: true,
value: value
});
}
function defineGetter(target, key, getter) {
Object.defineProperty(target, key, {
configurable: true,
enumerable: false,
get: getter
});
if (protector && protector.protectDescriptor) {
protector.protectDescriptor(target, key);
}
}
function createIndexedCollection(items, namedKey) {
const collection = {};
for (let index = 0; index < items.length; index += 1) {
Object.defineProperty(collection, index, {
configurable: true,
enumerable: false,
writable: false,
value: items[index]
});
if (items[index] && items[index][namedKey]) {
Object.defineProperty(collection, items[index][namedKey], {
configurable: true,
enumerable: false,
writable: false,
value: items[index]
});
}
}
defineValue(collection, "length", items.length);
defineValue(collection, "item", function item(index) {
return items[index] || null;
});
defineValue(collection, "namedItem", function namedItem(name) {
return items.find(function findItem(entry) {
return entry && entry[namedKey] === name;
}) || null;
});
defineValue(collection, "refresh", function refresh() {});
return collection;
}
const pluginArray = pluginSeed.map(function mapPlugin(plugin) {
return {
name: plugin.name || "",
filename: plugin.filename || "",
description: plugin.description || "",
length: 0
};
});
const mimeTypeArray = mimeSeed.map(function mapMime(mime) {
return {
type: mime.type || "",
suffixes: mime.suffixes || "",
description: mime.description || "",
enabledPlugin: null
};
});
for (let index = 0; index < mimeSeed.length; index += 1) {
const pluginName = mimeSeed[index].enabledPluginName || "";
const plugin = pluginArray.find(function findPlugin(entry) {
return entry.name === pluginName;
}) || null;
mimeTypeArray[index].enabledPlugin = plugin;
if (plugin) {
const itemIndex = plugin.length;
Object.defineProperty(plugin, itemIndex, {
configurable: true,
enumerable: false,
writable: false,
value: mimeTypeArray[index]
});
plugin.length += 1;
}
}
const plugins = createIndexedCollection(pluginArray, "name");
const mimeTypes = createIndexedCollection(mimeTypeArray, "type");
function Navigator() {}
const navigatorState = seed.navigator || {};
const navigatorInstance = Object.create(Navigator.prototype);
const scalarKeys = [
"userAgent",
"platform",
"vendor",
"language",
"webdriver",
"hardwareConcurrency",
"deviceMemory",
"maxTouchPoints",
"cookieEnabled",
"pdfViewerEnabled",
"appCodeName",
"appName",
"appVersion"
];
scalarKeys.forEach(function defineScalar(key) {
defineGetter(Navigator.prototype, key, function getScalar() {
return navigatorState[key];
});
});
defineGetter(Navigator.prototype, "languages", function getLanguages() {
return Array.isArray(navigatorState.languages) ? navigatorState.languages.slice() : [];
});
defineGetter(Navigator.prototype, "plugins", function getPlugins() {
return plugins;
});
defineGetter(Navigator.prototype, "mimeTypes", function getMimeTypes() {
return mimeTypes;
});
defineGetter(Navigator.prototype, "userAgentData", function getUserAgentData() {
return navigatorState.userAgentData || null;
});
defineGetter(Navigator.prototype, "connection", function getConnection() {
return navigatorState.connection || null;
});
defineValue(Navigator.prototype, "javaEnabled", function javaEnabled() {
return false;
});
defineValue(Navigator.prototype, "getBattery", function getBattery() {
return Promise.resolve(seed.battery || {
charging: true,
chargingTime: 0,
dischargingTime: Infinity,
level: 1
});
});
if (protector && protector.repairFunctionMeta) {
protector.repairFunctionMeta(Navigator, { name: "Navigator", length: 0 });
protector.repairFunctionMeta(Navigator.prototype.javaEnabled, { name: "javaEnabled", length: 0 });
protector.repairFunctionMeta(Navigator.prototype.getBattery, { name: "getBattery", length: 0 });
}
if (protector && protector.protectFunction) {
protector.protectFunction(Navigator, "Navigator");
protector.protectFunction(Navigator.prototype.javaEnabled, "javaEnabled");
protector.protectFunction(Navigator.prototype.getBattery, "getBattery");
}
defineValue(navigatorInstance, Symbol.toStringTag, "Navigator");
Object.defineProperty(globalObject, "Navigator", {
configurable: true,
enumerable: false,
writable: true,
value: Navigator
});
Object.defineProperty(globalObject, "navigator", {
configurable: true,
enumerable: false,
writable: true,
value: navigatorInstance
});
return navigatorInstance;
})(typeof globalThis !== "undefined" ? globalThis : window);
`;
}
function buildNavigatorModule(seed = {}) {
const module = scaffoldModule("navigator-module");
const normalizedSeed = normalizeNavigatorSeed(seed);
const pluginGraph = buildPluginGraph({
plugins: normalizedSeed.plugins,
mimeTypes: normalizedSeed.mimeTypes,
});
module.patchPlan = [
"构建 Navigator 构造器、prototype 和 navigator 实例。",
"把 userAgent/language/webdriver 等高频字段补成 prototype getter,而不是直接灌到实例上。",
"把 plugins/mimeTypes 构造成类数组图结构,并补 getBattery/userAgentData 等高频检测点。",
];
module.patchCode = renderNavigatorPatch(normalizedSeed);
module.runtimeState = {
navigator: normalizedSeed.navigator,
plugins: pluginGraph.pluginArray,
mimeTypes: pluginGraph.mimeTypeArray,
battery: normalizedSeed.battery,
};
module.validation = [
"检查 navigator.webdriver === false。",
"检查 navigator.plugins.length 与 navigator.mimeTypes.length。",
"检查 navigator.plugins.item(0) / namedItem(name) 与 mimeTypes.enabledPlugin 关系。",
"检查 navigator.getBattery() 返回 Promise。",
"检查 Navigator.prototype 上 getter 的描述符与 toString 保护是否生效。",
];
module.residualRisk = [
"这里只覆盖常见字段与图结构,未实现 permissions、mediaDevices、bluetooth 等更深层能力对象。",
"如果目标脚本会校验 brands/mobile/platformVersion 等 userAgentData 细节,仍需真实浏览器种子。",
];
return module;
}
module.exports = {
buildNavigatorModule,
normalizeNavigatorSeed,
};