2015-06-26 14 views
8

Voglio chiamare una funzione con una personalizzata thisArg.C'è un modo sicuro per chiamare `call` per chiamare una funzione in JavaScript?

Questo sembra banale, non mi resta che chiamare call:

func.call(thisArg, arg1, arg2, arg3); 

Ma aspetta! func.call potrebbe non essere Function.prototype.call.

Così ho pensato di usare

Function.prototype.call.call(func, thisArg, arg1, arg2, arg3); 

Ma aspettate! Function.prototype.call.call potrebbe non essere Function.prototype.call.

Quindi, supponendo che lo Function.prototype.call sia quello nativo, ma si potrebbe aver aggiunto proprietà arbitrarie non interne a esso, ECMAScript fornisce un modo sicuro per eseguire quanto segue?

func.[[Call]](thisArg, argumentsList) 
+4

Mi ha portato 3 letture ... ma bella domanda! –

risposta

4

Questo è il potere (e il rischio) di battitura anatra: if typeof func.call === 'function', allora si dovrebbe trattarlo come se fosse una normale funzione callable. Spetta al provider di func assicurarsi che la loro proprietà call corrisponda alla firma pubblica. In realtà lo uso in alcuni punti, poiché JS non fornisce un modo per sovraccaricare l'operatore () e fornire un functor classico.

Se avete veramente bisogno di evitare l'uso di func.call, vorrei andare con func() e richiedono func di prendere thisArg come primo argomento. Dato che func() non è delegato a call (ad esempio, f(g, h) non corrisponde a f.call(t, g, h)) e puoi utilizzare le variabili sul lato sinistro di Parens, ti darà risultati prevedibili.

È inoltre possibile memorizzare nella cache un riferimento a Function.prototype.call quando la libreria viene caricata, nel caso in cui venga sostituita in un secondo momento e utilizzarla per richiamare le funzioni in un secondo momento. Questo è un pattern usato da lodash/underscore per afferrare i metodi di matrice nativa, ma non fornisce alcuna garanzia effettiva che si otterrà il metodo di chiamata nativo originale. Si può ottenere abbastanza vicino e non è terribilmente brutta:

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); 

Questo è un problema fondamentale in qualsiasi lingua con il polimorfismo. Ad un certo punto, devi fidarti che l'oggetto o la libreria si comportino secondo il suo contratto. Come con qualsiasi altro caso, fiducia, ma verificare:

if (typeof duck.call === 'function') { 
    func.call(thisArg, ...args); 
} 

Con il tipo di controllo, si può fare un po 'la gestione degli errori così:

try { 
    func.call(thisArg, ...args); 
} catch (e) { 
    if (e instanceof TypeError) { 
    // probably not actually a function 
    } else { 
    throw e; 
    } 
} 

Se si può sacrificare thisArg (o forza di essere un vero e proprio argomento), quindi è possibile digitare-check e invocare con parentesi:

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

Ma qualcosa che assomiglia ad un'anatra carina potrebbe essere un mortale mantello mascherato! Quindi, tentare di alimentarlo con il pane non sarebbe un'attività sicura. – Oriol

+2

@Oriol Certamente potrebbe, ed è per questo 'try {duck.feed()} catch (lostFingers) {console.log ('It's a manticore!'); } 'è parte della lingua. Come ogni linguaggio con il polimorfismo, a un certo punto devi fidarti che l'oggetto funzioni correttamente. – ssube

+0

Sì, immagino che l'unico modo sicuro sia eseguire il codice il prima possibile e fare riferimento a 'Function.prototype.call'. E forse 'seal' o' freeze' it. – Oriol

2

ad un certo punto devi avere fiducia in ciò che è disponibile sulla finestra. Ciò significa memorizzare nella cache le funzioni che si sta pianificando di utilizzare o tentare di eseguire la sandbox del codice.

La soluzione "semplice" a chiamare call è quello di impostare temporaneamente una proprietà:

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))); 

Questo può quindi essere utilizzato come:

safeCall(fn, ctx, ...params); 

essere consapevoli del fatto che i parametri passati al safecall saranno raggruppati in un array. Avresti bisogno di apply per farlo funzionare correttamente, e sto solo cercando di semplificare le dipendenze qui.


versione migliorata del safeCall aggiungere una dipendenza per 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))); 

Questo può essere usato come:

safeCall(fn, ctx, ...params); 

Una soluzione alternativa a chiamare in modo sicuro invito è quello di utilizzare funziona da un contesto di finestra differente.

questo può essere fatto semplicemente creando un nuovo iframe e afferrando le funzioni dalla sua finestra. Avrai ancora bisogno di assumere una certa quantità di dipendenza da funzioni di manipolazione DOM di essere disponibile, ma che accade come un passo di installazione, in modo che eventuali modifiche non influiranno sul script esistente:

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; 
}()); 

Questo può quindi essere Usato come:

sandboxCall.call(fn, ctx, ...params); 

Sia safeCall e sandboxCall sono al sicuro da futuro modifiche Function.prototype.call, ma come si c un punto di vista si basano su alcune funzioni globali esistenti per funzionare in fase di runtime. Se uno script dannoso esegue prima del codice, il codice sarà ancora vulnerabile.

1

Se vi fidate Function.prototype.call, si può fare qualcosa di simile:

func.superSecureCallISwear = Function.prototype.call; 
func.superSecureCallISwear(thisArg, arg0, arg1 /*, ... */); 

Se vi fidate Function..call ma non Function..call.call, si può fare questo:

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; 

E forse anche avvolgere che in un aiutante.

Se le funzioni sono puri e gli oggetti serializzabile, è possibile creare un iframe e tramite scambio di messaggi (window.postMessage), passarlo il codice di funzione e gli argomenti, lasciate fare il call per voi (in quanto si tratta di una nuova iframe senza qualsiasi codice 3rd party sei abbastanza sicuro), e sei d'oro, qualcosa di simile (non testato a tutti, probabilmente pieno di errori):

// 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); 

Stessa cosa si può fare con i lavoratori web.

Se questo è il caso, puoi fare un ulteriore passo avanti e inviarlo al tuo server. Se stai eseguendo il nodo, puoi eseguire script arbitrari in modo sicuro tramite lo vm module. Sotto Java hai progetti come Rhino e Nashorn; Sono sicuro che .Net ha le sue implementazioni (forse anche eseguirlo come JScript!) E probabilmente ci sono un VM errato di VM javascript implementate in PHP.

Se si può fare che, perché non utilizzare un servizio come Runnable per on-the-fly creare javascript sandbox, forse anche impostare il proprio ambiente server-side per questo.

Problemi correlati