I need to hijack XHR on a page, I start from proxy the XHR object, first proxy new
operator:
class ClassHandler {
constructor(proxy) {
this.proxy = proxy;
}
construct(target, args) {
const obj = new target(...args);
return new Proxy(obj, new this.proxy(obj));
}
}
(function(XMLHttpRequest) {
unsafeWindow.XMLHttpRequest = new Proxy(
XMLHttpRequest,
new ClassHandler(XhrHandler)
);
})(XMLHttpRequest);
Then XhrProxy
needs to proxy everything to real XHR object, I found this is very complicated, so separate it into several components.
Basic properties
const ProxyGetTarget = Symbol('ProxyGetTarget');
const ProxyGetHandler = Symbol('ProxyGetHandler');
class ObjectHandler {
constructor(target) {
this.target = target;
}
get(target, prop, receiver) {
if (target.hasOwnProperty(prop)) {
return Reflect.get(target, prop, receiver);
} else if (prop == ProxyGetTarget) {
return target;
} else if (prop == ProxyGetHandler) {
return this;
} else {
const value = target[prop];
if (typeof value == 'function')
return new Proxy(value, new FunctionHandler(value));
return value;
}
}
set(target, prop, value) {
return Reflect.set(target, prop, value);
}
}
I injected a property ProxyGetTarget
which returns the real target for future use. Next I'll explain why proxy functions with FunctionHandler
in the object.
Member function calls
This is a bit tricky, when calling a function on the object:
xhr.open()
xhr
is our proxy object, not the real native XHR object, the open function will be called with this
set to the proxy object, In order to forward open call to real xhr object, we need proxy all functions returned by get
:
class FunctionHandlerBase extends ObjectHandler {
apply(target, thisArg, argumentsList) {
const realTarget = thisArg[ProxyGetTarget];
if (!realTarget) throw new Error('illegal invocations');
return this.call(this.target, thisArg, realTarget, argumentsList);
}
}
class FunctionHandler extends FunctionHandlerBase {
call(fn, proxy, target, argumentsList) {
fn.apply(target, argumentsList);
}
}
With this setup, I can inject any code into any member function using following method:
class XhrHandler extends ObjectHandler{
get(target, prop, receiver) {
if (prop === 'open') {
return new Proxy(target.open, new this.open(target.open));
} else {
return super.get(target, prop, receiver);
}
}
}
XhrHandler.prototype.open = class extends FunctionHandlerBase {
call(fn, proxy, realTarget, argumentsList) {
// Do whatever before real xhr.open call
// ...
// ...
// Call real xhr.open
return fn.apply(realTarget, argumentsList);
}
};
Event listeners
As event listeners will expose real object as this
argument in the callback function, I don't want anyone get my secret real target, I should also wrap it with the proxy.
class EventTargetHandler extends ObjectHandler {
constructor(target) {
super(target);
this.listeners = {};
}
getListeners(type) {
if (!this.listeners.hasOwnProperty(type))
this.listeners[type] = new Map();
return this.listeners[type];
}
get(target, prop, receiver) {
if (prop === 'addEventListener') {
return new Proxy(
target.addEventListener,
new this.addEventListener(target.addEventListener, this)
);
} else if (prop === 'removeEventListener') {
return new Proxy(
target.removeEventListener,
new this.removeEventListener(target.removeEventListener, this)
);
} else return super.get(target, prop, receiver);
}
}
EventTargetHandler.prototype.addEventListener = class extends FunctionHandlerBase {
call(fn, proxy, realTarget, argumentsList) {
const type = argumentsList[0];
const listener = argumentsList[1];
const bridge = listener.bind(proxy);
argumentsList[1] = bridge;
proxy[ProxyGetHandler].getListeners(type).set(listener, bridge);
return fn.apply(realTarget, argumentsList);
}
};
EventTargetHandler.prototype.removeEventListener = class extends FunctionHandlerBase {
call(fn, proxy, realTarget, argumentsList) {
const type = argumentsList[0];
const listener = argumentsList[1];
const cache = proxy[ProxyGetHandler].getListeners(type);
if (cache.has(listener)) {
argumentsList[1] = cache.get(listener);
cache.delete(listener);
}
return fn.apply(realTarget, argumentsList);
}
};
Event handlers
Not done yet ... I need to wrap all set
to the event handler properties.
Problems
Hmm, hijack XHR ? easy task, let's do it in 5 minutes.
...
...
...
After 5 hours, I'm still struggling with those proxies.
Maybe I misunderstand the propose of the design of Proxy
API, I found it very complicated to wrap an object, almost impossible.
I just want my Proxy behaves as same as the origin object, is there any easier approach ?