如何从 Greasemonkey 脚本拦截 XMLHttpRequests?

IT技术 javascript ajax greasemonkey
2021-01-15 09:43:48

我想使用 Greasemonkey 捕获 AJAX 请求的内容。

有人知道怎么做这个吗?

6个回答

接受的答案几乎是正确的,但可以稍作改进:

(function(open) {
    XMLHttpRequest.prototype.open = function() {
        this.addEventListener("readystatechange", function() {
            console.log(this.readyState);
        }, false);
        open.apply(this, arguments);
    };
})(XMLHttpRequest.prototype.open);

更喜欢使用 apply + 参数而不是调用,因为这样您就不必明确知道提供给 open 的所有参数,这些参数可能会改变!

此外,请参阅stackoverflow.com/a/25335826以了解其一些缺点。TL;DR - 如果您的意图是拦截由任意代码进行的所有AJAX 调用,您还需要关心fetch(..)和(希望不是,但可能)ActiveXObject
2021-03-24 09:43:48
我将如何更改进入 POST 参数的数据?
2021-03-26 09:43:48
falsethis.addEventListener(..., ..., false)是不必要的,因为这是默认值,但除此之外,这实在是优雅而简洁!
2021-04-10 09:43:48
(嗯,就像猴子补丁一样“优雅”,但我们在这里真的没有太多选择;))
2021-04-11 09:43:48

如何修改 XMLHttpRequest.prototype.open 或使用设置自己的回调并调用原始方法的替换发送方法?回调可以做它的事情,然后调用指定的原始代码的回调。

换句话说:

XMLHttpRequest.prototype.realOpen = XMLHttpRequest.prototype.open;

var myOpen = function(method, url, async, user, password) {
    //do whatever mucking around you want here, e.g.
    //changing the onload callback to your own version


    //call original
    this.realOpen (method, url, async, user, password);
}  


//ensure all XMLHttpRequests use our custom open method
XMLHttpRequest.prototype.open = myOpen ;

在 Chrome 55 和 Firefox 50.1.0 中测试

就我而言,我想修改 responseText,它在 Firefox 中是只读属性,因此我必须包装整个 XMLHttpRequest 对象。我还没有实现整个 API(特别是 responseType),但它足以用于我拥有的所有库。

用法:

    XHRProxy.addInterceptor(function(method, url, responseText, status) {
        if (url.endsWith('.html') || url.endsWith('.htm')) {
            return "<!-- HTML! -->" + responseText;
        }
    });

代码:

(function(window) {

    var OriginalXHR = XMLHttpRequest;

    var XHRProxy = function() {
        this.xhr = new OriginalXHR();

        function delegate(prop) {
            Object.defineProperty(this, prop, {
                get: function() {
                    return this.xhr[prop];
                },
                set: function(value) {
                    this.xhr.timeout = value;
                }
            });
        }
        delegate.call(this, 'timeout');
        delegate.call(this, 'responseType');
        delegate.call(this, 'withCredentials');
        delegate.call(this, 'onerror');
        delegate.call(this, 'onabort');
        delegate.call(this, 'onloadstart');
        delegate.call(this, 'onloadend');
        delegate.call(this, 'onprogress');
    };
    XHRProxy.prototype.open = function(method, url, async, username, password) {
        var ctx = this;

        function applyInterceptors(src) {
            ctx.responseText = ctx.xhr.responseText;
            for (var i=0; i < XHRProxy.interceptors.length; i++) {
                var applied = XHRProxy.interceptors[i](method, url, ctx.responseText, ctx.xhr.status);
                if (applied !== undefined) {
                    ctx.responseText = applied;
                }
            }
        }
        function setProps() {
            ctx.readyState = ctx.xhr.readyState;
            ctx.responseText = ctx.xhr.responseText;
            ctx.responseURL = ctx.xhr.responseURL;
            ctx.responseXML = ctx.xhr.responseXML;
            ctx.status = ctx.xhr.status;
            ctx.statusText = ctx.xhr.statusText;
        }

        this.xhr.open(method, url, async, username, password);

        this.xhr.onload = function(evt) {
            if (ctx.onload) {
                setProps();

                if (ctx.xhr.readyState === 4) {
                     applyInterceptors();
                }
                return ctx.onload(evt);
            }
        };
        this.xhr.onreadystatechange = function (evt) {
            if (ctx.onreadystatechange) {
                setProps();

                if (ctx.xhr.readyState === 4) {
                     applyInterceptors();
                }
                return ctx.onreadystatechange(evt);
            }
        };
    };
    XHRProxy.prototype.addEventListener = function(event, fn) {
        return this.xhr.addEventListener(event, fn);
    };
    XHRProxy.prototype.send = function(data) {
        return this.xhr.send(data);
    };
    XHRProxy.prototype.abort = function() {
        return this.xhr.abort();
    };
    XHRProxy.prototype.getAllResponseHeaders = function() {
        return this.xhr.getAllResponseHeaders();
    };
    XHRProxy.prototype.getResponseHeader = function(header) {
        return this.xhr.getResponseHeader(header);
    };
    XHRProxy.prototype.setRequestHeader = function(header, value) {
        return this.xhr.setRequestHeader(header, value);
    };
    XHRProxy.prototype.overrideMimeType = function(mimetype) {
        return this.xhr.overrideMimeType(mimetype);
    };

    XHRProxy.interceptors = [];
    XHRProxy.addInterceptor = function(fn) {
        this.interceptors.push(fn);
    };

    window.XMLHttpRequest = XHRProxy;

})(window);
很确定你的意思是this.xhr[prop] = value;在委托函数中
2021-03-18 09:43:48

您可以使用包装器替换文档中的 unsafeWindow.XMLHttpRequest 对象。一点代码(未测试):

var oldFunction = unsafeWindow.XMLHttpRequest;
unsafeWindow.XMLHttpRequest = function() {
  alert("Hijacked! XHR was constructed.");
  var xhr = oldFunction();
  return {
    open: function(method, url, async, user, password) {
      alert("Hijacked! xhr.open().");
      return xhr.open(method, url, async, user, password);
    }
    // TODO: include other xhr methods and properties
  };
};

但这有一个小问题:Greasemonkey 脚本页面加载执行,因此页面可以在加载序列期间使用或存储原始 XMLHttpRequest 对象,因此在脚本执行之前发出的请求或使用真实 XMLHttpRequest 对象的请求将不会被跟踪通过你的脚本。我无法看到解决此限制的方法。

不确定您是否可以使用greasemonkey 做到这一点,但是如果您创建了一个扩展,那么您可以使用观察者服务和http-on-examine-response 观察者。