繁体   English   中英

是否有一种安全的方法来调用`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)

这就是鸭子打字的力量(和风险): if typeof func.call === 'function' ,那么你应该把它看作是一个正常的,可调用的函数。 func提供者来确保他们的call属性与公共签名匹配。 我实际上在一些地方使用它,因为JS没有提供重载()运算符并提供经典仿函数的方法。

如果你真的需要避免使用func.call ,我会使用func()并要求functhisArg作为第一个参数。 因为func()不委托给call (即f(g, h)不会去f.call(t, g, h) )并且你可以在parens的左侧使用变量,它会给出你可预测的结果。

您还可以在加载库时缓存对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 (或强制它成为一个实际的参数),那么你可以使用parens进行类型检查和调用:

if (func instanceof Function) {
  func(...args);
}

在某些时候,你必须相信窗口上可用的内容。 它要么意味着缓存您计划使用的功能,要么尝试对代码进行沙箱处理。

呼叫call的“简单”解决方案是临时设置属性:

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的另一种解决方案是使用来自不同窗口上下文的函数。

这可以通过创建一个新的iframe并从其窗口中获取函数来完成。 您仍然需要假设对可用的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);

safeCallsandboxCallsandboxCall受到对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 (因为它是一个没有任何第三个的新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 Workers也可以做同样的事情。

如果是这种情况,您可以更进一步将其发送到您的服务器。 如果您正在运行节点,则可以通过vm模块安全地运行任意脚本。 在Java下你有像Rhino和Nashorn这样的项目; 我确定.Net有自己的实现(甚至可以像JScript一样运行它!)并且可能在php中实现了大量破解的javascript VM。

如果你能做到这一点 ,为什么不使用像Runnable这样的服务来动态创建javascript沙箱,甚至可以设置你自己的服务器端环境。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM