有没有安全的方法调用 `call` 来调用 JavaScript 中的函数?
Is there a safe way to call `call` to call a function in JavaScript?
我想用自定义 thisArg
调用一个函数。
这似乎微不足道,我只需要调用 call
:
func.call(thisArg, arg1, arg2, arg3);
但是等等! func.call
可能不是 Function.prototype.call
.
所以我考虑使用
Function.prototype.call.call(func, thisArg, arg1, arg2, arg3);
但是等等! Function.prototype.call.call
可能不是 Function.prototype.call
.
因此,假设 Function.prototype.call
是本机属性,但考虑到可能已将任意非内部属性添加到其中,ECMAScript 是否提供了一种安全的方法来执行以下操作?
func.[[Call]](thisArg, argumentsList)
这就是 duck typing 的威力(和风险):if typeof func.call === 'function'
,那么您应该将其视为一个普通的可调用函数。 func
的提供者需要确保他们的 call
属性 与 public 签名匹配。我实际上在几个地方使用了它,因为 JS 没有提供重载 ()
运算符和提供经典仿函数的方法。
如果你真的需要避免使用 func.call
,我会选择 func()
并要求 func
将 thisArg
作为第一个参数。由于 func()
不委托给 call
(即 f(g, h)
不去糖 f.call(t, g, h)
)并且您可以在括号的左侧使用变量,它将给出你预料到的结果。
您还可以在加载库时缓存对 Function.prototype.call
的引用,以防它稍后被替换,并在以后使用它来调用函数。这是 lodash/underscore 用于获取本机数组方法的模式,但不提供任何实际保证您将获得原始本机调用方法。它可以变得非常接近并且不是非常丑陋:
const call = Function.prototype.call;
export default function invokeFunctor(fn, thisArg, ...args) {
return call.call(fn, thisArg, ...args);
}
// Later...
function func(a, b) {
console.log(this, a, b);
}
invokeFunctor(func, {}, 1, 2);
这是任何具有多态性的语言中的一个基本问题。在某些时候,您必须相信对象或库会按照其约定行事。与任何其他情况一样,信任但验证:
if (typeof duck.call === 'function') {
func.call(thisArg, ...args);
}
通过类型检查,您还可以进行一些错误处理:
try {
func.call(thisArg, ...args);
} catch (e) {
if (e instanceof TypeError) {
// probably not actually a function
} else {
throw e;
}
}
如果你可以牺牲 thisArg
(或强制它成为一个实参),那么你可以进行类型检查并使用括号调用:
if (func instanceof Function) {
func(...args);
}
在某些时候,您必须相信 window 上可用的内容。它要么意味着缓存您计划使用的功能,要么试图将您的代码沙盒化。
调用call
的"simple"解决方法是临时设置一个属性:
var safeCall = (function (call, id) {
return function (fn, ctx) {
var ret,
args,
i;
args = [];
// The temptation is great to use Array.prototype.slice.call here
// but we can't rely on call being available
for (i = 2; i < arguments.length; i++) {
args.push(arguments[i]);
}
// set the call function on the call function so that it can be...called
call[id] = call;
// call call
ret = call[id](fn, ctx, args);
// unset the call function from the call function
delete call[id];
return ret;
};
}(Function.prototype.call, (''+Math.random()).slice(2)));
这可以用作:
safeCall(fn, ctx, ...params);
请注意,传递给 safeCall 的参数将集中到一个数组中。您需要 apply
才能使其正常运行,而我只是想在这里简化依赖关系。
safeCall
的改进版本添加了对 apply
的依赖:
var safeCall = (function (call, apply, id) {
return function (fn, ctx) {
var ret,
args,
i;
args = [];
for (i = 2; i < arguments.length; i++) {
args.push(arguments[i]);
}
apply[id] = call;
ret = apply[id](fn, ctx, args);
delete apply[id];
return ret;
};
}(Function.prototype.call, Function.prototype.apply, (''+Math.random()).slice(2)));
这可以用作:
safeCall(fn, ctx, ...params);
安全调用 call 的另一种解决方案是使用来自不同 window 上下文的函数。
这可以简单地通过创建一个新的 iframe
并从其 window 中获取函数来完成。您仍然需要假设对可用的 DOM 操作函数有一定的依赖性,但这是作为设置步骤发生的,因此未来的任何更改都不会影响现有脚本:
var sandboxCall = (function () {
var sandbox,
call;
// create a sandbox to play in
sandbox = document.createElement('iframe');
sandbox.src = 'about:blank';
document.body.appendChild(sandbox);
// grab the function you need from the sandbox
call = sandbox.contentWindow.Function.prototype.call;
// dump the sandbox
document.body.removeChild(sandbox);
return call;
}());
这可以用作:
sandboxCall.call(fn, ctx, ...params);
safeCall
和 sandboxCall
都是安全的 未来 更改为 Function.prototype.call
,但是如您所见,它们依赖于一些现有的全局函数在运行时工作。如果恶意脚本在 之前 执行此代码,您的代码仍然容易受到攻击。
如果你信任Function.prototype.call
,你可以这样做:
func.superSecureCallISwear = Function.prototype.call;
func.superSecureCallISwear(thisArg, arg0, arg1 /*, ... */);
如果您信任 Function..call
但不信任 Function..call.call
,您可以这样做:
var evilCall = Function.prototype.call.call;
Function.prototype.call.call = Function.prototype.call;
Function.prototype.call.call(fun, thisArg, arg0, arg1 /*, ... */);
Function.prototype.call.call = evilCall;
甚至可能将其包装在助手中。
如果您的函数是纯函数并且您的对象是可序列化的,您可以创建一个 iframe 并通过消息传递 (window.postMessage
),将函数代码和参数传递给它,让它执行 call
对你来说(因为它是一个没有任何第 3 方代码的新 iframe,你很安全),而且你是黄金,类似(根本没有测试,可能充满错误):
// inside iframe
window.addEventListener('message', (e) => {
let { code: funcCode, thisArg, args } = e.data;
let res = Function(code).apply(thisArg, args);
e.source.postMessage(res, e.origin);
}, false);
同样的事情也可以用 Web Worker 完成。
如果是这种情况,您可以更进一步,将其发送到您的服务器。如果您是 运行ning 节点,您可以通过 vm module 相当安全地 运行 任意脚本。在 Java 下,您有 Rhino 和 Nashorn 等项目;我确定 .Net 有自己的实现(甚至可能 运行 它作为 JScript!)并且可能有大量损坏的 javascript 虚拟机在 php.
中实现
如果你能做到那个,为什么不使用像 Runnable 这样的服务来即时创建 javascript 沙箱,甚至可以设置你的自己的服务器端环境。
我想用自定义 thisArg
调用一个函数。
这似乎微不足道,我只需要调用 call
:
func.call(thisArg, arg1, arg2, arg3);
但是等等! func.call
可能不是 Function.prototype.call
.
所以我考虑使用
Function.prototype.call.call(func, thisArg, arg1, arg2, arg3);
但是等等! Function.prototype.call.call
可能不是 Function.prototype.call
.
因此,假设 Function.prototype.call
是本机属性,但考虑到可能已将任意非内部属性添加到其中,ECMAScript 是否提供了一种安全的方法来执行以下操作?
func.[[Call]](thisArg, argumentsList)
这就是 duck typing 的威力(和风险):if typeof func.call === 'function'
,那么您应该将其视为一个普通的可调用函数。 func
的提供者需要确保他们的 call
属性 与 public 签名匹配。我实际上在几个地方使用了它,因为 JS 没有提供重载 ()
运算符和提供经典仿函数的方法。
如果你真的需要避免使用 func.call
,我会选择 func()
并要求 func
将 thisArg
作为第一个参数。由于 func()
不委托给 call
(即 f(g, h)
不去糖 f.call(t, g, h)
)并且您可以在括号的左侧使用变量,它将给出你预料到的结果。
您还可以在加载库时缓存对 Function.prototype.call
的引用,以防它稍后被替换,并在以后使用它来调用函数。这是 lodash/underscore 用于获取本机数组方法的模式,但不提供任何实际保证您将获得原始本机调用方法。它可以变得非常接近并且不是非常丑陋:
const call = Function.prototype.call;
export default function invokeFunctor(fn, thisArg, ...args) {
return call.call(fn, thisArg, ...args);
}
// Later...
function func(a, b) {
console.log(this, a, b);
}
invokeFunctor(func, {}, 1, 2);
这是任何具有多态性的语言中的一个基本问题。在某些时候,您必须相信对象或库会按照其约定行事。与任何其他情况一样,信任但验证:
if (typeof duck.call === 'function') {
func.call(thisArg, ...args);
}
通过类型检查,您还可以进行一些错误处理:
try {
func.call(thisArg, ...args);
} catch (e) {
if (e instanceof TypeError) {
// probably not actually a function
} else {
throw e;
}
}
如果你可以牺牲 thisArg
(或强制它成为一个实参),那么你可以进行类型检查并使用括号调用:
if (func instanceof Function) {
func(...args);
}
在某些时候,您必须相信 window 上可用的内容。它要么意味着缓存您计划使用的功能,要么试图将您的代码沙盒化。
调用call
的"simple"解决方法是临时设置一个属性:
var safeCall = (function (call, id) {
return function (fn, ctx) {
var ret,
args,
i;
args = [];
// The temptation is great to use Array.prototype.slice.call here
// but we can't rely on call being available
for (i = 2; i < arguments.length; i++) {
args.push(arguments[i]);
}
// set the call function on the call function so that it can be...called
call[id] = call;
// call call
ret = call[id](fn, ctx, args);
// unset the call function from the call function
delete call[id];
return ret;
};
}(Function.prototype.call, (''+Math.random()).slice(2)));
这可以用作:
safeCall(fn, ctx, ...params);
请注意,传递给 safeCall 的参数将集中到一个数组中。您需要 apply
才能使其正常运行,而我只是想在这里简化依赖关系。
safeCall
的改进版本添加了对 apply
的依赖:
var safeCall = (function (call, apply, id) {
return function (fn, ctx) {
var ret,
args,
i;
args = [];
for (i = 2; i < arguments.length; i++) {
args.push(arguments[i]);
}
apply[id] = call;
ret = apply[id](fn, ctx, args);
delete apply[id];
return ret;
};
}(Function.prototype.call, Function.prototype.apply, (''+Math.random()).slice(2)));
这可以用作:
safeCall(fn, ctx, ...params);
安全调用 call 的另一种解决方案是使用来自不同 window 上下文的函数。
这可以简单地通过创建一个新的 iframe
并从其 window 中获取函数来完成。您仍然需要假设对可用的 DOM 操作函数有一定的依赖性,但这是作为设置步骤发生的,因此未来的任何更改都不会影响现有脚本:
var sandboxCall = (function () {
var sandbox,
call;
// create a sandbox to play in
sandbox = document.createElement('iframe');
sandbox.src = 'about:blank';
document.body.appendChild(sandbox);
// grab the function you need from the sandbox
call = sandbox.contentWindow.Function.prototype.call;
// dump the sandbox
document.body.removeChild(sandbox);
return call;
}());
这可以用作:
sandboxCall.call(fn, ctx, ...params);
safeCall
和 sandboxCall
都是安全的 未来 更改为 Function.prototype.call
,但是如您所见,它们依赖于一些现有的全局函数在运行时工作。如果恶意脚本在 之前 执行此代码,您的代码仍然容易受到攻击。
如果你信任Function.prototype.call
,你可以这样做:
func.superSecureCallISwear = Function.prototype.call;
func.superSecureCallISwear(thisArg, arg0, arg1 /*, ... */);
如果您信任 Function..call
但不信任 Function..call.call
,您可以这样做:
var evilCall = Function.prototype.call.call;
Function.prototype.call.call = Function.prototype.call;
Function.prototype.call.call(fun, thisArg, arg0, arg1 /*, ... */);
Function.prototype.call.call = evilCall;
甚至可能将其包装在助手中。
如果您的函数是纯函数并且您的对象是可序列化的,您可以创建一个 iframe 并通过消息传递 (window.postMessage
),将函数代码和参数传递给它,让它执行 call
对你来说(因为它是一个没有任何第 3 方代码的新 iframe,你很安全),而且你是黄金,类似(根本没有测试,可能充满错误):
// inside iframe
window.addEventListener('message', (e) => {
let { code: funcCode, thisArg, args } = e.data;
let res = Function(code).apply(thisArg, args);
e.source.postMessage(res, e.origin);
}, false);
同样的事情也可以用 Web Worker 完成。
如果是这种情况,您可以更进一步,将其发送到您的服务器。如果您是 运行ning 节点,您可以通过 vm module 相当安全地 运行 任意脚本。在 Java 下,您有 Rhino 和 Nashorn 等项目;我确定 .Net 有自己的实现(甚至可能 运行 它作为 JScript!)并且可能有大量损坏的 javascript 虚拟机在 php.
中实现如果你能做到那个,为什么不使用像 Runnable 这样的服务来即时创建 javascript 沙箱,甚至可以设置你的自己的服务器端环境。